Todos os produtos
Search
Central de documentação

ApsaraVideo Live:Modo de transmissão conjunta

Última atualização: Aug 17, 2026

A transmissão conjunta permite que um espectador participe de uma transmissão ao vivo como coapresentador via Real-Time Communication (RTC), enquanto os demais espectadores continuam assistindo pelo CDN. A implementação envolve o envio de streams para uma sala RTC, o gerenciamento de tarefas de mixagem no servidor e a transição entre a reprodução via CDN e a participação via RTC.

Como funciona a transmissão conjunta

image

O apresentador e o coapresentador enviam streams para uma sala RTC por meio do ARTC SDK. O servidor de aplicativos monitora eventos de alteração de stream e chama a API StartLiveMPUTask para encaminhar as streams da sala RTC para o CDN do ApsaraVideo Live. Os espectadores regulares recebem e assistem à stream do CDN por meio do ApsaraVideo Player SDK.

Ciclo de vida da transmissão conjunta

Fase

Apresentador

Coapresentador

Servidor de aplicativos

Espectadores

Antes da transmissão conjunta

Entra na sala RTC e envia streams de áudio e vídeo via ARTC SDK.

Assiste à stream do CDN via ApsaraVideo Player.

Detecta o envio da stream do apresentador e chama StartLiveMPUTask com MixMode=0 (bypass) para encaminhar a stream ao CDN.

Recebem e reproduzem a stream do CDN via ApsaraVideo Player.

Durante a transmissão conjunta

Recebe a stream RTC do coapresentador e a renderiza.

Interrompe a reprodução do CDN, entra na sala RTC, envia streams e renderiza a stream RTC do apresentador.

Detecta o envio da stream do coapresentador e chama UpdateLiveMPUTask para alternar para MixMode=1 (mixagem de streams) com um layout para ambos os participantes.

Nenhuma ação necessária. A transmissão ao vivo passa a exibir automaticamente ambos os participantes.

Fim da transmissão conjunta

Para de receber a stream do coapresentador.

Sai da sala RTC, destrói a instância RTC e retoma a reprodução via CDN.

Detecta a saída do coapresentador e chama UpdateLiveMPUTask para reverter para MixMode=0 (bypass).

Nenhuma ação necessária. A transmissão ao vivo volta a exibir apenas o apresentador automaticamente.

Nota

Para monitorar eventos de alteração de streams na sala RTC, crie uma assinatura com CreateEventSub.

APIs principais

API

Finalidade

Quando chamar

StartLiveMPUTask

Encaminhar streams da sala RTC para o CDN

Após o apresentador iniciar o envio da stream

UpdateLiveMPUTask

Alternar entre bypass e mixagem de streams

Quando um coapresentador entra ou sai

CreateEventSub

Assinar eventos de alteração de streams na sala RTC

Antes de iniciar a transmissão (recomendado)

Etapa 1: Iniciar a transmissão (apresentador)

image

1. Enviar uma stream para a sala RTC

O apresentador inicializa o ARTC SDK, define os parâmetros de codificação e entra na sala RTC.

Para obter o guia completo de integração do ARTC SDK, consulte:

Inicializar o engine

Android

// Import ARTC related classes
import com.alivc.rtc.AliRtcEngine;
import com.alivc.rtc.AliRtcEngineEventListener;
import com.alivc.rtc.AliRtcEngineNotify;

private AliRtcEngine mAliRtcEngine = null;
if(mAliRtcEngine == null) {
    mAliRtcEngine = AliRtcEngine.getInstance(this);
}

// Set channel mode to interactive live streaming
mAliRtcEngine.setChannelProfile(AliRtcEngine.AliRTCSdkChannelProfile.AliRTCSdkInteractiveLive);
mAliRtcEngine.setClientRole(AliRtcEngine.AliRTCSdkClientRole.AliRTCSdkInteractive);

iOS (Swift)

import AliVCSDK_ARTC

private var rtcEngine: AliRtcEngine? = nil

// Create the engine and set the delegate
let engine = AliRtcEngine.sharedInstance(self, extras: nil)
self.rtcEngine = engine

// Set channel mode to interactive live streaming
engine.setChannelProfile(AliRtcChannelProfile.interactivelive)
engine.setClientRole(AliRtcClientRole.roleInteractive)

Configure a codificação de áudio e vídeo

Android

// Audio: high-quality music mode
mAliRtcEngine.setAudioProfile(
    AliRtcEngine.AliRtcAudioProfile.AliRtcEngineHighQualityMode,
    AliRtcEngine.AliRtcAudioScenario.AliRtcSceneMusicMode
);

// Video: 720x1280 at 20 fps, 1200 kbps
AliRtcEngine.AliRtcVideoEncoderConfiguration config =
    new AliRtcEngine.AliRtcVideoEncoderConfiguration();
config.dimensions = new AliRtcEngine.AliRtcVideoDimensions(720, 1280);
config.frameRate = 20;
config.bitrate = 1200;
config.keyFrameInterval = 2000;
config.orientationMode = AliRtcVideoEncoderOrientationModeAdaptive;
mAliRtcEngine.setVideoEncoderConfiguration(config);

iOS (Swift)

// Audio: high-quality music mode
engine.setAudioProfile(
    AliRtcAudioProfile.engineHighQualityMode,
    audio_scene: AliRtcAudioScenario.sceneMusicMode
)

// Video: 720x1280 at 20 fps, 1200 kbps
let config = AliRtcVideoEncoderConfiguration()
config.dimensions = CGSize(width: 720, height: 1280)
config.frameRate = 20
config.bitrate = 1200
config.keyFrameInterval = 2000
config.orientationMode = AliRtcVideoEncoderOrientationMode.adaptive
engine.setVideoEncoderConfiguration(config)
engine.setCapturePipelineScaleMode(.post)

Publicar streams locais e assinar streams remotas

Android

// Publish local audio and video
mAliRtcEngine.publishLocalAudioStream(true);
mAliRtcEngine.publishLocalVideoStream(true);

// Subscribe to all remote streams
mAliRtcEngine.setDefaultSubscribeAllRemoteAudioStreams(true);
mAliRtcEngine.subscribeAllRemoteAudioStreams(true);
mAliRtcEngine.setDefaultSubscribeAllRemoteVideoStreams(true);
mAliRtcEngine.subscribeAllRemoteVideoStreams(true);

iOS (Swift)

// Publish local audio and video
engine.publishLocalVideoStream(true)
engine.publishLocalAudioStream(true)

// Subscribe to all remote streams
engine.setDefaultSubscribeAllRemoteAudioStreams(true)
engine.subscribeAllRemoteAudioStreams(true)
engine.setDefaultSubscribeAllRemoteVideoStreams(true)
engine.subscribeAllRemoteVideoStreams(true)

Registrar callbacks

Gerencie alterações no status de conexão, eventos de usuários remotos e condições de erro.

Android

private AliRtcEngineEventListener mRtcEngineEventListener = new AliRtcEngineEventListener() {
    @Override
    public void onJoinChannelResult(int result, String channel, String userId, int elapsed) {
        super.onJoinChannelResult(result, channel, userId, elapsed);
        handleJoinResult(result, channel, userId);
    }

    @Override
    public void onLeaveChannelResult(int result, AliRtcEngine.AliRtcStats stats){
        super.onLeaveChannelResult(result, stats);
    }

    @Override
    public void onConnectionStatusChange(AliRtcEngine.AliRtcConnectionStatus status,
            AliRtcEngine.AliRtcConnectionStatusChangeReason reason){
        super.onConnectionStatusChange(status, reason);
        handler.post(() -> {
            if(status == AliRtcEngine.AliRtcConnectionStatus.AliRtcConnectionStatusFailed) {
                // Handle unrecoverable connection failure
                ToastHelper.showToast(VideoChatActivity.this,
                    R.string.video_chat_connection_failed, Toast.LENGTH_SHORT);
            }
        });
    }

    @Override
    public void OnLocalDeviceException(AliRtcEngine.AliRtcEngineLocalDeviceType deviceType,
            AliRtcEngine.AliRtcEngineLocalDeviceExceptionType exceptionType, String msg){
        super.OnLocalDeviceException(deviceType, exceptionType, msg);
        // Notify the user when the SDK cannot recover the device
        handler.post(() -> {
            String str = "OnLocalDeviceException deviceType: " + deviceType
                + " exceptionType: " + exceptionType + " msg: " + msg;
            ToastHelper.showToast(VideoChatActivity.this, str, Toast.LENGTH_SHORT);
        });
    }
};

private AliRtcEngineNotify mRtcEngineNotify = new AliRtcEngineNotify() {
    @Override
    public void onAuthInfoWillExpire() {
        super.onAuthInfoWillExpire();
        // Refresh the token before it expires.
        // Obtain new auth info for the current channel and user,
        // then call refreshAuthInfo.
    }

    @Override
    public void onRemoteUserOnLineNotify(String uid, int elapsed){
        super.onRemoteUserOnLineNotify(uid, elapsed);
    }

    @Override
    public void onRemoteUserOffLineNotify(String uid,
            AliRtcEngine.AliRtcUserOfflineReason reason){
        super.onRemoteUserOffLineNotify(uid, reason);
        // Remove the remote video rendering view
    }

    @Override
    public void onRemoteTrackAvailableNotify(String uid,
            AliRtcEngine.AliRtcAudioTrack audioTrack,
            AliRtcEngine.AliRtcVideoTrack videoTrack){
        // Set or remove remote video rendering.
            // See "Render the streamer's video" in Step 2.
    }

    @Override
    public void onBye(int code){
        // Handle session termination (e.g., another device using the same UserID)
    }
};

mAliRtcEngine.setRtcEngineEventListener(mRtcEngineEventListener);
mAliRtcEngine.setRtcEngineNotify(mRtcEngineNotify);

iOS (Swift)

extension VideoCallMainVC: AliRtcEngineDelegate {

    func onJoinChannelResult(_ result: Int32, channel: String, userId: String, elapsed: Int32) {
        "onJoinChannelResult result: \(result)".printLog()
    }

    func onRemoteUser(onLineNotify uid: String, elapsed: Int32) {
        "onRemoteUserOnlineNotify uid: \(uid)".printLog()
    }

    func onRemoteUserOffLineNotify(_ uid: String, offlineReason reason: AliRtcUserOfflineReason) {
        "onRemoteUserOffLineNotify uid: \(uid) reason: \(reason)".printLog()
    }

    func onRemoteTrackAvailableNotify(_ uid: String, audioTrack: AliRtcAudioTrack,
            videoTrack: AliRtcVideoTrack) {
        // Set or remove remote video rendering.
        // See "Render the streamer's video" in Step 2.
    }

    func onAuthInfoWillExpire() {
        // Refresh the token before it expires.
        // Obtain new auth info for the current channel and user,
        // then call refreshAuthInfo.
    }

    func onAuthInfoExpired() {
        // Token is invalid. Leave the channel and release the engine.
    }

    func onBye(_ code: Int32) {
        // Handle session termination (e.g., another device using the same UserID)
    }

    func onLocalDeviceException(_ deviceType: AliRtcLocalDeviceType,
            exceptionType: AliRtcLocalDeviceExceptionType, message msg: String?) {
        // Notify the user when the SDK cannot recover the device
    }

    func onConnectionStatusChange(_ status: AliRtcConnectionStatus,
            reason: AliRtcConnectionStatusChangeReason) {
        if status == .failed {
            // Handle unrecoverable connection failure
        }
    }
}

Configure a pré-visualização local e entrar na sala

Android

// Local preview
mLocalVideoCanvas = new AliRtcEngine.AliRtcVideoCanvas();
SurfaceView localSurfaceView = mAliRtcEngine.createRenderSurfaceView(VideoChatActivity.this);
localSurfaceView.setZOrderOnTop(true);
localSurfaceView.setZOrderMediaOverlay(true);

FrameLayout fl_local = findViewById(R.id.fl_local);
fl_local.addView(localSurfaceView, layoutParams);
mLocalVideoCanvas.view = localSurfaceView;
mAliRtcEngine.setLocalViewConfig(mLocalVideoCanvas, AliRtcVideoTrackCamera);
mAliRtcEngine.startPreview();

// Join the RTC room
mAliRtcEngine.joinChannel(token, null, null, null);

iOS (Swift)

// Local preview
let videoView = self.createVideoView(uid: self.userId)
let canvas = AliVideoCanvas()
canvas.view = videoView.canvasView
canvas.renderMode = .auto
canvas.mirrorMode = .onlyFrontCameraPreviewEnabled
canvas.rotationMode = ._0
self.rtcEngine?.setLocalViewConfig(canvas, for: AliRtcVideoTrack.camera)
self.rtcEngine?.startPreview()

// Join the RTC room
let ret = self.rtcEngine?.joinChannel(joinToken, channelId: nil, userId: nil, name: nil) {
    [weak self] errCode, channelId, userId, elapsed in
    if errCode == 0 {
        // Joined successfully
    } else {
        // Handle join failure
    }
}

2. Encaminhar streams RTC para o CDN (servidor de aplicativos)

Depois que o apresentador envia uma stream para a sala RTC:

  1. Assine eventos de alteração de stream. Chame CreateEventSub para receber notificações quando usuários iniciarem ou interromperem o envio de streams na sala RTC.

  2. Inicie uma tarefa de streaming em modo bypass. Após receber a notificação de envio de stream, chame StartLiveMPUTask com MixMode=0 (encaminhamento de stream única sem transcodificação). Informe a URL de ingestão do CDN, que deve usar o protocolo RTMP. Para gerar essa URL, consulte Generate ingest and playback URLs.

  3. Distribua a URL de reprodução. Monitore os callbacks de envio de stream do CDN para detectar quando o encaminhamento for bem-sucedido e distribua a URL de reprodução aos espectadores. Para detalhes sobre a configuração de callbacks, consulte Callback settings.

3. Reproduzir a stream do CDN (espectadores)

Ao receberem a URL de reprodução do servidor de aplicativos, os espectadores devem criar uma instância do ApsaraVideo Player e iniciar a reprodução. Para a referência completa da API do player, consulte ApsaraVideo Player SDK.

Nota

Utilize HTTP-FLV em vez de RTMP para a URL de reprodução do CDN. Ambos entregam o mesmo conteúdo, mas o HTTP-FLV usa as portas padrão 80/443, oferece melhor otimização de rede e proporciona uma reprodução mais estável, com menos travamentos e menor latência. A porta padrão do RTMP (1935) pode ser bloqueada por firewalls.

Android

AliPlayer aliPlayer = AliPlayerFactory.createAliPlayer(context);
aliPlayer.setAutoPlay(true);

UrlSource urlSource = new UrlSource();
// Use HTTP-FLV for CDN playback
urlSource.setUri("http://test.alivecdn.com/live/streamId.flv?auth_key=XXX");
aliPlayer.setDataSource(urlSource);
aliPlayer.prepare();

iOS (Objective-C)

self.cdnPlayer = [[AliPlayer alloc] init];
self.cdnPlayer.delegate = self;
self.cdnPlayer.autoPlay = YES;

// Use HTTP-FLV for CDN playback
AVPUrlSource *source = [[AVPUrlSource alloc] urlWithString:@"http://test.alivecdn.com/live/streamId.flv?auth_key=XXX"];
[self.cdnPlayer setUrlSource:source];
[self.cdnPlayer prepare];

Etapa 2: Iniciar a transmissão conjunta (espectador entra como coapresentador)

image

Lado do coapresentador

1. Interromper a reprodução do CDN

Destrua a instância do player antes de entrar na sala RTC.

Android

aliPlayer.stop();
aliPlayer = null;

iOS

[self.cdnPlayer stop];
[self.cdnPlayer clearScreen];
self.cdnPlayer.playerView = nil;
Nota

Interromper o player do CDN é obrigatório, não sendo apenas uma etapa opcional de limpeza. Se o player do CDN continuar em execução, o dispositivo do coapresentador reproduzirá o áudio do CDN e o áudio RTC simultaneamente, causando eco ou áudio duplicado. Após parar o player do CDN, o dispositivo reproduz apenas o áudio RTC e o eco desaparece.

2. Entrar na sala RTC

O coapresentador envia uma stream para a sala RTC seguindo o mesmo processo utilizado pelo apresentador.

3. Renderizar o vídeo do apresentador

Quando a track remota do apresentador estiver disponível, configure a visualização de renderização no callback onRemoteTrackAvailableNotify.

Android

@Override
public void onRemoteTrackAvailableNotify(String uid,
        AliRtcEngine.AliRtcAudioTrack audioTrack,
        AliRtcEngine.AliRtcVideoTrack videoTrack){
    handler.post(() -> {
        if(videoTrack == AliRtcVideoTrackCamera) {
            // Create and attach the remote rendering view
            SurfaceView surfaceView =
                mAliRtcEngine.createRenderSurfaceView(VideoChatActivity.this);
            surfaceView.setZOrderMediaOverlay(true);

            FrameLayout fl_remote = findViewById(R.id.fl_remote);
            if (fl_remote == null) return;

            fl_remote.addView(surfaceView,
                new FrameLayout.LayoutParams(
                    ViewGroup.LayoutParams.MATCH_PARENT,
                    ViewGroup.LayoutParams.MATCH_PARENT));

            AliRtcEngine.AliRtcVideoCanvas remoteVideoCanvas =
                new AliRtcEngine.AliRtcVideoCanvas();
            remoteVideoCanvas.view = surfaceView;
            mAliRtcEngine.setRemoteViewConfig(remoteVideoCanvas, uid, AliRtcVideoTrackCamera);

        } else if(videoTrack == AliRtcVideoTrackNo) {
            // Remove the remote rendering view
            FrameLayout fl_remote = findViewById(R.id.fl_remote);
            fl_remote.removeAllViews();
            mAliRtcEngine.setRemoteViewConfig(null, uid, AliRtcVideoTrackCamera);
        }
    });
}

iOS (Swift)

func onRemoteTrackAvailableNotify(_ uid: String, audioTrack: AliRtcAudioTrack,
        videoTrack: AliRtcVideoTrack) {
    if audioTrack != .no {
        let videoView = self.videoViewList.first { $0.uidLabel.text == uid }
        if videoView == nil {
            _ = self.createVideoView(uid: uid)
        }
    }

    if videoTrack != .no {
        // Create or reuse the video view, then configure rendering
        var videoView = self.videoViewList.first { $0.uidLabel.text == uid }
        if videoView == nil {
            videoView = self.createVideoView(uid: uid)
        }

        let canvas = AliVideoCanvas()
        canvas.view = videoView!.canvasView
        canvas.renderMode = .auto
        canvas.mirrorMode = .onlyFrontCameraPreviewEnabled
        canvas.rotationMode = ._0
        self.rtcEngine?.setRemoteViewConfig(canvas, uid: uid, for: AliRtcVideoTrack.camera)
    } else {
        self.rtcEngine?.setRemoteViewConfig(nil, uid: uid, for: AliRtcVideoTrack.camera)
    }

    if audioTrack == .no && videoTrack == .no {
        // Remote user stopped all tracks -- clean up
        self.removeVideoView(uid: uid)
        self.rtcEngine?.setRemoteViewConfig(nil, uid: uid, for: AliRtcVideoTrack.camera)
    }
}

Lado do apresentador

O apresentador renderiza o vídeo do coapresentador utilizando o mesmo callback onRemoteTrackAvailableNotify demonstrado acima.

Servidor de aplicativos

Após o coapresentador enviar uma stream, chame UpdateLiveMPUTask para atualize a tarefa de streaming em modo bypass existente:

  • Defina MixMode como 1 (mixagem de streams).

  • Configure o layout de mixagem de streams para incluir tanto o apresentador quanto o coapresentador.

Etapa 3: Encerrar a transmissão conjunta

image

Lado do coapresentador

1. Sair da sala RTC

Android

mAliRtcEngine.stopPreview();
mAliRtcEngine.setLocalViewConfig(null, AliRtcVideoTrackCamera);
mAliRtcEngine.leaveChannel();
mAliRtcEngine.destroy();
mAliRtcEngine = null;

iOS (Swift)

self.rtcEngine?.stopPreview()
self.rtcEngine?.leaveChannel()
AliRtcEngine.destroy()
self.rtcEngine = nil

2. Retomar a reprodução do CDN

Crie uma nova instância do ApsaraVideo Player e reproduza a stream do CDN.

Lado do apresentador

O apresentador para de receber as streams de áudio e vídeo do coapresentador. A visualização de renderização é limpa automaticamente no callback onRemoteTrackAvailableNotify quando videoTrack muda para AliRtcVideoTrackNo.

Servidor de aplicativos

Depois que o coapresentador sai da sala RTC, chame UpdateLiveMPUTask para atualize a tarefa de mixagem de streams:

  • Reverta MixMode para 0 (bypass, encaminhamento de stream única).

A visualização da transmissão ao vivo no CDN passa automaticamente de ambos os participantes para apenas o apresentador.

Referências