ApsaraVideo Live は、ストリーマーバトル機能をサポートしています。これにより、異なるライブルームの複数のストリーマーがリアルタイムで対戦を開始でき、視聴者のライブストリーミング体験が向上します。このトピックでは、RTC+CDN バイパスライブストリーミングに基づいてストリーマーバトルを実装する方法について説明し、ストリーマーバトル機能を迅速に統合するための関連サンプルコードを提供します。
ソリューション概要
ARTC SDK が提供するルーム間ストリームプル機能を使用して、異なるルーム間でのストリーマーバトルを実装できます。この機能は、異なるルームのストリーマーからリアルタイムの音声およびビデオストリームをプルします。各ストリーマーは、それぞれのライブルームで UpdateLiveMPUTask - 混合ストリーム転送タスクの更新 (新規) インターフェイスを呼び出して、混合ストリームモードに切り替えます。ターゲットルームの ChannelID とストリーマーの UserId を渡すことで、両方のストリーマーのビデオストリームを 1 つのビューに混合できます。CDN の視聴者には、表示が単一のストリーマービューから両方のストリーマーがいるバトルビューに変わることがわかります。ストリーマーのブロードキャストとバトルのワークフローは次のとおりです。
バトル前 | ストリーマー A: ARTC SDK を使用して RTC ルーム A に参加し、リアルタイムの音声/ビデオストリームをプッシュします。 アプリケーションサーバ: RTC ルーム A のストリーム変更イベントを監視します。ストリーマーがストリームのプッシュを開始すると、StartLiveMPUTask - 混合ストリーム転送タスクの作成 (新規) を呼び出してバイパス転送タスク Task1 を開始し、CDN アップストリーミング URL を渡して RTC ルーム A から CDN にストリームを転送します。 ライブルーム A の一般視聴者: ApsaraVideo Player SDK とライブルーム A の CDN ストリーミング URL を使用して、ストリームをプルして再生します。 ストリーマー B: ARTC SDK を使用して RTC ルーム B に参加し、リアルタイムの音声/ビデオストリームをプッシュします。 アプリケーションサーバ: RTC ルーム B のストリーム変更イベントを監視します。ストリーマーがストリームのプッシュを開始すると、StartLiveMPUTask - 混合ストリーム転送タスクの作成 (新規) を呼び出してバイパス転送タスク Task2 を開始し、CDN アップストリーミング URL を渡して RTC ルーム B から CDN にストリームを転送します。 ライブルーム B の一般視聴者: ApsaraVideo Player SDK とライブルーム B の CDN ストリーミング URL を使用して、ストリームをプルして再生します。 |
バトル中 | ストリーマー A: ARTC SDK のルーム間ストリームプルインターフェイスを呼び出してストリームのプルを開始し、ルーム B と ユーザー B を渡します。 ストリーマー B: ARTC SDK のルーム間ストリームプルインターフェイスを呼び出してストリームのプルを開始し、ルーム A と ユーザー A を渡します。 アプリケーションサーバ:
|
バトル終了 | ストリーマー A: ルーム間ストリームプルインターフェイスを呼び出して、ストリームのプルを停止します。 ストリーマー B: ルーム間ストリームプルインターフェイスを呼び出して、ストリームのプルを停止します。 アプリケーションサーバ:
|
ストリーマーバトルシナリオでは、一般視聴者は追加の操作を行う必要はありません。表示は自動的に単一のストリーマービューから混合ストリームビューに切り替わります。
実装手順
ステップ 1: ストリーマーがブロードキャストを開始
ストリーマーがブロードキャストを開始するための基本的なプロセス:
1. ストリーマーが RTC ルームにストリームをプッシュ
ストリーマーは ARTC SDK を使用して RTC ルームにストリームをプッシュします。
Android
ARTC SDK を使用して RTC ルームに参加し、ストリームをプッシュする詳細な手順については、「実装手順」をご参照ください。
// ARTC 関連クラスをインポート
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);
}
// チャンネルモードを設定
mAliRtcEngine.setChannelProfile(AliRtcEngine.AliRTCSdkChannelProfile.AliRTCSdkInteractiveLive);
mAliRtcEngine.setClientRole(AliRtcEngine.AliRTCSdkClientRole.AliRTCSdkInteractive);
mAliRtcEngine.setAudioProfile(AliRtcEngine.AliRtcAudioProfile.AliRtcEngineHighQualityMode, AliRtcEngine.AliRtcAudioScenario.AliRtcSceneMusicMode);
// ビデオエンコーディングパラメーターを設定
AliRtcEngine.AliRtcVideoEncoderConfiguration aliRtcVideoEncoderConfiguration = new AliRtcEngine.AliRtcVideoEncoderConfiguration();
aliRtcVideoEncoderConfiguration.dimensions = new AliRtcEngine.AliRtcVideoDimensions(
720, 1280);
aliRtcVideoEncoderConfiguration.frameRate = 20;
aliRtcVideoEncoderConfiguration.bitrate = 1200;
aliRtcVideoEncoderConfiguration.keyFrameInterval = 2000;
aliRtcVideoEncoderConfiguration.orientationMode = AliRtcVideoEncoderOrientationModeAdaptive;
mAliRtcEngine.setVideoEncoderConfiguration(aliRtcVideoEncoderConfiguration);
mAliRtcEngine.publishLocalAudioStream(true);
mAliRtcEngine.publishLocalVideoStream(true);
mAliRtcEngine.setDefaultSubscribeAllRemoteAudioStreams(true);
mAliRtcEngine.subscribeAllRemoteAudioStreams(true);
mAliRtcEngine.setDefaultSubscribeAllRemoteVideoStreams(true);
mAliRtcEngine.subscribeAllRemoteVideoStreams(true);
// 関連するコールバックを設定
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(new Runnable() {
@Override
public void run() {
if(status == AliRtcEngine.AliRtcConnectionStatus.AliRtcConnectionStatusFailed) {
/* TODO: 必ず例外を処理してください。SDK はさまざまな回復ポリシーを試しましたが、それでも回復できません。 */
ToastHelper.showToast(VideoChatActivity.this, R.string.video_chat_connection_failed, Toast.LENGTH_SHORT);
} else {
/* TODO: 必要に応じて例外を処理します。通常はデータ統計や UI の変更のために、ビジネスコードが追加されます。 */
}
}
});
}
@Override
public void OnLocalDeviceException(AliRtcEngine.AliRtcEngineLocalDeviceType deviceType, AliRtcEngine.AliRtcEngineLocalDeviceExceptionType exceptionType, String msg){
super.OnLocalDeviceException(deviceType, exceptionType, msg);
/* TODO: 必ず例外を処理してください。SDK がすべての回復ポリシーを試してもデバイスを使用できない場合は、デバイスエラーをユーザーに通知することをお勧めします。 */
handler.post(new Runnable() {
@Override
public void run() {
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();
/* TODO: 必ずこれを処理してください。トークンはまもなく期限切れになります。ビジネス側で現在のチャンネルとユーザーの新しい認証情報を取得し、refreshAuthInfo を設定する必要があります。 */
}
@Override
public void onRemoteUserOnLineNotify(String uid, int elapsed){
super.onRemoteUserOnLineNotify(uid, elapsed);
}
// onRemoteUserOffLineNotify コールバックでリモートビデオストリームのレンダリングコントロール設定を削除
@Override
public void onRemoteUserOffLineNotify(String uid, AliRtcEngine.AliRtcUserOfflineReason reason){
super.onRemoteUserOffLineNotify(uid, reason);
}
// onRemoteTrackAvailableNotify コールバックでリモートビデオストリームのレンダリングコントロールを設定
@Override
public void onRemoteTrackAvailableNotify(String uid, AliRtcEngine.AliRtcAudioTrack audioTrack, AliRtcEngine.AliRtcVideoTrack videoTrack){
handler.post(new Runnable() {
@Override
public void run() {
if(videoTrack == AliRtcVideoTrackCamera) {
SurfaceView surfaceView = mAliRtcEngine.createRenderSurfaceView(VideoChatActivity.this);
surfaceView.setZOrderMediaOverlay(true);
FrameLayout view = getAvailableView();
if (view == null) {
return;
}
remoteViews.put(uid, view);
view.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) {
if(remoteViews.containsKey(uid)) {
ViewGroup view = remoteViews.get(uid);
if(view != null) {
view.removeAllViews();
remoteViews.remove(uid);
mAliRtcEngine.setRemoteViewConfig(null, uid, AliRtcVideoTrackCamera);
}
}
}
}
});
}
/* ビジネスでは、異なるデバイスが同じ UserID を競合する状況が発生する可能性があるため、これも処理する必要があります */
@Override
public void onBye(int code){
handler.post(new Runnable() {
@Override
public void run() {
String msg = "onBye code:" + code;
}
});
}
};
mAliRtcEngine.setRtcEngineEventListener(mRtcEngineEventListener);
mAliRtcEngine.setRtcEngineNotify(mRtcEngineNotify);
// ローカルプレビュー
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();
// RTC ルームに参加
mAliRtcEngine.joinChannel(token, null, null, null);
iOS
ARTC SDK を使用して RTC ルームに参加し、ストリームをプッシュする詳細な手順については、「実装手順」をご参照ください。
// ARTC 関連クラスをインポート
import AliVCSDK_ARTC
private var rtcEngine: AliRtcEngine? = nil
// エンジンを作成してコールバックを設定
let engine = AliRtcEngine.sharedInstance(self, extras:nil)
...
self.rtcEngine = engine
// チャンネルモードを設定
engine.setChannelProfile(AliRtcChannelProfile.interactivelive)
engine.setClientRole(AliRtcClientRole.roleInteractive)
engine.setAudioProfile(AliRtcAudioProfile.engineHighQualityMode, audio_scene: AliRtcAudioScenario.sceneMusicMode)
// ビデオエンコーディングパラメーターを設定
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)
engine.publishLocalVideoStream(true)
engine.publishLocalAudioStream(true)
engine.setDefaultSubscribeAllRemoteAudioStreams(true)
engine.subscribeAllRemoteAudioStreams(true)
engine.setDefaultSubscribeAllRemoteVideoStreams(true)
engine.subscribeAllRemoteVideoStreams(true)
// 関連するコールバックを設定
extension VideoCallMainVC: AliRtcEngineDelegate {
func onJoinChannelResult(_ result: Int32, channel: String, elapsed: Int32) {
"onJoinChannelResult1 result: \(result)".printLog()
}
func onJoinChannelResult(_ result: Int32, channel: String, userId: String, elapsed: Int32) {
"onJoinChannelResult2 result: \(result)".printLog()
}
func onRemoteUser(onLineNotify uid: String, elapsed: Int32) {
// リモートユーザーがオンラインになる
"onRemoteUserOlineNotify uid: \(uid)".printLog()
}
func onRemoteUserOffLineNotify(_ uid: String, offlineReason reason: AliRtcUserOfflineReason) {
// リモートユーザーがオフラインになる
"onRemoteUserOffLineNotify uid: \(uid) reason: \(reason)".printLog()
}
func onRemoteTrackAvailableNotify(_ uid: String, audioTrack: AliRtcAudioTrack, videoTrack: AliRtcVideoTrack) {
"onRemoteTrackAvailableNotify uid: \(uid) audioTrack: \(audioTrack) videoTrack: \(videoTrack)".printLog()
}
func onAuthInfoWillExpire() {
"onAuthInfoWillExpire".printLog()
/* TODO: 必ずこれを処理してください。トークンはまもなく期限切れになります。ビジネス側で現在のチャンネルとユーザーの新しい認証情報を取得し、refreshAuthInfo を設定する必要があります。 */
}
func onAuthInfoExpired() {
"onAuthInfoExpired".printLog()
/* TODO: 必ずこれを処理してください。トークンが無効であることを通知し、会議から退出してエンジンを解放します。 */
}
func onBye(_ code: Int32) {
"onBye code: \(code)".printLog()
/* TODO: 必ずこれを処理してください。ビジネスでは、異なるデバイスが同じ UserID を競合する状況が発生する可能性があります。 */
}
func onLocalDeviceException(_ deviceType: AliRtcLocalDeviceType, exceptionType: AliRtcLocalDeviceExceptionType, message msg: String?) {
"onLocalDeviceException deviceType: \(deviceType) exceptionType: \(exceptionType)".printLog()
/* TODO: 必ずこれを処理してください。SDK がすべての回復ポリシーを試してもデバイスを使用できない場合は、デバイスエラーをユーザーに通知することをお勧めします。 */
}
func onConnectionStatusChange(_ status: AliRtcConnectionStatus, reason: AliRtcConnectionStatusChangeReason) {
"onConnectionStatusChange status: \(status) reason: \(reason)".printLog()
if status == .failed {
/* TODO: 必ずこれを処理してください。SDK がすべての回復ポリシーを試しても回復できない場合は、ユーザーに通知することをお勧めします。 */
}
else {
/* TODO: 必要に応じてこれを処理します。通常はデータ統計や UI の変更のために、ビジネスコードを追加します。 */
}
}
}
// ローカルプレビュー
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()
// RTC ルームに参加
let ret = self.rtcEngine?.joinChannel(joinToken, channelId: nil, userId: nil, name: nil) { [weak self] errCode, channelId, userId, elapsed in
if errCode == 0 {
// 成功
}
else {
// 失敗
}
let resultMsg = "\(msg) \n CallbackErrorCode: \(errCode)"
resultMsg.printLog()
UIAlertController.showAlertWithMainThread(msg: resultMsg, vc: self!)
}
let resultMsg = "\(msg) \n ReturnErrorCode: \(ret ?? 0)"
resultMsg.printLog()
if ret != 0 {
UIAlertController.showAlertWithMainThread(msg: resultMsg, vc: self)
}
2. アプリケーションサーバが転送タスクを開始して RTC ルームのストリームを CDN に転送
アプリケーションサーバは、RTC ルームメッセージコールバックのサブスクリプションを作成し、ルーム内のストリーマープッシュストリームイベントを監視します。RTC ルームメッセージのサブスクライブに関する詳細な API 情報については、「CreateEventSub - ルームメッセージコールバックのサブスクリプションを作成」をご参照ください。
ストリーマーが RTC ルームにストリームをプッシュしたという通知を受け取った後、バイパスストリーミング OpenAPI StartLiveMPUTask を呼び出して、RTC ルームから CDN にストリームを転送します。バイパスストリーミング API の詳細については、「StartLiveMPUTask - ストリーム混合タスクの作成 (新規)」をご参照ください。
説明ストリーマーがブロードキャストを開始するときに、
MixModeを 0 に設定できます。これは、トランスコーディングなしの単一ストリーム転送を示します。API にはライブストリーミングのアップストリーミング URL が必要で、RTMP プロトコルのみをサポートします。この URL の生成方法については、「アップストリーミング URL と再生 URL の生成」をご参照ください。アプリケーションサーバは CDN ストリームプッシュコールバックを監視します。ストリームが CDN に転送された後、ライブストリーミング再生 URL を配信して、視聴者に再生を開始するよう通知します。CDN ストリームプッシュコールバックの詳細については、「コールバック設定」をご参照ください。
3. 視聴者が ApsaraVideo Player SDK を使用してストリームをプルして再生
視聴者がアプリケーションサーバからストリームプル通知を受け取ると、ApsaraVideo Player インスタンスを作成し、ライブストリーミング再生 URL を使用して再生します。プレーヤー API と使用方法の詳細については、「ApsaraVideo Player SDK」をご参照ください。
一般視聴者向けの CDN 再生 URL を RTMP フォーマットから HTTP-FLV フォーマットに変更することを推奨します。どちらも同じコンテンツを含んでいますが、異なる伝送プロトコルを使用します。HTTP は主流のインターネットプロトコルとして、より成熟したネットワーク最適化基盤を持ち、デフォルトのポート 80/443 を使用するため、ファイアウォールを通過しやすくなっています。RTMP プロトコルは古く、一般的なポート 1935 が制限される可能性があり、再生の安定性に影響を与えることがあります。全体として、HTTP-FLV は互換性と再生体験 (カクつきや遅延など) の点で RTMP よりも優れているため、最初の選択肢として HTTP-FLV を使用することをお勧めします。
Android
AliPlayer aliPlayer = AliPlayerFactory.createAliPlayer(context);
aliPlayer.setAutoPlay(true);
UrlSource urlSource = new UrlSource();
urlSource.setUri("http://test.alivecdn.com/live/streamId.flv?auth_key=XXX"); // 視聴者の CDN ストリーミング URL。
aliPlayer.setDataSource(urlSource);
aliPlayer.prepare();iOS
self.cdnPlayer = [[AliPlayer alloc] init];
self.cdnPlayer.delegate = self;
self.cdnPlayer.autoPlay = YES;
AVPUrlSource *source = [[AVPUrlSource alloc] urlWithString:@""http://test.alivecdn.com/live/streamId.flv?auth_key=XXX"];
[self.cdnPlayer setUrlSource:source];
[self.cdnPlayer prepare];
ステップ 2: ストリーマーのルーム間バトル
ストリーマー A とストリーマー B のルーム間バトルの基本的なワークフロー:
1. ストリーマー A とストリーマー B がルーム間ストリームプルを開始
ストリーマー A とストリーマー B はそれぞれルーム間ストリームプルインターフェイスを呼び出し、ターゲットルーム ID とユーザー ID を渡してルーム間ストリームプルを開始します。
Android
mAliRtcEngine.subscribeRemoteDestChannelStream(channelId, userId, AliRtcVideoTrackCamera, AliRtcAudioTrackMic, true);iOS
[self.rtcEngine subscribeRemoteDestChannelStream:channelId uid:userId videoTrack:AliRtcVideoTrackCamera audioTrack:AliRtcAudioTrackMic sub:YES];2. ストリーマー A とストリーマー B のレンダリングビューを設定
Android
エンジンを初期化する際に、対応するコールバック mAliRtcEngine.setRtcEngineNotify を設定します。onRemoteTrackAvailableNotify コールバックでリモートユーザーのリモートビューを設定する必要があります。サンプルコードは次のとおりです。
@Override
public void onRemoteTrackAvailableNotify(String uid, AliRtcEngine.AliRtcAudioTrack audioTrack, AliRtcEngine.AliRtcVideoTrack videoTrack){
handler.post(new Runnable() {
@Override
public void run() {
if(videoTrack == AliRtcVideoTrackCamera) {
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) {
FrameLayout fl_remote = findViewById(R.id.fl_remote);
fl_remote.removeAllViews();
mAliRtcEngine.setRemoteViewConfig(null, uid, AliRtcVideoTrackCamera);
}
}
});
}iOS
リモートユーザーがストリーミングを開始または停止すると、onRemoteTrackAvailableNotify コールバックがトリガーされます。このコールバックで、リモートビューを設定または削除します。サンプルコードは次のとおりです。
func onRemoteTrackAvailableNotify(_ uid: String, audioTrack: AliRtcAudioTrack, videoTrack: AliRtcVideoTrack) {
"onRemoteTrackAvailableNotify uid: \(uid) audioTrack: \(audioTrack) videoTrack: \(videoTrack)".printLog()
// リモートユーザーストリームの状態
if audioTrack != .no {
let videoView = self.videoViewList.first { $0.uidLabel.text == uid }
if videoView == nil {
_ = self.createVideoView(uid: uid)
}
}
if videoTrack != .no {
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 {
self.removeVideoView(uid: uid)
self.rtcEngine?.setRemoteViewConfig(nil, uid: uid, for: AliRtcVideoTrack.camera)
}
}3. ビジネスサービスがバイパス転送から混合ストリーム転送に更新
ビジネスサービスがビジネスアプリからストリーマーのルーム間バトル開始イベントを受け取った後、UpdateLiveMPUTask - 混合ストリーム転送タスクの更新 (新規) を呼び出して、ライブルーム A とライブルーム B の転送タスクを更新します。UserInfos フィールドに、ストリーマー A とストリーマー B のルーム番号とユーザー ID をそれぞれ渡します。Layout フィールドに、混合ストリームレイアウトとその他の必要なフィールドを渡して、混合ストリームを更新します。
ステップ 3: バトル終了
ストリーマー A とストリーマー B がルーム間バトルを終了するための基本的なワークフロー:
1. ストリーマー A とストリーマー B がルーム間ストリームプルを停止
ストリーマー A とストリーマー B はそれぞれルーム間ストリームプルインターフェイスを呼び出し、ターゲットルーム ID とユーザー ID を渡してルーム間ストリームプルを停止します。
Android
mAliRtcEngine.subscribeRemoteDestChannelStream(channelId, userId, AliRtcVideoTrackCamera, AliRtcAudioTrackMic, false);iOS
[self.rtcEngine subscribeRemoteDestChannelStream:channelId uid:userId videoTrack:AliRtcVideoTrackCamera audioTrack:AliRtcAudioTrackMic sub:NO];2. ビジネスサービスが混合ストリーム転送からバイパスストリーム転送に更新
ビジネスサービスがビジネスアプリからストリーマーのルーム間バトル終了イベントを受け取った後、UpdateLiveMPUTask を呼び出して、ライブルーム A とライブルーム B の混合ストリーム転送タスクを更新します。MixMode を 0 に設定し、その他の必要なフィールドとともに混合ストリームタスクをバイパス転送タスクに更新します。UpdateLiveMPUTask インターフェイスのパラメーターの詳細については、「UpdateLiveMPUTask - 混合ストリーム転送タスクの更新 (新規)」をご参照ください。