A transmissão conjunta permite que um espectador entre na 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
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 os streams da sala RTC para o CDN. Os espectadores regulares recebem e assistem ao stream do CDN usando o 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 pelo ARTC SDK. |
Assiste ao stream do CDN via ApsaraVideo Player. |
Detecta o envio de stream do apresentador e chama a API StartLiveMPUTask com |
Recebem e reproduzem o stream do CDN via ApsaraVideo Player. |
|
Durante a transmissão conjunta |
Recebe o stream RTC do coapresentador e faz a renderização. |
Interrompe a reprodução do CDN, entra na sala RTC, envia streams e renderiza o stream RTC do apresentador. |
Detecta o envio de stream do coapresentador e chama a API UpdateLiveMPUTask para alternar para |
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 o 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 a API UpdateLiveMPUTask para reverter para |
Nenhuma ação necessária. A transmissão ao vivo volta automaticamente a exibir apenas o apresentador. |
Para monitorar eventos de alteração de stream na sala RTC, crie uma assinatura com a API CreateEventSub.
APIs principais
|
API |
Finalidade |
Quando chamar |
|
Encaminhar streams da sala RTC para o CDN |
Após o apresentador iniciar o envio de stream |
|
|
Alternar entre bypass e mixagem de streams |
Quando um coapresentador entra ou sai |
|
|
Assinar eventos de alteração de stream na sala RTC |
Antes de iniciar a transmissão (recomendado) |
Etapa 1: Iniciar a transmissão (apresentador)
1. Enviar um stream para a sala RTC
O apresentador inicializa o ARTC SDK, configure os parâmetros de codificação e entra na sala RTC.
Para consultar o guia completo de integração do ARTC SDK, visualize:
Android: Implementation steps
iOS: Implementation steps
Inicializar o mecanismo
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)
Configurar 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 remotos
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 da 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
}
}
}
Configurar a 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 um stream para a sala RTC:
Assine eventos de alteração de stream. Chame a API CreateEventSub para receber notificações quando usuários iniciarem ou interromperem o envio de streams na sala RTC.
Inicie uma tarefa de streaming em bypass. Ao receber a notificação de envio de stream, chame a API StartLiveMPUTask com
MixMode=0(encaminhamento de stream único 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.Distribua a URL de reprodução. Monitore os callbacks de envio de stream do CDN para detectar quando o encaminhamento for bem-sucedido e, em seguida, distribua a URL de reprodução aos espectadores. Para detalhes sobre a configuração de callbacks, visualize Callback settings.
3. Reproduzir o 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 consultar a referência completa da API do player, visualize ApsaraVideo Player SDK.
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)
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;
2. Entrar na sala RTC
O coapresentador envia um stream para a sala RTC seguindo o mesmo processo do apresentador. Consulte a seção Enviar um stream para a sala RTC acima.
3. Renderizar o vídeo do apresentador
Quando a faixa 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 um stream, chame a API UpdateLiveMPUTask para atualize a tarefa de streaming em bypass existente:
Defina
MixModecomo1(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
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 o stream do CDN. O processo é idêntico ao descrito na seção Reproduzir o stream do CDN (espectadores) acima.
Lado do apresentador
O apresentador interrompe o recebimento dos 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 a API UpdateLiveMPUTask para atualize a tarefa de mixagem de streams:
Reverta
MixModepara0(bypass, encaminhamento de stream único).
A visualização da transmissão ao vivo no CDN passa automaticamente de ambos os participantes para apenas o apresentador.