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

ApsaraVideo Live:iOS での音声・ビデオ通話の実装

最終更新日:Dec 07, 2025

このガイドでは、Alibaba Real-Time Communication (ARTC) のソフトウェア開発キット (SDK) を iOS プロジェクトに統合し、インタラクティブライブストリーミングやビデオ通話などのユースケースに適したリアルタイム音声・ビデオアプリケーションを構築する方法を説明します。

機能説明

開始する前に、以下の主要な概念を理解しておくと役立ちます:

  • ARTC SDK:Alibaba Cloud が提供する、開発者がリアルタイム音声・ビデオ機能を実装するためのソフトウェア開発キット (SDK) です。

  • GRTN:Alibaba Cloud が提供する Global Real-time Transport Network です。超低遅延、高品質、かつ安全な音声・ビデオ通信サービスを提供します。

  • チャンネル:ユーザーが参加して通信するための仮想的なルームです。同じチャンネル内のすべてのユーザーはリアルタイムで対話できます。

  • ホスト:チャンネル内で音声・ビデオストリームをパブリッシュし、他のホストがパブリッシュしたストリームをサブスクライブできるユーザーです。

  • 視聴者:チャンネル内の音声・ビデオストリームをサブスクライブできますが、ストリームをパブリッシュすることはできないユーザーです。

リアルタイム音声・ビデオインタラクションを実装するための基本的なプロセスは以下の通りです:

  1. チャンネルに参加するには、setChannelProfile を呼び出してチャンネルプロファイルを指定し、次に `joinChannel` を呼び出します。

    • ビデオ通話シナリオ:すべてのユーザーがホストであり、ストリームのパブリッシュとサブスクライブが可能です。

    • インタラクティブライブストリーミングシナリオ:setClientRole を呼び出してユーザーロールを設定します。ストリームをパブリッシュする必要があるユーザーにはロールをホストに設定し、ストリームのサブスクライブのみが必要なユーザーにはロールを視聴者に設定します。

  2. チャンネルに参加した後、ユーザーはロールに基づいて異なるパブリッシュおよびサブスクライブ動作をします:

    • チャンネル内のすべてのユーザーは音声・ビデオストリームを受信できます。

    • ホストはチャンネル内で音声・ビデオストリームをパブリッシュできます。

    • 視聴者がストリームをパブリッシュする必要がある場合は、setClientRole メソッドを呼び出してユーザーのロールをホストに切り替えることができます。

サンプルプロジェクト

ARTC SDK は、リアルタイム音声・ビデオアプリケーション向けのオープンソースのサンプルプロジェクトを提供しています。

環境要件

デモプロジェクトを実行する前に、開発環境が以下の要件を満たしていることを確認してください:

  • 開発ツール:Xcode 14.0 以降。最新の公式バージョンを推奨します。

  • 推奨構成:CocoaPods 1.9.3 以降。

  • テストデバイス:iOS 9.0 以降を実行しているデバイス。

説明

エミュレーターはすべての機能をサポートしていない可能性があるため、テストには物理デバイスを使用することを推奨します。

  • ネットワーク:安定したインターネット接続。

  • アプリケーション設定:ARTC アプリケーションの AppID と AppKey を取得します。

プロジェクトの作成 (任意)

このセクションでは、新しいプロジェクトを作成し、音声・ビデオインタラクションに必要な権限を追加する方法について説明します。すでにプロジェクトがある場合は、このセクションをスキップできます。

  1. Xcode を開き、[File] > [New] > [Project] に移動し、[App] テンプレートを選択します。次の画面で、[Interface] を [Storyboard] に、[Language] を [Swift] に設定します。

image.png

  1. Bundle IdentifierSigningMinimum Deployments など、必要に応じてプロジェクト設定を構成します。

プロジェクトの構成

ステップ 1:SDK のインポート

CocoaPods を使用した自動統合 (推奨)

  1. ターミナルを開き、開発デバイスに CocoaPods をインストールします。すでにインストールされている場合は、このステップをスキップできます。

sudo gem install cocoapods
  1. ターミナルを開き、プロジェクトのルートディレクトリに移動して、次のコマンドを実行して Podfile を作成します:

pod init
  1. 生成された Podfile を開いて編集し、ARTC SDK への依存関係を追加します。最新バージョンは 7.9.1 です。

target 'MyApp' do
  use_frameworks!
  # ${latest version} を特定のバージョン番号に置き換えます。
  pod 'AliVCSDK_ARTC', '~> ${latest version}'
end
  1. ターミナルウィンドウで、次のコマンドを実行してプロジェクト内の CocoaPods 依存ライブラリを更新します。

pod install
  1. コマンドが正常に実行されると、プロジェクトフォルダに .xcworkspace 拡張子のプロジェクトファイルが生成されます。このファイルをダブルクリックして Xcode でプロジェクトを開くことができます。ワークスペースは、統合された CocoaPods の依存関係を自動的に読み込みます。

    image

SDK をダウンロードして手動で統合

  1. SDK ダウンロードセクションで、最新の ARTC SDK ファイルをダウンロードして解凍します。

  2. 解凍した SDK パッケージからプロジェクトディレクトリにフレームワークファイルをコピーします。

  3. Xcode でプロジェクトを開きます。[File] -> [Add Files to "xxx"] を選択して、SDK ライブラリファイルをプロジェクトに追加します。

    image

  4. ターゲットを選択し、インポートしたフレームワークファイルを 「Embed & Sign」 に設定します。

image

ステップ 2:権限の設定

  • カメラとマイクの権限を追加します。

Info.plist ファイルに、Privacy - Camera Usage DescriptionPrivacy - Microphone Usage Description の権限を追加します。

image.png

  • バックグラウンド音声キャプチャモードを有効にします。(任意)

次の図に示すように、Audio, AirPlay, and Picture in Picture を選択します。

image.png

ステップ 3:ユーザーインターフェースの作成

リアルタイムインタラクションシナリオに適したユーザーインターフェースを作成します。たとえば、複数人でのビデオ通話では、ScrollView を作成できます。ユーザーが通話に参加すると、このコンテナーにビデオビューを追加できます。ユーザーが退出すると、そのビデオビューを削除してレイアウトを更新できます。

コード例

class VideoCallMainVC: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        // ビューのロード後に追加のセットアップを行います。
        self.title = self.channelId
        
        self.setup()
        self.startPreview()
        self.joinChannel()
    }
    
    override func viewDidDisappear(_ animated: Bool) {
        super.viewDidDisappear(animated)
        
        self.leaveAnddestroyEngine()
    }
    
    @IBOutlet weak var contentScrollView: UIScrollView!
    var videoViewList: [VideoView] = []

    // ビデオ通話のレンダリングビューを作成し、contentScrollView に追加します。
    func createVideoView(uid: String) -> VideoView {
        let view = VideoView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
        view.uidLabel.text = uid
        
        self.contentScrollView.addSubview(view)
        self.videoViewList.append(view)
        self.updateVideoViewsLayout()
        return view
    }

    // contentScrollView からビデオ通話のレンダリングビューを削除します。
    func removeVideoView(uid: String) {
        let videoView = self.videoViewList.first { $0.uidLabel.text == uid }
        if let videoView = videoView {
            videoView.removeFromSuperview()
            self.videoViewList.removeAll(where: { $0 == videoView})
            self.updateVideoViewsLayout()
        }
    }
    // contentScrollView のサブビューのレイアウトを更新します。
    func updateVideoViewsLayout() {
        let margin = 24.0
        let width = (self.contentScrollView.bounds.width - margin * 3.0) / 2.0
        let height = width // width * 16.0 / 9.0
        let count = 2
        for i in 0..<self.videoViewList.count {
            let view = self.videoViewList[i]
            let x = Double(i % count) * (width + margin) + margin
            let y = Double(i / count) * (height + margin) + margin
            view.frame = CGRect(x: x, y: y, width: width, height: height)
        }
        self.contentScrollView.contentSize = CGSize(width: self.contentScrollView.bounds.width, height: margin + Double(self.videoViewList.count / count + 1) * height + margin)
    }
}

実装

このセクションでは、ARTC SDK を使用して基本的なリアルタイム音声・ビデオアプリケーションを構築する方法を説明します。完全なコードサンプルをプロジェクトにコピーして、機能をテストできます。以下の手順では、コア API の呼び出しについて説明します。

次の図は、ビデオ通話の実装における基本的なワークフローを示しています:

以下は、基本的なビデオ通話の完全なリファレンスコードです:

コード例

class VideoCallMainVC: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        // ビューのロード後に追加のセットアップを行います。
        self.title = self.channelId

        self.setup()
        self.startPreview()
        self.joinChannel()
    }

    override func viewDidDisappear(_ animated: Bool) {
        super.viewDidDisappear(animated)

        self.leaveAnddestroyEngine()
    }

    @IBOutlet weak var contentScrollView: UIScrollView!
    var videoViewList: [VideoView] = []

    var channelId: String = ""
    var userId: String = ""

    var rtcEngine: AliRtcEngine? = nil

    var joinToken: String? = nil

    func setup() {

        // エンジンを作成して初期化します。
        let engine = AliRtcEngine.sharedInstance(self, extras:nil)

        // ログレベルを設定します。
        engine.setLogLevel(.info)

        // チャンネルプロファイルをインタラクティブモードに設定します。すべての RTC シナリオで AliRtcInteractivelive を使用します。
        engine.setChannelProfile(AliRtcChannelProfile.interactivelive)
        // クライアントロールを設定します。パブリッシュとサブスクライブの両方が必要なユーザーには AliRtcClientRoleInteractive を使用し、サブスクライブのみのユーザーには AliRtcClientRolelive を使用します。
        engine.setClientRole(AliRtcClientRole.roleInteractive)

        // オーディオプロファイルを設定します。デフォルトは高品質モード (AliRtcEngineHighQualityMode) と音楽シナリオ (AliRtcSceneMusicMode) です。
        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)

        // デフォルトでは、SDK はビデオストリームをパブリッシュします。publishLocalVideoStream(true) の呼び出しは任意です。
        engine.publishLocalVideoStream(true)
        // デフォルトでは、SDK はオーディオストリームをパブリッシュします。ビデオ通話の場合、publishLocalAudioStream(true) の呼び出しは任意です。
        // 音声のみの通話の場合は、publishLocalVideoStream(false) を呼び出してビデオのパブリッシュを停止します。
        engine.publishLocalAudioStream(true)

        // デフォルトでリモートの音声・ビデオストリームをサブスクライブするように設定します。
        engine.setDefaultSubscribeAllRemoteAudioStreams(true)
        engine.subscribeAllRemoteAudioStreams(true)
        engine.setDefaultSubscribeAllRemoteVideoStreams(true)
        engine.subscribeAllRemoteVideoStreams(true)

        self.rtcEngine = engine
    }

    func joinChannel() {

        // 単一パラメーターのトークンで参加します。
        if let joinToken = self.joinToken {
            let msg =  "JoinWithToken: \(joinToken)"

            let param = AliRtcChannelParam()
            let ret = self.rtcEngine?.joinChannel(joinToken, channelParam: param) { [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)
            }
            return
        }
    }
    
    func startPreview() {
        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()
    }
    
    func leaveAnddestroyEngine() {
        self.rtcEngine?.stopPreview()
        self.rtcEngine?.leaveChannel()
        AliRtcEngine.destroy()
        self.rtcEngine = nil
    }
    
    // ビデオ通話のレンダリングビューを作成し、contentScrollView に追加します。
    func createVideoView(uid: String) -> VideoView {
        let view = VideoView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
        view.uidLabel.text = uid
        
        self.contentScrollView.addSubview(view)
        self.videoViewList.append(view)
        self.updateVideoViewsLayout()
        return view
    }
    
    // contentScrollView からビデオ通話のレンダリングビューを削除します。
    func removeVideoView(uid: String) {
        let videoView = self.videoViewList.first { $0.uidLabel.text == uid }
        if let videoView = videoView {
            videoView.removeFromSuperview()
            self.videoViewList.removeAll(where: { $0 == videoView})
            self.updateVideoViewsLayout()
        }
    }
    
    // contentScrollView のサブビューのレイアウトを更新します。
    func updateVideoViewsLayout() {
        let margin = 24.0
        let width = (self.contentScrollView.bounds.width - margin * 3.0) / 2.0
        let height = width // width * 16.0 / 9.0
        let count = 2
        for i in 0..<self.videoViewList.count {
            let view = self.videoViewList[i]
            let x = Double(i % count) * (width + margin) + margin
            let y = Double(i / count) * (height + margin) + margin
            view.frame = CGRect(x: x, y: y, width: width, height: height)
        }
        self.contentScrollView.contentSize = CGSize(width: self.contentScrollView.bounds.width, height: margin + Double(self.videoViewList.count / count + 1) * height + margin)
    }
    
    /*
    // MARK: - ナビゲーション

    // ストーリーボードベースのアプリケーションでは、ナビゲーションの前に少し準備をしたいことがよくあります
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        // segue.destination を使用して新しいビューコントローラーを取得します。
        // 選択したオブジェクトを新しいビューコントローラーに渡します。
    }
    */

}

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()
        // リモートユーザーのストリームステータス。
        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)
        }
    }
    
    func onAuthInfoWillExpire() {
        "onAuthInfoWillExpire".printLog()
        
        /* TODO: 必須の処理です。トークンの有効期限が近づいています。アプリは現在のチャンネルとユーザー用の新しいトークンを取得し、refreshAuthInfo を呼び出す必要があります。 */
    }
    
    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 更新のために、ここにビジネスロジックを追加できます。 */
        }
    }
}

完全なサンプルコードについては、「iOS 用 ARTC デモの実行」をご参照ください。

1. 権限のリクエスト

SDK は通話開始時に必要な権限を確認しますが、スムーズなユーザーエクスペリエンスを確保するために、通話を開始する前にカメラとマイクの権限を確認することを推奨します。

func checkMicrophonePermission(completion: @escaping (Bool) -> Void) {
    let status = AVCaptureDevice.authorizationStatus(for: .audio)
    
    switch status {
    case .notDetermined:
        AVCaptureDevice.requestAccess(for: .audio) { granted in
            completion(granted)
        }
    case .authorized:
        completion(true)
    default:
        completion(false)
    }
}

func checkCameraPermission(completion: @escaping (Bool) -> Void) {
    let status = AVCaptureDevice.authorizationStatus(for: .video)
    
    switch status {
    case .notDetermined:
        AVCaptureDevice.requestAccess(for: .video) { granted in
            completion(granted)
        }
    case .authorized:
        completion(true)
    default:
        completion(false)
    }
}

// 使用例
checkMicrophonePermission { granted in
    if granted {
        print("マイクへのアクセスが許可されました。")
    } else {
        print("マイクへのアクセスが拒否されました。")
    }
}

checkCameraPermission { granted in
    if granted {
        print("カメラへのアクセスが許可されました。")
    } else {
        print("カメラへのアクセスが拒否されました。")
    }
}

2. 認証トークンの取得

ARTC チャンネルに参加するには、ユーザーの ID を検証するための認証トークンが必要です。トークンの生成方法の詳細については、「トークンベースの認証」をご参照ください。トークンは、単一パラメーター方式または複数パラメーター方式で生成できます。使用する方法によって、呼び出す必要がある joinChannel API が決まります。

本番環境の場合:

トークンの生成には AppKey が必要であり、クライアント側にハードコーディングするとセキュリティリスクが生じます。本番環境では、サーバーでトークンを生成し、クライアントに送信することを強く推奨します。

開発およびデバッグの場合:

開発中に、ビジネスサーバーにまだトークンを生成するロジックがない場合は、一時的に APIExample のトークン生成ロジックを使用して一時的なトークンを作成できます。リファレンスコードは次のとおりです:

class ARTCTokenHelper: NSObject {

    /**
    * RTC AppId
    */
    public static let AppId = "<RTC AppId>"

    /**
    * RTC AppKey
    */
    public static let AppKey = "<RTC AppKey>"

    /**
    * channelId、userId、timestamp に基づいてチャンネルに参加するための複数パラメーターのトークンを生成します。
    */
    public func generateAuthInfoToken(appId: String = ARTCTokenHelper.AppId, appKey: String =  ARTCTokenHelper.AppKey, channelId: String, userId: String, timestamp: Int64) -> String {
        let stringBuilder = appId + appKey + channelId + userId + "\(timestamp)"
        let token = ARTCTokenHelper.GetSHA256(stringBuilder)
        return token
    }

    /**
    * channelId、userId、nonce に基づいてチャンネルに参加するための単一パラメーターのトークンを生成します。
    */
    public func generateJoinToken(appId: String = ARTCTokenHelper.AppId, appKey: String =  ARTCTokenHelper.AppKey, channelId: String, userId: String, timestamp: Int64, nonce: String = "") -> String {
        let token = self.generateAuthInfoToken(appId: appId, appKey: appKey, channelId: channelId, userId: userId, timestamp: timestamp)

        let tokenJson: [String: Any] = [
            "appid": appId,
            "channelid": channelId,
            "userid": userId,
            "nonce": nonce,
            "timestamp": timestamp,
            "token": token
        ]

        if let jsonData = try? JSONSerialization.data(withJSONObject: tokenJson, options: []),
        let base64Token = jsonData.base64EncodedString() as String? {
            return base64Token
        }

        return ""
    }

    /**
    * SHA256 を使用して文字列に署名します。
    */
    private static func GetSHA256(_ input: String) -> String {
        // 入力文字列をデータに変換します。
        let data = Data(input.utf8)

        // ハッシュ結果を格納するバッファーを作成します。
        var hash = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH))

        // SHA-256 ハッシュを計算します。
        data.withUnsafeBytes {
            _ = CC_SHA256($0.baseAddress, CC_LONG(data.count), &hash)
        }

        // ハッシュを 16 進数の文字列に変換します。
        return hash.map { String(format: "%02hhx", $0) }.joined()
    }

}

3. ARTC SDK コンポーネントのインポート

// ARTC モジュールをインポートします。
import AliVCSDK_ARTC

4. エンジンの作成と初期化

  • RTC エンジンの作成

    <a baseurl="t2974573_v2_2_1.xdita" data-node="4087266" data-root="16090" data-tag="xref" href="t2309850.xdita#2db21d4f16s77" id="ec8b2fb525ugj">sharedInstance</a> メソッドを呼び出して AliRTCEngine インスタンスを作成します。

    private var rtcEngine: AliRtcEngine? = nil
    
    // エンジンを作成し、デリゲートを設定します。
    let engine = AliRtcEngine.sharedInstance(self, extras:nil)
    ...
    self.rtcEngine = engine
  • エンジンの初期化

    • setChannelProfile を呼び出して、チャンネルを AliRTCInteractiveLive (インタラクティブモード) に設定します。

      ビジネスニーズに応じて、インタラクティブなエンターテインメントシナリオに適したインタラクティブモード、または 1 対 1 や 1 対多の通話に適した通信モードを選択できます。適切なモードを選択することで、スムーズなユーザーエクスペリエンスとネットワークリソースの効率的な利用が保証されます。

      モード

      パブリッシュ

      サブスクライブ

      説明

      インタラクティブモード

      1. ロールによって制限されます。ストリーマーロールのユーザーのみがストリームをパブリッシュできます。

      2. 参加者はセッション中に柔軟にロールを切り替えることができます。

      ロールの制限はありません。すべての参加者がストリームをサブスクライブする権限を持っています。

      1. インタラクティブモードでは、ストリーマーがチャンネルに参加または退出したり、ストリームのパブリッシュを開始したりするなどのイベントが、リアルタイムで視聴者に通知されます。これにより、視聴者はストリーマーのステータスを把握できます。逆に、視聴者のアクティビティはストリーマーに通知されないため、ストリームが中断されることはありません。

      2. このモードでは、ストリーマーがインタラクションを担当し、視聴者は主にコンテンツを消費し、通常はインタラクションに参加しません。ビジネスニーズが変更される可能性がある場合は、デフォルトでインタラクティブモードを使用することを検討してください。その柔軟性により、ユーザーロールを調整することで、さまざまなインタラクション要件に適応できます。

      通信モード

      ロールの制限はありません。すべての参加者がストリームをパブリッシュする権限を持っています。

      ロールの制限はありません。すべての参加者がストリームをサブスクライブする権限を持っています。

      1. 通信モードでは、参加者はお互いの存在を認識します。

      2. このモードはユーザーロールを区別しませんが、機能的にはインタラクティブモードのストリーマーロールと同等です。目的は操作を簡素化することであり、これによりユーザーはより少ない API 呼び出しで目的の機能を実現できます。

    • setClientRole を呼び出して、ユーザーロールを AliRTCSdkInteractive (ストリーマー) または AliRTCSdkLive (視聴者) に設定します。ストリーマーロールはデフォルトでストリームをパブリッシュおよびサブスクライブすることに注意してください。視聴者ロールはサブスクライブのみで、ローカルプレビューとパブリッシュはデフォルトで無効になっています。

      注:ユーザーがストリーマーから視聴者に切り替わると、システムはローカルの音声・ビデオストリームのパブリッシュを停止しますが、既存のサブスクリプションには影響しません。ユーザーが視聴者からストリーマーに切り替わると、システムはローカルの音声・ビデオストリームのパブリッシュを開始し、既存のサブスクリプションには影響しません。

      // チャンネルプロファイルをインタラクティブモードに設定します。すべての RTC シナリオで AliRtcInteractivelive を使用します。
      engine.setChannelProfile(AliRtcChannelProfile.interactivelive)
      // クライアントロールを設定します。パブリッシュとサブスクライブの両方が必要なユーザーには AliRtcClientRoleInteractive を使用し、サブスクライブのみのユーザーには AliRtcClientRolelive を使用します。
      engine.setClientRole(AliRtcClientRole.roleInteractive)
  • 共通コールバックの実装

    SDK は、動作中に問題が発生した場合、まず内部のリトライメカニズムを使用して自動的に回復を試みます。解決できないエラーについては、SDK は定義済みのコールバックインターフェースを通じてアプリケーションに通知します。

    以下は、SDK が処理できない問題に対する主要なコールバックです。アプリケーションはこれらのコールバックをリッスンし、応答する必要があります:

    例外の原因

    コールバックとパラメーター

    解決策

    説明

    認証の失敗

    onJoinChannelResult の result が AliRtcErrJoinBadToken を返す

    トークンが正しいか確認します。

    ユーザーが API を呼び出したときに認証が失敗した場合、API のコールバックは認証失敗のエラーメッセージを返します。

    トークンの有効期限が近い

    onAuthInfoWillExpire

    新しいトークンを取得し、refreshAuthInfo を呼び出して情報を更新します。

    トークンの有効期限エラーは、API の呼び出し時または実行時に発生する可能性があります。エラーは API コールバックまたは別のエラーコールバックを通じて報告されます。

    トークンの有効期限切れ

    onAuthInfoExpired

    チャンネルに再参加します。

    トークンの有効期限エラーは、API の呼び出し時または実行時に発生する可能性があります。エラーは API コールバックまたは別のエラーコールバックを通じて報告されます。

    ネットワーク接続の問題

    onConnectionStatusChange コールバックが AliRtcConnectionStatusFailed を返す

    チャンネルに再参加します。

    SDK は短いネットワーク切断から自動的に回復できます。切断時間がしきい値を超えると、タイムアウトします。アプリはネットワークステータスを確認し、ユーザーに再参加を促す必要があります。

    チャンネルから強制退出

    onBye

    • AliRtcOnByeUserReplaced:別のユーザーが同じ userId で参加したかどうかを確認します。

    • AliRtcOnByeBeKickedOut:ユーザーがチャンネルから強制退出させられたため、再参加する必要があります。

    • AliRtcOnByeChannelTerminated:チャンネルが終了したため、ユーザーは再参加する必要があります。

    RTC サービスでは、管理者が参加者を削除できます。

    ローカルデバイスの例外

    onLocalDeviceException

    アプリの権限とハードウェアが正しく動作しているかを確認します。

    SDK が解決できないローカルデバイスの例外が発生した場合、コールバックを介してアプリに通知します。その後、アプリはデバイスのステータスを確認するために介入する必要があります。

    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 更新のために、ここにビジネスロジックを追加できます。 */
            }
        }
    }

5. 音声・ビデオプロパティの設定

  • 音声プロパティの設定

    setAudioProfile を呼び出して、音声エンコーディングモードとシナリオを設定します。

    // オーディオプロファイルを設定します。デフォルトは高品質モード (AliRtcEngineHighQualityMode) と音楽シナリオ (AliRtcSceneMusicMode) です。
    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)

6. パブリッシュとサブスクライブのプロパティ設定

音声・ビデオストリームのパブリッシュを構成し、すべてのユーザーのストリームをサブスクライブするデフォルトの動作を設定します:

  • publishLocalAudioStream を呼び出して音声ストリームをパブリッシュします。

  • publishLocalVideoStream を呼び出してビデオストリームをパブリッシュします。音声のみの通話の場合は、これを false に設定できます。

// デフォルトでは、SDK はビデオストリームをパブリッシュします。publishLocalVideoStream(true) の呼び出しは任意です。
engine.publishLocalVideoStream(true)
// デフォルトでは、SDK はオーディオストリームをパブリッシュします。ビデオ通話の場合、publishLocalAudioStream(true) の呼び出しは任意です。
// 音声のみの通話の場合は、publishLocalVideoStream(false) を呼び出してビデオのパブリッシュを停止します。
engine.publishLocalAudioStream(true)

// デフォルトでリモートの音声・ビデオストリームをサブスクライブするように設定します。
engine.setDefaultSubscribeAllRemoteAudioStreams(true)
engine.subscribeAllRemoteAudioStreams(true)
engine.setDefaultSubscribeAllRemoteVideoStreams(true)
engine.subscribeAllRemoteVideoStreams(true)
説明

デフォルトでは、SDK はローカルの音声・ビデオストリームを自動的にパブリッシュし、チャンネル内の他のすべてのユーザーの音声・ビデオストリームをサブスクライブします。上記のメソッドを呼び出して、このデフォルトの動作をオーバーライドできます。

7. ローカルプレビューの開始

  • setLocalViewConfig を呼び出してローカルレンダリングビューを設定し、ローカルビデオの表示プロパティを構成します。

  • startPreview メソッドを呼び出してローカルビデオプレビューを開始します。

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()

8. チャンネルへの参加

joinChannel を呼び出してチャンネルに参加します。単一パラメーターのメソッドを使用することを推奨します。これには joinChannel[3/3] 操作を呼び出す必要があります。joinChannel を呼び出した後、戻り値と onJoinChannelResult コールバックの結果の両方を確認します。戻り値が 0 で、コールバックの結果も 0 の場合、ユーザーは正常にチャンネルに参加しています。そうでない場合は、トークンが有効であることを確認してください。

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)
}
説明
  • ユーザーがチャンネルに参加すると、SDK は参加前に設定されたパラメーターに従ってストリームをパブリッシュおよびサブスクライブします。

  • SDK は、クライアントが行う必要のある API 呼び出しの数を減らすために、デフォルトで自動的にパブリッシュおよびサブスクライブします。

9. リモートビューの設定

リモートユーザーがストリームのパブリッシュを開始または停止すると、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)
    }
}

10. チャンネルからの退出とエンジンの破棄

セッションが終了したら、チャンネルから退出してエンジンを破棄し、リソースを解放します:

  1. stopPreview を呼び出してビデオプレビューを停止します。

  2. leaveChannel を呼び出してチャンネルから退出します。

  3. destroy を呼び出してエンジンを破棄し、関連するすべてのリソースを解放します。

self.rtcEngine?.stopPreview()
self.rtcEngine?.leaveChannel()
AliRtcEngine.destroy()
self.rtcEngine = nil

11. デモ

image.pngimage.png

関連ドキュメント

データ構造

AliRtcEngine クラス