Le co-streaming permet à un spectateur de rejoindre un streamer en direct en tant que co-animateur via la communication en temps réel (RTC), tandis que les autres spectateurs continuent de regarder le flux via le CDN. La mise en œuvre implique la diffusion de flux vers une salle RTC, la gestion des tâches de mixage côté serveur et la transition entre la lecture CDN et la participation RTC.
Fonctionnement du co-streaming
Le streamer et le co-streamer diffusent leurs flux vers une salle RTC via le SDK ARTC. Le serveur d'application surveille les événements de changement de flux et appelle l'API StartLiveMPUTask pour transférer les flux de la salle RTC vers ApsaraVideo Live CDN. Les spectateurs récupèrent et regardent le flux CDN via le SDK ApsaraVideo Player.
Cycle de vie du co-streaming
|
Phase |
Streamer |
Co-streamer |
Serveur d'application |
Spectateurs |
|
Avant le co-streaming |
Rejoint la salle RTC et diffuse les flux audio/vidéo via le SDK ARTC. |
Regarde le flux CDN via ApsaraVideo Player. |
Détecte la diffusion du streamer, puis appelle StartLiveMPUTask avec |
Récupère et lit le flux CDN via ApsaraVideo Player. |
|
Pendant le co-streaming |
Récupère le flux RTC du co-streamer et l'affiche. |
Arrête la lecture CDN, rejoint la salle RTC, diffuse son flux et affiche le flux RTC du streamer. |
Détecte la diffusion du co-streamer, puis appelle UpdateLiveMPUTask pour passer à |
Aucune action requise. Le flux en direct bascule automatiquement pour afficher les deux participants. |
|
Fin du co-streaming |
Arrête la récupération du flux du co-streamer. |
Quitte la salle RTC, détruit l'instance RTC et revient à la lecture CDN. |
Détecte le départ du co-streamer, puis appelle UpdateLiveMPUTask pour revenir à |
Aucune action requise. Le flux en direct revient automatiquement au streamer seul. |
Pour surveiller les événements de changement de flux dans la salle RTC, créez un abonnement avec CreateEventSub.
API clés
|
API |
Objectif |
Moment de l'appel |
|
Transférer les flux de la salle RTC vers le CDN |
Après que le streamer a commencé à diffuser |
|
|
Basculer entre le transfert direct et le mixage de flux |
Lorsqu'un co-streamer rejoint ou quitte la session |
|
|
S'abonner aux événements de changement de flux dans la salle RTC |
Avant de démarrer la diffusion (recommandé) |
Étape 1 : Démarrer la diffusion (streamer)
1. Diffuser un flux vers la salle RTC
Le streamer initialise le SDK ARTC, configure les paramètres d'encodage et rejoint la salle RTC.
Pour le guide complet d'intégration du SDK ARTC, consultez :
Android : Étapes d'implémentation
iOS : Étapes d'implémentation
Initialiser le moteur
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)
Configurer l'encodage audio et vidéo
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)
Publier les flux locaux et s'abonner aux flux distants
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)
Enregistrer les rappels
Gérez les changements d'état de connexion, les événements des utilisateurs distants et les conditions d'erreur.
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
}
}
}
Configurer l'aperçu local et rejoindre la salle
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. Transférer les flux RTC vers le CDN (serveur d'application)
Une fois que le streamer a diffusé un flux vers la salle RTC :
Abonnez-vous aux événements de changement de flux. Appelez CreateEventSub pour recevoir des notifications lorsque les utilisateurs commencent ou arrêtent de diffuser dans la salle RTC.
Démarrez une tâche de transfert direct. Après avoir reçu la notification de diffusion, appelez StartLiveMPUTask avec
MixMode=0(transfert de flux unique sans transcodage). Indiquez l'URL d'ingestion CDN, qui doit utiliser le protocole RTMP. Pour générer cette URL, consultez Générer les URL d'ingestion et de lecture.Distribuez l'URL de lecture. Surveillez les rappels de diffusion CDN pour détecter quand le transfert réussit, puis distribuez l'URL de lecture aux spectateurs. Pour plus de détails sur la configuration des rappels, consultez Paramètres de rappel.
3. Lire le flux CDN (spectateurs)
Lorsque les spectateurs reçoivent l'URL de lecture du serveur d'application, créez une instance ApsaraVideo Player et démarrez la lecture. Pour la référence complète de l'API du lecteur, consultez le SDK ApsaraVideo Player.
Utilisez HTTP-FLV plutôt que RTMP pour l'URL de lecture CDN. Bien que les deux protocoles diffusent le même contenu, HTTP-FLV utilise les ports standard 80/443, offre une meilleure optimisation réseau et assure une lecture plus stable avec moins de saccades et une latence réduite. Le port par défaut 1935 de RTMP peut être bloqué par les pare-feu.
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];
Étape 2 : Démarrer le co-streaming (le spectateur rejoint en tant que co-animateur)
Côté co-streamer
1. Arrêter la lecture CDN
Détruisez l'instance du lecteur avant de rejoindre la salle RTC.
Android
aliPlayer.stop();
aliPlayer = null;
iOS
[self.cdnPlayer stop];
[self.cdnPlayer clearScreen];
self.cdnPlayer.playerView = nil;
L'arrêt du lecteur CDN est obligatoire, il ne s'agit pas d'une étape de nettoyage facultative. Si le lecteur CDN continue de fonctionner, l'appareil du co-streamer lit simultanément l'audio CDN et l'audio RTC, ce qui produit un écho ou un doublement audio. Une fois le lecteur CDN arrêté, l'appareil ne lit plus que l'audio RTC et l'écho disparaît.
2. Rejoindre la salle RTC
Le co-streamer diffuse un flux vers la salle RTC en suivant le même processus que le streamer.
3. Afficher la vidéo du streamer
Lorsque la piste distante du streamer devient disponible, configurez la vue de rendu dans le rappel 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)
}
}
Côté streamer
Le streamer affiche la vidéo du co-streamer en utilisant le même rappel onRemoteTrackAvailableNotify présenté ci-dessus.
Serveur d'application
Une fois que le co-streamer a commencé à diffuser, appelez UpdateLiveMPUTask pour mettre à jour la tâche de transfert direct existante :
Définissez
MixModesur1(mixage de flux).Configurez la disposition du mixage de flux pour le streamer et le co-streamer.
Étape 3 : Terminer le co-streaming
Côté co-streamer
1. Quitter la salle 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. Reprendre la lecture CDN
Créez une nouvelle instance ApsaraVideo Player et lisez le flux CDN.
Côté streamer
Le streamer arrête la récupération des flux audio et vidéo du co-streamer. La vue de rendu est automatiquement nettoyée dans le rappel onRemoteTrackAvailableNotify lorsque videoTrack passe à AliRtcVideoTrackNo.
Serveur d'application
Une fois que le co-streamer a quitté la salle RTC, appelez UpdateLiveMPUTask pour mettre à jour la tâche de mixage de flux :
Rétablissez
MixModesur0(transfert direct, flux unique).
La vue du flux en direct CDN revient automatiquement à l'affichage du streamer seul.