|导语 使用企业微信跨组织间会议门槛较高,要求外部客户或合作伙伴先建立在企业微信的线上组织才可入会,通过引入小程序入会能力,降低跨组织会议的门槛; 为解决微信用户发起会议,邀请企业微信、微信好友入会的场景,企业微信会议小程序也提供在微信侧接入和发起会议的能力,实现微信用户发起会议邀请企业成员加入会议的能力;
产品功能说明
企业微信的会议是接入了腾讯云提供的XCast SDK,腾讯会议后台提供了Rest APi接口用于创建会议、加入会议、获取会议信息等;
企业微信的会议是接入了腾讯云提供的XCast SDK,腾讯会议后台提供了Rest APi接口用于创建会议、加入会议、获取会议信息等;
企业微信app发起的会议,虽然支持邀请微信好友加入会议,但是需要用户下载和安装企业微信,注册企业微信后才可以加入会议,加入会议的门槛较高;
虽然有利于企业微信引流拉新,但是对于用户而言是不太友好的,所以我们启动了微信小程序可以直接加入会议的方案;
功能表现: 企业微信app发起会议后通过邀请微信好友,好友会收到小程序卡片,打开小程序可以直接加入会议;或是微信用户发起会议,发起邀请企业微信或微信用户入会;
视频会议在小程序侧的UI表现:
视频会议支持美颜、会议附件、文档共享和屏幕共享等能力:
音频会议及会议管理:
支持企业微信发起的预约会议,邀请微信用户参加,在会议开始时会收到微信的服务通知,提醒进入会议;
会议技术流程介绍
首先要介绍一下TRTC
TRTC:腾讯实时音视频(Tencent Real-Time Communication,TRTC);
腾讯云提供了多人音视频通话方案和低延时互动直播方案两种服务,提供覆盖手机、桌面全平台的客户端 SDK 以及云端 API,终端用户还可以在微信、QQ、企业微信的小程序中使用 TRTC 服务,Web 网页也可轻松使用。
下图是TRTC官方的一个产品架构图:
腾讯会议与TRTC的关系
腾讯会议基础服务是基于TRTC的音视频媒体服务 进房权限保护(建立私有的房间集合,与TRTC的房间是不互通的),再结合腾讯会议自己建设的会控能力、会议模式下强悍的混音模块等,也包括腾讯会议自己扩展的一些功能;
TRTC进房权限保护机制
privateMapKey 是 TRTCParamEnc 中的一个可选字段,它的作用是让腾讯云检查用户是否拥有进入指定房间的权限。
privateMapKey是由服务器端计算提供,是在加入会议房间时腾讯会议的后台会返回web_user_signature,也就是获取RoomSIg时必要的privateMapKey字段;
小程序接入会议的整体技术方案
企业微信会议依赖模块示意图
腾讯会议有个自己的小程序,是建立在内部API的体系下,并不是可以对外开放的能力,而企业微信终端接入的XCastSDK是基于Rest API这套开放体系接入的;
RestAPI之前也未提供给第三方提供过小程序的开放能力,此次企业微信启动的小程序接入,是腾讯会议首次对外提供会议小程序入会的通道,赶上过年疫情期间的暴发,腾讯会议的后台同学也是经常通宵,无人力支持的情况下导致项目整个过程极其漫长而又痛苦;
TRTC官方提供的小程序demo,是使用微信小程序提供的live-pusher和live-player组件实现的,小程序加入腾讯会议私有域的房间,主体技术流程如下图所示:
会议小程序接入整体架构示意图:
接入腾讯会议的整体流程概述:
- 小程序用户通过RestAPI加入腾讯会议逻辑房间;
- 加入成功后获取音视频房间信息,包括实时音视频控制台侧媒体流动态分配的机器ip、端口等;
- 获取加入音视频媒体房间使用的用户信息以及鉴权信息,包含音视频房间RTMP代理的服务器及端口信息,是根据用户的地域通过云端动态分发最优线路下发,最大限度提升用户在会议中的音视频和通话的流畅度;
- 获取到音视频鉴权必要的信息后,通过live-pusher建立音视频通道链接,响应音视频通道推送的用户音视频流以及采集音视频流的推送,以及音视频混音流或辅助流的推送和收发,屏幕共享就是基于辅助视频流的方式实现的,发起屏幕共享的人通过live-pusher可以推送当前屏幕录屏的数据流,腾讯会议侧音视频房间的接口机负责转发到RTMP代理,再通过音视频建立的通道进行数据流的分发,推送给房间其它的用户;
- 同时建立企业微信会议逻辑房间的WebSocket长链接通道,并初始化当前用户在逻辑房间的状态;企业微信会议拥有独立的会控能力,包括文档共享、屏幕共享、灵活的主持人会议控制能力等,都是基于企业微信会议的长链接服务;
- 企业微信独立的会控制能力,具体的会控指令包括(人员上/下线、开关麦、开关视频、主持人控制、请求上台发言、主持人控制会议的人员进入、或灵活的管理规则等),这部分能力是企业微信后台单独控制,例如人员上/下线也是通过REST API通知腾讯会议侧的后台进行更新会议状态,以保持音视频房间的成员状态尽可能与企业微信逻辑房间的状态保持同步;
企业微信会议完整技术方案示意图:
会议终端接入方案以及注意事项
- 企业微信App通过接入腾讯会议提供的XCast SDK,做为客户端接入音视频房间的基础能力,包括音视频采集、播放、美颜、推流控制等,同样也建立与企业微信后台逻辑房间层的长链接通道;
- 企业微信App通过企业微信后台请求RestAPI创建会议的接口发起会议,创建会议后会返回音视频媒体房间的信息,企业微信App拿到音视频房间信息后,通过XCast SDK发起推流行为,以及接收推流的事件和音视频数据,包括媒体房间的用户信息等;
- 音视频房间用户的列表是基于创建会议时传入的instanceID_userID_appid生成的userOpenId,这里就依赖逻辑房间层也要在用户列表中也增加此用户的UserID信息,用于逻辑房间与媒体房间的用户关联;
- 接受当前同一房间用户的音视频流数据,使用live-player渲染用户侧的画面;
- 小程序发起长链接与企业微信后台建立sync通道,用于会议控制指令下发和上行的交互;
- 企业微信app发起者可以屏幕共享,是通过TRTC的视频流通道采集和推送能力,通过TRTC的音视频房间为其它用户推流,其它用户收到的共享者的视频画面则更换为屏幕的画面(TRTC支持了辅助流,也就是视频画面和共享屏幕的画面都可以显示,但微信小程序暂不支持);
会议小程序独立会议控制
企业微信会议过程中的会议控制,是通过单独的逻辑房间长链接通道实现,会控逻辑相关的时序操作流程如下图所示:
企业微信的会议控制包括主持人控制人员上下台,或单独控制开启关闭视频、音频等能力;
企业微信用户在会议中发起文档共享,是企业微信提供的私有能力,发起者共享文档时,通过企业微信后台转换为共享的数据流,通过长链推送到其它用户,小程序接受共享的数据后实时更新,包括发起者共享中的翻页、画箭头等行为,同步在小程序中渲染;
音视频RestAPI接入层
- RestAPI负责对接企业微信后台,负责创建、进入、退出会议、获取音视频鉴权信息等逻辑通道能力;
- RestAPI负责与会议的Center Server进行通信,控制会议的状态以及人员加入房间的权限校验等逻辑处理;
- Center Server负责与音视频代理转发层Server进行交互,包括管理会议房间的权限控制以及与音视频媒体房间的状态同步等逻辑;
- 音视频代理转发层负责与接口机进行数据通信,包括接入混音模块、音视频转码模块,TRTC通过RTMP转发到小程序的服务能力等;
- 接口机是TEG提供根据用户接入层的动态分发模块,以及音视频接入转发的底层服务;
音视频引擎介绍
来自腾讯会议的同学提供的引擎介绍
- Engine Interface
引擎接口层主要给上层提供启动、停止、设置各种参数的接口,和引擎的一些事件通知回调。另外,引擎不提供实际的网络传输,而是把收发数据的接口给上层来实现。
- IO Stream
IO流是引擎很重要的一个抽象概念(有点类似响应式编程和TensorFlow的思想),各个数据处理模块(比如解包、FEC、音频解码、混音等)通过IO流串联成一条或者多条单向流动的树形链路,各个模块的处理按照顺序分布在各节点上,IO流的拓扑结构由CTopoNode组织构成。
IO流是引擎很重要的一个抽象概念(有点类似响应式编程和TensorFlow的思想),各个数据处理模块(比如解包、FEC、音频解码、混音等)通过IO流串联成一条或者多条单向流动的树形链路,各个模块的处理按照顺序分布在各节点上,IO流的拓扑结构由CTopoNode组织构成。
每个CTopoNode节点绑定一个CParser对象,CParser需要实现两个函数:ProcessInput和ProcessOutput,前者在数据流入本模块的时候解析和处理数据、后者在数据流出本模块的时候把处理好的数据交给下一个模块。
- 最底层就是各模块实现细节,包括各个平台的音频设备管理、QoS、传输协议封装、信号处理(重采样、PLC、3A等)、音频编解码 Qos;
引擎的基本处理流程,引擎有两条主要的流:
接收流和发送流。这两条流都在引擎启动的时候就构建好了拓扑结构
1. 接收流
接收流从网络收包以后再Dmx节点进行解包,然后根据uin分成若干个DecChannel,每个DecChannel都有单独的IO调用链,分别对应FEC解码(也包括ARQ)、QT解码、抗抖、音频解码。然后所有的DecChannel合并到一起,进入混音模块,最后输出到终端播放。
2. 发送流
发送流是从录音设备采集开始,然后数据流经过3A、编码、QT编码、FEC编码,最后送到网络发送。
我们遇到的问题及解决方案
我们在开发会议小程序的过程中遇到了各种各样的问题,下面记录分享一下我们遇到的问题以及解决思路;
如果也有遇到类似的问题的同学,可以企业微信联系一起交流经验;
1、文档共享/屏幕共享相关的问题
文档共享、屏幕共享时live-pusher临时断开导致数据流无法渲染;
问题:
腾讯会议提供的音视频服务都依赖于live-pusher建立的通道,如果在文档共享或屏幕共享时view的切换导致live-pusher组件有临时中断的情况,会导致会议音视频中断,只有再建立成功后才可以恢复;
解决办法:
避免view的重新渲染,通过class控制view节点的布局调整,保持live-pusher一直在链接状态;
文档共享的技术实现
简企业微信app的会议主持人可以发起文档共享时,通过标注图标绘制在文档上,小程序会接受文档共享的文档内容以及指令信息,指令信息为箭头开始的坐标x/y,以及结束坐标的x/y;
小程序提供一个文档共享查看的窗口,同时调整live-pusher和live-player的表现,通过长链接接受指令的信息后,在文档内容的上层创建一个同样大小的canvas用于绘制箭头,指令的实时变化会通过长链通知,实现演示中箭头指向的问题;
目前遇到一个比较严重的bug是canvas在缩放一定的比例后,会有一个超出绘制范围的bug,导致箭头的绘制不会被渲染(老版本canvas api存在的问题);
屏幕共享的技术实现流程
会议中的屏幕共享是使用一个辅助视频流上行推送,其它侧用户会通过live-pusher的onPush事件进行推送的,在推送的用户列表信息中会出现一个userlist_aux用于标识屏幕共享的视频流信息;
小程序在接收到有屏幕共享视频流的情况下,会切换到屏幕共享的状态下,大屏显示屏幕共享的数据,同时将共享人的视频画面使用live-player中正常播放;
屏幕共享的视频流使用live-player播放;
2、音视频控制相关的问题
音视频上下台时推流中断出现画面闪烁的问题
- 上下台切换音/视频流时如果数组发生大的变化会导致live-pusher和live-player中断,导致画面出现严重的闪烁问题;
- 通过对原有人员数据增加diff方法,添加上、下台的人员数据以及清理退出会议的人员,则可以避免此问题;
音视频房间与逻辑房间userID不一致的问题
- 腾讯会议逻辑房间的userID与音视频房间的userID是不同的;
- 我们通过在逻辑房间增加与音视频房间统一的userID用于匹配逻辑房间用户与音视频房间用户的一致性,需要腾讯会议RestApi后台增加返回一致的userID字段;
音量控制动画
- live-pusher中未提供当前用户的音量大小的能力,导致当前用户无法感知到自己的语音情况是否正常(预计近期的版本中会支持);
- 通过会议中其它用户的live-player的音量值大小实时推送给live-pusher的用户,再进行展示音量的状态;
- 音量大小的动画则是使用根据音量大小提供不同音量下的png图片,叠加变化生成的动画效果,也比较准确的表现当前音量值的大小;
视频画面方面各个端采集方向不同(移动端、小程序、桌面端的差异性)
- 移动端、桌面端企业微信采集的视频画面方向会有所不同,小程序的画面需要适配才可以支持不同的视频采集方向;
- 根据用户的来源适配画面的展示方向,兼容视频画面采集的格式差异;
3、其它一些问题
腾讯会议房间鉴权的问题
前期遇到最多的问题是来自于此,首先这里存在2个概念,一个是逻辑房间,一个是音视频房间,所需的鉴权信息不同,另外腾讯会议测试环境与正式环境不互通,导致这里出问题会比较多;
加入房间时用户身份信息与签名时使用的字段差异,依赖腾讯会议后台提供必要的字段
因音视频媒体房间所需要的签名是腾讯会议center处理的,依赖他们转换后的UserID以及SDKAppID去生成userSig,这里同样存在测试环境与正式环境不互通的问题;
测试环境不稳定的情况
- 腾讯会议测试环境会遇到共享屏幕数据流无法接收的问题;
- 小程序偶尔会异常退出房间,音视频画面中断等情况出现;
- 腾讯会议的这套流程,出问题后排查问题非常复杂,涉及的环节非常多,不太利于未来开放;
涉及原生组件同层渲染相关的问题
一、 小程序原生组件存在的问题
小程序的原生组件的层级是最高的,所以页面中的其他组件无论设置 z-index 为多少,都无法盖在原生组件上。同层渲染是为了解决小程序中普通元素之间无法覆盖native的原生组件,解决特定组件下UI表现的各种异常问题;
会议小程序中使用的native-component组件有以下这些(查看更多原生组件,可以参考官方文档): - live-player - live-pusher - input(仅在focus时表现为原生组件) - canvas
官方介绍的原生组件的使用限制:
由于原生组件脱离在 WebView 渲染流程外,因此在使用时有以下限制:
- 原生组件的层级是最高的,所以页面中的其他组件无论设置 z-index 为多少,都无法盖在原生组件上。
- 后插入的原生组件可以覆盖之前的原生组件。
- 原生组件还无法在 picker-view 中使用。
- 部分CSS样式无法应用于原生组件, 例如: 无法对原生组件设置 CSS 动画 无法定义原生组件为 position: fixed 不能在父级节点使用 overflow: hidden 来裁剪原生组件的显示区域
- 原生组件的事件监听不能使用 bind:eventname 的写法,只支持 bindeventname。原生组件也不支持 catch 和 capture 的事件绑定方式。
- 原生组件会遮挡 vConsole 弹出的调试面板。在工具上,原生组件是用web组件模拟的,因此很多情况并不能很好的还原真机的表现,建议开发者在使用到原生组件时尽量在真机上进行调试。
二、Cover-View适配原生组件的方案
存在严重缺陷,但在不支持同层渲染的原生组件需要使用
cover-view 与 cover-image
为了解决原生组件层级最高的限制。小程序专门提供了 cover-view 和 cover-image 组件,可以覆盖在部分原生组件上面。
这两个组件也是原生组件,但是使用限制与其他原生组件有所不同。为了解决依赖button按钮的事件行为,cover-view中支持嵌套button的view类型;
使用Cover-View覆盖live-pusher和live-player的view元素
- cover-view不能嵌套在slot传入组件内,会导致无法覆盖原生组件的层级出现混乱;
- 最佳的实现方式是包裹在live-pusher和live-player内,使用cover-view和cover-image元素;
- cover-view元素之间可以通过插入的先后顺序和z-index确定层级关系;
- components组件内的cover-view元素会出现无法覆盖父级原生组件的;
- cover-view在动态渲染到屏幕时,会偶尔出现层级混乱被原生组件覆盖的问题(目前看应该是同层渲染的bug)
严重缺陷:
cover-view是不支持滚动列表响应滚动事件和行为的,导致有涉及滚动页面的列表会有问题;
三、同层渲染遇到的问题及解决方法
如果发现同层渲染有无法解决的问题,可以强制关闭同层渲染
//app.json window 中配置
{ "renderingMode": "seperated" }
同层渲染是为了解决原生组件的层级问题,在支持同层渲染后,原生组件与其它组件可以随意叠加,有关层级的限制将不再存在。
但需要注意的是,组件内部仍由原生渲染,样式一般还是对原生组件内部无效。当前 video, map, live-player, live-pusher, canvas(2d) 组件已支持同层渲染。
原生组件相对层级,为了可以调整原生组件之间的相对层级位置,支持在样式中声明 z-index 来指定原生组件的层级。该 z-index 仅调整原生组件之间的层级顺序,其层级仍高于其他非原生组件。
1、 组件live-player和live-pusher不支持点击事件,支持全屏操作的切换;
- 小程序live-pusher/live-player不支持点击事件,可以用一个透明的view覆盖在原生组件上用于响应事件;
- view css的透明度低于5%,则不会被渲染,可以选一个合适的透明度的黑色,只是降低了透光量,不会影响画面的展示效果;
2、 同层渲染情况下view元素跳动的问题
问题表现:
覆盖在原生组件上的普通view元素,在列表滚动时位置会跟随变化,偶尔会跳出live-player的视图之外,无法跟随容器的范围变化;
解决办法:
在普通的view的根节点下增加will-change和transform,告知webview该元素会有哪些变化的方法,这里也就是会有transform缩放的变化,强制触发webview的重新渲染;
will-change: transform;
transform: scale(1);
3、 视频流出现黑屏
问题表现:
视频流地址有推送的情况下,播放中并没有视图流信息导致播放窗口黑屏;
解决方案:
在live-player的change事件监听中判断当前视频流的帧率是否正常,如果不正常则使用头像显示,覆盖黑屏的表现;
4、 屏幕共享视频流中断续传
问题表现:
企业微信app用户发起屏幕共享过程中,如果用户未结束共享,但是视频流推送中断了,导致画面暂停或黑屏;
解决方案:
在感知用户结束屏蔽共享行为时,我们在逻辑房间补充一个通知逻辑,告知小程序主动结束屏幕共享的状态; 如果是用户还在共享,腾讯会议音视频房间推送的视频流中断了,则为用户提示重新进出房间恢复画面(同时反馈给腾讯会议修复此bug);
5、 live-player可滚动的问题(遗留)
问题表现:
当用户点击某个用户头像全屏后,再回列表,有一定概率出现小窗口可以滚动的情况;
解决方案:
初步确定的方案是在全屏视图下把普通view节点与live-player进行分离,以同层级并列关系存在,因调整较大,后续做为技术优化完善;