すべてのプロダクト
Search
ドキュメントセンター

ApsaraVideo Live:ストリーマーバトルの開発者ガイド

最終更新日:Nov 05, 2025

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 を渡します。

アプリケーションサーバ:

  1. Task1 を混合ストリームモードに更新し、ルーム Aユーザー Aルーム Bユーザー B、および混合ストリームレイアウト情報を渡します。

  2. Task2 を混合ストリームモードに更新し、ルーム Bユーザー Bルーム Aユーザー A、および混合ストリームレイアウト情報を渡します。

バトル終了

ストリーマー A: ルーム間ストリームプルインターフェイスを呼び出して、ストリームのプルを停止します。

ストリーマー B: ルーム間ストリームプルインターフェイスを呼び出して、ストリームのプルを停止します。

アプリケーションサーバ:

  1. task1 をバイパス転送モードに更新し、ルーム A からユーザー A のストリームのみを転送します。

  2. task2 をバイパス転送モードに更新し、ルーム B からユーザー 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 - 混合ストリーム転送タスクの更新 (新規)」をご参照ください。