WebRTC (Web Real-Time Communications) 是一项实时通讯技术,在 2011 年由 Google 提出,经过 10 年的发展,W3C 于 2021 年正式发布 WebRTC 1.0 标准。
:-: ![](https://img.kancloud.cn/bf/7a/bf7aa23f2c6c9a98eecf24898ef302b0_688x388.png =400x)
  WebRTC 标准概括介绍了两种不同的技术:媒体捕获设备和点对点连接(P2P,Peer-to-Peer),可让用户无需安装任何插件或第三方软件的情况下,实现共享桌面、文件传输、视频直播等功能。
  下图是[官方](https://webrtc.github.io/webrtc-org/architecture/)给出的一张 WebRTC 整体架构设计图:
:-: ![](https://img.kancloud.cn/57/e7/57e738ed8019aa6a79b4345aeed5c3bb_740x482.png =500x)
* 紫色部分是前端开发所使用的 API。
* 蓝色实线部分是各大浏览器厂商所使用的 API。
* 蓝色虚线部分包含可自定义的 3 块:音频引擎、视频引擎和网络传输。
  由于各个浏览器对 WebRTC 的实现有所不同,因此 Google 官方提供了一个适配器脚本库:[adapter.js](https://github.com/webrtc/adapter/),省去很多兼容工作。
  本文的源码已上传至[Github](https://github.com/pwstrick/webrtc),有需要的可以随意下载。
## 一、自拍
  自拍是指通过摄像头拍照生成图片,先看下 HTML 结构,其实就 4 个元素。
~~~html
<video id="video"></video>
<button id="btn">拍照</button>
<canvas id="canvas" width="300" height="300"></canvas>
<img id="img" alt="照片"/>
~~~
**1)getUserMedia()**
  然后在脚本中声明各个元素,通过 navigator.[mediaDevices](https://developer.mozilla.org/zh-CN/docs/Web/API/MediaDevices).getUserMedia() 方法获取媒体流。
~~~
const video = document.getElementById('video');
const canvas = document.getElementById('canvas');
const btn = document.getElementById('btn');
const img = document.getElementById('img');
const size = 300;
/**
* 获取媒体流
*/
navigator.mediaDevices.getUserMedia({
video: {
width: size,
height: size,
},
audio: false
}).then((stream) => {
video.srcObject = stream;
video.play();
});
~~~
  getUserMedia() 的参数是一个包含了video 和 audio 两个成员的 MediaStreamConstraints 对象,上面代码将摄像头的分辨率限制为 300 x 300。
  then() 中的 stream 参数是一个[MediaStream](https://developer.mozilla.org/zh-CN/docs/Web/API/MediaStream)媒体流,一个流相当于容器,可以包含几条轨道,例如视频和音频轨道,每条轨道都是独立的。
  video 元素中的 src 和[srcObject](https://developer.mozilla.org/zh-CN/docs/Web/API/HTMLMediaElement/srcObject)是一对互斥的属性,后者可关联媒体源,根据规范也可以是 Blob 或者 File 等类型的数据。
  接着为按钮绑定点击事件,并且在点击时从流中捕获帧,画到 Canvas 内,再导出赋给 img 元素。
~~~
/**
* 点击拍照
*/
btn.addEventListener('click', (e) => {
const context = canvas.getContext('2d');
// 从流中捕获帧
context.drawImage(video, 0, 0, size, size);
// 将帧导出为图片
const data = canvas.toDataURL('image/png');
img.setAttribute('src', data);
}, false);
~~~
  在下图中,左边是 video 元素,打开摄像头后就会有画面,在点击拍照按钮后,右边显示捕获的帧。
:-: ![](https://img.kancloud.cn/31/c8/31c84cd431fd843277237c65c91befb8_1374x642.png =600x)
**2)enumerateDevices()**
  MediaDevices 提供了访问媒体输入和输出的设备,例如摄像头、麦克风等,得到硬件资源的媒体数据。
  mediaDevices.[enumerateDevices()](https://developer.mozilla.org/zh-CN/docs/Web/API/MediaDevices/enumerateDevices)会得到一个描述设备的[MediaDeviceInfo](https://developer.mozilla.org/en-US/docs/Web/API/MediaDeviceInfo)的数组。
  其中 groupId 用于标识多个设备属于同一个物理设备,例如一个显示器内置了摄像头和麦克风。
~~~
navigator.mediaDevices.enumerateDevices()
.then((devices) => {
devices.forEach((device) => {
console.log(`${device.kind}: ${device.label} id = ${device.deviceId}`);
});
})
~~~
**3)devicechange**
  当媒体设备(例如麦克风、摄像头等)连接到系统或从系统中移除时,[devicechange](https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/devicechange_event)事件就会被发送给设备实例。
~~~
navigator.mediaDevices.ondevicechange = (event) => { };
~~~
  event 参数没有附加任何特殊的属性。
## 二、共享桌面
  Windows 系统采用的共享桌面协议是 RDP(Remote Desktop Protocal),另一种可在不同操作系统共享桌面的协议是 VNC(Virtual Network Console)。
  像 TeamViewer 采用的就是后一种协议,而 WebRTC 的远程桌面没有采用传统的 RDP、VNC 等协议,因为不需要远程控制。
  WebRTC 提供了[getDisplayMedia()](https://developer.mozilla.org/zh-CN/docs/Web/API/MediaDevices/getDisplayMedia)方法采集桌面,在使用上与之前的 getUserMedia() 方法类似。
~~~
navigator.mediaDevices.getDisplayMedia({
video: {
width: 2000,
height: 1000
}
}).then((stream) => {
video.srcObject = stream;
video.play();
});
~~~
  在刷新页面后,会要求选择共享的桌面,包括整个屏幕、窗口或 Chrome 标签页。
:-: ![](https://img.kancloud.cn/18/b6/18b6c43f47ff38b4f4237fbb68b2fbea_1216x1076.png =800x)
## 三、录像
  WebRTC 的录像包括录制音频和视频两种流,通过[Blob](https://developer.mozilla.org/zh-CN/docs/Web/API/Blob)对象将数据保存成多媒体文件。
**1)MediaRecorder**
  WebRTC 提供了[MediaRecorder](https://developer.mozilla.org/zh-CN/docs/Web/API/MediaRecorder)类,它能接收两个参数,第一个是远程的 MediaStream 媒体流,第二个是配置项。
  其配置项包括编解码器、音视频码率、容器的[MIME](https://www.iana.org/assignments/media-types/media-types.xhtml)类型(例如 video/webm、video/mp4 )等相关信息。
  先看个示例,HTML结构如下所示,一个 video 元素和两个 button 元素:回放和下载。
~~~html
<video id="video"></video>
<button id="playback">回放</button>
<button id="download">下载</button>
~~~
  然后看下录像的整体逻辑,和之前自拍一节类似,也需要调用 getUserMedia() 获取媒体流。
  在 then() 的回调中实例化 MediaRecorder 类,并配置多媒体格式。
  其中WebM是一个由Google资助,免版权费用的视频文件格式;VP8是一个开放的影像压缩格式。
~~~
const video = document.getElementById('video');
const playback = document.getElementById('playback');
const download = document.getElementById('download');
const size = 300;
const chunks = []; // 一个由 Blob 对象组成的数组
navigator.mediaDevices.getUserMedia({
video: {
width: size,
height: size,
},
audio: true
}).then((stream) => {
// 配置多媒体格式
const options = { mimeType: 'video/webm;codecs=vp8' };
// 实例化录制对象
const recorder = new MediaRecorder(stream, options);
// 当收到数据时触发该事件
recorder.ondataavailable = function(e) {
chunks.push(e.data); // data 是一个可用的 Blob 对象
}
// 开始录制
recorder.start(10);
});
~~~
  recorder 的[dataavailable](https://developer.mozilla.org/zh-CN/docs/Web/API/MediaRecorder/dataavailable_event)事件会在收到数据时触发,e 参数的 data 属性是一个可用的 Blob 对象。
  最后在开始录制调用 start() 方法时,可以配置一个毫秒级的时间片,那么在录制时会按照配置的值分割成一个个单独的区块,而不是录制一个非常大的整块内容。
  分块可以提高效率和可靠性,如果是一整块,那么会变得越来越大,读写效率也会变差。
**2)回放**
  首先根据 chunks 生成 Blob 对象,再根据 Blob 对象生成 URL 对象。
~~~
playback.addEventListener('click', () => {
// 根据 chunks 生成 Blob 对象
const blob = new Blob(chunks, {type: 'video/webm'});
// 根据 Blob 对象生成 URL 对象
video.src = window.URL.createObjectURL(blob);
video.play();
}, false);
~~~
  URL.[createObjectURL](https://developer.mozilla.org/zh-CN/docs/Web/API/URL/createObjectURL)是一个静态方法,返回值是一个指定的[File](https://developer.mozilla.org/zh-CN/docs/Web/API/File)对象或 Blob 对象。
**3)下载**
  首先与回放一样,也是生成一个 URL 对象,然后创建 a 元素,将对象赋给 href 属性。
  并且要指定 download 属性,告诉浏览器下载 URL 而不是导航。
~~~
download.addEventListener('click', (e) => {
const blob = new Blob(chunks, {type: 'video/webm'});
const url = window.URL.createObjectURL(blob);
// 创建 a 元素
const a = document.createElement('a');
a.href = url;
// 指示浏览器下载 URL 而不是导航
a.download = 'test.webm';
a.click();
}, false);
~~~
参考资料:
[WebRTC官方](https://webrtc.org/getting-started/overview)
[WebRTC MDN](https://developer.mozilla.org/zh-CN/docs/Web/API/WebRTC_API)
[Build the backend services needed for a WebRTC app](https://web.dev/webrtc-infrastructure/)
*****
> 原文出处:
[博客园-HTML躬行记](https://www.cnblogs.com/strick/category/1770829.html)
[知乎专栏-HTML躬行记](https://zhuanlan.zhihu.com/c_1250826149041238016)
已建立一个微信前端交流群,如要进群,请先加微信号freedom20180706或扫描下面的二维码,请求中需注明“看云加群”,在通过请求后就会把你拉进来。还搜集整理了一套[面试资料](https://github.com/pwstrick/daily),欢迎阅读。
![](https://box.kancloud.cn/2e1f8ecf9512ecdd2fcaae8250e7d48a_430x430.jpg =200x200)
推荐一款前端监控脚本:[shin-monitor](https://github.com/pwstrick/shin-monitor),不仅能监控前端的错误、通信、打印等行为,还能计算各类性能参数,包括 FMP、LCP、FP 等。
- ES6
- 1、let和const
- 2、扩展运算符和剩余参数
- 3、解构
- 4、模板字面量
- 5、对象字面量的扩展
- 6、Symbol
- 7、代码模块化
- 8、数字
- 9、字符串
- 10、正则表达式
- 11、对象
- 12、数组
- 13、类型化数组
- 14、函数
- 15、箭头函数和尾调用优化
- 16、Set
- 17、Map
- 18、迭代器
- 19、生成器
- 20、类
- 21、类的继承
- 22、Promise
- 23、Promise的静态方法和应用
- 24、代理和反射
- HTML
- 1、SVG
- 2、WebRTC基础实践
- 3、WebRTC视频通话
- 4、Web音视频基础
- CSS进阶
- 1、CSS基础拾遗
- 2、伪类和伪元素
- 3、CSS属性拾遗
- 4、浮动形状
- 5、渐变
- 6、滤镜
- 7、合成
- 8、裁剪和遮罩
- 9、网格布局
- 10、CSS方法论
- 11、管理后台响应式改造
- React
- 1、函数式编程
- 2、JSX
- 3、组件
- 4、生命周期
- 5、React和DOM
- 6、事件
- 7、表单
- 8、样式
- 9、组件通信
- 10、高阶组件
- 11、Redux基础
- 12、Redux中间件
- 13、React Router
- 14、测试框架
- 15、React Hooks
- 16、React源码分析
- 利器
- 1、npm
- 2、Babel
- 3、webpack基础
- 4、webpack进阶
- 5、Git
- 6、Fiddler
- 7、自制脚手架
- 8、VSCode插件研发
- 9、WebView中的页面调试方法
- Vue.js
- 1、数据绑定
- 2、指令
- 3、样式和表单
- 4、组件
- 5、组件通信
- 6、内容分发
- 7、渲染函数和JSX
- 8、Vue Router
- 9、Vuex
- TypeScript
- 1、数据类型
- 2、接口
- 3、类
- 4、泛型
- 5、类型兼容性
- 6、高级类型
- 7、命名空间
- 8、装饰器
- Node.js
- 1、Buffer、流和EventEmitter
- 2、文件系统和网络
- 3、命令行工具
- 4、自建前端监控系统
- 5、定时任务的调试
- 6、自制短链系统
- 7、定时任务的进化史
- 8、通用接口
- 9、微前端实践
- 10、接口日志查询
- 11、E2E测试
- 12、BFF
- 13、MySQL归档
- 14、压力测试
- 15、活动规则引擎
- 16、活动配置化
- 17、UmiJS版本升级
- 18、半吊子的可视化搭建系统
- 19、KOA源码分析(上)
- 20、KOA源码分析(下)
- 21、花10分钟入门Node.js
- 22、Node环境升级日志
- 23、Worker threads
- 24、低代码
- 25、Web自动化测试
- 26、接口拦截和页面回放实验
- 27、接口管理
- 28、Cypress自动化测试实践
- 29、基于Electron的开播助手
- Node.js精进
- 1、模块化
- 2、异步编程
- 3、流
- 4、事件触发器
- 5、HTTP
- 6、文件
- 7、日志
- 8、错误处理
- 9、性能监控(上)
- 10、性能监控(下)
- 11、Socket.IO
- 12、ElasticSearch
- 监控系统
- 1、SDK
- 2、存储和分析
- 3、性能监控
- 4、内存泄漏
- 5、小程序
- 6、较长的白屏时间
- 7、页面奔溃
- 8、shin-monitor源码分析
- 前端性能精进
- 1、优化方法论之测量
- 2、优化方法论之分析
- 3、浏览器之图像
- 4、浏览器之呈现
- 5、浏览器之JavaScript
- 6、网络
- 7、构建
- 前端体验优化
- 1、概述
- 2、基建
- 3、后端
- 4、数据
- 5、后台
- Web优化
- 1、CSS优化
- 2、JavaScript优化
- 3、图像和网络
- 4、用户体验和工具
- 5、网站优化
- 6、优化闭环实践
- 数据结构与算法
- 1、链表
- 2、栈、队列、散列表和位运算
- 3、二叉树
- 4、二分查找
- 5、回溯算法
- 6、贪心算法
- 7、分治算法
- 8、动态规划
- 程序员之路
- 大学
- 2011年
- 2012年
- 2013年
- 2014年
- 项目反思
- 前端基础学习分享
- 2015年
- 再一次项目反思
- 然并卵
- PC网站CSS分享
- 2016年
- 制造自己的榫卯
- PrimusUI
- 2017年
- 工匠精神
- 2018年
- 2019年
- 前端学习之路分享
- 2020年
- 2021年
- 2022年
- 2023年
- 日志
- 2020