このドキュメントでは、Alibaba Real-Time Communication (ARTC) ソフトウェア開発キット (SDK) を iOS プロジェクトに統合して、シンプルな音声のみのインタラクティブアプリケーションを作成する方法について説明します。このアプリケーションは、音声通話や音声チャットルームなどのシナリオで使用できます。
機能紹介
開始する前に、リアルタイムの音声およびビデオインタラクションに関する次の基本概念を理解してください:
ARTC SDK: ApsaraVideo Real-time Communication のソフトウェア開発キットです。これは、開発者がリアルタイムの音声およびビデオインタラクションを迅速に実装するのに役立つ Alibaba Cloud プロダクトです。
チャンネル: ルームに似た概念です。同じチャンネル内のユーザーはリアルタイムで対話できます。
ストリーマー: チャンネル内で音声およびビデオストリームを公開し、他のストリーマーが公開したストリームをサブスクライブできるユーザーです。
視聴者: チャンネル内の音声およびビデオストリームをサブスクライブできますが、公開はできないユーザーです。
次の図は、音声通話または音声チャットルームを実装するための基本的なフローを示しています:
ユーザーは、ストリームをアップストリーミングまたはプルする前に、
joinChannelを呼び出してチャンネルに参加する必要があります:音声のみの通話シナリオ: すべてのユーザーがストリーマーであり、ストリームをアップストリーミングおよびプルできます。
音声チャットルームシナリオ: チャンネル内でストリームをアップストリーミングする必要があるユーザーは、ストリーマーのロールを持っている必要があります。ユーザーがストリームをプルするだけでよい場合は、そのロールを視聴者に設定できます。
setClientRoleを使用して、ユーザーに異なるロールを設定できます。
チャンネルに参加した後、ストリームのアップストリーミングとプルの動作はユーザーのロールによって異なります:
チャンネル内のすべてのユーザーは、同じチャンネル内の他のユーザーから音声およびビデオストリームを受信できます。
ストリーマーは、音声およびビデオストリームをチャンネルにアップストリーミングできます。
視聴者がストリームをアップストリーミングする必要がある場合、ストリームをアップストリーミングする前に
setClientRoleメソッドを呼び出してロールをストリーマーに切り替える必要があります。
サンプルプロジェクト
ARTC SDK は、参照用のオープンソースのサンプルプロジェクトを提供しています。サンプルソースコードをダウンロードまたは表示できます。
前提条件
開始する前に、開発環境が次の要件を満たしていることを確認してください:
開発ツール: Xcode 14.0 以降。最新の公式バージョンの使用を推奨します。
推奨構成: CocoaPods 1.9.3 以降。
テストデバイス: iOS 9.0 以降を実行するテストデバイス。
テストには実機を使用することをお勧めします。シミュレーターには特定の機能が欠けている場合があります。
ネットワーク環境: 安定したネットワーク接続が必要です。
アプリケーションの準備: ARTC アプリケーションの AppID と AppKey を取得します。詳細については、「アプリケーションの作成」をご参照ください。
プロジェクトの作成と構成: プロジェクトを作成し、音声およびネットワークインタラクションに必要な権限を追加し、ARTC SDK を統合していること。詳細については、「音声・映像通話の実装」をご参照ください。
実装手順
次のセクションでは、音声チャットルームのシナリオを例として使用します。プロセスは次のとおりです:
音声チャットルームシナリオの主な特徴は次のとおりです:
音声のみ: チャンネルには音声のみが含まれ、ビデオは含まれません。
ストリーマーと視聴者のロール: チャンネル内のロールはストリーマーと視聴者に分かれています。ストリーマーは音声ストリームをアップストリーミングおよびプルできます。視聴者はストリーマーがアップストリーミングした音声ストリームのみをプルできます。視聴者は自分のロールをストリーマーに切り替えることができます。
音声のみのインタラクションの実装
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、およびタイムスタンプに基づいてチャンネルに参加するための複数パラメーターのトークンを生成します。
*/
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 を使用して文字列に署名します。
* 文字列署名 (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. エンジンの作成と初期化
RTC エンジンの作成
getInstance を呼び出して RTC エンジンオブジェクトを作成できます。
private var rtcEngine: AliRtcEngine? = nil
// エンジンを作成し、コールバックを設定します。
let engine = AliRtcEngine.sharedInstance(self, extras:nil)
self.rtcEngine = engineエンジンの初期化
setChannelProfileメソッドを呼び出して、チャンネルプロファイルをインタラクティブモードに設定できます。setClientRoleメソッドを呼び出して、シナリオに基づいてユーザーのロールをストリーマーまたは視聴者に設定できます。setAudioProfileメソッドを呼び出して、音質とシナリオモードを設定できます。
// チャンネルプロファイルをインタラクティブライブに設定します。AliRtcInteractivelive はすべての RTC シナリオで使用されます。
engine.setChannelProfile(AliRtcChannelProfile.interactivelive)
// ロールを設定します。
if self.isAnchor {
// 音声およびビデオストリームのアップストリーミングが必要なストリーマーモードの場合、ロールを AliRtcClientRoleInteractive に設定します。
engine.setClientRole(AliRtcClientRole.roleInteractive)
}
else {
// 音声およびビデオストリームのアップストリーミングが不要な視聴者モードの場合、ロールを AliRtcClientRolelive に設定します。
engine.setClientRole(AliRtcClientRole.rolelive)
}
// 音声プロファイルを設定します。デフォルトでは、高品質モード (AliRtcEngineHighQualityMode) と音楽モード (AliRtcSceneMusicMode) が使用されます。
engine.setAudioProfile(AliRtcAudioProfile.engineHighQualityMode, audio_scene: AliRtcAudioScenario.sceneMusicMode)共通のコールバックの実装
操作中、SDK はまず内部のリトライメカニズムを使用して例外から自動的に回復しようとします。内部で解決できないエラーについては、SDK は事前定義されたコールバックを通じてアプリケーションに通知します。
以下は、SDK が処理できないエラーに対する主要なコールバックです。アプリケーション層は、これらのコールバックをリッスンして応答する必要があります:
原因 | コールバックとパラメーター | 解決策 | 説明 |
認証に失敗しました |
| このエラーが発生した場合、アプリケーションはトークンが正しいかどうかを確認する必要があります。 | ユーザーが API を呼び出したときに認証に失敗した場合、システムは API コールバックでエラーメッセージを返します。 |
トークンの有効期限がまもなく切れます |
| この例外が発生した場合、アプリケーションは最新の認証情報を取得し、 | トークンの有効期限切れエラーは、ユーザーが API を呼び出したとき、またはプログラムの実行中という 2 つの状況で発生する可能性があります。したがって、エラーは API コールバックまたは別のエラーコールバックを通じて報告されます。 |
トークンの有効期限が切れました |
| この例外が発生した場合、アプリケーションはユーザーにチャンネルに再参加させる必要があります。 | トークンの有効期限切れエラーは、ユーザーが API を呼び出したとき、またはプログラムの実行中という 2 つの状況で発生する可能性があります。したがって、エラーは API コールバックまたは別のエラーコールバックを通じて報告されます。 |
ネットワーク接続エラー |
| この例外が発生した場合、アプリケーションはユーザーにチャンネルに再参加させる必要があります。 | SDK は、一定期間内のネットワーク切断から自動的に回復できます。ただし、切断時間がプリセットされたしきい値を超えると、タイムアウトが発生して接続が切断されます。この場合、アプリケーションはネットワークステータスを確認し、ユーザーにチャンネルへの再参加を案内する必要があります。 |
オフラインにキックされました |
|
| RTC サービスでは、管理者が参加者を削除できます。 |
オンプレミスデバイスの例外 |
| この例外が発生した場合、アプリケーションは権限とデバイスのハードウェアが正しく動作しているかどうかを確認する必要があります。 | RTC サービスは、デバイスの検出と診断をサポートしています。オンプレミスデバイスの例外が発生すると、RTC サービスはコールバックを通じてクライアントに通知します。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: このコールバックを処理する必要があります。ビジネスによっては、同じユーザー ID を持つ異なるデバイスがアクセスを競合するシナリオがトリガーされる場合があります。 */
}
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 メソッドは無効になります。
次の構成は、ストリーマーと視聴者の両方に設定できます。
// 音声プロファイルを設定します。デフォルトでは、高品質モード (AliRtcEngineHighQualityMode) と音楽モード (AliRtcAudioScenario.sceneMusicMode) が使用されます。
engine.setAudioProfile(AliRtcAudioProfile.engineHighQualityMode, audio_scene: AliRtcAudioScenario.sceneMusicMode)
// 音声チャットシナリオでは、ビデオストリームを公開する必要はありません。
engine.publishLocalVideoStream(false)
// リモート音声ストリームへのデフォルトのサブスクリプションを設定します。
engine.setDefaultSubscribeAllRemoteAudioStreams(true)
engine.subscribeAllRemoteAudioStreams(true)5. チャンネルに参加して音声のみのインタラクションを開始する
joinChannel メソッドを呼び出してチャンネルに参加できます。
注:
トークンが単一パラメーターのルールを使用して生成された場合、SDK の単一パラメーターの joinChannel[1/3] メソッドを呼び出す必要があります。トークンが複数パラメーターのルールを使用して生成された場合、SDK の複数パラメーターの joinChannel[2/3] メソッドを呼び出す必要があります。メソッドを呼び出してチャンネルに参加した後、onJoinChannelResult コールバックから結果を取得できます。結果が 0 の場合、ユーザーは正常にチャンネルに参加しました。それ以外の場合は、トークンが有効かどうかを確認してください。
self.rtcEngine?.joinChannel(joinToken, channelId: nil, userId: nil, name: nil) 6. 音声のみのインタラクションを終了する
音声インタラクションが終了したら、チャンネルから退出してエンジンを破棄する必要があります。音声およびビデオインタラクションを終了するには、次の手順を実行します:
leaveChannelを呼び出してチャンネルから退出します。destroyを呼び出してエンジンを破棄し、そのリソースを解放します。
self.rtcEngine?.leaveChannel()
AliRtcEngine.destroy()
self.rtcEngine = nil7. (オプション) 視聴者のマイクのオン/オフを許可する
ビジネスシナリオで、視聴者ロールを持つユーザーがストリームをアップストリーミングしたい場合は、setClientRole メソッドを呼び出してロールをストリーマーに切り替えることができます。
// ストリーマーロールに切り替えます。
self.rtcEngine?.setClientRole(AliRtcClientRole.roleInteractive)
// 視聴者ロールに切り替えます。
self.rtcEngine?.setClientRole(AliRtcClientRole.rolelive)リファレンス
インイヤーモニタリング、ボリュームコントロール、スピーカーコールバックなどの音声操作の詳細については、「一般的な音声操作と構成」をご参照ください。
ボイスチェンジャー、美声、リバーブなどのボーカルエフェクトを設定するには、「ボイスチェンジャー、リバーブ、美声の設定」をご参照ください。
BGM や伴奏オーディオファイルを再生するには、「外部音声入力 (効果音と伴奏を含む) の再生とアップストリーミング」をご参照ください。