微信小程序 WebView 支持 WebRTC

最近微信小程序的业务场景,使用 WebRTC 对设备进行拉流,针对遇到的一些问题简单记录分享下。

一、音视频传输链路

通过上面的图,可以清楚的看到,分为两条链路

  • 设备 SDK 支持 WebRTC,小程序端可以直接跟设备端进行 WebRTC 通信
  • 设备 SDK 不支持 WebRTC,小程序端可以通过 WebRTC 跟流媒体通信,流媒体通过自研 p2p 跟设备通信

相比走流媒体转发,小程序直接跟设备通信有两大优势(特别是当 candidate 组合为 host 或 srflx 时)

  • 首帧出流速度整体会更快
  • 会更加节省云端流量

二、小程序端 WebRTC 兼容性

找到一张腾讯云提供的兼容性表格,仅供参考:

目前,我们测试过的一些机型,持续测试中,后续有遇到不兼容的机型,也会记录在表格

机型 系统版本 微信版本 是否正常
三星Galaxy NOTE9/N9600 8.1.0 8.0.42 是
OPPO realme Q9 11 8.0.42 是
小米10青春版本 11 8.0.42 是
华为nova8/ANG-AN00 11 8.0.42 是
华为p40 pro 鸿蒙4.0 8.0.41 是
iPhone 12 15.4.1 8.0.43 是
iPhone Xr 14.0.1 8.0.43 是

PC 环境中其实 WebRTC的支持已经非常好,移动端一直有所欠缺;这几年微信对 WebRTC的支持,移动端 WebRTC的生态预计将越来越好

三、 WebRTC offer 控制

我们的业务场景,只是去获取设备端的音视频,以及对讲的时候,会向设备传输音频,但不会向设备传输视频。因此在协商的时候,需要简单对 offer 处理下

  • 第一种方式,通过在 createOffer 的时候,通过 options 进行控制,但offerToReceiveVideo 是控制是否接收视频(需要控制的是是否发送视频),且 options 已被废弃,因此行不通
  • 第二种方式,使用 addTransceiver 进行控制: addTransceiver('video', { direction: 'recvonly' }),代表仅接收不发送

补充一点,addTransceiver、addTrack、addStream 容易混淆,简单小结:

  • addStream 已弃用,可以不考虑
  • addTransceiver、addTrack 均可用,但要对 offer 进行控制时,addTransceiver 更加方便

四、移动端 video 视频自动播放

我们的业务场景是,进入页面,直接对设备进行拉流,且需要自动播放画面跟声音。根据之前在 PC 端的经验,我的第一反应是没有办法实现带声音的自动播放,如果是静音 autoplay 才可行。

事实证明,很多时候经验并非事实,一切得根据测试实际结果来看!

先说结论: 经过代码兼容测试,最终的效果是,在 WebRTC 这种场景下,无论是安卓或ios,均可实现带声音自动播放视频。

代码层做了哪些兼容?

微信官方文档

javascript 复制代码
// 引入微信 sdk, 1.6.0 版本
<script src="https://res2.wx.qq.com/open/js/jweixin-1.6.0.js" />

// 调用 config 初始化
window.wx.config({
  debug: false,
  appId: 'xxx',
  timestamp: '',
  nonceStr: '',
  signature: '',
  jsApiList: []
});

// ready 回调中手动调用 video.plsy()
window.wx.ready(() => {
  video.play();
})

// video 标签设置为 autoplay

代码层其实就做了上述兼容,就实现了 WebRTC 场景下(注意这里强调了是 WebRTC 场景,普通 mp4 并不行)安卓、ios下带声音自动播放。更准确的说,上述代码,其实只是针对 ios 下部分机型,不能带声音自动播放的兼容。

自动播放端倪

先上一个表格,这样会比较清晰,在未做任何兼容的情况下:

WebRTC video 带声音自动播放视频 mp4 video 带声音自动播放视频
安卓 可以 不可以
ios 部分机型可以 不可以
PC端 不可以 不可以

通过表格发现,WebRTC video 在未做兼容的情况下,表现跟 mp4 video 是不一样的,两者到底差在哪儿呢?

经过测试得知,主要差别在 video 标签播放视频流的方式不一样:

javascript 复制代码
// WebRTC video
video.srcObject = mediaStream

// mp4 video
<video src='xxxxxx.mp4' />

使用 'srcObject' 的方式,在安卓跟部分 ios 下,直接就可以带声音自动播放;而使用 'src' 的方式,移动端跟 PC 都是不行的。这部分根据实际测试结果得来,并非官方文档,有小伙伴知道官方规范在哪里的,可以告知一下。

五、ios 自动全屏问题

移动端 ios video 播放时,会自动全屏,添加如下属性即可

html 复制代码
<video playsinline webkit-playsinline />

六、视频旋转、比例拉伸问题

我们的设备,推流过来的视频比例大部分为 16:9, 且带有旋转角度 90/270,因此针对有旋转角度的设备,需要进行旋转及拉伸操作,才能铺满整个屏幕。

实际做下来的效果还是ok的,因为 16:9 的比例跟大部分的机型比例差的不太多

代码比较简单,纯 css transform(rotate、scale)实现即可

七、监听设备拉流状态、计算视频流 kb/s

测试过程中发现一种场景,WebRTC 建连成功,但画面黑屏。排查下来是设备端没有推流过来,针对这种场景,前端希望感知到,并进行重试按钮的展示。

可以基于 WebRTC RTCPeerConnection getStats 方法实时获取收到的数据,以及解码成功的数据。代码示意如下:

javascript 复制代码
const stats = await RTCPeerConnectioIns.getStats(null);
stats.forEach((report) => {
    if (
      report.type === 'inbound-rtp' &&
      (report.mediaType === 'video' || report.kind === 'video')
    ) {
      if (report.bytesReceived === 0) {
        // 未收到数据
        return;
      }
      if (report.keyFramesDecoded === 0 && report.framesDecoded === 0) {
        // 解码失败
        return;
      }

      // 收到数据且解码成功
    }
});

另外,产品希望在页面上实时展示一秒钟收到的视频数据(kb/s)。实现方式也是基于上述 API 来做即可。代码示意:

javascript 复制代码
const stats = await RTCPeerConnectioIns.getStats(null);
let kbReceived = 0;
stats.forEach((report) => {
    if (
      report.type === 'inbound-rtp' &&
      (report.mediaType === 'video' || report.kind === 'video')
    ) {
      kbReceived = Math.trunc(report.bytesReceived / 1024);
    }
});
const curTime = Math.trunc(new Date().getTime() / 1000);

// kb/s
const speed = kbReceived / (curTime - 拉流成功时的时间);

八、WebRTC 对讲变声处理

之前在 PC 端的对讲,没有过变声的需求。趁小程序有变声的场景,简单了解了下如何来做变声。

获取麦克风权限兼容

对讲需要获取麦克风权限,需要简单兼容一下 API

javascript 复制代码
getUserMediaUtil(constrains, success, error, fail) {
    if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
      // 最新标准 API
      navigator.mediaDevices
        .getUserMedia(constrains)
        .then(success)
        .catch(error);
    } else if (navigator.webkitGetUserMedia) {
      // webkit 内核浏览器
      navigator.webkitGetUserMedia(constrains, success, error);
    } else if (navigator.mozGetUserMedia) {
      // Firefox 浏览器
      navigator.mozGetUserMedia(constrains, success, error);
    } else if (navigator.getUserMedia) {
      // 旧版 API
      navigator.getUserMedia(constrains, success, error);
    } else {
      fail();
    }
  }
变声方法

变声实际上跟 WebRTC 没有什么关系,只要前置处理好音频数据,然后塞给 WebRTC 即可

第一种方式是基于 js 自行处理,借助 web audio API。奇舞团这篇文章不错,可以参考看看。对于一些简单的变调且变速的,基于 js 实现比较方便

第二种方式是,借用 wasm 调用 c++ 提供的内部音频处理算法

  • 我们的处理流程是 mediastream 转 pcm 塞给 c++ 处理, c++ 处理完回调处理后的 pcm 给前端,前端将 pcm 转 mediastream 喂给 WebRTC 音频通道
  • 一般对于变调不变速(我们的场景),或者是变速不变调,直接采用 js 实现相对复杂,此时可借助成熟的算法,配合 wasm 来处理
  • wasm 处理音频相对简单,不像视频那么耗 CPU

小结

针对需要在微信小程序视频通信的场景,通过 WebView 使用 WebRTC 或许是一个不错的选择。

相比安装庞大的 APP, 小程序显得更加轻量便捷,比如家用安防摄像头,看猫的摄像头等比较轻量的业务场景。

备注: 文章中若有错误,还请指出,这边会及时更正,感谢。

相关推荐
智能直播7 小时前
RK3518:1.5G 内存 + 8G 存储,如何跑起硬件编解码、Web、AI 识别、绿幕特效
前端·人工智能·5g
掘金酱9 小时前
【10.24 特别活动】AI 时代,程序员的新饭碗在哪里?
前端·人工智能·后端
excel9 小时前
多进程环境下使用 PM2 部署 Socket.IO:需要注意什么
前端
这个DBA有点耶9 小时前
深入 OLTP 锁与并发:从行锁到分布式事务的一致性权衡
数据库·架构·产品
IT_陈寒9 小时前
Redis的原子性竟然失效了?记一次分布式锁翻车实录
前端·人工智能·后端
时间的情敌11 小时前
《回顾 2018 前端:Vue2 成熟期,Webpack4 发布,PWA 大火》
前端
Java后端的Ai之路11 小时前
前端技术Vue-09-webpack-vuejs-实战
前端·vue.js·webpack
LMT流氓兔11 小时前
第五单元——第4课:给工具参数加校验
java·前端·算法·ai·langchain
涛涛ing11 小时前
React Compiler正式“Rust化”:你项目里的Babel插件,正在被编译进打包器
前端
这个DBA有点耶11 小时前
innodb_autoinc_lock_mode的三种模式:批量插入时自增锁的持有逻辑与调优
数据库·架构·代码规范