跨房间转发媒体流,指可见用户的媒体流可以同时转发到多个 RTC 房间。转发目标房间的数量在 RTC 侧没有限制。
使用转发媒体流功能并不会产生额外的费用,与其他音视频通话遵循相同的计费原则,详见音视频通话计费 。
适用场景
本功能适用于以下场景,观众在原来的房间中就能够接收到来自其他房间主播的媒体流。
- 主播连麦 PK:接受连麦邀请后,主播 A 的音视频流可以转发到主播 B 所在的房间,同时,B 的音视频流也可以转发到 A 房间。观众和主播均无需离开原有房间,即可观赏到两位主播连麦 PK 的音视频内容。
- 子母直播间:母直播间的音视频为集团总部统一制作,可以同时转发到多个子直播间。子直播间的主播们直接转播母直播间内容,边与用户进行互动,也可以合入自己的个性化内容后进行直播。
前提条件
你已经集成 RTC SDK,实现了基本的音视频通话。
支持跨房间转发功能的 SDK 详见API 及回调。
名词解释
转推流:跨房间转发的媒体流
目标房间:转推流到达的房间
功能实现
以下时序图以 Android SDK 中的 API 名称为例。不同端的 SDK 中 API 或回调名称可能略有不同,以 API 及回调为准。

1. 加入房间
创建引擎实例并加入房间,本地用户默认对他人可见。
请将示例中的 appid、roomId_token、roomId 和 userid 分别替换为实际的 AppID、临时 Token、房间 ID 和用户 ID。
import VERTC, { ForwardStreamState } from '@volcengine/rtc';
// 创建引擎实例
const engine = VERTC.createEngine('appid');
// 加入房间
await engine.joinRoom('roomId_token', 'roomId', { userId: 'userid' });
// 创建引擎
EngineConfig config = new EngineConfig();
config.appID = appId;
config.context = applicationContext;
rtcEngine = RTCEngine.createRTCEngine(config, engineEventHandler)
// 加入房间
private void joinRoom(String roomId) {
rtcRoom = rtcEngine.createRTCRoom(roomId);
rtcRoom.setRTCRoomEventHandler(roomEventHandler);
String token = requestRoomToken(roomId);
// 用户信息
UserInfo userInfo = new UserInfo(localUid, "");
// 设置房间配置
boolean isAutoPublishAudio = true;
boolean isAutoPublishVideo = true;
boolean isAutoSubscribeAudio = true;
boolean isAutoSubscribeVideo = true;
RTCRoomConfig roomConfig = new RTCRoomConfig(ChannelProfile.CHANNEL_PROFILE_LIVE, isAutoPublishAudio, isAutoPublishVideo, isAutoSubscribeAudio, isAutoSubscribeVideo);
// 加入房间
rtcRoom.joinRoom(token, userInfo, true, roomConfig);
}
//创建引擎
let engineCfg = ByteRTCEngineConfig.init()
engineCfg.appID = appId
//私有参数,可以填空
engineCfg.parameters = [:]
//delegate: 用于接收 ByteRTCEngine 回调的接口实例
self.rtcEngine = ByteRTCEngine.createRTCEngine(engineCfg, delegate: self)
//创建房间
self.rtcRoom = self.rtcEngine?.createRTCRoom(roomId)
self.rtcRoom?.delegate = self
//加入房间
let userInfo = ByteRTCUserInfo.init()
userInfo.userId = userId
let roomCfg = ByteRTCRoomConfig.init()
roomCfg.isPublishAudio = true
roomCfg.isPublishVideo = true
roomCfg.isAutoSubscribeAudio = true
roomCfg.isAutoSubscribeVideo = true
self.rtcRoom?.joinRoom(token, userInfo: userInfo, userVisibility: true, roomConfig: roomCfg)
//创建引擎
bytertc::EngineConfig conf;
conf.app_id = g_appid.c_str();
engine = bytertc::IRTCEngine::createRTCEngine(conf, handler.get());
engine->startAudioCapture();
engine->startVideoCapture();
//创建房间
bytertc::IRTCRoom *room = engine->createRTCRoom(roomid);
room->setRTCRoomEventHandler(room_handler);
bytertc::UserInfo info;
bytertc::RTCRoomConfig config;
info.uid = str_uid.c_str();
config.is_publish_audio = true;
config.is_publish_video = true;
config.is_auto_subscribe_audio = true;
config.is_auto_subscribe_video = true;
int ret = room->joinRoom(token, info, true, config);
参考 构建 RTC 应用 获取详细步骤。
2. 开启跨房间转发
startForwardStreamToRooms 中所传入的 token 是与目标房间相对应的 token,而非当前用户所在房间的 token。- 在调用
stopForwardStreamToRooms 前,仅可调用一次 startForwardStreamToRooms。 - 向开启音频选路的房间转发媒体流,该媒体流可能无法被播放。
// 管理目标房间列表
const targetRoomList: string[] = [];
// 开启跨房间转发媒体流
const startResult = await engine.startForwardStreamToRooms([
{ roomId: 'targetRoomIdA', token: 'targetRoomIdA_token' },
// 可以同时指定多个目标房间
{ roomId: 'targetRoomIdB', token: 'targetRoomIdB_token' }
]);
// 处理调用结果
startResult.forEach(item => {
if (item.state === ForwardStreamState.FORWARD_STREAM_STATE_SUCCESS) {
// 保存成功的目标房间
targetRoomList.push(item.roomId);
} else {
// 根据错误码进行对应处理
console.error(item.error);
}
});
//开启转推
private void startForwardStreamToRooms(String roomId) {
// 可以向多个房间转推,以下代码仅展示向一个房间转推
ForwardStreamInfo forwardStreamInfo = new ForwardStreamInfo(roomId, requestRoomToken(roomId));
if (rtcRoom != null) {
rtcRoom.startForwardStreamToRooms(Collections.singletonList(forwardStreamInfo));
}
}
let config = ForwardStreamConfiguration.init()
config.roomId = roomId
// 获取token,建议从服务端获取
let token = generatorToken(roomId: roomId, userId: userId)
config.token = token
// 支持向多个房间转推,数组内传入多个config即可
self.rtcRoom?.startForwardStreamToRooms([config])
// list 存储每个 roomid 及对应 token
for (int i=0; i<list.size(); i++) {
bytertc::ForwardStreamInfo one;
one.room_id = roomids[i].c_str();
one.token = tokens[i].c_str();
forward_stream_dests.push_back(one);
}
// 开启跨房间转发媒体流
bytertc::ForwardStreamConfiguration configuration;
configuration.dest_count = forward_stream_dests.size();
configuration.forward_stream_dests = forward_stream_dests.data();
int ret = m_room->startForwardStreamToRooms(configuration);
3. 更新转发配置
更新跨房间媒体流转发请求。
如果调用 updateForwardStreamToRooms 时不传入任何参数或者传空值,会停止所有正在转发的目标房间流。此时仍需要调用 stopForwardStreamToRooms 后才可以再次调用 startForwardStreamToRooms 。
// 更新跨房间转发媒体流的目标房间列表
const updateResult = await engine.updateForwardStreamToRooms([
// 列表中不包含 targetRoomIdA,会停止该房间的转发媒体流
// targetRoomIdB 在之前的目标房间列表中,会更新对应的 token。
// 在暂停状态下调用更新无法对 token 进行校验。在调用恢复 resumeForwardStreamToAllRooms 后才会进行 token 校验。
{ roomId: 'targetRoomIdB', token: 'targetRoomIdB_new_token' },
// targetRoomIdC 不在之前的目标房间列表中,会开启向该房间转发媒体流
{ roomId: 'targetRoomIdC', token: 'targetRoomIdC_token' }
]);
// 处理调用结果
// 停止转发的目标房间也会在 updateForwardStreamToRooms 的返回结果中。
updateResult.forEach(item => {
if (item.state === ForwardStreamState.FORWARD_STREAM_STATE_SUCCESS) {
if (!['targetRoomIdB', 'targetRoomIdC'].includes(item.roomId)) {
// 不在请求列表中,但在返回结果中的应当删除
targetRoomList.splice(targetRoomList.indexOf(item.roomId), 1);
} else if (!targetRoomList.includes(item.roomId)) {
// 不在已有房间中,需要新增
targetRoomList.push(item.roomId);
}
} else {
// 根据错误码进行对应处理
console.error(item.error);
}
});
ForwardStreamInfo forwardStreamInfo = new ForwardStreamInfo(roomId, requestRoomToken(roomId));
if (rtcRoom != null) {
rtcRoom.updateForwardStreamToRooms(Collections.singletonList(forwardStreamInfo));
}
let config = ForwardStreamConfiguration.init()
config.roomId = roomId
// 获取 token,建议从服务端获取
let token = generatorToken(roomId: roomId, userId: userId)
config.token = token
// 支持向多个房间转推,数组内传入多个 config
self.rtcRoom?.updateForwardStreamToRooms([config])
// 创建 list,存放每个房间 roomid 及对应 token
for (int i=0; i<list.size(); i++) {
bytertc::ForwardStreamInfo one;
one.room_id = roomids[i].c_str();
one.token = tokens[i].c_str();
forward_stream_dests.push_back(one);
}
bytertc::ForwardStreamConfiguration configuration;
configuration.dest_count = forward_stream_dests.size();
configuration.forward_stream_dests = forward_stream_dests.data();
int ret = m_room->updateForwardStreamToRooms(configuration);
4. 暂停和恢复转发
暂停与恢复向所有目标房间转发媒体流。
在调用 resumeForwardStreamToAllRooms 恢复媒体流转发前,仅可调用一次 pauseForwardStreamToAllRooms 。
// 暂停跨房间转发媒体流的目标房间列表
await engine.pauseForwardStreamToAllRooms();
// 恢复跨房间转发媒体流的目标房间列表
const result = await engine.resumeForwardStreamToAllRooms();
// 处理调用结果
result.forEach(item => {
if (item.state !== ForwardStreamState.FORWARD_STREAM_STATE_SUCCESS) {
// 根据错误码进行对应处理
console.error(item.error);
}
});
// 暂停转推
rtcRoom.pauseForwardStreamToAllRooms();
// 恢复转推
rtcRoom.resumeForwardStreamToAllRooms();
// 暂停
self.rtcRoom?.pauseForwardStreamToAllRooms()
// 恢复
self.rtcRoom?.resumeForwardStreamToAllRooms()
// 暂停
int ret = room->pauseForwardStreamToAllRooms();
// 恢复
int ret = room->resumeForwardStreamToAllRooms();
5. 停止转发
停止向所有目标房间转发媒体流。
await engine.stopForwardStreamToRooms();
rtcRoom.stopForwardStreamToRooms();
self.rtcRoom?.stopForwardStreamToRooms()
int ret = room->stopForwardStreamToRooms();
6. 回调
监听 onForwardStreamStateChanged,感知跨房间转发媒体流状态。
- 转发媒体流至目标房间和进入目标房间,将分别触发一次以下回调:
onUserJoin 、 onUserLeave 、 onUserPublishStream / onUserPublishScreen 、 onUserUnpublishStream / onUserUnpublishScreen。
注: 方法 onUserPublishStream 已被拆分为 onUserPublishStreamAudio 和 onUserPublishStreamVideo,两个新方法都增加了 (BOOL)isPublish 参数用于控制是 publish 流还是 unpublish 流。方法 onUserUnpublishStream 和 onUserUnpublishScreen 已被废弃。在 RTCRoom 业务场景下,方法 onUserPublishScreen 已被废弃。
- 如果媒体流转发过程中,用户切换为不可见,媒体流转发将停止。
function onForwardError(info) {
console.error(info);
}
engine.on(EngineEventsTypes.onForwardStreamError, onForwardError);
@Override
public void onForwardStreamEvent(ForwardStreamEventInfo[] eventInfos) {
//跨房间媒体流转发状态和错误回调
}
@Override
public void onForwardStreamStateChanged(ForwardStreamStateInfo[] stateInfos) {
//跨房间媒体流转发事件回调
}
func rtcRoom(_ rtcRoom: ByteRTCRoom, onForwardStreamStateChanged infos: [ForwardStreamStateInfo]) {
//跨房间媒体流转发状态和错误回调
}
func rtcRoom(_ rtcRoom: ByteRTCRoom, onForwardStreamEvent infos: [ForwardStreamEventInfo]) {
//跨房间媒体流转发事件回调
}
void ByteRTCRoomHandler::onForwardStreamStateChanged(bytertc::ForwardStreamStateInfo* infos, int info_count) {
//跨房间媒体流转发状态和错误回调
}
void ByteRTCRoomHandler::onForwardStreamEvent(bytertc::ForwardStreamEventInfo* infos, int info_count)
{
//跨房间媒体流转发事件回调
}
示例项目
API 及回调
以下客户端 SDK 均支持跨房间转发媒体流功能。你可以根据上文中的描述和实例,使用不同的 SDK,在不同的端上实现这个功能。
说明:表格中的 macOS API 接口为 Objective-C,而示例项目中的 macOS 项目使用的是 Windows SDK 中的 API 接口。
功能变更日志
- 自 Native SDK v3.34 起,本功能在 Native 端可用。
- 自 Web SDK v4.54 起,本功能在 Web 端可用。