rtc-room组件
收藏
我的收藏

基础库 2.42.0 开始支持本组件。​
RTC (Real Time Communication)多人音视频通话视频画面展示组件,相关 API 请参考 tt.createRtcRoomContext。​
接入 RTC 前置工作:​
    1.该能力需申请权限使用,满足申请条件后,开发者登录控制台,在小程序应用中筛选“能力-互动能力-小程序直播”,申请RTC 能力,能力使用规范详情查看RTC能力开通及使用规范;申请条件如下:​
    a.信用分 >= 90分​
    b.服务类目​
一级类目
二级类目
三级类目
教育 ​
教育 ​
在线教育 ​
医疗 ​
医疗 ​
公立医疗机构、私立医疗机构、互联网医院 ​
备注:具体开放类目依据后台申请入口显示状态为准,更多类目开放可关注平台公告或进入rtc官网交流群咨询。​
    2.需要申请开通 rtc 服务,注册生成 AppId,参考:https://www.volcengine.com/docs/6348/69865
    3.需要申请 Token,Token 申请可参考:https://www.volcengine.com/docs/6348/70121

属性说明​

属性
类型
默认值
必填
说明
最低支持版本
user-id​
string​
是​
进入房间用户的 userId(开发者自定义,唯一区分用户即可)​
2.42.0​
mode​
string​
camera​
否​
对话窗口类型,可选值:​
camera:自身摄像头预览​
video:远端用户普通画面​
screen: 远端用户共享屏幕画面​
2.42.0​
binderror​
eventhandle​
否​
创建对话窗口失败时触发​
2.42.0​

binderror 事件对象的 detail​

object 类型,属性如下:​
属性名
类型
说明
errNo​
number​
错误码​
errMsg​
string​
错误信息​
错误说明​
errNo
errMsg
说明
最低支持版本
10101​
platform auth deny​
权限校验失败(需要申请白名单才能使用 rtc-room 能力)​
2.53.0​
21200​
rtc-room is not available​
rtc-room组件不可用(Android 可以使用 rtc-room 组件,iOS 系统小于等于 10 情况下,rtc-room 组件无法使用)​
2.68.0​
21301​
rtcRoom creation failed​
Android rtc-room 不支持​
2.68.0​

小程序全功能 Demo​

抖音开放平台提供了 RTC 小程序相关全功能示例项目。获取该项目后,你可以参照本文进行跑通,体验小程序提供的 RTC 相关全部小程序能力。你也可以通道阅读代码,以了解相关 API 的最佳实践。​
该示例项目由 rtc-room 组件tt.createRtcRoomContext 相关 API 组成。rtc-room 组件为多人音视频通话视频画面展示组件,tt.createRtcRoomContext 相关 API 为实现具体实时音视频能力 API。​

前提条件

    已申请开通小程序 RTC 组件能力权限​

获取示例项目​

你可点击工程示例获取相关代码项目。​
该工程有效期至2025/01/31​
示例项目的目录结构如下:​
Plain Text
复制
- 项目
- pages
- index
- rtc-room // rtc 示例
- utils
- constant.js // rtc AppId 和 Token 的配置
- app.js
- app.json
- app.ttss
- project.config.json // 抖音小程序项目配置

配置示例项目​

    1.打开文件 constant.js,你需要在该文件的 RTC_APP_IDRTC_TOKEN 分别填入你的 AppId 和临时 Token。​

编译并运行示例项目​

若有疑问,通过飞书扫码进入话题群咨询​

代码示例​

调试时,需要修改 demo 中的 RTC_APP_IDRTC_TOKEN信息​
HTML
复制
<view class="title">rtc 组件</view>
<view class="rtc-container">
<view>{{ error }}</view>
<rtc-room
tt:for="{{userIdList}}"
tt:key="item.userId"
id="{{ item.userId }}"
class="rtc-item {{ item.class }}"
userId="{{ item.userId }}"
device-position="front"
bindtap="scaleCamera"
binderror="handleError"
mode="{{ item.mode }}">
</rtc-room>
</view>
<view class="title">房间信息</view>
<view class="rtc-api-name">appId</view>
<view class="room-message">{{ appId }}</view>
<view class="rtc-api-name">token</view>
<view class="room-message token">{{ token }}</view>
<view class="rtc-api-name">roomId</view>
<view class="room-message">{{ roomId }}</view>
<view class="rtc-api-name">useId</view>
<view class="room-message">{{ userId }}</view>
<view class="title">rtc Api</view>
<view class="rtc-api-name">api名称: joinRtcRoom</view>
<button class="rtc-api" type="default" size="default" bindtap="joinRtcRoom">加入房间</button>
<view class="rtc-api-name">api名称: exitRtcRoom</view>
<button class="rtc-api" type="default" size="default" bindtap="exitRtcRoom">退出房间</button>
<view class="rtc-api-name">api名称: switchCamera</view>
<button class="rtc-api" type="default" size="default" bindtap="switchCamera">切换前后置摄像头</button>
<view class="rtc-api-name">api名称: changeVideoCapture</view>
<button class="rtc-api" type="default" size="default" bindtap="changeVideoCapture">切换开启/关闭摄像头采集</button>
<view class="rtc-api-name">api名称: changeAudioCapture</view>
<button class="rtc-api" type="default" size="default" bindtap="changeAudioCapture">切换开启/关闭麦克风采集</button>
<view class="rtc-api-name">api名称: changeUserVisibility</view>
<button class="rtc-api" type="default" size="default" bindtap="changeUserVisibility">切换用户是否对房间内其他用户可见</button>
<view class="rtc-api-name">api名称: setVideoEncoderConfig</view>
<button class="rtc-api" type="default" size="default" bindtap="setVideoEncoderConfig">设置视频发布的最大视频流参数</button>
<view class="rtc-api-name">api名称: setAudioPlaybackDevice</view>
<button class="rtc-api" type="default" size="default" bindtap="setAudioPlaybackDevice">切换音频播放设备</button>
<view class="rtc-api-name">api名称: changeStreamPublishState</view>
<button class="rtc-api" type="default" size="default" bindtap="changeStreamPublishState">切换开启/关闭发布流</button>
<view class="rtc-api-name">api名称: changeStreamSubscribeState</view>
<button class="rtc-api" type="default" size="default" bindtap="changeStreamSubscribeState">切换开启/关闭订阅流</button>
<view class="rtc-api-name">api名称: changeScreenSubscribeState</view>
<button class="rtc-api" type="default" size="default" bindtap="changeScreenSubscribeState">切换开启/关闭订阅屏幕流</button>
<view class="rtc-api-name">api名称: updateToken</view>
<button class="rtc-api" type="default" size="default" bindtap="updateToken">更新 token</button>
<view class="rtc-api-name">api名称: offRtcVideoMembersChanged</view>
<button class="rtc-api" type="default" size="default" bindtap="offRtcVideoMembersChanged">取消订阅视频成员变化</button>
<view class="rtc-api-name">api名称: offRtcChatMembersChanged</view>
<button class="rtc-api" type="default" size="default" bindtap="offRtcChatMembersChanged">取消订阅音频(在线、离线)</button>
<view class="rtc-api-name">api名称: offRtcChatSpeakersChanged</view>
<button class="rtc-api" type="default" size="default" bindtap="offRtcChatSpeakersChanged">取消订阅音频(开始、停止说话)</button>
<view class="rtc-api-name">api名称: offRtcStateChanged</view>
<button class="rtc-api" type="default" size="default" bindtap="offRtcStateChanged">取消订阅状态变化</button>
<view class="rtc-api-name">api名称: offRtcChatInterrupted</view>
<button class="rtc-api" type="default" size="default" bindtap="offRtcChatInterrupted">取消监听被动断开实时音视频通话事件</button>
<view class="rtc-api-name">api名称: offRtcPublishScreenMembersChanged</view>
<button class="rtc-api" type="default" size="default" bindtap="offRtcPublishScreenMembersChanged">取消监听实时共享屏幕用户改变事件</button>
<view class="rtc-api-name">api名称: offRoomTokenWillExpire</view>
<button class="rtc-api" type="default" size="default" bindtap="offRoomTokenWillExpire">取消监听房间token即将过期通知事件</button>
JavaScript
复制
import { RTC_APP_ID } from "../../utils/constant";
Page({
data: {
ctx: null,
userIdList: [],
videoState: 0,
audioState: 0,
streamPublishState: 0,
streamSubscribeState: 0,
screenSubscribeState: 0,
visible: false,
device: "speakerphone",
appId: RTC_APP_ID, // 每个应用的唯一标识符,由 RTC 控制台随机生成的。不同的 AppId 生成的实例在 RTC 中进行音视频通话完全独立,无法互通。
token: "",
roomId: "",
userId: "",
camera: "front",
scaleUserId: "",
error: "no error",
},
onLoad(options) {
const { userId, roomId, token } = options;
this.setData({
userId,
roomId,
token: decodeURIComponent(token),
});
this.data.ctx = tt.createRtcRoomContext({
appId: this.data.appId,
});
// 注册事件监听回调
this.data.ctx.onRtcStateChanged(this.onRtcStateChanged);
this.data.ctx.onRtcVideoMembersChanged(this.onRtcVideoMembersChanged);
this.data.ctx.onRtcChatMembersChanged(this.onRtcChatMembersChanged);
this.data.ctx.onRtcChatSpeakersChanged(this.onRtcChatSpeakersChanged);
this.data.ctx.onRtcChatInterrupted(this.onRtcChatInterrupted);
this.data.ctx.onRtcPublishScreenMembersChanged(
this.onRtcPublishScreenMembersChanged
);
this.data.ctx.onRoomTokenWillExpire(this.onRoomTokenWillExpire);
},
handleError(e) {
const { errNo, errMsg } = e.detail;
this.setData({
error: `errNo = ${errNo}, errMsg = ${errMsg}`,
});
},
scaleCamera(e) {
const userIdList = this.data.userIdList;
if (this.data.scaleUserId === e.currentTarget.id) {
for (let i = 0, len = userIdList.length; i < len; i++) {
if (userIdList[i].userId === this.data.scaleUserId) {
this.data.userIdList[i].class = "rtc-default";
}
}
this.setData({ scaleUserId: "" });
} else {
this.setData({ scaleUserId: e.currentTarget.id });
for (let i = 0, len = userIdList.length; i < len; i++) {
if (userIdList[i].userId === this.data.scaleUserId) {
this.data.userIdList[i].class = "rtc-select";
} else {
this.data.userIdList[i].class = "rtc-default";
}
}
}
this.setData({
userIdList: this.data.userIdList,
});
},
onRtcVideoMembersChanged({ userIdList }) {
console.log("onRtcVideoMembersChanged res ", userIdList);
const resOpenId = [];
for (let i = 0; i < userIdList.length; i += 1) {
resOpenId.push({
class: "rtc-default",
userId: userIdList[i],
mode: this.data.userId === userIdList[i] ? "camera" : "video",
});
}
this.setData({
userIdList: resOpenId,
});
},
onRtcChatMembersChanged(res) {
console.log("onRtcChatMembersChanged ", res);
},
onRtcStateChanged(res) {
console.log("onRtcStateChanged ", res);
},
onRtcChatSpeakersChanged(res) {
console.log("onRtcChatSpeakersChanged ", res);
},
onRtcChatInterrupted(res) {
console.log("onRtcChatInterrupted ", res);
},
onRtcPublishScreenMembersChanged(res) {
console.log("onRtcPublishScreenMembersChanged ", res);
},
onRoomTokenWillExpire() {
console.log("onRoomTokenWillExpire");
},
offRtcVideoMembersChanged() {
this.data.ctx.offRtcVideoMembersChanged(this.onRtcVideoMembersChanged);
},
offRtcStateChanged() {
this.data.ctx.offRtcStateChanged(this.onRtcStateChanged);
},
offRtcChatMembersChanged() {
this.data.ctx.offRtcChatMembersChanged(this.onRtcChatMembersChanged);
},
offRtcChatSpeakersChanged() {
this.data.ctx.offRtcChatSpeakersChanged(this.onRtcChatSpeakersChanged);
},
offRtcChatInterrupted() {
this.data.ctx.offRtcChatInterrupted(this.onRtcChatInterrupted);
},
offRtcPublishScreenMembersChanged() {
this.data.ctx.offRtcPublishScreenMembersChanged(
this.onRtcPublishScreenMembersChanged
);
},
offRoomTokenWillExpire() {
this.data.ctx.offRoomTokenWillExpire(this.onRoomTokenWillExpire);
},
joinRtcRoom() {
this.data.ctx.joinRtcRoom({
roomId: this.data.roomId,
token: this.data.token,
userId: this.data.userId,
success: (res) => {
console.log("joinRtcRoom success ", res);
},
fail(res) {
console.log("joinRtcRoom fail ", res);
},
complete(res) {
console.log("joinRtcRoom complete ", res);
},
});
},
switchCamera() {
const camera = this.data.camera === "front" ? "back" : "front";
this.setData({ camera });
this.data.ctx.switchCamera({
camera,
success(res) {
console.log("switchCamera success ", res);
},
fail(res) {
console.log("switchCamera fail ", res);
},
complete(res) {
console.log("switchCamera complete ", res);
},
});
},
exitRtcRoom() {
this.data.ctx.exitRtcRoom({
roomId: this.data.roomId,
success(res) {
console.log("exitRtcRoom success ", res);
},
fail(res) {
console.log("exitRtcRoom fail ", res);
},
complete(res) {
console.log("exitRtcRoom complete ", res);
},
});
},
changeVideoCapture() {
const videoState = this.data.videoState === 0 ? 1 : 0;
this.setData({ videoState });
this.data.ctx.changeVideoCapture({
state: videoState,
success(res) {
console.log("changeVideoCapture success ", res);
},
fail(res) {
console.log("changeVideoCapture fail ", res);
},
complete(res) {
console.log("changeVideoCapture complete ", res);
},
});
},
changeAudioCapture() {
const audioState = this.data.audioState === 0 ? 1 : 0;
this.setData({ audioState });
this.data.ctx.changeAudioCapture({
state: audioState,
success(res) {
console.log("changeAudioCapture success ", res);
},
fail(res) {
console.log("changeAudioCapture fail ", res);
},
complete(res) {
console.log("changeAudioCapture complete ", res);
},
});
},
changeUserVisibility() {
const visible = this.data.visible ? false : true;
this.setData({ visible });
this.data.ctx.changeUserVisibility({
visible,
success(res) {
console.log("changeUserVisibility success ", res);
},
fail(res) {
console.log("changeUserVisibility fail ", res);
},
complete(res) {
console.log("changeUserVisibility complete ", res);
},
});
},
setVideoEncoderConfig() {
this.data.ctx.setVideoEncoderConfig({
maxSolution: {
width: 640,
height: 360,
frameRate: 24,
},
success(res) {
console.log("setVideoEncoderConfig success ", res);
},
fail(res) {
console.log("setVideoEncoderConfig fail ", res);
},
complete(res) {
console.log("setVideoEncoderConfig complete ", res);
},
});
},
setAudioPlaybackDevice() {
const device =
this.data.device === "speakerphone" ? "earpiece" : "speakerphone";
this.setData({ device });
this.data.ctx.setAudioPlaybackDevice({
device,
success(res) {
console.log(" setAudioPlaybackDevice success ", res);
},
fail(res) {
console.log(" setAudioPlaybackDevice fail ", res);
},
complete(res) {
console.log(" setAudioPlaybackDevice complete ", res);
},
});
},
changeStreamPublishState() {
const streamPublishState = this.data.streamPublishState ? 0 : 1;
this.setData({ streamPublishState });
this.data.ctx.changeStreamPublishState({
state: streamPublishState,
streamType: "both",
success(res) {
console.log("changeStreamPublishState success ", res);
},
fail(res) {
console.log("changeStreamPublishState fail ", res);
},
complete(res) {
console.log("changeStreamPublishState complete ", res);
},
});
},
changeStreamSubscribeState() {
const streamSubscribeState = this.data.streamSubscribeState ? 0 : 1;
this.setData({ streamSubscribeState });
this.data.ctx.changeStreamSubscribeState({
state: streamSubscribeState,
userId: this.data.userId === "user1" ? "user2" : "user1",
streamType: "both",
success(res) {
console.log("changeStreamSubscribeState success ", res);
},
fail(res) {
console.log("changeStreamSubscribeState fail ", res);
},
complete(res) {
console.log("changeStreamSubscribeState complete ", res);
},
});
},
changeScreenSubscribeState() {
const screenSubscribeState = this.data.screenSubscribeState ? 0 : 1;
this.setData({ screenSubscribeState });
this.data.ctx.changeScreenSubscribeState({
state: screenSubscribeState,
userId: this.data.userId === "user1" ? "user2" : "user1",
streamType: "both",
success(res) {
console.log("changeScreenSubscribeState success ", res);
},
fail(res) {
console.log("changeScreenSubscribeState fail ", res);
},
complete(res) {
console.log("changeScreenSubscribeState complete ", res);
},
});
},
updateToken() {
this.data.ctx.updateToken({
token:
"0015dc114b50538d600388f7b18PgDxNOADsJDmYTDL72EDADAwMgUAdXNlcjcGAAAAMMvvYQEAMMvvYQIAMMvvYQMAMMvvYQQAMMvvYQUAMMvvYSAAYUqG/tHPKk44g5iN6UQFcQugzPDX7ryKQ2Qiu0V/EmA=",
success(res) {
console.log("updateToken success ", res);
},
fail(res) {
console.log("updateToken fail ", res);
},
complete(res) {
console.log("updateToken complete ", res);
},
});
},
});

Bug & Tip​

无​