全部產品
Search
文件中心

ApsaraVideo Live:iOS端實現語聊房

更新時間:Nov 19, 2025

本文檔將介紹如何在您的 iOS 專案中整合 ARTC SDK, 快速實現一個簡單的純音頻互動App,適用於語音通話、語聊房等情境。

功能介紹

在開始前,您需要瞭解以下有關音視頻即時互動的基本概念:

  • ARTC SDK:阿里雲即時音視頻產品,協助開發中快速實現即時音視頻互動的SDK。

  • 頻道:房間的概念,在同一個頻道內的使用者可以進行即時互動。

  • 主播:可在頻道內發布音視頻流,並可訂閱其他主播發布的音視頻流。

  • 觀眾:可在頻道內訂閱音視頻流,不能發布音視頻流。

下圖展示了實現語音通話及語聊房的基本流程:

  1. 使用者需要先調用joinChannel加入頻道,才能進行推流、拉流:

    • 普通純語音通話情境:所有使用者都是主播角色,可以進行推流和拉流;

    • 語聊房情境:需要在頻道內推流的使用者佈建主播角色;如果使用者只需要拉流,不需要推流,則設定觀眾角色;

    • 通過setClientRole為使用者佈建不同的角色。

  2. 加入頻道後,不同角色的使用者有不同的推拉流行為:

    • 所有頻道內的使用者都可以接收相同頻道內的音視頻流;

    • 主播角色可以在頻道內推音視頻流;

    • 觀眾如果需要推流,需要調用setClientRole方法,將使用者角色切換成主播,便可以推流。

樣本專案

阿里雲ARTC SDK提供了開源的樣本專案供客戶參考,您可以前往下載或查看樣本源碼

前提條件

在實現功能以前,請確保您的開發環境滿足:

  • 開發工具:Xcode 14.0 及以上版本,推薦使用最新正式版本。

  • 配置推薦:CocoaPods 1.9.3 及以上版本。

  • 測試裝置:iOS 9.0 及以上版本的測試裝置。

說明

推薦使用真機測試,類比機可能存在功能缺失。

  • 網路環境:需要穩定的網路連接。

  • 應用準備:擷取即時音視頻應用的AppID和AppKey,詳情請參見建立應用

  • 建立專案和配置:已建立專案並為專案添加了音頻、網路等音視頻互動的相關許可權,此外需要整合 ARTC SDK,相關步驟請參考實現音視訊通話

實現步驟

下面將以語聊房情境為例進行示範,相關功能時序如下:

語聊房情境主要特點如下:

  • 純音頻:頻道內僅包含音頻,不包含視頻。

  • 主播/觀眾角色:頻道內角色分為主播和觀眾角色,主播角色可以推拉音頻流,觀眾角色只能拉取主播推送的音頻流;觀眾角色可以切換為主播角色。

實現純音頻互動

1、申請許可權請求

進入音視訊通話時,雖然SDK會檢查是否已在App中授予了所需要的許可權,當為保障體驗,建議在發起通話前檢查視頻拍攝及麥克風採集的許可權。

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、鑒權Token

加入ARTC頻道需要一個鑒權Token,用於鑒權使用者的合法身份,其鑒權Token建置規則參見:Token鑒權。Token 產生有兩種方式:單參數方式和多參數方式,不同的Token產生方式需要調用SDK不同的加入頻道(joinChannel)的介面。

上線發布階段

由於Token的產生需要使用AppKey,寫死在用戶端存在泄漏的風險,因此強烈建議線上業務通過業務Server產生下發給用戶端。

開發調試階段

開發調試階段,如果業務Server還沒有產生Token的邏輯,可以暫時參考APIExample上的Token產生邏輯,產生臨時Token,其參考代碼如下:

class ARTCTokenHelper: NSObject {

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

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

    /**
    * 根據channelId,userId, timestamp 產生多參數入會的 token
    * Generate a multi-parameter meeting token based on channelId, userId, and 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 產生單參數入會 的token
    * Generate a single-parameter meeting token based on channelId, userId, and 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 ""
    }

    /**
    * 字串簽名
    * String signing (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)
        }

        // 將雜湊值轉換為十六進位字串
        return hash.map { String(format: "%02hhx", $0) }.joined()
    }

}

3. 建立並初始化引擎

  • 建立 RTC 引擎

調用getInstance建立 RTC 引擎對象。

private var rtcEngine: AliRtcEngine? = nil

// 建立引擎並設定回調
let engine = AliRtcEngine.sharedInstance(self, extras:nil)
self.rtcEngine = engine
  • 初始化引擎

    • 調用setChannelProfile介面設定頻道為互動模式。

    • 根據業務情境中使用者的角色,調用setClientRole介面為使用者佈建主播/觀眾角色。

    • 調用setAudioProfile介面設定音頻品質與情境模式。

// 設定頻道模式為互動模式,RTC下都使用AliRtcInteractivelive
engine.setChannelProfile(AliRtcChannelProfile.interactivelive)
// 設定角色
if self.isAnchor {
    // 主播模式,需要推音視頻流,設定AliRtcClientRoleInteractive
    engine.setClientRole(AliRtcClientRole.roleInteractive)
}
else {
    // 觀眾模式,不需要推音視頻流,設定AliRtcClientRolelive
    engine.setClientRole(AliRtcClientRole.rolelive)
}

// 設定音頻Profile,預設使用高音質模式AliRtcEngineHighQualityMode及音樂模式AliRtcSceneMusicMode
engine.setAudioProfile(AliRtcAudioProfile.engineHighQualityMode, audio_scene: AliRtcAudioScenario.sceneMusicMode)
  • 實現常用回調

SDK 在運行過程中如遇到異常情況,會優先嘗試內部重試機制以自動回復。對於無法自行解決的錯誤,SDK 會通過預定義的回調介面通知您的應用程式。

以下是一些 SDK 無法處理、需由應用程式層監聽和響應的關鍵回調:

異常發生原因

回調及參數

解決方案

說明

鑒權失敗

onJoinChannelResult回調中的result返回AliRtcErrJoinBadToken

發生錯誤時App需要檢查Token是否正確。

在使用者主動調用API時,若鑒權失敗,系統將在調用API的回調中返回鑒權失敗的錯誤資訊。

鑒權將要到期

onWillAuthInfoExpire

發生該異常時App需要重新擷取最新的鑒權資訊後,再調用refreshAuthInfo重新整理鑒權資訊。

鑒權到期錯誤在兩種情況下出現:使用者調用API或程式執行期間。因此,錯誤反饋將通過API回調或通過獨立的錯誤回調通知。

鑒權到期

onAuthInfoExpired

發生該異常時App需要重新入會。

鑒權到期錯誤在兩種情況下出現:使用者調用API或程式執行期間。因此,錯誤反饋將通過API回調或通過獨立的錯誤回調通知。

網路連接異常

onConnectionStatusChange回調返回AliRtcConnectionStatusFailed

發生該異常時APP需要重新入會。

SDK具備一定時間斷網自動回復能力,但若斷線時間超出預設閾值,會觸發逾時並中斷連線。此時,App應檢查網路狀態並指導使用者重新加入會議。

被踢下線

onBye

  • AliRtcOnByeUserReplaced:當發生該異常時排查使用者userid是否相同。

  • AliRtcOnByeBeKickedOut:當發生該異常時,表示被業務踢下線,需要重新入會。

  • AliRtcOnByeChannelTerminated:當發生該異常時,表示房間被銷毀,需要重新入會。

RTC服務提供了管理員可以主動移除參與者的功能。

本地裝置異常

onLocalDeviceException

發生該異常時App需要檢測許可權、裝置硬體是否正常。

RTC服務支援裝置檢測和異常診斷的能力;當本地裝置發生異常時,RTC服務會通過回調的方式通知客戶本地裝置異常,此時,若SDK無法自行解決問題,則App需要介入以查看裝置是否正常。

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: 務必處理;Token即將到期,需要業務觸發重新擷取當前channel,user的鑒權資訊,然後設定refreshAuthInfo即可 */
    }

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

        /* TODO: 務必處理;提示Token失效,並執行離會與釋放引擎 */
    }

    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變化 */
        }
    }
}

4. 設定推拉流屬性

SDK 預設情況下會自動推送和拉取頻道內的音視頻流

  • 設定為觀眾模式後只能拉流,publishLocalAudioStream 無效

  • 對於主播和觀眾均可以設定為下面的配置

// 設定音頻Profile,預設使用高音質模式AliRtcEngineHighQualityMode及音樂模式AliRtcSceneMusicMode
engine.setAudioProfile(AliRtcAudioProfile.engineHighQualityMode, audio_scene: AliRtcAudioScenario.sceneMusicMode)

// 語聊情境,不需要publish視頻
engine.publishLocalVideoStream(false)

// 設定預設訂閱遠端的音頻
engine.setDefaultSubscribeAllRemoteAudioStreams(true)
engine.subscribeAllRemoteAudioStreams(true)

5. 加入頻道開始純音頻互動

調用joinChannel介面加入頻道。

注意:

如果token是單參數規則產生的,需要調用SDK單參數的joinChannel[1/3]介面,如果是多參數規則產生的,需要調用SDK多參數的joinChannel[2/3]介面。調用完加入頻道後,可以在onJoinChannelResult回調中拿到加入頻道結果,如果result為0,則表示加入頻道成功,否則需要檢查傳進來的Token是否非法。

self.rtcEngine?.joinChannel(joinToken, channelId: nil, userId: nil, name: nil) 

6. 結束純音頻互動

音頻互動結束,需要離開房間並銷毀引擎,按照下列步驟結束音視頻互動

  1. 調用leaveChannel離會。

  2. 調用destroy銷毀引擎,並釋放相關資源。

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

7. (可選)觀眾上下麥

業務情境中,如果觀眾角色的使用者想要推流,需要調用setClientRole將觀眾角色切換為主播角色。

// 切換為主播角色
self.rtcEngine?.setClientRole(AliRtcClientRole.roleInteractive)

// 切換為觀眾角色
self.rtcEngine?.setClientRole(AliRtcClientRole.rolelive)

相關文檔