rtc-room组件收藏我的收藏
收藏
我的收藏基础库 2.42.0 开始支持本组件。
接入 RTC 前置工作:
- 1.该能力需申请权限使用,满足申请条件后,开发者登录控制台,在小程序应用中筛选“能力-互动能力-小程序直播”,申请RTC 能力,能力使用规范详情查看RTC能力开通及使用规范;申请条件如下:
- a.信用分 >= 90分
- b.服务类目
一级类目 | 二级类目 | 三级类目 |
教育 | 教育 | 在线教育 |
医疗 | 医疗 | 公立医疗机构、私立医疗机构、互联网医院 |
- 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 组件能力权限
- •已开通 RTC 服务,参考 开通服务
- •已获取 AppId,参考 获取 AppId
- •已获取 Token。参考 使用临时 Token
- •已安装抖音开发者工具 IDE V4.0.1+
- •已加入官网交流群申请技术支持
获取示例项目
该工程有效期至2025/01/31
示例项目的目录结构如下:
- 项目 - 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_ID
和 RTC_TOKEN
分别填入你的 AppId 和临时 Token。编译并运行示例项目
若有疑问,通过飞书扫码进入话题群咨询
代码示例
调试时,需要修改 demo 中的
RTC_APP_ID
和 RTC_TOKEN
信息<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>
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
无