• 製品
  • 価格
  • リソース
  • サポート

iOS

業務フロー

このセクションでは、ボイスチャットルームでよくある業務フローをまとめて、全体のシナリオの実装プロセスをよりよく理解するのに役立ちます。
ルーム管理プロセス
ルームマスターの座席管理の流れ
聞き手の座席管理の流れ
下図は、ルーム管理プロセスを示しており、作成、参加、退出、解散の実装が含まれています。



下の図は、ルームマスターによる座席管理プロセスを示しており、マイクオン招待、強制マイクオフ、マイクミュートの実装が含まれています。



下図は、聞き手の座席管理の流れを示しており、自分からマイクオン、自分からマイクオフ、マイクポジション移動の実装が含まれています。




アクセスの準備

ステップ1:サービスを利用する

音声チャットルームシーンは通常、ChatRTC Engine の2つの有償PaaSサービスに依存して構築されます。
1. まず コンソール にログインしてアプリケーションを作成し、RTC Engine アプリケーションを作成した後、Chat アプリケーションを作成する必要があります。



説明:
テスト環境と本番環境にそれぞれ別々のアプリケーションを作成することをお勧めします。1年間、各アカウント(UIN)には毎月10,000分の無料時間が付与されます。
RTC Engineの月額プランは体験版(デフォルト)、軽量版、標準版、プロフェッショナル版に分かれており、さまざまな付加価値機能サービスを利用できます。詳細はバージョン機能と月額プランの説明をご参照ください。
2. アプリケーションが作成された後、アプリケーション管理-アプリケーション概要セクションでそのアプリケーションの基本情報を確認できます。その中で、後で使用するためにSDKAppIDSDKSecretKeyを大切に保管してください。同時に、キーの漏洩はトラフィックの不正利用に繋がりますのでご注意ください。




ステップ2:SDKをインポートする

TC Engine SDK と Chat SDK は CocoaPods に公開されています。CocoaPods を通じて SDK を統合することをお勧めします。
1. CocoaPodsのインストール。
ターミナルウィンドウで以下のコマンドを入力してください(Macで事前にRuby環境をインストールする必要があります)。
sudo gem install cocoapods
2. Podfileを作成。
プロジェクトのパスに移動し、以下のコマンドラインを入力すると、プロジェクトのパスにPodfileが表示されます。
pod init
3. Podfileを編集。
プロジェクトのニーズに応じて適切なバージョンを選択し、Podfileファイルを編集してください。
platform :ios, '8.0'
target 'App' do

# TRTC Lite版
# インストールパッケージのサイズ増加は最小限だが、RTC Engineとライブプレーヤー(TXLivePlayer)の2つの機能のみをサポートします。
pod 'TXLiteAVSDK_TRTC', :podspec => 'https://liteav.sdk.qcloud.com/pod/liteavsdkspec/TXLiteAVSDK_TRTC.podspec'
# Add the Chat SDK
pod 'TXIMSDK_Plus_iOS'
# pod 'TXIMSDK_Plus_iOS_XCFramework'
# pod 'TXIMSDK_Plus_Swift_iOS_XCFramework'
# If you need to add the Quic plugin, please uncomment the next line.
# Note: This plugin must be used with the Objective-C edition or XCFramework edition of the Chat SDK, and the plugin version number must match the Chat SDK version number.
# pod 'TXIMSDK_Plus_QuicPlugin'

end
4. SDKを更新してインストール。
ターミナルウィンドウで以下のコマンドを入力して、ローカルライブラリファイルを更新し、SDKをインストールします。
pod install
あるいは以下のコマンドを使用して、ローカルライブラリのバージョンを更新してください。
pod update
pod命令を実行した後、SDKが統合されたxcworkspaceの拡張子を持つプロジェクトファイルが生成され、ダブルクリックで開けます。
説明:
podの検索に失敗した場合、podのローカルrepoキャッシュを更新することをお勧めします。更新コマンドは以下の通りです。
pod setup
pod repo update
rm ~/Library/Caches/CocoaPods/search_index.json
CocoaPods 統合方式に加えて、SDK をダウンロードして手動でインポートする方法も選択できます。詳細は RTC Engine SDK の手動統合 及び Chat SDK の手動統合 をご参照ください。
Quicプラグインは、axp-quicマルチパストランスポートプロトコルを提供し、劣悪なネットワークに対する耐性がさらに優れています。ネットワークのパケット損失率が70%に達した条件下でも、サービスを提供できます。プロフェッショナル版、プロフェッショナル版plus、およびエンタープライズ版ユーザーのみに開放されています。プロフェッショナル版、プロフェッショナル版plus、またはエンタープライズ版を購入した後にご利用いただけます。機能を正常に使用するために、端末SDKを7.7.5282以上のバージョンに更新してください。

ステップ3:プロジェクトの設定

1. 音声チャットシーンでは、RTC Engine SDK及びChat SDKはAppによるマイク権限の認可を必要とします。AppのInfo.plistに以下の内容を追加してください。これは、システムが権限付与ダイアログを表示する際のマイクへのプロンプトメッセージに対応します。
Privacy - Microphone Usage Description, 、そしてマイク使用目的も入力してください。



2. バックグラウンドでApp関連機能を継続して実行する必要がある場合は、Xcodeで現在のプロジェクトを選択し、Capabilitiesで設定項目Background ModesをONに設定し、Audio, AirPlay and Picture in Pictureにチェックを入れてください。以下の図のように。




アクセスの流れ

ステップ1:認証クレデンシャルを生成する

UserSigは、Tencentのリアルタイム通信サービスが設計したセキュリティ保護署名であり、悪意のある攻撃者によるクラウドサービスの使用権の盗用を防ぐことを目的としています。RTC EngineとChatサービスはどちらもこのセキュリティ保護メカニズムを採用しており、RTC Engineは入室時に認証を行い、Chatはログイン時に認証を行います。
デバッグフェーズ:クライアントサンプルコードコントロールパネル取得の2つの方法でUserSigを計算生成でき、デバッグテストのみに使用します。
本番フェーズ:クライアントがリバースエンジニアリングでキーが漏洩するのを防ぐため、より高いセキュリティレベルのサーバー側UserSig計算を推奨します。
具体的な実装は以下の通りです。
1. AppがSDKの初期化関数を呼び出す前に、最初にサーバーにUserSigをリクエストします。
2. サーバーはSDKAppIDとUserIDに基づいてUserSigを計算します。
3. サーバーは計算されたUserSigをAppに返します。
4. Appは、特定のAPIを通じてSDKにUserSigを伝達します。
5. SDKがSDKAppID + UserID + UserSigをTencent Cloudのクラウドサーバーに提出して検証します。
6. Tencent CloudはUserSigを検証し、合法性を確認します。
7. 検証が完了すると、Chat SDKにインスタントメッセージングサービスを提供し、RTC Engine SDKにリアルタイム・オーディオ・ビデオサービスを提供します。



注意:
デバッグフェーズのローカルUserSig計算方式は、オンライン環境に適用することは推奨しません。逆コンパイルによって容易に解読され、キーが漏洩する可能性があります。
複数の言語(Java/Go/PHP/Nodejs/Python/C#/C++)のUserSigサーバーサイド計算のソースコードを提供しています。詳細はサーバーサイドUserSig計算を参照してください。

ステップ2:初期化とリスニング

タイムライン図




1. Chat SDKの初期化とイベントリスナーの追加。
// ChatコントロールパネルからアプリケーションのSDKAppIDを取得します。
// V2TIMSDKListenerのイベントリスナーを追加、selfはid<V2TIMSDKListener>の実装クラス。IM SDKのイベントをリスニングする必要がない場合、このステップは無視できます。
[[V2TIMManager sharedInstance] addIMSDKListener:self];
// Chat SDKを初期化、このインターフェースを呼び出した後、すぐにログインインターフェースを呼び出すことができます。
[[V2TIMManager sharedInstance] initSDK:sdkAppID config:config];

// SDK初期化後にはいくつかのイベントが発生します。例えば、接続状態、ログインチケットの有効期限切れなど。
- (void)onConnecting {
NSLog(@"Chat SDKがTencent Cloudクラウドサーバーに接続中");
}

- (void)onConnectSuccess {
NSLog(@"Chat SDKはすでにTencent Cloudクラウドサーバーに接続しました");
}

// イベントリスナーを削除
// selfはid<V2TIMSDKListener>の実装クラス
[[V2TIMManager sharedInstance] removeIMSDKListener:self];
// SDKの初期化解除
[[V2TIMManager sharedInstance] unInitSDK];
説明:
アプリケーションのライフサイクルがSDKのライフサイクルと一致している場合、アプリケーションを終了する前に初期化解除を行う必要はありません。特定の画面に入った後にのみSDKを初期化し、その画面を離れた後は使用しない場合は、SDKを初期化解除することができます。
2. RTC Engine SDK インスタンスの作成とイベントリスナーの設定。
// RTC Engine SDK インスタンスの作成(シングルトンパターン)
_trtcCloud = [TRTCCloud sharedInstance];
// イベントリスナーを設定する
_trtcCloud.delegate = self;

// SDKからの各種イベント通知(例:エラーコード、警告コード、オーディオ・ビデオの状態パラメータなど)
- (void)onError:(TXLiteAVError)errCode errMsg:(nullable NSString *)errMsg extInfo:(nullable NSDictionary *)extInfo {
NSLog(@"%d: %@", errCode, errMsg);
}

- (void)onWarning:(TXLiteAVWarning)warningCode warningMsg:(nullable NSString *)warningMsg extInfo:(nullable NSDictionary *)extInfo {
NSLog(@"%d: %@", warningCode, warningMsg);
}

// イベントリスナーを削除
_trtcCloud.delegate = nil;
// RTC Engine SDK インスタンスの破棄(シングルトンパターン)
[TRTCCloud destroySharedIntance];
説明:
SDKイベント通知のリスニングを推奨します。一般的なエラーに関するログ出力と処理についての詳細はエラーコード表を参照してください。

ステップ3:ログインとログアウト

Chat SDKを初期化した後、SDKのログインインターフェースを呼び出してアカウントの身元を確認し、アカウントの機能使用権限を取得する必要があります。そのため、他の機能を使用する前に、必ずログインが成功していることを確認してください。そうでない場合、機能異常または使用不可になる可能性があります。RTC Engineのオーディオ・ビデオサービスのみを使用する場合は、このステップを省略できます。

タイムライン図




1. ログイン
// ログイン:userIDはカスタマイズ可能、userSigはステップ1を参照して取得します。
[[V2TIMManager sharedInstance] login:userID userSig:userSig succ:^{
NSLog(@"success");
} fail:^(int code, NSString *desc) {
// 以下のエラーコードが返された場合、UserSigの使用期限が切れている。新しく発行されたUserSigを使用して再ログインします。
// 1. ERR_USER_SIG_EXPIRED(6206)
// 2. ERR_SVR_ACCOUNT_USERSIG_EXPIRED(70001)
// 注意:他のエラーコードの場合は、ここでログインインターフェースを呼び出さないでください。Chat SDK のログインが無限ループになるのを避けるためです。
NSLog(@"failure, code:%d, desc:%@", code, desc);
}];
2. ログアウト
// ログアウト
[[V2TIMManager sharedInstance] logout:^{
NSLog(@"success");
} fail:^(int code, NSString *desc) {
NSLog(@"failure, code:%d, desc:%@", code, desc);
}];
説明:
アプリのライフサイクルがChat SDKのライフサイクルと一致する場合、アプリケーション終了前にログアウトする必要はありません。特定のインターフェースに入った後にのみChat SDKを使用し、インターフェース退出後は使用しない場合は、ログアウト操作と Chat SDK の初期化解除を行うことができます。

ステップ4:ルーム管理

タイムライン図




1. ルームを作成。
配信者(ルームオーナー)が配信を開始する際にルームを作成する必要があります。ここでの「ルーム」という概念は、Chatにおける「グループ」に対応します。本例ではクライアント側でChatグループを作成する方法のみを示していますが、実際にはサーバー側でグループを作成することも可能です。
// グループを作成
[[V2TIMManager sharedInstance] createGroup:GroupType_AVChatRoom groupID:groupID groupName:groupName succ:^(NSString *groupID) {
// グループの作成に成功
} fail:^(int code, NSString *desc) {
// グループの作成に失敗
}];


// グループ作成通知をリスニング
[[V2TIMManager sharedInstance] addGroupListener:self];
- (void)onGroupCreated:(NSString *)groupID {
// グループ作成コールバック、groupIDは新しく作成されたグループのID
}
注意:
音声チャットルームシーンでChatグループを作成するには、ライブグループタイプGROUP_TYPE_AVCHATROOMを選択する必要があります。
RTC Engineにはルームを作成するAPIはありません。ユーザーが参加しようとするルームが存在しない場合、バックグラウンドで自動的にルームが作成されます。
2. ルームに参加。
Chat グループに参加。
// グループに参加
[[V2TIMManager sharedInstance] joinGroup:groupID msg:message succ:^{
// グループへの参加に成功
} fail:^(int code, NSString *desc) {
// グループへの参加に失敗
}];

// グループ参加イベントをリスニング
[[V2TIMManager sharedInstance] addGroupListener:self];
- (void)onMemberEnter:(NSString *)groupID memberList:(NSArray<V2TIMGroupMemberInfo *>*)memberList {
// 誰かがグループに参加しました。
}
RTC Engine ルームに参加。
- (void)enterRoomWithRoomId:(NSString *)roomId userID:(NSString *)userId {
TRTCParams *params = [[TRTCParams alloc] init];
// 文字列のルーム番号を例にしています。Chatのグループ番号と一致させることをお勧めします。
params.strRoomId = roomId;
params.userId = userId;
// 業務バックエンドから取得したUserSig
params.userSig = getUserSig(userId);
// 自分のSDKAppIDに置き換える
params.sdkAppId = SDKAppID;
// ボイスチャットインタラクションシナリオでの入室には、指定されたユーザーロールが必要です。
params.role = TRTCRoleAudience;
// ボイスチャットのインタラクションでの入室シナリオを例に
[self.trtcCloud enterRoom:params appScene:TRTCAppSceneVoiceChatRoom];
}

// 入室結果イベントコールバック
- (void)onEnterRoom:(NSInteger)result {
if (result > 0) {
// resultは入室にかかった時間(ミリ秒)
[self toastTip:@"Enter room succeed!"];
} else {
// result入室失敗のエラーコード
[self toastTip:@"Enter room failed!"];
}
}
注意:
RTC Engineのルーム番号は整数型のroomIdと文字列型のstrRoomIdに分かれており、2種類のルームは相互接続されません。ルーム番号のタイプを統一することをお勧めします。
ボイスチャットインタラクションシナリオでは、入室時にユーザーのロール(アンカー/視聴者)を指定する必要があります。アンカーのみがプッシュ権限を持っています。指定されていない場合は、デフォルトでアンカーロールとなります。
ボイスチャットインタラクションでの入室シナリオはTRTCAppSceneVoiceChatRoomがお勧めです。
3. ルームから退出。
Chat グループから退室。
[[V2TIMManager sharedInstance] quitGroup:groupID succ:^{
// グループからの退出に成功
} fail:^(int code, NSString *desc) {
// グループからの退出に失敗
}];

[[V2TIMManager sharedInstance] addGroupListener:self];
- (void)onMemberLeave:(NSString *)groupID member:(V2TIMGroupMemberInfo *)member {
// グループメンバー退出コールバック
}
注意:
ライブグループ(AVChatRoom)内では、グループマスターがグループを退出することはできません。グループマスターはdismissGroupを使用してグループを解散することのみが可能です。
RTC Engine ルームから退室。
- (void)exitTrtcRoom {
self.trtcCloud = [TRTCCloud sharedInstance];
[self.trtcCloud stopLocalAudio];
[self.trtcCloud exitRoom];
}

// onExitRoomコールバックをリスニングすれば自分の退室理由が取得可能です。
- (void)onExitRoom:(NSInteger)reason {
if (reason == 0) {
// exitRoomのアクティブコールでルームから退出
NSLog(@"Exit current room by calling the 'exitRoom' api of sdk ...");
} else if (reason == 1) {
// 現在のルームからサーバーによってキックされました。
NSLog(@"Kicked out of the current room by server through the restful api...");
} else if (reason == 2) {
// 現在のルームは解散されました
NSLog(@"Current room is dissolved by server through the restful api...");
}
}
注意:
SDKが使うすべてのリソースがリリースされた後、SDKはonExitRoomコールバック通知をスローして知らせます。
再度enterRoomを呼び出す場合や他のオーディオ・ビデオSDKに切り替える場合は、onExitRoomのコールバックが返ってくるまで関連操作を行わないでください。そうしないと、カメラやマイクが強制的に使用されるなど、さまざまな異常が発生する可能性があります。
4. ルームを解散する。
Chat グループを解散。
本例ではクライアント側で Chat グループを解散する方法のみを示していますが、実際には サーバー側でグループを解散することも可能です。
[[V2TIMManager sharedInstance] dismissGroup:groupID succ:^{
// グループ解散に成功
} fail:^(int code, NSString *desc) {
// グループ解散に失敗
}];

[[V2TIMManager sharedInstance] addGroupListener:self];
- (void)onGroupDismissed:(NSString *)groupID opUser:(V2TIMGroupMemberInfo *)opUser {
// グループ解散コールバック
}
RTC Engine ルームを解散。
サーバー側解散:RTC Engineはサーバー側でルームを解散するAPI DismissRoom(数字ルームIDと文字列ルームIDを区別)を提供しています。このインターフェースを呼び出すことで、ルーム内の全ユーザーを退室させ、ルームを解散することができます。
クライアント側解散:各クライアントのルーム退室 exitRoom インターフェースを通じて、ルーム内の全ての配信者と視聴者の退室を完了させます。RTC Engineのルームライフサイクルルールに従い、ルームは自動的に解散されます。詳細はルーム退室をご参照ください。
警告:
ライブ配信が終了した後、サーバー側でAPIを呼び出してルームを確実に解散することをお勧めします。聞き手が誤って入室し、予期しない費用が発生するのを防げます。

ステップ5:座席管理

タイムライン図




まず、マイク情報を保存するためのmodelを作成します。
#import "JSONModel.h"

typedef NS_ENUM(NSUInteger, SeatInfoStatus) {
SeatInfoStatusUnused = 0,
SeatInfoStatusUsed = 1,
SeatInfoStatusLocked = 2,
};

NS_ASSUME_NONNULL_BEGIN

@interface SeatInfoModel : JSONModel

/// 座席状態、3つの状態に対応
@property (nonatomic, assign) SeatInfoStatus status;
/// 座席はミュート状態か
@property (nonatomic, assign) BOOL mute;
/// 座席が埋まっている場合、ユーザー情報を保存
@property (nonatomic, copy) NSString *userId;

@end

NS_ASSUME_NONNULL_END
1. 自分からマイクオン。
自分からマイクオンとは、聞き手がルームマスターや管理者にマイクオンの申請を送り、同意の信号を受け取った後にマイクオンになることを指します。常に有効の場合は、シグナルリクエスト部分を無視できます。
聞き手からのマイクオン申請
// 聞き手がマイクオンの申請を送信、userIdはアンカーのID、dataは識別シグナルを伝えるためのjson
- (void)sendInvitationWithUserId:(NSString *)userId data:(NSString *)data {
[[V2TIMManager sharedInstance] invite:userId data:data onlineUserOnly:YES offlinePushInfo:nil timeout:0 succ:^{
NSLog(@"sendInvitation success");
} fail:^(int code, NSString *desc) {
NSLog(@"sendInvitation error %d", code);
}];
}

// アンカーがマイクオンの申請を受信、, inviteIDはこの申請のID、inviterは申請者のID
[[V2TIMManager sharedInstance] addSignalingListener:self];
- (void)onReceiveNewInvitation:(NSString *)inviteID inviter:(NSString *)inviter groupID:(NSString *)groupID inviteeList:(NSArray<NSString *> *)inviteeList data:(NSString * __nullable)data {
NSLog(@"received invitation: %@ from %@", inviteID, inviter);
}
アンカーがマイクオンの申請を処理します
// マイクオンの申請を承認
- (void)acceptInvitationWithInviteID:(NSString *)inviteID data:(NSString *)data {
[[V2TIMManager sharedInstance] accept:inviteID data:data succ:^{
NSLog(@"acceptInvitation success");
} fail:^(int code, NSString *desc) {
NSLog(@"acceptInvitation error %d", code);
}];
}

// マイクオンの申請を拒否
- (void)rejectInvitationWithInviteID:(NSString *)inviteID data:(NSString *)data {
[[V2TIMManager sharedInstance] reject:inviteID data:data succ:^{
NSLog(@"rejectInvitation success");
} fail:^(int code, NSString *desc) {
NSLog(@"rejectInvitation error %d", code);
}];
}
聞き手がマイクオン
アンカーが視聴者のマイクオンの申請を承認する場合、聞き手はグループ属性を変更することでマイク情報を追加でき、他のユーザーはグループ属性の変更コールバックを受け取り、ローカルのマイク情報を更新します。
// ローカルに保存された全部のマイク情報リスト
@property (nonatomic, copy) NSArray<SeatInfoModel *> *seatInfoArray;

// マイクオン申請承認のコールバック
- (void)onInviteeAccepted:(NSString *)inviteID invitee:(NSString *)invitee data:(NSString * __nullable)data {
NSLog(@"received accept invitation: %@ from %@", inviteID, invitee);
NSInteger seatIndex = [self findSeatIndex:inviteID];
[self takeSeatWithIndex:seatIndex];
}

// 聞き手がマイクオン
- (void)takeSeatWithIndex:(NSInteger)seatIndex {
// マイク情報のインスタンスを作成し、変更後のマイク情報を保存
SeatInfoModel *localInfo = self.seatInfoArray[seatIndex];
SeatInfoModel *seatInfo = [[SeatInfoModel alloc] init];
seatInfo.status = SeatInfoStatusUsed;
seatInfo.mute = localInfo.mute;
seatInfo.userId = self.userId;
// マイク情報オブジェクトをJSON形式にシリアライズ
NSString *jsonStr = seatInfo.toJSONString;
NSDictionary *dict = @{ [NSString stringWithFormat:@"seat%ld", seatIndex]: jsonStr };
// グループ属性を設定し、そのグループ属性が既に存在する場合はそのvalueの値を更新、存在しない場合はその属性を追加
[[V2TIMManager sharedInstance] setGroupAttributes:self.groupId attributes:dict succ:^{
// グループ属性の変更に成功、TRTCのロールを切り替えてプッシュを開始
[self.trtcCloud switchRole:TRTCRoleAnchor];
[self.trtcCloud startLocalAudio:TRTCAudioQualityDefault];
} fail:^(int code, NSString *desc) {
// グループ属性の変更に失敗、マイクオン失敗
}];
}
2. マイクオン招待。
配信者が視聴者を強制的にマイクオンさせる(視聴者の同意不要)場合、グループ属性に保存されたマイク情報を直接変更し、対応する視聴者はグループ属性変更コールバックを受信後、userIdのマッチングに成功すればRTC Engineのロールを切り替えて配信を開始できます。招待によるマイクオンのモードの場合、能動的マイクオンの実装ロジックを参照し、シグナリングの送信側と受信側を入れ替えるだけで済みます。
// ローカルに保存された全部のマイク情報リスト
@property (nonatomic, copy) NSArray<SeatInfoModel *> *seatInfoArray;

// アンカー側がこのインターフェースを呼び出し、グループ属性に保存されたマイク情報を変更
- (void)pickSeatWithUserId:(NSString *)userId seatIndex:(NSInteger)seatIndex {
// マイク情報のインスタンスを作成し、変更後のマイク情報を保存
SeatInfoModel *localInfo = self.seatInfoArray[seatIndex];
SeatInfoModel *seatInfo = [[SeatInfoModel alloc] init];
seatInfo.status = SeatInfoStatusUsed;
seatInfo.mute = localInfo.mute;
seatInfo.userId = self.userId;
// マイク情報オブジェクトをJSON形式にシリアライズ
NSString *jsonStr = seatInfo.toJSONString;
NSDictionary *dict = @{ [NSString stringWithFormat:@"seat%ld", seatIndex]: jsonStr };
// グループ属性を設定し、そのグループ属性が既に存在する場合はそのvalueの値を更新、存在しない場合はその属性を追加
[[V2TIMManager sharedInstance] setGroupAttributes:self.groupId attributes:dict succ:^{
// グループ属性の変更に成功、onGroupAttributeChangedコールバックをトリガー
} fail:^(int code, NSString *desc) {
// グループ属性の変更に失敗、マイクオン失敗
}];
}

// 聞き手側がグループ属性の変更コールバックを受け取り、自身の情報と一致した後にプッシュを開始
[[V2TIMManager sharedInstance] addGroupListener:self];
- (void)onGroupAttributeChanged:(NSString *)groupID attributes:(NSMutableDictionary<NSString *,NSString *> *)attributes {
// 最後にローカルに保存された全マイク情報リスト
NSArray *oldSeatArray = self.seatInfoArray;
// groupAttributeMapから解析された全てのマイク情報リスト
NSArray *newSeatArray = [self getSeatListFromAttr:attributes seatSize:self.seatSize];
// 全マイク情報リストをトラバースし、新旧のマイク情報を比較
for (int i = 0; i < self.seatSize; i++) {
SeatInfoModel *oldInfo = oldSeatArray[i];
SeatInfoModel *newInfo = newSeatArray[i];
if (oldInfo.status != newInfo.status && newInfo.status == SeatInfoStatusUsed) {
if ([newInfo.userId isEqualToString:self.userId]) {
// 自身情報のマッチングに成功、TRTCのロールに切り替えてプッシュを開始
[self.trtcCloud switchRole:TRTCRoleAnchor];
[self.trtcCloud startLocalAudio:TRTCAudioQualityDefault];
} else {
// ローカルマイクリストを更新し、ローカルマイクのビューをレンダリング
}
}
}
}
3. 自分からマイクオフ。
マイクオン聞き手は、グループ属性を変更することでマイク情報をリセットでき、他のユーザーはグループ属性の変更コールバックを受け取り、ローカルのマイク情報を更新します。
// ローカルに保存された全部のマイク情報リスト
@property (nonatomic, copy) NSArray<SeatInfoModel *> *seatInfoArray;

- (void)leaveSeatWithIndex:(NSInteger)seatIndex {
// マイク情報のインスタンスを作成し、変更後のマイク情報を保存
SeatInfoModel *localInfo = self.seatInfoArray[seatIndex];
SeatInfoModel *seatInfo = [[SeatInfoModel alloc] init];
seatInfo.status = SeatInfoStatusUnused;
seatInfo.mute = localInfo.mute;
seatInfo.userId = @"";
// マイク情報オブジェクトをJSON形式にシリアライズ
NSString *jsonStr = seatInfo.toJSONString;
NSDictionary *dict = @{ [NSString stringWithFormat:@"seat%ld", seatIndex]: jsonStr };
// グループ属性を設定し、そのグループ属性が既に存在する場合はそのvalueの値を更新、存在しない場合はその属性を追加
[[V2TIMManager sharedInstance] setGroupAttributes:self.groupId attributes:dict succ:^{
// グループ属性の変更に成功、TRTCのロールに切り替えプッシュを停止
[self.trtcCloud switchRole:TRTCRoleAudience];
[self.trtcCloud stopLocalAudio];
} fail:^(int code, NSString *desc) {
// グループ属性の変更に失敗、マイクオフに失敗
}];
}
4. 強制マイクオフ。
配信者が視聴者を強制的にマイクオフさせる場合、グループ属性に保存されたマイク情報を直接変更し、対応する視聴者はグループ属性変更コールバックを受信後、userIdのマッチングに成功すればRTC Engineのロールを切り替えて配信を停止できます。
// ローカルに保存された全部のマイク情報リスト
@property (nonatomic, copy) NSArray<SeatInfoModel *> *seatInfoArray;

// アンカー側がこのインターフェースを呼び出し、グループ属性に保存されたマイク情報を変更
- (void)kickSeatWithIndex:(NSInteger)seatIndex {
// マイク情報のインスタンスを作成し、変更後のマイク情報を保存
SeatInfoModel *localInfo = self.seatInfoArray[seatIndex];
SeatInfoModel *seatInfo = [[SeatInfoModel alloc] init];
seatInfo.status = SeatInfoStatusUnused;
seatInfo.mute = localInfo.mute;
seatInfo.userId = @"";
// マイク情報オブジェクトをJSON形式にシリアライズ
NSString *jsonStr = seatInfo.toJSONString;
NSDictionary *dict = @{ [NSString stringWithFormat:@"seat%ld", seatIndex]: jsonStr };
// グループ属性を設定し、そのグループ属性が既に存在する場合はそのvalueの値を更新、存在しない場合はその属性を追加
[[V2TIMManager sharedInstance] setGroupAttributes:self.groupId attributes:dict succ:^{
// グループ属性の変更に成功、onGroupAttributeChangedコールバックをトリガー
} fail:^(int code, NSString *desc) {
// グループ属性の変更に失敗、強制マイクオフ失敗
}];
}

// マイクオンの聞き手側がグループ属性の変更コールバックを受信し、自身の情報とマッチした後にプッシュを停止
[[V2TIMManager sharedInstance] addGroupListener:self];
- (void)onGroupAttributeChanged:(NSString *)groupID attributes:(NSMutableDictionary<NSString *,NSString *> *)attributes {
// 最後にローカルに保存された全マイク情報リスト
NSArray *oldSeatArray = self.seatInfoArray;
// groupAttributeMapから解析された全てのマイク情報リスト
NSArray *newSeatArray = [self getSeatListFromAttr:attributes seatSize:self.seatSize];
// 全マイク情報リストをトラバースし、新旧のマイク情報を比較
for (int i = 0; i < self.seatSize; i++) {
SeatInfoModel *oldInfo = oldSeatArray[i];
SeatInfoModel *newInfo = newSeatArray[i];
if (oldInfo.status != newInfo.status && newInfo.status == SeatInfoStatusUnused) {
if ([newInfo.userId isEqualToString:self.userId]) {
// 自身情報のマッチングに成功、TRTCのロールに切り替えプッシュを停止
[self.trtcCloud switchRole:TRTCRoleAudience];
[self.trtcCloud stopLocalAudio];
} else {
// ローカルマイクリストを更新し、ローカルマイクのビューをレンダリング
}
}
}
}
5. マイクミュート。
アンカーが特定のマイクをミュート/ミュート解除します。グループ属性に保存されているマイク情報を直接変更し、対応のマイクオン聞き手はグループ属性の変更コールバックを受け取った後、userIdがマッチすればローカルプッシュを一時停止/再開します。
// ローカルに保存された全部のマイク情報リスト
@property (nonatomic, copy) NSArray<SeatInfoModel *> *seatInfoArray;

// アンカー側がこのインターフェースを呼び出し、グループ属性に保存されたマイク情報を変更
- (void)muteSeatWithIndex:(NSInteger)seatIndex mute:(BOOL)mute {
// マイク情報のインスタンスを作成し、変更後のマイク情報を保存
SeatInfoModel *localInfo = self.seatInfoArray[seatIndex];
SeatInfoModel *seatInfo = [[SeatInfoModel alloc] init];
seatInfo.status = localInfo.status;
seatInfo.mute = mute;
seatInfo.userId = localInfo.userId;
// マイク情報オブジェクトをJSON形式にシリアライズ
NSString *jsonStr = seatInfo.toJSONString;
NSDictionary *dict = @{ [NSString stringWithFormat:@"seat%ld", seatIndex]: jsonStr };
// グループ属性を設定し、そのグループ属性が既に存在する場合はそのvalueの値を更新、存在しない場合はその属性を追加
[[V2TIMManager sharedInstance] setGroupAttributes:self.groupId attributes:dict succ:^{
// グループ属性の変更に成功、onGroupAttributeChangedコールバックをトリガー
} fail:^(int code, NSString *desc) {
// グループ属性の変更に失敗、マイクミュートに失敗
}];
}

// マイクオンの聞き手側がグループ属性の変更コールバックを受信し、自身の情報とマッチした後にプッシュを一時停止/再開
[[V2TIMManager sharedInstance] addGroupListener:self];
- (void)onGroupAttributeChanged:(NSString *)groupID attributes:(NSMutableDictionary<NSString *,NSString *> *)attributes {
// 最後にローカルに保存された全マイク情報リスト
NSArray *oldSeatArray = self.seatInfoArray;
// groupAttributeMapから解析された全てのマイク情報リスト
NSArray *newSeatArray = [self getSeatListFromAttr:attributes seatSize:self.seatSize];
// 全マイク情報リストをトラバースし、新旧のマイク情報を比較
for (int i = 0; i < self.seatSize; i++) {
SeatInfoModel *oldInfo = oldSeatArray[i];
SeatInfoModel *newInfo = newSeatArray[i];
if (oldInfo.mute != newInfo.mute) {
if ([newInfo.userId isEqualToString:self.userId]) {
// 自身情報のマッチングに成功し、ローカルプッシュの一時停止/再開
[self.trtcCloud muteLocalAudio:newInfo.mute];
} else {
// ローカルマイクリストを更新し、ローカルマイクのビューをレンダリング
}
}
}
}
6. マイクロック。
アンカーが特定のマイクポジションをロック/アンロックします。グループ属性に保存されているマイク情報を直接変更し、聞き手はグループ属性の変更コールバックを受け取った後、対応するマイクのビューを更新します。
// ローカルに保存された全部のマイク情報リスト
@property (nonatomic, copy) NSArray<SeatInfoModel *> *seatInfoArray;

// アンカー側がこのインターフェースを呼び出し、グループ属性に保存されたマイク情報を変更
- (void)lockSeatWithIndex:(NSInteger)seatIndex isLock:(BOOL)isLock {
// マイク情報のインスタンスを作成し、変更後のマイク情報を保存
SeatInfoModel *localInfo = self.seatInfoArray[seatIndex];
SeatInfoModel *seatInfo = [[SeatInfoModel alloc] init];
seatInfo.status = isLock? SeatInfoStatusLocked : SeatInfoStatusUnused;
seatInfo.mute = localInfo.mute;
seatInfo.userId = @"";
// マイク情報オブジェクトをJSON形式にシリアライズ
NSString *jsonStr = seatInfo.toJSONString;
NSDictionary *dict = @{ [NSString stringWithFormat:@"seat%ld", seatIndex]: jsonStr };
// グループ属性を設定し、そのグループ属性が既に存在する場合はそのvalueの値を更新、存在しない場合はその属性を追加
[[V2TIMManager sharedInstance] setGroupAttributes:self.groupId attributes:dict succ:^{
// グループ属性の変更に成功、onGroupAttributeChangedコールバックをトリガー
} fail:^(int code, NSString *desc) {
// グループ属性の変更に失敗、マイクロックに失敗
}];
}

// 聞き手側がグループ属性の変更コールバックを受信し、対応するマイクのビューを更新
[[V2TIMManager sharedInstance] addGroupListener:self];
- (void)onGroupAttributeChanged:(NSString *)groupID attributes:(NSMutableDictionary<NSString *,NSString *> *)attributes {
// 最後にローカルに保存された全マイク情報リスト
NSArray *oldSeatArray = self.seatInfoArray;
// groupAttributeMapから解析された全てのマイク情報リスト
NSArray *newSeatArray = [self getSeatListFromAttr:attributes seatSize:self.seatSize];
// 全マイク情報リストをトラバースし、新旧のマイク情報を比較
for (int i = 0; i < self.seatSize; i++) {
SeatInfoModel *oldInfo = oldSeatArray[i];
SeatInfoModel *newInfo = newSeatArray[i];
if (oldInfo.status == SeatInfoStatusLocked && newInfo.status == SeatInfoStatusUnused) {
// マイクのロックを解除
} else if (oldInfo.status != newInfo.status && newInfo.status == SeatInfoStatusLocked) {
// マイクをロック
}
}
}
7. マイクポジション移動。
マイクオンアンカーがマイクポジションを移動する場合、グループ属性に保存されているソースとターゲットのマイク情報をそれぞれ変更する必要があります。聞き手はグループ属性の変更コールバックを受け取った後、対応するマイクのビューを更新します。
// ローカルに保存された全部のマイク情報リスト
@property (nonatomic, copy) NSArray<SeatInfoModel *> *seatInfoArray;

// マイクオンアンカーがこのインターフェースを呼び出して、グループ属性に保存されたマイク情報を変更します
- (void)moveSeatToIndex:(NSInteger)dstIndex {
// userIdからソースマイクポジションの番号を取得
__block NSInteger srcIndex = -1;
[self.seatInfoArray enumerateObjectsUsingBlock:^(SeatInfoModel * _Nonnull seatInfo, NSUInteger idx, BOOL * _Nonnull stop) {
if ([seatInfo.userId isEqualToString:self.userId]) {
srcIndex = idx;
*stop = YES;
}
}];
if (srcIndex < 0 || dstIndex < 0 || dstIndex >= self.seatInfoArray.count) {
return;
}
// マイクポジション番号に基づいて対応するマイク情報を取得
SeatInfoModel *srcSeatInfo = self.seatInfoArray[srcIndex];
SeatInfoModel *dstSeatInfo = self.seatInfoArray[dstIndex];
// マイク情報インスタンスを作成し、変更後のソースマイク情報を保存
SeatInfoModel *srcChangeInfo = [[SeatInfoModel alloc] init];
srcChangeInfo.status = SeatInfoStatusUnused;
srcChangeInfo.mute = srcSeatInfo.mute;
srcChangeInfo.userId = @"";
// マイク情報インスタンスを作成し、変更後のターゲットマイク情報を保存
SeatInfoModel *dstChangeInfo = [[SeatInfoModel alloc] init];
dstChangeInfo.status = SeatInfoStatusUsed;
dstChangeInfo.mute = dstSeatInfo.mute;
dstChangeInfo.userId = self.userId;
// マイク情報オブジェクトをJSON形式にシリアライズ
NSString *srcJsonStr = srcChangeInfo.toJSONString;
NSString *dstJsonStr = dstChangeInfo.toJSONString;
NSDictionary *dict = @{ [NSString stringWithFormat:@"seat%ld", srcIndex]: srcJsonStr,
[NSString stringWithFormat:@"seat%ld", dstIndex]: dstJsonStr
};
// グループ属性を設定し、そのグループ属性が既に存在する場合はそのvalueの値を更新、存在しない場合はその属性を追加
[[V2TIMManager sharedInstance] setGroupAttributes:self.groupId attributes:dict succ:^{
// グループ属性の変更に成功、マイクポジション移動に成功
} fail:^(int code, NSString *desc) {
// グループ属性の変更に失敗、マイクポジション移動に失敗
}];
}

ステップ6:オーディオ管理

タイムライン図




1. 購読モード。
RTC Engine SDKはデフォルトでオーディオストリームの自動購読ロジックを採用しており、ユーザーがルームに入ると自動的にリモートユーザーの音声の再生を開始します。手動でオーディオストリームを購読する必要がある場合は、muteRemoteAudio(userId, mute)を追加で呼び出してリモートユーザーのオーディオストリームを購読および再生する必要があります。
// 自動購読モード(デフォルト)
[self.trtcCloud setDefaultStreamRecvMode:YES video:YES];

// 手動購読モード(カスタム)
[self.trtcCloud setDefaultStreamRecvMode:NO video:NO];
注意:
購読モードの設定setDefaultStreamRecvModeは、入室enterRoomする前に呼び出す必要があります。
2. キャプチャーとパブリッシュ。
// ローカルオーディオのキャプチャーとパブリッシュを有効にします。
[self.trtcCloud startLocalAudio:TRTCAudioQualityDefault];

// ローカルオーディオのキャプチャーとパブリッシュを停止する
[self.trtcCloud stopLocalAudio];
説明:
startLocalAudioはマイクの使用権限を申請し、stopLocalAudioはマイクの使用権限をリリースします。
3. マイクオフとマイクオン。
// ローカルオーディオストリームのパブリッシュを一時停止(マイクオフ)
[self.trtcCloud muteLocalAudio:YES];
// ローカルオーディオストリームのパブリッシュを再開(マイクオン)
[self.trtcCloud muteLocalAudio:NO];

// 特定のリモートユーザーのオーディオストリームの購読と再生を一時停止
[self.trtcCloud muteRemoteAudio:userId mute:YES];
// 特定のリモートユーザーのオーディオストリームの購読と再生を再開
[self.trtcCloud muteRemoteAudio:userId mute:NO];

// すべてのリモートユーザーのオーディオストリームの購読と再生を一時停止
[self.trtcCloud muteAllRemoteAudio:YES];
// すべてのリモートユーザーのオーディオストリームの購読と再生を再開
[self.trtcCloud muteAllRemoteAudio:NO];
説明:
一方、muteLocalAudioはソフトウェアレベルでデータフローを一時停止または再開するだけでよいため、効率が高くスムーズで、頻繁にマイクのオンオフが必要なシナリオに適しています。
4. 音質および音量タイプ。
音質設定
// ローカルオーディオのキャプチャーとパブリッシュ時の音質設定
[self.trtcCloud startLocalAudio:TRTCAudioQualityDefault];

// オーディオプッシュ中に音質を動的に設定
[self.trtcCloud setAudioQuality:TRTCAudioQualityDefault];
説明:
RTC Engineのプリセット音質は全部で3段階(Speech/Default/Music)に分かれており、それぞれ異なるオーディオパラメータに対応しています。詳細はTRTCAudioQualityをご参照ください。
音量タイプの設定
RTC Engineの各音質レベルにはデフォルトの音量タイプが対応しています。音量タイプを強制的に指定する必要がある場合は、以下のインターフェースを使用できます。
// 音量タイプの設定
[self.trtcCloud setSystemVolumeType:TRTCSystemVolumeTypeAuto];
説明:
RTC Engine の音量タイプは全部で3段階(VOIP/Auto/Media)に分かれており、それぞれ異なる音量チャネルに対応します。詳細はTRTCSystemVolumeTypeをご参照ください。
オーディオルーティング設定
携帯電話などのモバイルデバイスには通常、スピーカーとイヤピースの2つの再生経路があります。音声ルーティングを強制的に指定する必要がある場合は、以下のインターフェースを使用できます。
// オーディオルーティングの設定
[self.trtcCloud setAudioRoute:TRTCAudioModeSpeakerphone];
説明:
RTC Engine のオーディオルートは全部で2種類(Speaker/Earpiece)に分かれており、それぞれ異なる発声位置に対応します。詳細はTRTCAudioRouteをご参照ください。

高度機能

弾幕メッセージのインタラクション

音声ライブ配信ルームでは通常、テキスト形式の弾幕メッセージによるインタラクションがあり、これは Chat のグループチャット通常テキストメッセージの送信及び受信によって実現できます。
// パブリックチャットに弾幕メッセージを送信
[[V2TIMManager sharedInstance] sendGroupTextMessage:text to:groupID priority:V2TIM_PRIORITY_NORMAL succ:^{
// 弾幕メッセージの送信に成功
} fail:^(int code, NSString *desc) {
// 弾幕メッセージの送信に失敗
}];

// パブリックチャットの弾幕メッセージを受信
[[V2TIMManager sharedInstance] addSimpleMsgListener:self];
- (void)onRecvGroupTextMessage:(NSString *)msgID groupID:(NSString *)groupID sender:(V2TIMGroupMemberInfo *)info text:(NSString *)text {
NSLog(@"%@: %@", info.nickName, text);
}

音量コールバック

RTC Engine は固定頻度でマイク上の配信者の音量サイズをコールバックすることができ、通常は音波やサウンドビジュアライザーの表示、発言中の配信者を示すために使用されます。
// 音量コールバックを有効にすることをお勧めします。入室に成功した後すぐに開始してください。
// interval: コールバック間(ms); enable_vad: ボイス検出の有無
[self.trtcCloud enableAudioVolumeEvaluation:interval enable_vad:enable_vad];
self.trtcCloud.delegate = self;

- (void)onUserVoiceVolume:(NSArray<TRTCVolumeInfo *> *)userVolumes totalVolume:(NSInteger)totalVolume {
// userVolumesはすべての話しているユーザーの音量で、ローカルユーザーとリモートプッシュユーザーを含みます。
// totalVolumeはリモートのプッシュユーザーの最大音量値のフィードバックです。
...
// 音量の大きさに応じてUI上で適切な表示を行います。
...
}
注意:
ボイス検出は、ローカルのボイス検出結果のみをフィードバックし、自身のロールがアンカーでなければならず、ユーザーにマイクオンの提示に便利です。
userVolumesは配列であり、配列内の各要素において、userIdが空の場合はローカルのマイクからキャプチャーした音量の大きさを表し、userIdが空でない場合はリモートユーザーの音量の大きさを表します。

音楽および効果音の再生

BGMや効果音の再生は、ボイスチャットルームシナリオで高頻度な需要です。以下では、一般的なBGM関連のインターフェースの使用と注意事項について説明します。
1. 開始/停止/一時停止/再開。
// BGM、効果音、およびボイスエフェクトの設定を行うための管理クラスを取得
self.audioEffectManager = [self.trtcCloud getAudioEffectManager];

TXAudioMusicParam *param = [[TXAudioMusicParam alloc] init];
param.ID = musicID;
param.path = musicPath;
// 音楽をリモートにパブリッシュするか(そうでなければローカルのみで再生)
param.publish = YES;
// 効果音ファイルか
param.isShortFile = NO;

// BGMの再生を開始
__weak typeof(self) weakSelf = self;
[self.audioEffectManager startPlayMusic:param onStart:^(NSInteger errCode) {
__strong typeof(weakSelf) strongSelf = weakSelf;
// 再生開始コールバック
// -4001: パスのオープンに失敗
// -4002: デコード失敗
// -4003: URLアドレス無効
// -4004: 再生中
if (errCode < 0) {
// 再生失敗後、再開する前に現在の再生を停止する必要があります。
[strongSelf.audioEffectManager stopPlayMusic:musicID];
}
} onProgress:^(NSInteger progressMs, NSInteger durationMs) {
// 再生進行状況コールバック
// progressMs現在の再生時間(ミリ秒)
// durationMs現在の音楽の総時間(ミリ秒)
} onComplete:^(NSInteger errCode) {
// 再生終了コールバック
// 回線品質低下によって引き起こされる再生の失敗もこのコールバックをスロー、この時errCode < 0
// 途中で一時停止または停止してもonCompleteコールバックはトリガーされません。
}];
// BGMの再生を停止
[self.audioEffectManager stopPlayMusic:musicID];
// BGMの再生を一時停止
[self.audioEffectManager pausePlayMusic:musicID];
// BGMの再生を再開
[self.audioEffectManager resumePlayMusic:musicID];
注意:
RTC Engine は複数の音楽の同時再生をサポートしており、musicID で一意に識別されます。同一時刻に1曲のみ再生したい場合は、他の音楽の再生を停止してから新しい再生を開始するように注意する必要があります。または、同じ musicID を使用して異なる音楽を再生することもでき、その場合 SDK は古い音楽の再生を停止してから新しい音楽を再生します。
RTC Engine はローカル及びネットワークオーディオファイルの再生をサポートしており、musicPath にローカルの絶対パスまたは URL アドレスを渡します。MP3/AAC/M4A/WAV 形式をサポートします。
2. 音楽とボーカルの音量の比率を調整。
// BGMのローカル再生ボリュームの設定
[self.audioEffectManager setMusicPlayoutVolume:musicID volume:volume];
// BGMのリモート再生ボリュームの設定
[self.audioEffectManager setMusicPublishVolume:musicID volume:volume];
// すべてのBGMのローカルとリモート音量の設定
[self.audioEffectManager setAllMusicVolume:volume];
// ボーカルのキャプチャーボリュームの設定
[self.audioEffectManager setVoiceVolume:volume];
注意:
音量値volumeの正常な範囲は0-100で、デフォルト値は60、最大設定可能値は150ですが、音割れのリスクがあります。
BGMがボーカルを圧倒する場合は、音楽の再生音量を適切に下げ、ボーカルのキャプチャーボリュームを上げてください。
マイクをオフにしてもBGMはオフにしないmuteLocalAudio(true)setVoiceVolume(0)で置き換えてください。
3. BGMと効果音のループ再生。
プラン1:AudioMusicParamloopCountパラメータを使用して、ループ再生回数を設定します。
値の範囲は0から任意の正の整数です。デフォルト値:0。0は音楽を1回再生することを意味し、1は音楽を2回再生することを意味し、以降同様です。
- (void)startPlayMusicWithId:(int32_t)musicId path:(NSString *)path loopCount:(NSInteger)loopCount {
TXAudioMusicParam *param = [[TXAudioMusicParam alloc] init];
param.ID = musicId;
param.path = path;
param.publish = YES;
// 効果音ファイルか
param.isShortFile = YES;
// ループ再生回数を設定、負数は無限ループ
param.loopCount = loopCount < 0 ? NSIntegerMax : loopCount;
[self.audioEffectManager startPlayMusic:param onStart:nil onProgress:nil onComplete:nil];
}
説明:
プラン1では、各ループが完了してもonCompleteコールバックはトリガーされません。設定されたループ回数がすべて完了した後にのみ、そのコールバックがトリガーされます。
プラン2:「BGMが再生終了した」というイベントコールバックonCompleteを利用してループ再生を実装します。通常はリストループまたは単曲ループに使用されます。
- (void)repeatPlayMusicWithParam:(TXAudioMusicParam *)param {
__weak typeof(self) weakSelf = self;
[self.audioEffectManager startPlayMusic:param onStart:nil onProgress:nil onComplete:^(NSInteger errCode) {
__strong typeof(weakSelf) strongSelf = weakSelf;
// ここで再生インターフェースを再呼び出しして、音楽のループ再生を実装できます。
if (errCode >= 0) {
[strongSelf repeatPlayMusicWithParam:param];
}
}];
}

ミキシング転送及びプッシュバック

1. ミキシングストリームを RTC Engine ルームにプッシュバックします。
- (void)startPublishMediaToRoom:(NSString *)roomId userID:(NSString *)userId {
// メディアストリームのパブリッシュ先アドレス
TRTCPublishTarget *target = [[TRTCPublishTarget alloc] init];
// ミキシング後にルームにプッシュバック
target.mode = TRTCPublishMixStreamToRoom;
target.mixStreamIdentity.strRoomId = roomId;
// ミキシングロボットのuseridは、ルームの他のユーザーのuseridと重複してはいけません。
target.mixStreamIdentity.userId = [NSString stringWithFormat:@"%@%@", userId, MIX_ROBOT];
TRTCStreamEncoderParam* encoderParam = [[TRTCStreamEncoderParam alloc] init];
// トランスコード後のオーディオストリームのエンコードパラメータを設定する(カスタマイズ可能)
encoderParam.audioEncodedSampleRate = 48000;
encoderParam.audioEncodedChannelNum = 2;
encoderParam.audioEncodedKbps = 64;
encoderParam.audioEncodedCodecType = 2;
// トランスコードされたビデオストリームのエンコードパラメータを設定(オーディオストリームミックスの場合は無視しても良い)
encoderParam.videoEncodedWidth = 64;
encoderParam.videoEncodedHeight = 64;
encoderParam.videoEncodedFPS = 15;
encoderParam.videoEncodedGOP = 3;
encoderParam.videoEncodedKbps = 30;
// オーディオストリームミックスのパラメータを設定
TRTCStreamMixingConfig *config = [[TRTCStreamMixingConfig alloc] init];
// デフォルトでは空欄のままで大丈夫です。これは、ルーム内のすべてのオーディオがミキシングされることを意味します。
config.audioMixUserList = nil;
// ビデオストリームミックステンプレートの配置(オーディオストリームミックスの場合は無視しても良い)
TRTCVideoLayout *layout = [[TRTCVideoLayout alloc] init];
config.videoLayoutList = @[layout];
// ミキシング転送を開始
[self.trtcCloud startPublishMediaStream:target encoderParam:encoderParam mixingConfig:config];
}
2. イベントコールバックおよび更新停止タスク
タスク結果イベントコールバック
#pragma mark - TRTCCloudDelegate

- (void)onStartPublishMediaStream:(NSString *)taskId code:(int)code message:(NSString *)message extraInfo:(NSDictionary *)extraInfo {
// taskId: リクエストが成功した場合、TRTCバックエンドはコールバックでこのタスクのtaskIdを提供し、その後、そのtaskIdをupdatePublishMediaStreamとstopPublishMediaStreamと組み合わせて更新および停止することができます。
// code: コールバック結果、0は成功を意味し、その他の値は失敗を意味します。
}

- (void)onUpdatePublishMediaStream:(NSString *)taskId code:(int)code message:(NSString *)message extraInfo:(NSDictionary *)extraInfo {
// メディアストリームのパブリッシュインターフェース(updatePublishMediaStream)を呼び出す際にに渡したtaskIdは、このコールバックを通じて再度返され、どの更新リクエストに属するかを識別するために使用されます。
// code: コールバック結果、0は成功を意味し、その他の値は失敗を意味します。
}

- (void)onStopPublishMediaStream:(NSString *)taskId code:(int)code message:(NSString *)message extraInfo:(NSDictionary *)extraInfo {
// メディアストリームのパブリッシュ停止(stopPublishMediaStream)を呼び出す際にに渡したtaskIdは、このコールバックを通じて再度返され、どの停止リクエストに属するかを識別するために使用されます。
// code: コールバック結果、0は成功を意味し、その他の値は失敗を意味します。
}
メディアストリームの更新とパブリッシュ
このインターフェースは RTC Engine サーバーに指令を送信し、startPublishMediaStream で起動したメディアストリームを更新します。
// taskId: onStartPublishMediaStreamでコールバックされたタスクID
// target: 例えば、パブリッシュしたCDN URLの追加、削除
// params: メディアストリームのエンコード出力パラメータを一貫して保持することがお勧めです。これにより、再生側での中断を避けることができます。
// config: ストリームミックストランスコーディングに参加するユーザーリストを更新。例えば、クロスルームPKなど。
[self.trtcCloud updatePublishMediaStream:taskId publishTarget:target encoderParam:trtcStreamEncoderParam mixingConfig:trtcStreamMixingConfig];
注意:
同じタスクでは、オーディオのみ、オーディオ・ビデオ、ビデオのみの間での切り替えはサポートされていません。
メディアストリームのパブリッシュ停止
このインターフェースは RTC Engine サーバーに指令を送信し、startPublishMediaStream で起動したメディアストリームを停止します。
// taskId: onStartPublishMediaStreamでコールバックされたタスクID
[self.trtcCloud stopPublishMediaStream:taskId];
注意:
taskIdに空の文字列を入力すると、startPublishMediaStreamで開始されたそのユーザーのすべてのメディアストリームが停止します。1つのメディアストリームのみを開始した場合や、自分が開始したすべてのメディアストリームを停止したい場合は、この方法をお勧めします。

ネットワーク品質リアルタイムコールバック

ローカルおよびリモートユーザーのネットワーク品質をリアルタイムで統計するために、onNetworkQualityをリスニングすることができます。このコールバックは2秒ごとに一度発生します。
#pragma mark - TRTCCloudDelegate

- (void)onNetworkQuality:(TRTCQualityInfo *)localQuality remoteQuality:(NSArray<TRTCQualityInfo *> *)remoteQuality {
// localQuality userIdは空、ローカルユーザーのネットワーク品質評価結果を表します
// remoteQualityは、リモートユーザーのネットワーク品質評価結果を表しており、その結果はリモートとローカルの両方の影響を受けます。
switch(localQuality.quality) {
case TRTCQuality_Unknown:
NSLog(@"未定義");
break;
case TRTCQuality_Excellent:
NSLog(@"現在のネットワークは非常に良い");
break;
case TRTCQuality_Good:
NSLog(@"現在のネットワークは比較的良い");
break;
case TRTCQuality_Poor:
NSLog(@"現在のネットワークは普通");
break;
case TRTCQuality_Bad:
NSLog(@"現在のネットワークが不安定");
break;
case TRTCQuality_Vbad:
NSLog(@"現在のネットワークが非常に悪い");
break;
case TRTCQuality_Down:
NSLog(@"現在のネットワークはTRTC最低要件を満たしていない");
break;
default:
break;
}
}

高度な権限制御

RTC Engine 高度な権限制御は、異なるルームに異なる入室権限を設定する(例:高级VIPルーム)ため、またはリスナーのマイクオン権限を制御する(例:ゴーストマイクの処理)ために使用できます。具体的な操作手順は以下の通りです。
1. RTC Engine コンソール Engine 控制台 のアプリケーション機能設定ページで、高度な権限制御スイッチをオンにします。
2. 業務バックエンドでPrivateMapKeyを生成し、コード例はPrivateMapKey計算ソースコードを参照してください。
3. PrivateMapKeyの入室検証&マイクオン検証。
入室検証
TRTCParams *params = [[TRTCParams alloc] init];
params.sdkAppId = SDKAppID;
params.roomId = self.roomId;
params.userId = self.userId;
// 業務バックエンドから取得したUserSig
params.userSig = [self getUserSig];
// 業務バックエンドから取得したPrivateMapKey
params.privateMapKey = [self getPrivateMapKey];
params.role = TRTCRoleAudience;
[self.trtcCloud enterRoom:params appScene:TRTCAppSceneVoiceChatRoom];
マイクオン検証
// 業務バックエンドから最新のPrivateMapKeyを取得し、ロール切り替えインターフェースに渡します。
[self.trtcCloud switchRole:TRTCRoleAnchor privateMapKey:[self getPrivateMapKey]];

異常処理

異常エラー処理

RTC Engine SDK が回復不能なエラーに遭遇すると、onError コールバックでスローされます。詳細は エラーコード表 をご覧ください。
UserSig関連。
UserSig検証に失敗すると入室に失敗します。UserSigツールを使用して検証できます。
列挙値
取得値
説明
ERR_TRTC_INVALID_USER_SIG
-3320
入室パラメータUserSigが正しくありません。TRTCParams.userSigが空であるかどうかを確認してください。
ERR_TRTC_USER_SIG_CHECK_FAILED
-100018
UserSig検証失敗、パラメータTRTCParams.userSigが正しく入力されているか、または期限切れでないかを確認してください。
入退室関連。
入室に失敗した場合は、まず入室パラメータが正しいかどうかを確認してください。また、入退室インターフェースは必ずペアで呼び出す必要があります。入室に失敗した場合でも、退室インターフェースを呼び出す必要があります。
列挙値
取得値
説明
ERR_TRTC_CONNECT_SERVER_TIMEOUT
-3308
入室リクエストがタイムアウトしました。ネットワークが切断されているか、VPNが使用されているかを確認してください。また、4Gに切り替えてテストすることもできます。
ERR_TRTC_INVALID_SDK_APPID
-3317
入室パラメータSDKAppIdエラー。TRTCParams.sdkAppIdがnullかどうか確認してください
ERR_TRTC_INVALID_ROOM_ID
-3318
入室パラメータroomIdエラー。TRTCParams.roomIdまたはTRTCParams.strRoomIdが空であるかどうか確認してください。roomIdとstrRoomIdは混在できません。
ERR_TRTC_INVALID_USER_ID
-3319
入室パラメータUserIDが正しくありません。TRTCParams.userIdが空であるかどうかを確認してください。
ERR_TRTC_ENTER_ROOM_REFUSED
-3340
入室リクエストが拒否されました。enterRoomで同じIDのルームに連続して入室しようとしていないか確認してください。
デバイス関連.
デバイス関連のエラーをリスニングし、関連するエラーが発生した場合にUIでユーザーに通知します。
列挙値
取得値
説明
ERR_MIC_START_FAIL
-1302
マイクの起動に失敗しました。例えば、WindowsまたはMacデバイスで、マイクの設定プログラム(ドライバー)に異常があります。デバイスを無効にしてから再度有効にするか、マシンを再起動するか、設定プログラムを更新してください。
ERR_SPEAKER_START_FAIL
-1321
スピーカーの起動に失敗しました。例えば、WindowsやMacのデバイスで、スピーカーの設定プログラム(ドライバー)に異常があります。デバイスを無効にしてから再度有効にするか、マシンを再起動するか、設定プログラムを更新してください。
ERR_MIC_OCCUPY
-1319
マイクが使用中です。たとえば、モバイルデバイスが通話中の場合、マイクを開くと失敗します。

異常終了処理

1. 断線検知とタイムアウトによる退室。
以下のコールバックを通じて、RTC Engine のネットワーク切断及び再接続イベント通知を監視できます。
onConnectionLostコールバック受信後、ローカルのマイクポジションUIにネットワーク切断の警告を表示し、ユーザーに通知します。同時に、ローカルでタイマーを起動し、設定された時間閾値を超えてもonConnectionRecoveryコールバックが受信されない場合、つまりネットワークが継続して切断状態にある場合は、ローカルでマイクオフにし、退室プロセスを開始し、同時にポップアップウィンドウでユーザーにルームからの退出とページの破棄を通知します。ネットワーク切断が90秒(デフォルト)を超えると、タイムアウトによる退房がトリガーされ、RTC Engine サーバーは該当ユーザーをルームから退出させます。もし該当ユーザーが配信者ロールの場合、ルーム内の他のユーザーは onRemoteUserLeaveRoom コールバックを受信します。
#pragma mark - TRTCCloudDelegate

- (void)onConnectionLost {
// SDKクラウドとの接続が切断されました。
}

- (void)onTryToReconnect {
// SDKクラウドに再接続しています。
}

- (void)onConnectionRecovery {
// SDKクラウドとの接続が復旧されました。
}
2. オフライン状態で自動的にマイクオフ。
Chat ユーザーの通常状態は、オンライン(ONLINE)、オフライン(OFFLINE)、未ログイン(UNLOGGED)に分かれており、其中オフライン状態は通常、ユーザーによる強制終了やネットワーク異常中断が原因で発生します。アンカーがマイクオン聞き手ユーザーの状態を購読することで、オフラインしたマイクオン聞き手ユーザーを検出し、彼らを強制マイクオフすることができます。
// アンカーがマイクオン聞き手ユーザーの状態を購読
[[V2TIMManager sharedInstance] subscribeUserStatus:userList succ:^{
// ユーザーステータスの購読に成功
} fail:^(int code, NSString *desc) {
// ユーザーステータスの購読に失敗
}];

// アンカーがマイクオフの聞き手ユーザーのステータスの購読をキャンセル
[[V2TIMManager sharedInstance] unsubscribeUserStatus:userList succ:^{
// ユーザーステータスの購読解除に成功
} fail:^(int code, NSString *desc) {
// ユーザーステータスの購読解除に失敗
}];

// ユーザーステータス変更通知と処理
[[V2TIMManager sharedInstance] addIMSDKListener:self];

- (void)onUserStatusChanged:(NSArray<V2TIMUserStatus *> *)userStatusList {
for (V2TIMUserStatus *userStatus in userStatusList) {
NSString *userId = userStatus.userID;
V2TIMUserStatusType status = userStatus.statusType;
if (status == V2TIM_USER_STATUS_OFFLINE) {
// オフライン状態での強制マイクオフ
[self kickSeatWithIndex:[self getSeatIndexWithUserId:userId]];
}
}
}



注意:
ユーザー状態の購読にはプロフェッショナル版パッケージへのアップグレードが必要です。詳細は 基礎サービス詳細 をご参照ください。
ユーザー状態を購読するには、事前に Chat コンソールユーザー状態クエリー及び状態変更通知設定 を有効にする必要があります。有効にしていない場合、subscribeUserStatus を呼び出すとエラーが報告されます。

サーバー側のユーザーキックとルーム解散

1. サーバー側のユーザーキック。
まず、RTC Engine サーバー側退出インターフェース RemoveUser(整数型ルーム番号)または RemoveUserByStrRoomId(文字列型ルーム番号)を呼び出して、対象ユーザーを RTC Engine ルームから退出させます。入力例は以下の通りです:
https://trtc.tencentcloudapi.com/?Action=RemoveUser
&SdkAppId=1400000001
&RoomId=1234
&UserIds.0=test1
&UserIds.1=test2
&<公共リクエストパラメータ>
ユーザーをキックした後、対象ユーザーはクライアントでonExitRoom()コールバックを受け取り、reasonの値は1になります。この時、このコールバック内でマイクオフ、Chat グループ退出などの操作を処理できます。
// RTC Engineルーム退出イベントコールバック
- (void)onExitRoom:(NSInteger)reason {
if (reason == 0) {
// exitRoomのアクティブコールでルームから退出
NSLog(@"Exit current room by calling the 'exitRoom' api of sdk ...");
} else {
// reason 1: 現在のルームからサーバーによってキックされました。
// reason 2: 現在のルームは解散されました。
NSLog(@"Kicked out of the current room by server or current room is dissolved ...");
// マイクオフ
[self leaveSeatWithIndex:seatIndex];
// Chatグループから退出
[[V2TIMManager sharedInstance] quitGroup:groupID succ:^{
// グループからの退出に成功
} fail:^(int code, NSString *desc) {
// グループからの退出に失敗
}];
}
}
2. サーバー側のルーム解散.
まず、Chat サーバー側グループ解散インターフェース destroy_group を呼び出して対象グループを解散します。リクエストURL例は以下の通りです:
https://xxxxxx/v4/group_open_http_svc/destroy_group?sdkappid=88888888&identifier=admin&usersig=xxx&random=99999999&contenttype=json
グループ解散の実行に成功した後、対象グループ内の全メンバーはクライアントでonGroupDismissed()コールバックを受け取ります。ここの時、このコールバック内でRTC Engineルームからの退室などの操作を処理できます。
// グループ解散コールバック
[[V2TIMManager sharedInstance] addGroupListener:self];
- (void)onGroupDismissed:(NSString *)groupID opUser:(V2TIMGroupMemberInfo *)opUser {
// RTC Engineルームから退出
[self.trtcCloud stopLocalAudio];
[self.trtcCloud exitRoom];
}
説明:
ルーム内の全ユーザーが exitRoom() を呼び出して退房を完了すると、RTC Engine ルームは自動的に解散します。もちろん、サーバー側インターフェース DismissRoom(整数型ルーム番号)または DismissRoomByStrRoomId(文字列型ルーム番号)を呼び出して RTC Engine ルームを強制的に解散することもできます。

入室してライブルームのメッセージ履歴を確認します。

AVChatRoomはデフォルトではライブルームのメッセージ履歴を保存せず、新しいユーザーがライブルームに入ると、入室後に送信されたメッージのみを見ることができます。新しいグループユーザーの体験を最適化するために、コントロールパネルでライブグループユーザーが閲覧できるグループに参加する前のメッセージ数を設定ができます。以下の図に示されたように。



ライブグループユーザーがグループ参加前の履歴メッセージを取得するのは、他のグループの履歴メッセージを取得するのと同じです。コード例:
V2TIMMessageListGetOption *option = [[V2TIMMessageListGetOption alloc] init];
option.getType = V2TIM_GET_CLOUD_OLDER_MSG; // クラウドからより古いメッセージを取得
option.getTimeBegin = 1640966400; // 2022-01-01 00:00:00から開始
option.getTimePeriod = 1 * 24 * 60 * 60; // 丸一日のメッセージを取得
option.count = INT_MAX; // 指定された時間範囲内のすべてのメッセージを返します。
option.groupID = #your group id#; // グループチャットメッセージの取得
[V2TIMManager.sharedInstance getHistoryMessageList:option succ:^(NSArray<V2TIMMessage *> *msgs) {
NSLog(@"success");
} fail:^(int code, NSString *desc) {
NSLog(@"failure, code:%d, desc:%@", code, desc);
}];
注意:
この機能はプレミアムプランのユーザーのみ利用可能で、24時間以内に最大20件のメッセージ履歴しか閲覧できません。

入室時のマイク上配信者ミュート状態の認識

方案1: 入室時には全ての配信者をデフォルトでミュート状態とし、その後 onUserAudioAvailable(userId, true) コールバックに基づいて対応する配信者のミュート状態を解除します。
- (void)onUserAudioAvailable:(NSString *)userId available:(BOOL)available {
if (available) {
// 対応するアンカーのミュート状態を解除
}
}
方案2: 配信者のミュート状態を Chat グループ属性に保存し、リスナーが入室時に全量グループ属性を取得し、マイク上配信者のミュート状態を解析します。
[[V2TIMManager sharedInstance] getGroupAttributes:groupID keys:nil succ:^(NSMutableDictionary<NSString *,NSString *> *groupAttributeList) {
// グループ属性の取得に成功しました。配信者のミュート状態を保存するキーはmuteStatusと仮定します
NSString *muteStatus = groupAttributeList[@"muteStatus"];
// muteStatusを分析し、各マイクオンアンカーのミュート状態を取得
} fail:^(int code, NSString *desc) {
// グループ属性の取得に失敗
}];