同一个用户可以加入多个房间,分别订阅和接收这些房间中的音视频流,并在其中一个房间中发布音视频。也可以发送和接收实时消息。
适用场景
- 大班小组课:主讲老师在大班房间内讲课,学生在该房间内听讲,同时可以在小组房间交流提问,并由助教老师答疑。
- 游戏场景:玩家可以在已加入的小队房间内和其他小队成员进行语音互动,同时能收听到世界房间内的广播音频。
- 会议场景:云端会议过程中,主持人可以发起分组讨论。参会者无需离开原先的会议,即可在各自小组中进行音视频互动,不同讨论组互不干扰。
前提条件
你已经集成 RTC SDK v3.43 或更新的版本,实现了基本的音视频通话。
支持多房间功能的 SDK 详见API 及回调。
功能实现
在集成 RTC SDK,并完成业务逻辑代码时,你已发现,音视频引擎类和房间类两个常用的主调类有明显的功能区分:
- 通过使用音视频引擎类的方法,你可以启动音视频采集,并进行相关设置(如切换摄像头等);
- 通过房间类的方法,你可以在房间内发布/订阅流,并进行相关设置(如用户自身在房间内的可见性等)。
要让一个用户加入多个房间,可以通过一个音视频引擎实例,创建多个房间实例,并为这些实例设置不同的房间 ID。同一个用户在多个房间中,可以订阅在这些房间中发布的音视频流。
本文以加入两个房间为例,你可以按照相同流程,让用户加入更多房间。
以下时序图以 Android SDK 中的 API 名称为例。不同端的 SDK 中 API 或回调名称可能略有不同,以 API 及回调为准。

1. 创建引擎类
创建和初始化一个音视频引擎类。
参考 构建 RTC 应用 获取详细步骤。
// APP_ID: 已经在控制台申请的app_id
// engineEventHandler: 用于接收 RTCEngine 回调的接口实例
EngineConfig config = new EngineConfig();
config.appID = appId;
config.context = applicationContext;
rtcEngine = RTCEngine.createRTCEngine(config, engineEventHandler);
// kAppID: 已经在控制台申请的app_id
let engineCfg = ByteRTCEngineConfig.init()
engineCfg.appID = appId
//私有参数,可以填空
engineCfg.parameters = [:]
//delegate: 用于接收 ByteRTCEngine 回调的接口实例
self.rtcEngine = ByteRTCEngine.createRTCEngine(engineCfg, delegate: self)
// app_id: 已经在控制台申请的app_id
// handler: 用于接收 RTCEngine 回调的接口实例
// parameters: 私有参数,可以填 nullptr
bytertc::EngineConfig conf;
conf.app_id = g_appid.c_str();
engine = bytertc::IRTCEngine::createRTCEngine(conf, handler.get());
2. 启动音视频采集
创建音视频引擎类后,你可以启动音视频采集,并设置渲染视图。
// 开启音视频采集
rtcEngine.startAudioCapture();
rtcEngine.startVideoCapture();
// 设置本地渲染视图,支持 TextureView 和 SurfaceView
private void setLocalRenderView() {
VideoCanvas videoCanvas = new VideoCanvas();
videoCanvas.renderView = localView;
videoCanvas.renderMode = VideoCanvas.RENDER_MODE_HIDDEN;
rtcEngine.setLocalVideoCanvas(videoCanvas);
}
// 开启本地音视频采集
self.rtcEngine?.startVideoCapture()
self.rtcEngine?.startAudioCapture()
// 设置本地渲染视图
let canvas = ByteRTCVideoCanvas.init()
canvas.view = self.localView.videoView
canvas.renderMode = .hidden
self.rtcEngine?.setLocalVideoCanvas(withCanvas: canvas);
//开始音视频采集
engine->startAudioCapture();
engine->startVideoCapture();
//设置本地渲染窗口
bytertc::VideoCanvas cas;
cas.view = view;
engine->setLocalVideoCanvas(cas);
3. 创建房间实例
创建房间实例后,你可以加入房间发布和订阅音视频流。建议设置房间回调接口,以便监听房间和音视频流的状态回调。
继续调用 createRTCRoom 并传入不同房间 ID,以创建多个房间实例,并分别加入这些房间。
// 创建房间1
rtcRoom1 = rtcEngine.createRTCRoom(roomID);
rtcRoom1.setRTCRoomEventHandler(firstRoomEventHandler);
// 创建房间2
rtcRoom2 = rtcEngine.createRTCRoom(roomID);
rtcRoom2.setRTCRoomEventHandler(secondRoomEventHandler);
//创建房间1
self.rtcRoom1 = self.rtcEngine?.createRTCRoom(roomId1)
self.rtcRoom1?.delegate = self
//创建房间2
self.rtcRoom2 = self.rtcEngine?.createRTCRoom(roomId2)
self.rtcRoom2?.delegate = self
//创建房间1
bytertc::IRTCRoom *room1 = engine->createRTCRoom("roomid1");
room1->setRTCRoomEventHandler(room_handler);
//创建房间2
bytertc::IRTCRoom *room2 = engine->createRTCRoom("roomid2");
room2->setRTCRoomEventHandler(room_handler);
4. 加入 RTC 房间

- 加入每个房间时,默认自动订阅房间中的音视频流。
// 用户信息
UserInfo userInfo = new UserInfo(localUid, "");
// 房间1配置
RTCRoomConfig room1Config = new RTCRoomConfig(ChannelProfile.CHANNEL_PROFILE_LIVE, true, true, true, true);
// 加入房间
rtcRoom1.joinRoom(room1token, userInfo, true, room1Config);
// 房间2配置
// 一个用户只能在一个房间中发布视频流。
RTCRoomConfig room2Config = new RTCRoomConfig(ChannelProfile.CHANNEL_PROFILE_LIVE, false, false, true, true);
// 加入房间
rtcRoom2.joinRoom(room2token, userInfo, true, room2Config);
//room1 进房,默认可以发布音视频流
// 获取 token,建议从服务端获取
let token1 = generatorToken(roomId: roomId1, userId: userId1)
let userInfo = ByteRTCUserInfo.init()
userInfo.userId = userId
let room1Cfg = ByteRTCRoomConfig.init()
room1Cfg.isPublishAudio = true
room1Cfg.isPublishVideo = true
room1Cfg.isAutoSubscribeAudio = true
room1Cfg.isAutoSubscribeVideo = true
self.rtcRoom1?.joinRoom(token, userInfo: userInfo, userVisibility: true, roomConfig: room1Cfg)
// 一个用户只能在一个房间中发布视频流。
// 获取 token,建议从服务端获取
let token2 = generatorToken(roomId: roomId2, userId: userId2)
let room2Cfg = ByteRTCRoomConfig.init()
room2Cfg.isPublishAudio = false
room2Cfg.isPublishVideo = false
room2Cfg.isAutoSubscribeAudio = true
room2Cfg.isAutoSubscribeVideo = true
self.rtcRoom2?.joinRoom(token2, userInfo: userInfo, userVisibility: true, roomConfig: room2Cfg)
//room1 进房,默认可以发布音视频流
bytertc::UserInfo user;
bytertc::RTCRoomConfig config;
config.is_publish_audio = true;
config.is_publish_video = true; //第一个房间发流,发布后可以用unPublishStream 取消发布
config.is_auto_subscribe_audio = true;
config.is_auto_subscribe_video = true;
config.room_profile_type = bytertc::kRoomProfileTypeCommunication;
user.uid = uid1.c_str();
int ret = room1->joinRoom(token.c_str(), user, true, config);
bytertc::UserInfo user2;
bytertc::RTCRoomConfig config2;
config2.is_publish_audio = false; //第二个房间不发流
config2.is_publish_video = false;
config2.is_auto_subscribe_audio = true;
config2.is_auto_subscribe_video = true;
config2.room_profile_type = bytertc::kRoomProfileTypeCommunication;
user2.uid = uid2.c_str();
int ret2 = room2->joinRoom(token.c_str(), user2, true, config2);
- 监听进房状态,处理进房失败。
public void onRoomStateChanged(String roomId, String uid, int state, String extraInfo) {
if (state == 0) { //进房成功
} else if (state == -1000) { //token错误
} else {// .... 其他错误
}
}
func rtcRoom(_ rtcRoom: ByteRTCRoom, onRoomStateChanged roomId: String, withUid uid: String, state: Int, extraInfo: String) {
if (state == 0) { //进房成功
} else if (state == -1000) { //token错误
} else {// .... 其他错误
}
}
void RoomHandler::onRoomStateChanged(const char* room_id, const char* uid, int state, const char* extra_info) {
if (state == 0) { //进房成功
} else if (state == -1000) { //token错误
} else {// .... 其他错误
}
}
- 订阅端在接收到其他用户发布/取消发布音视频流回调时,设置渲染视图。
在收到远端用户的 onUserPublishVideoStream 或 onFirstRemoteVideoFrameDecoded 回调后,你需要调用 setRemoteVideoCanvas 设置远端视图以在通话中查看远端视频。建议您在 onFirstRemoteVideoFrameDecoded 回调中设置视频渲染,确保视频渲染在视频解码完成后进行,以避免卡顿。
@Override
public void onFirstRemoteVideoFrameDecoded(String streamId, StreamInfo streamInfo, VideoFrameInfo frameInfo) {
runOnUiThread(() -> {
// 设置远端视频渲染视图
VideoCanvas videoCanvas = new VideoCanvas();
videoCanvas.renderView = firstRoomRemoteView;
videoCanvas.renderMode = VideoCanvas.RENDER_MODE_HIDDEN;
rtcEngine.setRemoteVideoCanvas(streamId, videoCanvas);
});
}
@Override
public void onUserPublishStreamVideo(String streamId, StreamInfo streamInfo, boolean isPublish) {
if (!isPublish) {
runOnUiThread(() -> {
// 解除远端视频渲染视图绑定
rtcEngine.setRemoteVideoCanvas(streamId, null);
});
}
}
// 远端用户发布流
func rtcEngine(_ rtcEngine: ByteRTCEngine, onFirstRemoteVideoFrameDecoded streamId: String, info: ByteRTCStreamInfo, withFrameInfo: ByteRTCVideoFrameInfo) {
// 渲染远端用户
DispatchQueue.main.async {
// 设置远端用户视频渲染视图
let canvas = ByteRTCVideoCanvas.init()
canvas.view = view.videoView
canvas.renderMode = .hidden
self.rtcEngine?.setRemoteVideoCanvas(streamId, withCanvas: canvas);
}
}
func rtcRoom(_ rtcRoom: ByteRTCRoom, onUserPublishStreamVideo streamId: String, info: ByteRTCStreamInfo, isPublish: Bool) {
if !isPublish {
//移除 UI 显示
DispatchQueue.main.async {
let canvas = ByteRTCVideoCanvas.init()
canvas.view = nil // 置为空
canvas.renderMode = .hidden
self.rtcEngine?.setRemoteVideoCanvas(streamId, withCanvas: canvas)
}
}
}
//远端用户发布流
void EngineHandler::onFirstRemoteVideoFrameDecoded(std::string streamId, bytertc::StreamInfo info, bytertc::VideoFrameInfo frameInfo) {
bytertc::VideoCanvas canvas;
canvas.view = (void*)ui->widget->winId();
engine->setRemoteVideoCanvas(streamId.c_str(), canvas);
}
void RoomHandler::onUserPublishStreamVideo(std::string streamId, bytertc::StreamInfo info, bool publish) {
if (!publish) { //远端用户取消发布流
bytertc::VideoCanvas cas;
cas.view = nullptr;
engine->setRemoteVideoCanvas(streamId.c_str(), cas);
}
}
5. 离开 RTC 房间
用户结束通话时,退出各个房间,并销毁本地房间实例。
rtcRoom.leaveRoom();
rtcRoom.destroy();
rtcRoom = null;
self?.rtcRoom?.leave()
self?.rtcRoom?.destroy()
self?.rtcRoom = nil
room->leaveRoom();
room->destroy();
6. 销毁引擎类
在销毁所有房间实例后可销毁音视频引擎。
RTCEngine.destroyRTCEngine();
ByteRTCEngine.destroyRTCEngine()
self.rtcEngine = nil
bytertc::IRTCEngine::destroyRTCEngine();
示例项目
参考以下项目获取完整代码。在示例项目中,本端用户加入了两个不同的 RTC 房间。你可以按照相同流程,让用户加入更多房间。
API 及回调
你可以根据上文的描述和示例,使用以下客户端 SDK,在不同的端上实现同一用户加入多房间功能。
说明:表格中的 macOS API 接口为 Objective-C,而示例项目中的 macOS 项目使用的是 Windows SDK 中的 API 接口。
常见问题
Q1: 同一用户如何在多个房间内同时发送音视频流?
A:对于一个音视频引擎实例,即使加入了多个房间,也仅能同时在其中的一个房间中发布音视频流。如果需要在多个房间中同时发布音视频流,参看 跨房间转发媒体流 。
Q2: 如何处理 Token 相关错误?
A:确保每个房间的 token 使用了对应的 roomId 生成。更多 Token 相关问题,参见 Token 使用常见问题