通过参会者事件回调开发者可获取到参会者信息,根据参会者信息可通过平台拉取音视频流。 平台默认返回 20 路以内的音视频流,如需获取更多,可咨询技术支持人员。
将拉流的数据结合布局策略开发者可自由定义多种布局。布局的解决方案包含自动布局(auto)及自定义布局(custom),开发者可具体参考各平台文档详情。 自动布局指封装了参会者位置、请流策略等基础的布局业务逻辑的解决方案,开发者可通过接口快速实现画面布局。 自定义布局适用于开发者需要根据应用场景自定义布局策略的情况,比如设置参会者的位置、设置画面视频分辨率等。
自动布局是指SDK内部封装了参会者位置、请流策略、和基础的布局能力,并直接渲染到UI组件上,开发者可以通过集成
组件可快速实现会议效果;
在v3.8版本之前,小程序SDK提供了基于竖屏方式通过旋转元素模拟横屏的模式展示画面,这种处理方式是因为较早之前小程序不支持横屏模式的历史解决方案,存在一定的滞后性:
伴随着小程序的迭代,现在小程序已经支持横屏模式,也因此次腾讯TRTC服务的安全升级,导致历史方案的画面存在画面被旋转90的问题,腾讯侧暂时不再支持这种竖屏方式下的横屏画面展示。所以在最终的方案中,我们将全面支持UI和非UI两种模式的横屏展示。
新的方案下,UI组件在自动布局下,需要将页面切换到横屏模式下,依赖横屏提供的基础布局能力,所有的元素将不再进行旋转,并且支持使用小程序的Toast/Message提示框功能,不在出现弹出框的方向不正确问题,用户体验将会更友好;
新的方案示意图如下:
可以看到整体上会议界面显示更加简单和有效,避免了无用的信息干扰;
将详细介绍自动布局的实现方法,整体实现比较简单:
在会议页面的json配置文件中指定使用
组件,并配置屏幕为横屏模式:
// 使用SDK UI组件
"usingComponents": {
"xylink-room": "@xylink/xy-mp-sdk/component"
},
// 设置屏幕方向为横屏
"pageOrientation": "landscape"
在会议页面得wxml文件中加载
组件,组件内部会自动处理画面和声音的播放、布局的计算和展示,开发者无需过多的参与逻辑开发;
<!-- 小鱼小程序SDK UI组件 -->
<xylink-room
template="{{ template }}"
beauty="{{ 6 }}"
muted="{{ muted }}"
camera="{{camera}}"
devicePosition="{{ devicePosition }}"
id="xylink"
bindonRoomEvent="onRoomEvent"
/>
// 定义Page中的data template数据
data: {
...
template: {
layout: 'auto',
detail: []
},
}
创建XYClient实例,并配置自动布局画面容器的偏移量,此处针对container的配置是可选项。
提示
SDK内部UI组件的画面是基于可视容器的宽高(windowWidth、windowHeight)计算得到的,如果需要针对画面容器进行上下左右的偏移以适配在合理的空间显示画面,可以在创建client实例时配置container的offset用来减去上下左右的偏移量。
注意,此处的尺寸单位都是PX;
onLoad(option) {
// XYRTC.createClient()创建了一个单例对象client,在多个小程序页面之间共享一个实例,可以重复调用获取最新的实例;
this.XYClient = XYRTC.createClient({
container: {
// 自动布局可以配置上/下/左/右偏移量,默认是可视区域的宽高计算画面大小和位置
offset: [0, 0, 0, 0],
},
});
}
登录并加入会议即可完成集成;
自动布局因为限制只支持3画面的显示,并且无法控制每个人的画面位置,如果无法满足业务需求,可以采用自定义布局方式,来灵活定制参会者的数量、位置、大小和分辨率;
自定义布局单页最多请求8画面数据,加上本地画面,最多可以显示9画面内容;
自定义布局整体在新版本下和之前的差异不大,唯一一点区别是因为屏幕方向变化,参会者的位置、画面大小计算时对应的起始点坐标不一致,需要适配新的坐标位置和画面大小信息即可;
历史方案中,因为屏幕是竖屏方式布局,通过旋转元素实现了横屏效果,所以整体的坐标起始点位于竖屏的左上角位置,最终计算得到template中每个参会者画面的坐标和画面大小:
其中坐标单位是left和width是vw,top和height是vh;
新的方案中,需要设定屏幕为横屏模式,因为对应的起始坐标位于横屏得左上角位置,示意图如下:
坐标单位:
position: ["0", "10PX", "50PX", "50PX"]
,则代表left为0,top是距顶部10PX,宽高均为:50PX;举例:
通过自定义布局实现左右等分的对称布局,那么对应的template定义为:
template: {
layout: 'custom',
detail: [
{
// 固定位置,转化为left:0vw, top:0vh, width: 50vw, height: 100vh
position: [0, 0, 50, 100],
// ...
},
{
// 自定义位置,最终转化为left:50PX, top:0, width: 50PX, height: 100PX
position: ["50PX", "0", "50PX", "100PX"],
// ...
}
]
}
在会议页面的json配置文件中指定使用
组件,并配置屏幕为横屏模式:
// 使用SDK UI组件
"usingComponents": {
"xylink-room": "@xylink/xy-mp-sdk/component"
},
// 设置屏幕方向为横屏
"pageOrientation": "landscape"
在会议页面得wxml文件中加载
组件;
<!-- 小鱼小程序SDK UI组件 -->
<xylink-room
template="{{ template }}"
beauty="{{ 6 }}"
muted="{{ muted }}"
camera="{{camera}}"
devicePosition="{{ devicePosition }}"
id="xylink"
bindonRoomEvent="onRoomEvent"
/>
初始配置template为custom布局;
// 定义Page中的data template数据
data: {
...
template: {
layout: 'custom',
detail: []
}
}
在onLoad函数中设置Local数据,并启动本地画面推流;
this.setData({
template: {
layout: "custom",
detail: [
{
position: [10, 0, 90, 100],
// this.callNumber 是Local数据,从登录接口获取,或者通过XYClient实例上获取getUserInfo信息
callNumber: this.callNumber,
// name可以从本地或者Roster数据中获取
name: "LocalName",
quality: "normal",
isContent: false,
},
],
},
})
此步骤需要做如下事情:
onRoomEvent(ms) {
const { type, detail, message } = ms.detail;
switch (type) {
case 'roster':
// 参会者列表数据,当有人员变动或者状态变动,会实时推送最新的列表数据
console.log('demo get roster message: ', detail);
// 自动布局不需要处理Roster数据
if (this.data.template.layout !== 'custom') {
return;
}
this.handleCustomLayout(detail);
break;
}
},
handleCustomLayout(detail) {
// 处理custom模式自定义布局
const newDetails = [];
// 获取roster数据
const roster = detail.rosterV;
// 最多显示9个画面(包括Local)
if (roster.length > 8) roster.length = 8;
const len = roster.length + 1;
const selfDetailObj = {
// CUSTOM_TEMPLATE模版定义参见Demo
position: CUSTOM_TEMPLATE[len].self,
callNumber: this.userInfo.callNumber,
name: this.pageOption.displayName,
quality: 'normal',
isContent: false,
};
roster.forEach((item, index) => {
newDetails.push({
// CUSTOM_TEMPLATE模版定义参见Demo
position: CUSTOM_TEMPLATE[len].other[index].position,
callNumber: item.callNumber,
name: item.displayName || '',
quality: item.isContent ? 'hd' : 'normal',
isContent: item.isContent,
});
});
// 自己的数据补充到第一位
newDetails.unshift(selfDetailObj);
const nextTemplate = Object.assign({}, this.data.template, {
detail: newDetails,
})
this.setData({ template: nextTemplate });
},