全部产品
Search
文档中心

ApsaraVideo Live:Panduan pengembang untuk pertarungan streamer

更新时间:Nov 05, 2025

ApsaraVideo Live mendukung fitur pertarungan streamer. Fitur ini memungkinkan beberapa streamer dari ruang siaran langsung yang berbeda untuk memulai kompetisi waktu nyata, meningkatkan pengalaman streaming langsung bagi pemirsa. Topik ini menjelaskan cara mengimplementasikan pertarungan streamer berdasarkan streaming langsung RTC+CDN bypass dan menyediakan kode contoh terkait untuk membantu Anda dengan cepat mengintegrasikan fitur pertarungan streamer.

Ikhtisar solusi

Anda dapat mengimplementasikan pertarungan streamer lintas ruangan menggunakan kemampuan penarikan aliran lintas ruangan yang disediakan oleh ARTC SDK. Kemampuan ini menarik aliran audio dan video waktu nyata dari streamer di ruangan yang berbeda. Setiap streamer di ruang siaran langsung masing-masing kemudian memanggil antarmuka UpdateLiveMPUTask - Perbarui Tugas Penerusan Aliran Campuran (baru) untuk beralih ke mode aliran campuran. Dengan mengirimkan ChannelID ruangan target dan UserId streamer, aliran video kedua streamer dapat dicampur menjadi satu tampilan. Pemirsa CDN akan melihat perubahan tampilan dari tampilan streamer tunggal menjadi tampilan pertarungan dengan kedua streamer. Alur kerja untuk penyiaran streamer dan pertarungan adalah sebagai berikut:

Sebelum pertarungan

Streamer A: Menggunakan ARTC SDK untuk bergabung dengan Ruang A RTC dan mendorong aliran audio/video waktu nyata.

Server aplikasi: Memantau peristiwa perubahan aliran di Ruang A RTC. Ketika streamer mulai mendorong aliran, memanggil StartLiveMPUTask - Buat tugas penerusan aliran campuran (baru) untuk memulai tugas penerusan bypass Tugas1, mengirimkan URL ingest CDN untuk meneruskan aliran dari Ruang A RTC ke CDN.

Pemirsa reguler Ruang Siaran Langsung A: Menggunakan ApsaraVideo Player SDK dengan URL streaming CDN Ruang Siaran Langsung A untuk menarik dan memainkan aliran.

Streamer B: Menggunakan ARTC SDK untuk bergabung dengan Ruang B RTC dan mendorong aliran audio/video waktu nyata.

Server aplikasi: Memantau peristiwa perubahan aliran di Ruang B RTC. Ketika streamer mulai mendorong aliran, memanggil StartLiveMPUTask - Buat tugas penerusan aliran campuran (baru) untuk memulai tugas penerusan bypass Tugas2, mengirimkan URL ingest CDN untuk meneruskan aliran dari Ruang B RTC ke CDN.

Pemirsa reguler Ruang Siaran Langsung B: Menggunakan ApsaraVideo Player SDK dengan URL streaming CDN Ruang Siaran Langsung B untuk menarik dan memainkan aliran.

Selama pertarungan

Streamer A: Memanggil antarmuka penarikan aliran lintas ruangan ARTC SDK untuk mulai menarik aliran, mengirimkan Ruang B dan User B.

Streamer B: Memanggil antarmuka penarikan aliran lintas ruangan ARTC SDK untuk mulai menarik aliran, mengirimkan Ruang A dan User A.

Server aplikasi:

  1. Memperbarui Tugas1 ke mode aliran campuran, mengirimkan Ruang A dan User A, Ruang B dan User B, serta informasi tata letak aliran campuran.

  2. Memperbarui Tugas2 ke mode aliran campuran, mengirimkan Ruang B dan User B, Ruang A dan User A, serta informasi tata letak aliran campuran.

Akhir pertarungan

Streamer A: Memanggil antarmuka penarikan aliran lintas ruangan untuk menghentikan penarikan aliran.

Streamer B: Memanggil antarmuka penarikan aliran lintas ruangan untuk menghentikan penarikan aliran.

Server aplikasi:

  1. Memperbarui task1 ke mode penerusan bypass, hanya meneruskan aliran User A dari Room A.

  2. Memperbarui task2 ke mode penerusan bypass, hanya meneruskan aliran User B dari Room B.

Dalam skenario pertarungan streamer, pemirsa reguler tidak perlu melakukan operasi tambahan apa pun. Tampilan secara otomatis beralih dari tampilan streamer tunggal ke tampilan aliran campuran.

Langkah-langkah implementasi

Langkah 1: Streamer memulai penyiaran

Proses dasar untuk seorang streamer memulai penyiaran:

1. Streamer mendorong aliran ke ruang RTC

Streamer menggunakan ARTC SDK untuk mendorong aliran ke ruang RTC.

Android

Untuk langkah-langkah terperinci tentang menggunakan ARTC SDK untuk bergabung dengan ruang RTC dan mendorong aliran, lihat: Langkah-langkah Implementasi.

// Impor kelas ARTC terkait
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);
}
// Atur mode saluran
mAliRtcEngine.setChannelProfile(AliRtcEngine.AliRTCSdkChannelProfile.AliRTCSdkInteractiveLive);
mAliRtcEngine.setClientRole(AliRtcEngine.AliRTCSdkClientRole.AliRTCSdkInteractive);
mAliRtcEngine.setAudioProfile(AliRtcEngine.AliRtcAudioProfile.AliRtcEngineHighQualityMode, AliRtcEngine.AliRtcAudioScenario.AliRtcSceneMusicMode);

//Atur parameter pengkodean video
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);

//Atur callback terkait
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: Pastikan menangani pengecualian. SDK telah mencoba berbagai kebijakan pemulihan tetapi masih tidak dapat pulih. */
                    ToastHelper.showToast(VideoChatActivity.this, R.string.video_chat_connection_failed, Toast.LENGTH_SHORT);
                } else {
                    /* TODO: Tangani pengecualian sesuai kebutuhan. Kode bisnis ditambahkan, biasanya untuk statistik data dan perubahan UI. */
                }
            }
        });
    }
    @Override
    public void OnLocalDeviceException(AliRtcEngine.AliRtcEngineLocalDeviceType deviceType, AliRtcEngine.AliRtcEngineLocalDeviceExceptionType exceptionType, String msg){
        super.OnLocalDeviceException(deviceType, exceptionType, msg);
        /* TODO: Pastikan menangani pengecualian. Disarankan untuk memberi tahu pengguna tentang kesalahan perangkat ketika SDK telah mencoba semua kebijakan pemulihan tetapi masih tidak dapat menggunakan perangkat. */
        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: Pastikan menangani ini. Token akan segera kedaluwarsa. Bisnis perlu memicu mendapatkan informasi autentikasi baru untuk saluran dan pengguna saat ini, lalu atur refreshAuthInfo. */
    }

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

    //Hapus pengaturan kontrol rendering aliran video jarak jauh dalam callback onRemoteUserOffLineNotify
    @Override
    public void onRemoteUserOffLineNotify(String uid, AliRtcEngine.AliRtcUserOfflineReason reason){
        super.onRemoteUserOffLineNotify(uid, reason);
    }

    //Atur kontrol rendering aliran video jarak jauh dalam callback 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);
                        }
                    }
                }
            }
        });
    }

    /* Bisnis mungkin memicu situasi di mana perangkat yang berbeda bersaing untuk UserID yang sama, jadi ini juga perlu ditangani */
    @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);

//Pratinjau lokal
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();

//Gabung ruang RTC
mAliRtcEngine.joinChannel(token, null, null, null);
iOS

Untuk langkah-langkah terperinci tentang menggunakan ARTC SDK untuk bergabung dengan ruang RTC dan mendorong aliran, lihat: Langkah-langkah Implementasi.

// Impor kelas ARTC terkait
import AliVCSDK_ARTC

private var rtcEngine: AliRtcEngine? = nil
// Buat mesin dan atur callback
let engine = AliRtcEngine.sharedInstance(self, extras:nil)
...
self.rtcEngine = engine
// Atur mode saluran
engine.setChannelProfile(AliRtcChannelProfile.interactivelive)
engine.setClientRole(AliRtcClientRole.roleInteractive)
engine.setAudioProfile(AliRtcAudioProfile.engineHighQualityMode, audio_scene: AliRtcAudioScenario.sceneMusicMode)

//Atur parameter pengkodean video
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)

//Atur callback terkait
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) {
        // Pengguna jarak jauh online
        "onRemoteUserOlineNotify uid: \(uid)".printLog()
    }

    func onRemoteUserOffLineNotify(_ uid: String, offlineReason reason: AliRtcUserOfflineReason) {
        // Pengguna jarak jauh offline
        "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: Pastikan menangani ini. Token akan segera kedaluwarsa. Bisnis perlu memicu mendapatkan informasi autentikasi baru untuk saluran dan pengguna saat ini, lalu atur refreshAuthInfo. */
    }

    func onAuthInfoExpired() {
        "onAuthInfoExpired".printLog()

        /* TODO: Pastikan menangani ini. Beri tahu bahwa token tidak valid, dan lakukan keluar dari rapat dan lepaskan mesin. */
    }

    func onBye(_ code: Int32) {
        "onBye code: \(code)".printLog()

        /* TODO: Pastikan menangani ini. Bisnis mungkin memicu situasi di mana perangkat yang berbeda bersaing untuk UserID yang sama. */
    }

    func onLocalDeviceException(_ deviceType: AliRtcLocalDeviceType, exceptionType: AliRtcLocalDeviceExceptionType, message msg: String?) {
        "onLocalDeviceException deviceType: \(deviceType)  exceptionType: \(exceptionType)".printLog()

        /* TODO: Pastikan menangani ini. Disarankan untuk memberi tahu pengguna tentang kesalahan perangkat ketika SDK telah mencoba semua kebijakan pemulihan tetapi masih tidak dapat menggunakan perangkat. */
    }

    func onConnectionStatusChange(_ status: AliRtcConnectionStatus, reason: AliRtcConnectionStatusChangeReason) {
        "onConnectionStatusChange status: \(status)  reason: \(reason)".printLog()

        if status == .failed {
            /* TODO: Pastikan menangani ini. Disarankan untuk memberi tahu pengguna ketika SDK telah mencoba semua kebijakan pemulihan tetapi masih tidak dapat pulih. */
        }
        else {
            /* TODO: Tangani ini sesuai kebutuhan. Tambahkan kode bisnis, biasanya untuk statistik data dan perubahan UI. */
        }
    }
}


//Pratinjau lokal
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()

//Gabung ruang RTC
let ret = self.rtcEngine?.joinChannel(joinToken, channelId: nil, userId: nil, name: nil) { [weak self] errCode, channelId, userId, elapsed in
    if errCode == 0 {
        // sukses

    }
    else {
        // gagal
    }
    
    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. Server aplikasi memulai tugas penerusan untuk meneruskan aliran ruang RTC ke CDN

  • Server aplikasi membuat langganan untuk callback pesan ruang RTC untuk memantau peristiwa dorongan aliran streamer di ruangan tersebut. Untuk informasi API terperinci tentang berlangganan pesan ruang RTC, lihat CreateEventSub - Buat Langganan untuk Callback Pesan Ruangan.

  • Setelah menerima notifikasi bahwa streamer telah mendorong aliran ke ruang RTC, panggil OpenAPI streaming bypass StartLiveMPUTask untuk meneruskan aliran dari ruang RTC ke CDN. Untuk detail tentang API streaming bypass, lihat StartLiveMPUTask - Buat Tugas Pencampuran Aliran (baru).

    Catatan

    Ketika streamer mulai menyiarkan, Anda dapat mengatur MixMode ke 0, yang menunjukkan penerusan aliran tunggal tanpa transkoding. API memerlukan URL ingest streaming langsung, yang hanya mendukung protokol RTMP. Untuk informasi tentang cara menghasilkan URL ini, lihat Hasilkan URL Ingest dan Pemutaran.

  • Server aplikasi memantau callback pendorongan aliran CDN. Setelah aliran diteruskan ke CDN, ia mendistribusikan URL pemutaran streaming langsung ke pemirsa untuk memulai pemutaran. Untuk detail tentang callback pendorongan aliran CDN, lihat Pengaturan Callback.

3. Pemirsa menggunakan ApsaraVideo Player SDK untuk menarik dan memainkan aliran

Ketika pemirsa menerima notifikasi penarikan aliran dari server aplikasi, mereka membuat instance Pemutar ApsaraVideo dan menggunakan URL pemutaran streaming langsung untuk pemutaran. Untuk informasi API pemain terperinci dan penggunaannya, lihat ApsaraVideo Player SDK.

Catatan

Disarankan untuk mengubah URL pemutaran CDN untuk pemirsa reguler dari format RTMP menjadi format HTTP-FLV. Keduanya berisi konten yang sama tetapi menggunakan protokol transmisi yang berbeda. HTTP, sebagai protokol Internet utama, memiliki dasar optimalisasi jaringan yang lebih matang dan menggunakan port default 80/443, sehingga lebih mudah melewati firewall. Protokol RTMP lebih tua, dan port umumnya 1935 mungkin dibatasi, memengaruhi stabilitas pemutaran. Secara keseluruhan, HTTP-FLV unggul dari RTMP dalam hal kompatibilitas dan pengalaman pemutaran (seperti tersendat dan latensi), jadi disarankan untuk menggunakan HTTP-FLV sebagai pilihan pertama.

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");  // URL streaming CDN pemirsa.
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];

Langkah 2: Pertarungan lintas ruangan streamer

Alur kerja dasar untuk pertarungan lintas ruangan Streamer A dan Streamer B:

1. Streamer A dan Streamer B memulai penarikan aliran lintas ruangan

Streamer A dan Streamer B masing-masing memanggil antarmuka penarikan aliran lintas ruangan, mengirimkan ID ruangan target dan ID pengguna untuk memulai penarikan aliran lintas ruangan.

Android

mAliRtcEngine.subscribeRemoteDestChannelStream(channelId, userId, AliRtcVideoTrackCamera, AliRtcAudioTrackMic, true);

iOS

[self.rtcEngine subscribeRemoteDestChannelStream:channelId uid:userId videoTrack:AliRtcVideoTrackCamera audioTrack:AliRtcAudioTrackMic sub:YES];

2. Atur tampilan rendering untuk Streamer A dan Streamer B

Android

Atur callback terkait mAliRtcEngine.setRtcEngineNotify saat menginisialisasi mesin. Anda perlu mengatur tampilan jarak jauh untuk pengguna jarak jauh dalam callback onRemoteTrackAvailableNotify. Contoh kode adalah sebagai berikut:

@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

Ketika pengguna jarak jauh mulai atau berhenti streaming, callback onRemoteTrackAvailableNotify dipicu. Dalam callback ini, Anda mengatur atau menghapus tampilan jarak jauh. Contoh kode adalah sebagai berikut:

func onRemoteTrackAvailableNotify(_ uid: String, audioTrack: AliRtcAudioTrack, videoTrack: AliRtcVideoTrack) {
    "onRemoteTrackAvailableNotify uid: \(uid) audioTrack: \(audioTrack)  videoTrack: \(videoTrack)".printLog()
    // Status aliran pengguna jarak jauh
    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. Layanan bisnis memperbarui dari penerusan bypass ke penerusan aliran campuran

Setelah layanan bisnis menerima peristiwa dimulainya pertarungan lintas ruangan streamer dari aplikasi bisnis, ia memanggil UpdateLiveMPUTask - Perbarui Tugas Penerusan Aliran Campuran (baru) untuk memperbarui tugas penerusan untuk Ruang Siaran Langsung A dan Ruang Siaran Langsung B. Di bidang UserInfos, kirimkan nomor ruangan dan ID pengguna Streamer A dan Streamer B masing-masing. Di bidang Layout, kirimkan tata letak aliran campuran dan bidang lain yang diperlukan untuk memperbarui aliran campuran.

Langkah 3: Mengakhiri pertarungan

Alur kerja dasar untuk Streamer A dan Streamer B mengakhiri pertarungan lintas ruangan:

1. Streamer A dan Streamer B menghentikan penarikan aliran lintas ruangan

Streamer A dan Streamer B masing-masing memanggil antarmuka penarikan aliran lintas ruangan, mengirimkan ID ruangan target dan ID pengguna untuk menghentikan penarikan aliran lintas ruangan.

Android

mAliRtcEngine.subscribeRemoteDestChannelStream(channelId, userId, AliRtcVideoTrackCamera, AliRtcAudioTrackMic, false);

iOS

[self.rtcEngine subscribeRemoteDestChannelStream:channelId uid:userId videoTrack:AliRtcVideoTrackCamera audioTrack:AliRtcAudioTrackMic sub:NO];

2. Layanan bisnis memperbarui dari penerusan aliran campuran ke penerusan aliran bypass

Setelah layanan bisnis menerima peristiwa berakhirnya pertarungan lintas ruangan streamer dari aplikasi bisnis, ia memanggil UpdateLiveMPUTask untuk memperbarui tugas penerusan aliran campuran untuk Ruang Siaran Langsung A dan Ruang Siaran Langsung B. Atur MixMode ke 0 bersama dengan bidang lain yang diperlukan untuk memperbarui tugas aliran campuran menjadi tugas penerusan bypass. Untuk detail tentang parameter antarmuka UpdateLiveMPUTask, lihat: UpdateLiveMPUTask - Perbarui Tugas Penerusan Aliran Campuran (baru).