webRTC实战总结

前言

前段时间一直在忙一个基于WebRTC的PC和移动端双向视频的项目。第一次接触webRTC,难免遇到了许多问题,比如:webRTC移动端兼容性检测,如何配置MediaStreamConstraints, 信令(iceCandidate, sessionDescription)传输方式的选择,iceCandidate和sessionDescription设置的先后顺序,STUN和TURN的概念,如何实现截图及录制视频及上传图片和视频功能,如何高效跟踪错误等等。好记性不如烂笔头,特写此文以记之。

移动端兼容性

对PC端来说,webRTC早已被各大浏览器支持了,Chrome 28,FF22,Edge…随着不久之前发布的IOS11也宣布支持webRTC及getUserMedia,webRTC在移动端的应用前景也令人憧憬。

具体到实际项目中,经过测试,各大国产安卓手机自带的浏览器基本不支持webRTC,但这些安卓手机的微信内置浏览器均能良好地支持webRTC,虽然Chrome及Firefox的移动端版本也能良好的支持webRTC,但国情决定了微信内置浏览器作为最佳切入点。另一方面。IOS11中微信内置浏览器还不支持webRTC(我坚信不久的将来就会支持),但在Safari中能够完美支持。因此本项目选择了微信公众号为切入点,通过检测userAgent引导IOS11用户在Safari中打开页面。

检测webRTC的可行性,主要从getUserMedia和webRTC本身来入手:

function detectWebRTC() { const WEBRTC_CONSTANTS = ['RTCPeerConnection', 'webkitRTCPeerConnection', 'mozRTCPeerConnection', 'RTCIceGatherer']; const isWebRTCSupported = WEBRTC_CONSTANTS.find((item) => { return item in window; }); const isGetUserMediaSupported = navigator && navigator.mediaDevices && navigator.mediaDevices.getUserMedia; if (!isWebRTCSupported || typeof isGetUserMediaSupported === 'undefined' ) { return false; } return true; }

如果返回false,再去检测userAgent给予用户不支持的具体提示。

配置MediaStreamConstraints

所谓MediaStreamConstraints,就是navigator.mediaDevices.getUserMedia(constraints)传入的constraints,至于它的写法及功能,参考MDN,本文不做赘述。我在这里想要强调的是,对于移动端来说控制好视频图像的大小是很重要的,例如本项目中想要对方的图像占据全屏,这不仅是改变video元素的样式或者属性能做到的,首先要做的是改变MediaStreamConstraints中的视频分辨率(width, height),使其长宽比例大致与移动端屏幕的类似,然后再将video元素的长和宽设置为容器的长和宽(例如100%)。

另外对于getUserMedia一定要捕获可能出现的错误,如果是老的API,设置onErr回调,如果是新的(navigator.mediaDevices.getUserMedia),则catch异常。这样做的原因:getUserMedia往往不会完全符合我们的预期,有时即使设置的是ideal的约束,仍然会报错,如果不追踪错误,往往一脸懵逼。这也是后文要提到的高效追踪错误的方法之一。

搭建信令传输服务

要传输的信令包括两个部分:sessionDescription和iceCandidate。为了便于传输可将其处理成字符串,另一端接收时还原并用对应的构造函数构造对应的实例即可。

webRTC并没有规定信令的传输方式,而是完全由开发者自定义。常见的方式有短轮询、webSocket(socket.io等),短轮询的优点无非是简单,兼容性强,但在并发量较大时,服务器负荷会很重。而webSocket就不存在这个问题,但webSocket搭建起来较为复杂,并不是所有的浏览器都支持websocket。综合来说socket.io是个不错的解决方案,事件机制和自带的房间概念对撮合视频会话都是天然有利的,并且当浏览器不支持websocket时可以切换为轮询,也解决了兼容性的问题。

发起视频会话的流程

可以看到无论是发起方还是接受方,第一步都是getUserMedia获取本地媒体流,然后新建一个RTCPeerConnection实例,并指定好onicecandidate、onaddstream等回调:

// 指定TURN及STUN
const peerConnectionConfig = { 'iceServers': [ { 'urls': 'turn:numb.viagenie.ca', 'username': 'muazkh', 'credential': 'webrtc@live.com' }, { 'urls': 'stun:stun.l.google.com:19302' } ], bundlePolicy: 'max-bundle', }; const pc = new RTCPeerConnection(peerConnectionConfig); pc.onicecandidate = ...; pc.onaddstream = ...;

然后addTrack指定要传输的视频流

stream.getTracks().forEach((track) => { pc.addTrack(track, stream); });

发起方通过createOffer生成localDescription并传给pc.setLocalDescription(),pc获取了本地的sdp后开始获取candidate,这里的candidate指的是网络信息(ip、端口、协议),根据优先级从高到低分为三类:

  • host: 设备的ipv4或ipv6地址,即内网地址,一般会有两个,分别对应udp和tcp,ip相同,端口不同;
  • srflx(server reflexive): STUN返回的外网地址;
  • relay: 当STUN不适用时(某些NAT会为每个连接分配不同的端口,导致获取的端口和视频连接端口并不一致),中继服务器的地址;

三者之中只需要有一类连接成功即可,所以如果通信双方在同一内网,不配置STUN和TURN也可以直接连接。其实这里隐藏着性能优化的点:如上图所示,webRTC通信双方在交换candidate时,首先由发起方先收集所有的candidate,然后在icegatheringstatechange事件中检测iceGatheringState是否为’complete’,再发送给接收方。接收方设置了发送方传来的sdp和candidate后,同样要收集完自己所有的candidate,再发送给对方。如果这些candidate中有一对可以连接成功,则P2P通信建立,否则连接失败。

问题来了,接受端要等待发起方收集完所有的candidate之后才开始收集自己的candidate,这其实是可以同时进行的;另外其实不一定需要所有的candidate才能建立连接,这也是可以省下时间的;最后如果网络,STUN或者TURN出现问题,在上述传输模式下是非常致命的,会让连接的时间变得很长不可接受。

解决方案就是IETF提出的Trickle ICE。即发起方每获取一个candidate便立即发送给接收方,这样做的好处在于第一类candidate即host,会立即发送给接收方,这样接收方收到后可以立刻开始收集candidate,也就是发起方和接收方同时进行收集candidate的工作。另外,接收方每收到一个candidate会立即去检查它的有效性,如果有效直接接通视频,如果无效也不至于浪费时间。详情可以参见ICE always tastes better when it trickles.

至于sessionDescription及iceCandidate的传输,因为JavaScript没有处理sdp格式数据的方法,所以直接将其当做字符串处理,这样做的坏处是难以改变sdp中的信息(如果非要改,通过正则匹配还是能改的)。

在挂断视频时,不仅要关闭peerConnection,也要停止本地及远程的媒体流:


const tracks = localStream.getTracks().concat(remoteStream.getTracks()); tracks.forEach((track) => { track.stop(); }); peerConnection.close();

截图&录制视频

截图其实并不算什么新鲜的东西,无非是利用canvas的drawImage函数获取video元素在某一帧的图像,得到的是图片的base64格式字符串,但要注意的是这样得到的base64码之前有这样一串文本:

data:image/png;base64,

这是对数据协议,格式,编码方式的声明,是给浏览器看的。所以在将drawImage得到的字符串上传给服务器时,最好将这串文本去掉,防止后端在转换图片时出现错误。

录制视频使用的是MediaRecorder API 详情参考MDN MediaRecorder,目前仅支持录制webm格式的视频。可以在新建MediaRecorder实例的时候,设置mimeType、videoBitsPerSecond、audioBitsPerSecond:

const options = { mimeType: 'video/webm;codecs=vp8', // 视频格式及编码格式 videoBitsPerSecond: 2500000, // 视频比特率,影响文件大小和质量  audioBitsPerSecond: 128000 // 音频比特率,影响文件大小和质量 }; const recorder = new MediaRecorder(options);

在recorder的ondataavailable事件中拿到数据,将其转换为Blob对象,再通过Formdata异步上传至服务器。

错误追踪

整个双向视频涉及到的步骤较多,做好错误追踪是非常重要的。像getUserMedia时,一定要catch可能出现的异常。因为不同的设备,不同的浏览器或者说不同的用户往往不能完全满足我们设置的constraints。还有在实例化RTCPeerConnection时,往往会出现不可预期的错误,常见的有STUN、TURN格式不对,还有createOffer时传递的offerOptions格式不对,正确的应该为:

const offerOptions = { 'offerToReceiveAudio': true, 'offerToReceiveVideo': true };

CAVEAT

因为webRTC标准还在不断地更新中,所以相关的API经常会有改动。

  • navigator.getUserMeida(已废弃),现在改为navigator.mediaDevices.getUserMedia;
  • RTCPeerConnection.addStream被RTCPeerConnection.addTrack取代;
  • STUN,TURN配置里的url现被urls取代;

另外,对video元素也要特殊处理。设置autoPlay属性,对播放本地视频源的video还要设置muted属性以去除回音。针对IOS播放视频自动全屏的特性,还要设置playsinline属性的值为true。

转载于:https://www.cnblogs.com/liuhao-web/p/8706528.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/453070.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

程序员眼中的2007:寻找软件开发利器

软件开发生命周期包括需求分析、设计、开发、测试、交付部署等各个阶段,以及贯穿在整个开发过程的软件开发项目管理环节,2006年,在每一个阶段都出现了令人眼花缭乱的技术与应用,同时这些技术还将进一步影响2007年的发展&#xff0…

谷歌虚拟服务器申请,【美国podserver.info】免费300M虚拟主机空间申请使用教程

【美国podserver.info】注册使用教程:1、打开申请地址,点击下图位置中的“Sign Up”开始注册。2、点击“Sign Up”开始注册后,进入到注册检查页面,我们按下图选择“I’m a human.”然后点击“Submit”进入到注册信息填写页面。3、…

物理层协议有哪四大特性

1.机械特性 指明接口所用接线器的形状和尺寸、引线数目和排列、固定和锁定装置等等。2.电气特性 指明在接口电缆的各条线上出现的电压的范围。3.功能特性 指明某条线上出现的某一电平的电压表示何种意义。4.过程特性 指明对于不同功能的各种可能事件的出现顺序。

phpstudy中的mysql

1.进入mysql命令台,执行 select version()即可 2status;

度量相似性数学建模_一种基于粒子群位置更新思想灰狼优化算法的K-Means文本分类方法与流程...

技术特征:1.一种基于粒子群位置更新思想灰狼优化算法的k-means文本分类方法,其特征在于:包括以下步骤:s1:对文本数据进行预处理,得到预处理后文本数据;s2:采用余弦角度为相似性度量,…

Overload重載和Override重写的区别。Overloaded的方法是否可以改变返回值的类型?

Overload是重载的意思,Override是覆盖的意思,也就是重写。 重载Overload表示同一个类中可以有多个名称相同的方法,但这些方法的参数列表各不相同(即参数个数或类型不同)。 重写Override表示子类中的方法可以与父类中的…

web服务器文件管理,web文件管理服务器

web文件管理服务器 内容精选换一换该任务指导用户在SSL证书管理平台下载证书。证书状态为已签发或托管中。仅支持在证书有效期内,不限次数的下载证书,下载后即可在服务器(华为云的或非华为云的均可)上进行部署。证书请求文件选择的是系统生成CSR&#xf…

简单计算器 (关于栈的一种应用)

题目:简单计算器读入一个只包含 , -, *, / 的非负整数计算表达式,计算该表达式的值。Input测试输入包含若干测试用例,每个测试用例占一行,每行不超过200个字符,整数和运算符之间用一个空格分隔。没有非法表达式。当一行…

python中模运算_Python中的模运算

所谓取模运算&#xff0c;就是计算两个数相除之后的余数&#xff0c;符号是%。如a % b就是计算a除以b的余数。用数学语言来描述&#xff0c;就是如果存在整数n和m&#xff0c;其中0 < m < b&#xff0c;使得$ a n * b m $&#xff0c;那么$ a \% b a - n * b m $。先…

伟大公司为什么需要技术型领导?

Facebook前工程总监黄易山撰写了一系列文章&#xff0c;很好地总结了Facebook卓越研发文化中的宝贵经验。本文是这一系列文章的第五篇&#xff0c;也是最后一篇。 何谓技术型领导 所有从外部聘用的管理人员包括技术部门负责人&#xff0c;都必须能够编写代码&#xff0c;并且…

css样式变 及实际用法

<html xmlns"http://www.w3.org/1999/xhtml"><head><meta http-equiv"Content-Type" content"text/html; charsetutf-8" /><title>引入外部样式</title><link rel" stylesheet" href"home.css&…

服务器部署的参数文档,服务器参数配置

服务器参数配置 内容精选换一换源端服务器迁移至华为云后&#xff0c;最终将迁移到弹性云服务器上。因此在迁移前&#xff0c;您需要在华为云中创建一个或多个弹性云服务器。进入“弹性云服务器”页面。关于参数的详细信息&#xff0c;请参见购买弹性云服务器。Windows系统的目…

扩散法及其改进

扩散法是一种静态路由算法,每一个输入的分组都被从除输入线路之外的所有其它线路上转发出去.扩散法显然会产生大量的分组副本,因此必须有一些办法来抑制无限的转发.1.一种办法是在分组头中携带一个跳数计数器,分组每到一个节点其跳数计数器就减1,当计数器为0时分组被丢弃.计数器…

h5页 点击返回时关闭_在微信、支付宝、百度钱包实现点击返回按钮关闭当前页面和窗口...

最近在使用微信、支付宝、百度钱包实现网页支付&#xff0c;对支付成功将自动关闭页面&#xff0c;对于支付失败&#xff0c;将显示错误信息。当在错误页面的时候&#xff0c;点击返回或者Android物理按键上一步的时候&#xff0c;将关闭页面。在微信、支付宝、百度钱包中&…

串操作指令

串操作指令可以方便对一组连续的数据进行操作。串操作后自动根据DF标志位修改ESI和EDI&#xff0c;DF 0时&#xff0c;ESI&#xff0c;EDI递增&#xff0c;DF 1时&#xff0c;ESI&#xff0c;EDI递减。串操作指令有2组&#xff0c;1组实现数据串传送&#xff0c;另1组实现数据…

IS-IS基本配置

实验内容&#xff1a;现在网络中有4台路由器&#xff0c;用户希望利用这4台路由器通过IS-IS协议实现网络互联&#xff0c;并且因为R1和R2性能相对较低&#xff0c;所以还要使这两台路由器处理的数据信息相对较少。 网络拓扑&#xff1a; 实验分析&#xff1a; 根据题意可知R1和…

高并发高流量网站架构

Web2.0的兴起&#xff0c;掀起了互联网新一轮的网络创业大潮。以用户为导向的新网站建设概念&#xff0c;细分了网站功能和用户群&#xff0c;不仅成功的造就了一大批新生的网站&#xff0c;也极大的方便了上网的人们。但Web2.0以用户为导向的理念&#xff0c;使得新生的网站有…

多处理机的进程调度方式

一.自调度方式 二.成组调度方式 三.专用处理机分配方式

枚举类型用法_Mybatis-plus常见用法总结三

前面已经介绍了Mybatis-plus基本用法&#xff0c;今天为大家分享一些Mybatis-plus高级应用逻辑删除自动注入枚举类型处理Sql注入器多租户表结构CREATE TABLE sys_role ( id varchar(64) NOT NULL COMMENT 主键, code varchar(64) NOT NULL DEFAULT COMMENT 角色编码, name …