共同ストリーミングにより、視聴者はリアルタイムコミュニケーション (RTC) を介して共同ホストとしてライブストリーマーに参加でき、通常の視聴者は CDN を通じて視聴を継続できます。 この実装には、RTC ルームへのストリームのプッシュ、サーバーサイドのミキシングタスクの管理、CDN 再生と RTC 参加間の切り替え処理が含まれます。
共同ストリーミングの仕組み
ストリーマーとコラボストリーマーは、ARTC SDK を通じて RTC ルームにストリームをプッシュします。 アプリケーションサーバーはストリームの変更イベントを監視し、StartLiveMPUTask API を呼び出して RTC ルームから ApsaraVideo Live CDN にストリームを転送します。 通常の視聴者は、ApsaraVideo Player SDK を介して CDN ストリームをプルして再生します。
共同ストリーミングのライフサイクル
フェーズ | ストリーマー | コラボストリーマー | アプリケーションサーバー | 視聴者 |
共同ストリーミング前 | RTC ルームに参加し、ARTC SDK を介して音声/動画ストリームをプッシュします。 | ApsaraVideo Player を介して CDN ストリームを視聴します。 | ストリーマーのストリームプッシュを検出し、 | ApsaraVideo Player を介して CDN ストリームをプルして再生します。 |
共同ストリーミング中 | コラボストリーマーの RTC ストリームをプルしてレンダリングします。 | CDN の再生を停止し、RTC ルームに参加してストリームをプッシュし、ストリーマーの RTC ストリームをレンダリングします。 | コラボストリーマーのストリームプッシュを検出し、UpdateLiveMPUTask を呼び出して、両参加者用のレイアウトで | 操作は不要です。 ライブストリームは自動的に両参加者の表示に切り替わります。 |
共同ストリーミングの終了 | コラボストリーマーのストリームのプルを停止します。 | RTC ルームから退出して RTC インスタンスを破棄し、CDN 再生に切り替えます。 | コラボストリーマーの退出を検出し、UpdateLiveMPUTask を呼び出して | 操作は不要です。 ライブストリームは自動的にストリーマーのみの表示に戻ります。 |
RTC ルームのストリーム変更イベントを監視するには、CreateEventSub を使用してサブスクリプションを作成します。
主要 API
API | 目的 | 呼び出しタイミング |
RTC ルームのストリームを CDN へ転送 | ストリーマーがストリームをプッシュした後 | |
バイパスとストリームミキシングの切り替え | コラボストリーマーが参加または退出したとき | |
RTC ルームのストリーム変更イベントのサブスクライブ | ブロードキャスト開始前 (推奨) |
ステップ 1:ブロードキャストの開始 (ストリーマー)
1. RTC ルームへのストリームのプッシュ
ストリーマーは ARTC SDK を初期化し、エンコーディングパラメーターを設定して、RTC ルームに参加します。
ARTC SDK の完全な統合ガイドについては、以下をご参照ください:
エンジンの初期化
Android
// 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);iOS (Swift)
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)音声と動画のエンコーディング設定
Android
// 音声:高品質音楽モード
mAliRtcEngine.setAudioProfile(
AliRtcEngine.AliRtcAudioProfile.AliRtcEngineHighQualityMode,
AliRtcEngine.AliRtcAudioScenario.AliRtcSceneMusicMode
);
// 動画:720x1280、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)
// 音声:高品質音楽モード
engine.setAudioProfile(
AliRtcAudioProfile.engineHighQualityMode,
audio_scene: AliRtcAudioScenario.sceneMusicMode
)
// 動画:720x1280、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)ローカルストリームのパブリッシュとリモートストリームのサブスクライブ
Android
// ローカルの音声と動画をパブリッシュ
mAliRtcEngine.publishLocalAudioStream(true);
mAliRtcEngine.publishLocalVideoStream(true);
// すべてのリモートストリームをサブスクライブ
mAliRtcEngine.setDefaultSubscribeAllRemoteAudioStreams(true);
mAliRtcEngine.subscribeAllRemoteAudioStreams(true);
mAliRtcEngine.setDefaultSubscribeAllRemoteVideoStreams(true);
mAliRtcEngine.subscribeAllRemoteVideoStreams(true);iOS (Swift)
// ローカルの音声と動画をパブリッシュ
engine.publishLocalVideoStream(true)
engine.publishLocalAudioStream(true)
// すべてのリモートストリームをサブスクライブ
engine.setDefaultSubscribeAllRemoteAudioStreams(true)
engine.subscribeAllRemoteAudioStreams(true)
engine.setDefaultSubscribeAllRemoteVideoStreams(true)
engine.subscribeAllRemoteVideoStreams(true)コールバックの登録
接続ステータスの変更、リモートユーザーイベント、およびエラー状態を処理します。
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) {
// 回復不能な接続障害の処理
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);
// SDK がデバイスを回復できない場合にユーザーに通知
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();
// トークンが期限切れになる前に更新します。
// 現在のチャンネルとユーザーの新しい認証情報を取得し、
// 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);
// リモート動画レンダリングビューの削除
}
@Override
public void onRemoteTrackAvailableNotify(String uid,
AliRtcEngine.AliRtcAudioTrack audioTrack,
AliRtcEngine.AliRtcVideoTrack videoTrack){
// リモート動画レンダリングの設定または削除。
// ステップ 2 の「ストリーマーの動画のレンダリング」をご参照ください。
}
@Override
public void onBye(int code){
// セッション終了の処理 (例:同じ 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) {
// リモート動画レンダリングの設定または削除。
// ステップ 2 の「ストリーマーの動画のレンダリング」をご参照ください。
}
func onAuthInfoWillExpire() {
// トークンが期限切れになる前に更新します。
// 現在のチャンネルとユーザーの新しい認証情報を取得し、
// refreshAuthInfo を呼び出します。
}
func onAuthInfoExpired() {
// トークンが無効です。 チャンネルから退出してエンジンを解放します。
}
func onBye(_ code: Int32) {
// セッション終了の処理 (例:同じ UserID を使用する別のデバイス)
}
func onLocalDeviceException(_ deviceType: AliRtcLocalDeviceType,
exceptionType: AliRtcLocalDeviceExceptionType, message msg: String?) {
// SDK がデバイスを回復できない場合にユーザーに通知
}
func onConnectionStatusChange(_ status: AliRtcConnectionStatus,
reason: AliRtcConnectionStatusChangeReason) {
if status == .failed {
// 回復不能な接続障害の処理
}
}
}ローカルプレビューの設定とルームへの参加
Android
// ローカルプレビュー
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 (Swift)
// ローカルプレビュー
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 {
// 参加失敗の処理
}
}2. RTC ストリームの CDN への転送 (アプリケーションサーバー)
ストリーマーが RTC ルームにストリームをプッシュした後:
ストリーム変更イベントのサブスクライブ。 CreateEventSub を呼び出して、ユーザーが RTC ルームでストリームをプッシュまたは停止したときに通知を受け取ります。
バイパスストリーミングタスクの開始。 ストリームプッシュ通知を受け取った後、
MixMode=0(トランスコーディングなしの単一ストリーム転送) を指定して StartLiveMPUTask を呼び出します。 CDN のインジェスト URL を渡します。この URL は RTMP プロトコルを使用する必要があります。 この URL を生成するには、「インジェスト URL と再生 URL の生成」をご参照ください。再生 URL の配信。 CDN ストリームのプッシュコールバックを監視して転送が成功したことを検出し、再生 URL を視聴者に配信します。 コールバックの設定の詳細については、「コールバック設定」をご参照ください。
3. CDN ストリームの再生 (視聴者)
視聴者がアプリケーションサーバーから再生 URL を受け取ったら、ApsaraVideo Player インスタンスを作成して再生を開始します。 完全な Player API リファレンスについては、「ApsaraVideo Player SDK」をご参照ください。
CDN 再生 URL には、RTMP の代わりに HTTP-FLV を使用してください。 どちらも同じコンテンツを配信しますが、HTTP-FLV は標準ポート 80/443 を使用し、より優れたネットワーク最適化を提供し、スタッタリングが少なく低遅延でより安定した再生を提供します。 RTMP のデフォルトポート 1935 は、ファイアウォールによってブロックされる可能性があります。
Android
AliPlayer aliPlayer = AliPlayerFactory.createAliPlayer(context);
aliPlayer.setAutoPlay(true);
UrlSource urlSource = new UrlSource();
// CDN 再生には HTTP-FLV を使用
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;
// CDN 再生には HTTP-FLV を使用
AVPUrlSource *source = [[AVPUrlSource alloc] urlWithString:@"http://test.alivecdn.com/live/streamId.flv?auth_key=XXX"];
[self.cdnPlayer setUrlSource:source];
[self.cdnPlayer prepare];ステップ 2:共同ストリーミングの開始 (視聴者が共同ホストとして参加)
コラボストリーマー側
1. CDN 再生の停止
RTC ルームに参加する前に、プレーヤーインスタンスを破棄します。
Android
aliPlayer.stop();
aliPlayer = null;iOS
[self.cdnPlayer stop];
[self.cdnPlayer clearScreen];
self.cdnPlayer.playerView = nil;CDN プレーヤーの停止は、オプションのクリーンアップ手順ではなく、必須です。 CDN プレーヤーが実行し続けると、コラボストリーマーのデバイスは CDN の音声と RTC の音声を同時に再生し、エコーや音声の二重再生が発生します。 CDN プレーヤーを停止すると、デバイスは RTC の音声のみを再生し、エコーはなくなります。
2. RTC ルームへの参加
コラボストリーマーは、ストリーマーと同じプロセスを使用して RTC ルームにストリームをプッシュします。
3. ストリーマーの動画のレンダリング
ストリーマーのリモートトラックが利用可能になったら、onRemoteTrackAvailableNotify コールバックでレンダリングビューを設定します。
Android
@Override
public void onRemoteTrackAvailableNotify(String uid,
AliRtcEngine.AliRtcAudioTrack audioTrack,
AliRtcEngine.AliRtcVideoTrack videoTrack){
handler.post(() -> {
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 (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 {
// 動画ビューを作成または再利用し、レンダリングを設定
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)
}
}ストリーマー側
ストリーマーは、上記と同じ onRemoteTrackAvailableNotify コールバックを使用してコラボストリーマーの動画をレンダリングします。
アプリケーションサーバー
コラボストリーマーがストリームをプッシュした後、UpdateLiveMPUTask を呼び出して既存のバイパスストリーミングタスクを更新します:
MixModeを1(ストリームミキシング) に設定します。ストリーマーとコラボストリーマーの両方のストリームミキシングレイアウトを設定します。
ステップ 3:共同ストリーミングの終了
コラボストリーマー側
1. 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 = nil2. CDN 再生の再開
新しい ApsaraVideo Player インスタンスを作成し、CDN ストリームを再生します。
ストリーマー側
ストリーマーは、コラボストリーマーの音声および動画ストリームのプルを停止します。 レンダリングビューは、videoTrack が AliRtcVideoTrackNo に変更されると、onRemoteTrackAvailableNotify コールバックで自動的にクリーンアップされます。
アプリケーションサーバー
コラボストリーマーが RTC ルームから退出した後、UpdateLiveMPUTask を呼び出してストリームミキシングタスクを更新します:
MixModeを0(バイパス、単一ストリーム転送) に戻します。
CDN ライブストリームビューは、両方の参加者の表示からストリーマーのみの表示に自動的に切り替わります。