# 音视频组件使用
### 介绍
- 抽象封装的音视频组件,支持腾讯云、融云等三方厂商。
- 跨端框架`8.3.12-beta.6`版本开始支持,该组件支持腾讯云,组件版本`0.0.3`开始支持融云。
### 使用前准备
在使用音视频组件之前,需要先在对应厂商创建音视频应用并准备好相关参数。
**不同厂商官方文档说明**
腾讯云:https://web.sdk.qcloud.com/trtc/webrtc/v5/doc/zh-cn/tutorial-11-basic-video-call.html
融云:https://doc.rongcloud.cn/rtc/Web/5.X/demo-meeting
**参数清单**
| 参数 | 说明 | 类型 |
| :-------- | :-----------------------------------------: | :----- |
| appKey | 音视频应用 ID | string |
| secretKey | 腾讯云音视频应用密钥,用于组件计算`userSig` | string |
| navi | 融云的私有化部署地址,私有云环境下必填 | string |
**浏览器兼容性检测**
受限于各浏览器厂商对于 `WebRTC` 的支持情况不同,组件在各浏览器上的能力可能会存在差异。若使用遇到问题可先通过以下手段检测浏览器对`WebRTC`的支持度:
腾讯云官方检测页面:https://web.sdk.qcloud.com/trtc/webrtc/demo/detect/index.html
融云官方示例 demo 页面:[web-quickdemo-rtc-meeting](//app.epoint.com.cn/m8mpdoc/showcase/web-quickdemo-rtc-meeting/index.html)
### 步骤演示
#### 1、安装并引入对应厂商的 js 文件
安装:
```
npm i @epoint-mrc/rtc
```
页面引入对应厂商的 js 文件:
```js
// 腾讯云的js
import RTC from '@epoint-mrc/rtc/dist/trtc/trtc-rtc.min.js';
// 融云的js
import RTC from '@epoint-mrc/rtc/dist/rongyun/rongyun-rtc.min.js';
// 华为云的js
import RTC from '@epoint-mrc/rtc/dist/huawei/huawei-rtc.min.js';
```
#### 2、实例化音视频组件
将厂商需要的参数传入初始化方法中,以腾讯云为例:
```js
this.client = new RTC({
type: 'all', // 切换音频或视频,默认是都有,audio/video/all
roomId: '1', // 房间号
appKey: 'sdkAppId', // 厂商音视频应用ID
userId: '1', // 腾讯云需要一个userId,其他厂商可忽略
secretKey: 'secretKey' // 腾讯云音视频应用密钥,用于计算userSig
});
```
**注意,以下每一步都是异步操作,请确保上一步完成后再进行下一步操作,在成功回调中进行下一步操作,同样都支持 promise 链式调用**
#### 3、初始化对应厂商音视频 sdk 实例
这一步会去检查媒体授权,创建对应厂商的 sdk 实例,为后续其他 api 调用作准备。
```js
this.client.init({
success() {
console.log('初始化trtc成功');
},
error(err) {
console.error('初始化失败', err);
}
});
```
#### 4、加入房间
这一步首先添加对远端资源的监听,进入房间后,远端资源会自动播放。
```html
<!-- 远端视频展示区 -->
<div v-for="item in remoteUsers" :key="item">
<div :id="item" :ref="item"></div>
<!-- 注意,融云传入的必须是一个video标签 -->
<!-- <video :id="item" :ref="item"></video> -->
</div>
```
```js
const self = this;
// 页面需要维护一个远端用户列表,用于渲染多个远端视频资源
self.remoteUsers = [];
this.client.joinRoom({
// 该回调函数在每次播放远端资源时会调用,给组件提供播放远端视频资源的DOM节点
getRemoteVideoNode(remoteUserId, streamType, isScreenShare) {
return new Promise((resolve, reject) => {
if (isScreenShare) {
// 如果是屏幕共享流,可以选择不播放
reject();
return;
}
// 播放远端资源的DOM节点的id建议设置成如下id
let view = `${remoteUserId}_${streamType}`;
if (self.remoteUsers.indexOf(view) === -1) {
self.remoteUsers.push(view);
}
self.$nextTick(() => {
// 可以返回一个domId,也可以返回一个dom节点
// 注意融云只能返回一个video元素节点
// 融云返回示例:resolve(self.$refs[view][0].$el.querySelector('video'))
resolve(view);
});
});
},
// 该回调函数在每次取消订阅远端资源时会被调用(例如远端用户取消发布、本地用户离开房间)
removeRemoteVideoNode(remoteUserId, streamType) {
let view = `${remoteUserId}_${streamType}`;
self.remoteUsers = self.remoteUsers.filter((item) => {
return item !== view;
});
},
// 该回调函数在每次远端资源发布且可播放时会被调用
// 部分浏览器会限制自动播放,可在此处处理远端用户资源发布时的逻辑,如:弹窗提醒后在点击事件中对远端资源重新播放等
// `v0.0.11`版本开始支持
onTrackPublish(track) {
// 融云track对象结构参考:https://doc.rongcloud.cn/apidoc/rtc-web/latest/classes/RCRemoteTrack.html
// 腾讯云track对象结构参考:https://web.sdk.qcloud.com/trtc/webrtc/v5/doc/zh-cn/module-EVENT.html#.REMOTE_VIDEO_AVAILABLE
console.log('远端用户资源发布', track);
ejs.ui.alert({
title: '提醒',
message: '有新加入的用户,请注意接听',
cancelable: 0,
success: function (result) {
// 重新播放远端资源
self.client.replayRemote();
},
error: function (err) {}
});
},
success() {
console.log('进入房间成功');
},
error(err) {
console.error('进入房间失败', err);
}
});
```
#### 5、发布本端资源
通过调用`openAudio`、`openVideo`方法发布本端资源至房间。
```html
<!-- 本端视频展示区 -->
<div :id="localVideoId"></div>
<!-- 注意,融云传入的必须是一个video标签 -->
<!-- <video :id="localVideoId"></video> -->
```
```js
// 发布本端音频资源至房间
this.client.openAudio({
success() {
console.log('发布本端音频成功');
},
error(err) {
console.error('发布本端音频失败', err);
}
});
// 发布本端视频资源至房间
this.client.openVideo({
// 传入预览本端视频资源的DOM节点id
domId: this.localVideoId,
// 或者通过dom传入一个DOM元素节点
// 如果是融云,只能通过dom字段传入一个video元素
// dom: this.$refs[this.localVideoId].$el.querySelector('video'),
cameraOpt: {
// 传入视频配置项,可选,组件版本 `0.0.11`开始支持
profile: '480p' // 视频分辨率, 腾讯云传参示例
},
success() {
console.log('发布本端视频成功');
},
error(err) {
console.error('发布本端视频失败', err);
}
});
```
#### 6、禁用/启用本端资源
通过调用`muteLocal`方法禁用/启用本端资源。
切换视频播放以及麦克风状态时建议使用该方法,该方法不会取消本端资源发布,不会生成新的资源流。
`v0.0.11`版本开始支持。
```js
// 静音本端麦克风
this.client.muteLocal({
type: 'audio', // 禁用/启用的资源类型,可选值 audio、video,不填则禁用/启用所有资源
mute: true, // 为 true 禁用资源,为 false 启用资源,不填默认为 true
success() {
console.log('静音本端麦克风成功');
},
error(err) {
console.error('静音本端麦克风失败', err);
}
});
// 取消静音本端麦克风
this.client.muteLocal({
type: 'audio', // 禁用/启用的资源类型,可选值 audio、video,不填则禁用/启用所有资源
mute: false, // 为 true 禁用资源,为 false 启用资源,不填默认为 true
success() {
console.log('取消静音成功');
},
error(err) {
console.error('取消静音失败', err);
}
});
```
#### 7、离开房间
通话结束后离开房间,自动取消本端所有的资源发布。
```js
this.client.leaveRoom({
success() {
console.log('离开房间成功');
},
error(err) {
console.error('离开房间失败', err);
}
});
```
### 实例化音视频组件
```js
new RTC(option);
```
`option`的配置项说明;
| 参数 | 说明 | 类型 | 默认值 |
| :-------- | :------------------------------------------------------------------------------------------------------------------: | :----- | :----- |
| type | 切换音频或视频,选填,该项主要决定会监听且自动播放的远端资源,可选值`audio` `video` `all` | string | `all` |
| roomId | 房间号,必填 | string | - |
| appKey | 厂商音视频应用 ID,必填 | string | - |
| userId | 用户 ID,必填,支持大小写英文字母、数字、特殊符号 `_` 的组合方式,最大长度 64 字节 | string | - |
| userSig | 腾讯云参数,若不传则由组件计算,其他厂商可忽略 | string | - |
| secretKey | 腾讯云音视频应用密钥,组件计算`userSig`时需要该参数,其他厂商可忽略 | string | - |
| token | 融云参数,可通过后端获取,若不传则由组件获取,其他厂商可忽略 | string | - |
| url | 融云获取 token 的地址前缀,组件获取`token`时需要传入,其他厂商可忽略 | string | - |
| name | 融云获取 token 时需要传入用户名称,组件获取`token`时需要传入,不区分符号、英文字符、中文字符,统一限制最多 64 个字符 | string | - |
| navi | 融云的私有化部署地址,私有云环境下必填,其他厂商可忽略 | string | - |
| Util | 请传入`Util`用于组件请求接口 | object | - |
### API 列表
| 方法名 | 说明 | 参数 | 返回值 |
| :----------------------- | :--------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------ |
| init | 检查媒体权限,初始化厂商 sdk 实例 | opt: { success: Function(成功回调), error: Function(失败回调) } | - |
| joinRoom | 加入房间 | opt: { roomId: String(选填,房间号,若实例化组件时没有传入房间号,也可以在这里传入),getRemoteVideoNode: Function(必填,需要返回一个 promise,并传递展示远端资源的节点), removeRemoteVideoNode:Function(必填,每次取消订阅远端资源时会被调用),onTrackPublish: Function(`0.0.11`,选填,每次远端资源发布时会被调用), success: Function(成功回调), error: Function(失败回调) } | - |
| openAudio | 发布本端语音 | opt: { success: Function(成功回调), error: Function(失败回调) } | - |
| closeAudio | 停止发布本端语音 | opt: { success: Function(成功回调), error: Function(失败回调) } | - |
| openVideo | 发布本端视频 | opt: { domId: String(展示本端视频的 DOM 节点 id), cameraOpt: Object (`0.0.11`,可修改分辨率等摄像头配置), success: Function(成功回调), error: Function(失败回调) } | - |
| changeCamera | 切换摄像头 | opt: { cameraIndex: Number(要切换的摄像头索引,0 是默认的前置摄像头,1 是后置摄像头,不填则自动切换另一个摄像头),cameraOpt: Object (`0.0.11`), success: Function(成功回调), error: Function(失败回调) } | - |
| closeVideo | 停止发布本端视频 | opt: { success: Function(成功回调), error: Function(失败回调) } | - |
| leaveRoom | 离开房间 | opt: { success: Function(成功回调), error: Function(失败回调) } | - |
| destroy | 销毁 rtc 实例,会销毁本地资源流 | opt: { success: Function(成功回调), error: Function(失败回调) } | - |
| getMediaDevices `0.0.11` | 获取设备列表,包括摄像头、麦克风等 | opt: { type: String(设备类型,可选值 cameras、microphones,不填返回所有设备列表), success: Function(成功回调), error: Function(失败回调) } | [MediaDeviceInfo](https://developer.mozilla.org/en-US/docs/Web/API/MediaDeviceInfo)[] |
| replayRemote `0.0.11` | 重新播放远端资源(避免部分机型无法自动播放远端资源) | opt: { success: Function(成功回调), error: Function(失败回调) } | - |
| muteLocal `0.0.11` | 禁用本端资源,包括音频、视频(非停止资源发布) | opt: { type: string(禁用的资源类型,可选值 audio、video,不填则禁用所有资源), mute: boolean(为 true 禁用资源,为 false 启用资源,不填默认为 true), success: Function(成功回调), error: Function(失败回调) } | - |
**cameraOpt 配置项说明**
该配置项自该组件版本`0.0.11`开始支持。
融云配置项参考文档:https://doc.rongcloud.cn/apidoc/rtc-web/latest/interfaces/ICameraVideoProfile.html
腾讯云配置项参考 startLocalVideo 方法的 option 配置:https://web.sdk.qcloud.com/trtc/webrtc/v5/doc/zh-cn/TRTC.html#startLocalVideo
### 音视频组件拓展
若要自己封装其他三方厂商的音视频组件,且与框架的音视频组件保持一套用法,可以基于框架提供的音视频组件管理器进行拓展。
在音视频组件`0.0.4`及以上版本中,组件提供拓展功能。
#### 自定义插件演示
1. 首先需要开发自定义插件实现音视频组件具体功能。
插件必须提供以下示例中的所有方法:
```js
export default class GXHPlugin {
constructor() {
this.mode = 'gxh'; // 记录当前厂商
// 以下字段仅作示例,具体实现时可自定义
this._client = null; // 厂商的sdk服务
this._localDOM = null; // 记录本端视频dom节点id
this._opt = null; // 传入的配置项
...
}
// 插件执行内容定义在apply方法中
apply(compiler) {
// 初始化钩子
compiler.hooks.init.tap('GXHPlugin', (opt) => {
return this.init(opt);
});
// 获取传入实例的配置项
compiler.hooks.getOpt.tap('GXHPlugin', (opt) => {
this._opt = opt;
});
// 加入房间
compiler.hooks.joinRoom.tapAsync('GXHPlugin', (opt) => {
return this.joinRoom(opt);
});
// 发布本端语音
compiler.hooks.openAudio.tapAsync('GXHPlugin', (opt) => {
return this.openAudio(opt);
});
// 取消本端语音发布
compiler.hooks.closeAudio.tapAsync('GXHPlugin', (opt) => {
return this.closeAudio(opt);
});
// 发布本端视频
compiler.hooks.openVideo.tapAsync('GXHPlugin', (opt) => {
return this.openVideo(opt);
});
// 切换摄像头
compiler.hooks.changeCamera.tapAsync('GXHPlugin', (opt) => {
return this.changeCamera(opt);
});
// 关闭摄像头
compiler.hooks.closeVideo.tapAsync('GXHPlugin', (opt) => {
return this.closeVideo(opt);
});
// 离开房间
compiler.hooks.leaveRoom.tapAsync('GXHPlugin', (opt) => {
return this.leaveRoom(opt);
});
// 检查必填参数项钩子
compiler.hooks.checkOtherOpt.tap('GXHPlugin', (option) => {
return this.checkOtherOpt(option);
});
// 销毁实例钩子
compiler.hooks.destroy.tap('GXHPlugin', (option) => {
return this.destroy(option);
});
// 重置内部变量钩子,该钩子在实例创建时及实例销毁时都会调用
compiler.hooks.reset.tap('GXHPlugin', () => {
return this.reset();
});
}
// 初始化
async init(opt) {
...
// 创建rtc类
if (!this._client) {
this._client = {};
}
if (this._client) {
opt.success && opt.success();
}
}
// 加入房间
async joinRoom(opt) {
...
opt.success && opt.success();
}
// 发布本端语音
async openAudio(opt) {
...
opt.success && opt.success();
}
// 停止本端语音发布
async closeAudio(opt) {
...
opt.success && opt.success();
}
// 发布本端视频
async openVideo(opt) {
...
opt.success && opt.success();
}
// 切换摄像头
async changeCamera(opt) {
...
opt.success && opt.success();
}
// 停止本地视频发布
async closeVideo(opt) {
...
opt.success && opt.success();
}
// 离开会议室
leaveRoom(opt) {
...
opt.success && opt.success();
}
// 销毁
destroy(opt) {
...
opt.success && opt.success();
}
// 检查腾讯云需要的参数, 若检查不通过直接返回false
checkOtherOpt(option) {
...
return option;
}
// 重置内部变量
reset() {
this._client = null;
this._localDOM = null;
this._opt = null;
this._currRemoteUsers = [];
this._isBackCamera = false;
this._isPublishCamera = false;
}
}
```
2. 插件开发完成后,页面上引入音视频组件管理器文件及自定义插件,进行初始化。
```js
import RTCManager from '@epoint-mrc/rtc/dist/manager.min.js';
import GXHPlugin from './GXHPlugin.js';
export default {
...
created(){
this.client = new RTCManager({
type: this.type, // 切换音频或视频,audio/video/all
roomId: '1', // 房间号
appKey: '1234567890', // 厂商音视频应用ID
userId: '1',
...
plugins: [new GXHPlugin()] // 引入个性化拓展插件
});
}
}
```
然后就可以像使用框架音视频组件一样使用个性化拓展的音视频组件。
## FAQ
1. 安卓可以正常播放远端音视频资源,但 ios 没有正常播放?
相关知识库:https://fdoc.epoint.com.cn/onlinedoc/kfzknowledge/kfzknowledge/handlequestionworkflow?ProcessVersionInstanceGuid=714d9dd8-f010-40a5-869c-2cae9c2bc3bd&WorkItemGuid=e14eb4ad-857a-486b-8213-3cfd9e1ca732&MessageItemGuid=bb1229e5-5af8-4a1b-b373-253426968704
在 ios 端,video 的播放必须在事件回调中进行,否则会播放失败。
官方文档说明地址:[融云开发者文档-Web 兼容性](https://doc.rongcloud.cn/rtc/Web/5.X/intro/compatibility)
2. 接入三方 app 时,获取应用媒体授权失败?
相关知识库:https://fdoc.epoint.com.cn/onlinedoc/kfzknowledge/kfzknowledge/handlequestionworkflow?ProcessVersionInstanceGuid=cd9dc5f4-1df5-47dd-86bc-1752ffbea8d5&WorkItemGuid=3a02e405-814f-4d40-9438-dd3a0909e8fe&MessageItemGuid=7a27d3ff-5351-4752-a31d-e34126816ce0
H5 页面内嵌三方 APP 时,获取摄像头麦克风等权限是向 APP 申请,所以若调用 Web API: `navigator.mediaDevices.getUserMedia` 方法去检查麦克风和摄像头的权限失败,需要三方 APP 提供获取权限的 API。
3. 华为手机无法切换后置摄像头?
相关知识库:https://fdoc.epoint.com.cn/onlinedoc/kfzknowledge/kfzknowledge/handlequestionworkflow?ProcessVersionInstanceGuid=310602b4-41cf-4056-bf63-2c33ae4787a4&WorkItemGuid=fb0ce09c-d9b4-43cd-8dc0-2dccfa9dba18&MessageItemGuid=c3f94900-d3ac-4e5f-b24a-db362ad37337
华为手机无法同时获取前置摄像头和后置摄像头资源,所以切换后置摄像头时,需要先将当前摄像头资源销毁。
4. 如何判断前置还是后置摄像头?
调用音视频 sdk 方法返回的设备列表,都会带有固定字段对设备接口进行描述。
这些字段信息可查阅相关文档:[MediaDeviceInfo - Web APIs | MDN (mozilla.org)](https://developer.mozilla.org/en-US/docs/Web/API/MediaDeviceInfo)
可通过正则校验其中的`label`字段是否带有 `front`、`前置`,以及`back`、`后置`这些文字判断该摄像头是前置还是后置。默认情况下,设备列表第一个是前置摄像头,第二个是后置摄像头。
5. 音视频通话过程中熄屏/跳转其他页面/切换到后台,音视频通话会中断或者返回页面后没有视频声音?
通常是由于浏览器或操作系统的节能策略导致的,或者应用没有获得适当的权限(例如后台播放权限),当设备进入休眠模式时,它将无法继续运行其功能,从而导致播放中断。某些浏览器对`WebRTC`的后台处理有严格限制,当页面被切换到后台时,浏览器可能会暂停或停止`WebRTC`会话。
一般情况下,可以监听页面的`visibilitychange`事件,当页面可见时,重新播放远端资源。
```js
document.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'visible') {
// 重新播放远端资源
this.client && this.client.replayRemote();
}
});
```