作者:荣顶、github
声明:文章为稀土技能社区首发签约文章,14天内禁止转载,14天后未获授权禁止转载,侵权必究!

这篇文章能够学到什么

  • 学会怎么运用 WebRTC 的一些 API
  • 学会怎么同享屏幕录制屏幕
  • 学会怎么经过 WebRTC 完成摄影
  • 学会怎么完成视频虚拟布景
  • 搭建一个 1v1 的 WebRTC 实时音视频通话

一切示例在线体会地址:frontend-park
源码地址:frontend-park

前语

很开心在九月签约了,首要感谢平台的必定,后边几个月我会仔细的给咱们带来一些有价值的文章。 所以我开了一个专栏 《WebRTC 从实战到未来》,我将经过这个专栏,同享一下自己在 WebRTC 相关技能栈上的一些经验和考虑,期望能够帮助到咱们。

尽管这篇文章作为专栏的第一篇,但我也不会从 WebRTC 的基础常识和相关概念开始讲起,因为那样会十分的枯燥乏味,我会从实践项目中的一些能快速上手的运用动身,讲一下我在实践项目中遇到的一些问题,以及我是怎么处理和完成的。这样的优点是,你能够快速的上手,愈加专心于实践项目中的运用,而不是上来便是大量的概念和协议相关常识直接给整劝退了。

WebRTC 从实战到未来!迎候风口,前端必学的技能

什么是 WebRTC

WebRTC (Web Real-Time Communications) 是一项实时通讯技能,它答应网络运用或许站点,在不凭借中心前言的情况下,建立浏览器之间点对点(Peer-to-Peer)的连接,完成视频流和(或)音频流或许其他任意数据的传输。WebRTC 包括的这些规范运用户在无需安装任何插件或许第三方的软件的情况下,创立点对点(Peer-to-Peer)的数据同享和电话会议成为或许。

WebRTC 的运用场景

  • 直播
  • 游戏
  • 视频会议/在线教育
  • 屏幕同享/长途操控
  • 等等等

可玩性很强,能够做许多风趣的事情。(哪怕是被玩烂了概念“元宇宙”) 在网速与硬件越来越好的趋势下,WebRTC 它有无限或许。

媒体流

要想了解 WebRTC,首要要了解媒体流,媒体流能够是来自本地设备的,也能够是来自长途设备的。媒体流能够是实时的,也能够对错实时的。上述的运用场景中,咱们都需求运用到媒体流,咱们能够经过摄像头,麦克风,屏幕同享等方法获取到媒体流,然后经过 WebRTC 技能将媒体撒播输到远端完成实时通讯。

WebRTC 从实战到未来!迎候风口,前端必学的技能

摄像头获取媒体流及一些其他操作

要完成 音视频通话,咱们必定要先获取到摄像头的媒体流,然后经过 WebRTC 技能将媒体撒播输到远端完成实时通讯。下面咱们先经过一个简略的摄影小运用来看一下怎么经过摄像头获取媒体流。

WebRTC 从实战到未来!迎候风口,前端必学的技能

先设置好用于播映媒体流的 video 标签,增加一个 autoplay 特点,这样就能够在摄像头获取到媒体流后自动播映了。

<video id="localVideo" autoplay playsinline muted></video>

需求注意的是,WebRTC 只能在 HTTPS 协议或许 localhost 下运用,假如是 HTTP 协议,会报错。

WebRTC 从实战到未来!迎候风口,前端必学的技能

这儿咱们暂时运用 localhost 做简略的演示,后边经过信令服务器完成实时音视频的章节我会讲到怎么在本地用 mkcert 做自签名证书。

ok, continue ~

咱们主要经过navigator.mediaDevices.getUserMedia(constraints)这个 api 来获取媒体流,这个办法接收一个装备方针作为参数,装备方针中包括了媒体流的类型,以及媒体流的分辨率等信息。

// 获取本地音视频流
async function getLocalStream(constraints: MediaStreamConstraints) {
  // 获取媒体流
  const stream = await navigator.mediaDevices.getUserMedia(constraints)
}

其间constraints指定了恳求的媒体类型和相对应的参数,用于装备视频流和音频流的信息。

我能够看下constraints参数中详细支撑哪些装备项,能够经过navigator.mediaDevices.getSupportedConstraints()这个办法来获取。

console.log(
  ' / SupportedConstraints',
  navigator.mediaDevices.getSupportedConstraints(),
)

咱们把它打印出来,能够看到它支撑的装备项有:

WebRTC 从实战到未来!迎候风口,前端必学的技能

通常咱们不设置constraints参数,那么默许便是获取摄像头和麦克风的媒体流,假如咱们只想要获取摄像头的媒体流,那么咱们能够这样设置:

navigator.mediaDevices.getUserMedia({
  audio: false,
  video: true,
})

假如咱们想要获取视频的高度宽度,那么咱们能够这样设置:

navigator.mediaDevices.getUserMedia({
  audio: false,
  video: {
    width: 1280,
    height: 720,
  },
})

诸如此类,video 中也能够设置设备 id 或许前后置摄像头…, 咱们能够在支撑的情况下依据自己的需求来设置即可。详细能够检查MDN。这儿我不做过多的介绍了,咱们持续。

获取经过摄像头获取媒体流后,将媒体流赋值给 video 标签的 srcObject 特点,让其播映。

// 获取本地音视频流
async function getLocalStream(constraints: MediaStreamConstraints) {
  // 获取媒体流
  const stream = await navigator.mediaDevices.getUserMedia(constraints)
  // 将媒体流设置到 video 标签上播映
  playLocalStream(stream)
}
// 播映本地视频流
function playLocalStream(stream: MediaStream) {
  const videoEl = document.getElementById('localVideo') as HTMLVideoElement
  videoEl.srcObject = stream
}
getLocalStream({
  audio: false,
  video: true,
})

完成摄影功用,canvas 标签能够将媒体流制作到 canvas 上,也能够经过 toDataURL 办法将 canvas 转换为 base64 图片后做一些其他操作。

附上一个 体会地址

WebRTC 从实战到未来!迎候风口,前端必学的技能

<video id="localVideo" autoplay playsinline muted></video>
<div v-for="(item,index) in imgList.length" :key="index" class="item">
  <img :src="item" alt="" />
</div>

咱们经过获取现已在播映媒体流的 video 标签,然后将其制作到 canvas 上,再经过 toDataURL 办法将 canvas 转换为 base64 图片。

这儿你能够加一些滤镜或许加一些美颜功用或是其他的操作,最终生成的 imgUrl 给到 img 标签让其展现就行了。

// 摄影
function takePhoto() {
  const videoEl = document.getElementById('localVideo') as HTMLVideoElement
  const canvas = document.createElement('canvas')
  canvas.width = videoEl.videoWidth
  canvas.height = videoEl.videoHeight
  const ctx = canvas.getContext('2d')!
  ctx.drawImage(videoEl, 0, 0, canvas.width, canvas.height)
  imgList.value.push(canvas.toDataURL('image/png'))
  console.log(' / imgList', imgList)
  // 增加滤镜
  const filterList = [
    'blur(5px)', // 模糊
    'brightness(0.5)', // 亮度
    'contrast(200%)', // 对比度
    'grayscale(100%)', // 灰度
    'hue-rotate(90deg)', // 色相旋转
    'invert(100%)', // 反色
    'opacity(90%)', // 透明度
    'saturate(200%)', // 饱和度
    'saturate(20%)', // 饱和度
    'sepia(100%)', // 褐色
    'drop-shadow(4px 4px 8px blue)', // 阴影
  ]
  for (let i = 0; i < filterList.length; i++) {
    ctx.filter = filterList[i]
    ctx.drawImage(videoEl, 0, 0, canvas.width, canvas.height)
    imgList.value.push(canvas.toDataURL('image/png'))
  }
}

拍照的时分咱们也能够切换摄像头,这儿咱们经过调用 navigator.mediaDevices.enumerateDevices 办法,获取到一切的设备,然后筛选出 videoinput 类型的设备,最终经过不同的设备 id 来完成切换摄像头。

// 获取一切视频输入设备
async function getDevices() {
  const devices = await navigator.mediaDevices.enumerateDevices()
  console.log(' / devices', devices)
  let videoDevices = devices.filter((device) => device.kind === 'videoinput')
}
// 切换设备
function handleDeviceChange(deviceId: string) {
  getLocalStream()
  const stream = await navigator.mediaDevices.getUserMedia({
    audio: false,
    video: {
      deviceId: { exact: deviceId },
    },
  })
}

这儿咱们把获取到的设备列表信息打印看看,咱们能够看到每个设备都有一个 deviceId,咱们便是经过这个 id 来切换设备的。

WebRTC 从实战到未来!迎候风口,前端必学的技能

能够看到,获得了多个摄像头设备,我这儿是一个笔记本自带的摄像头和一个 OBS 虚拟摄像头,包括最近 MacOs 更新到 Ventura 13 ,IOS 更新到 16 后的接连互通摄像头,都能够获取到。这样咱们就能够在视频的时分,就能够经过拍照更清晰的手机后置来拍照了。

虚拟摄像头更有意思,在 OBS 中开启虚拟摄像头后,能够播映一个视频,然后进行视频会议,这样你乃至能够提早录制好一个端坐的视频(简直是上网课必备!),我之前试过播映特朗普的视频,然后微信视频,对面看到的确实是特朗普在演讲,所以说这方面很有安全隐患,所以咱们在网上和别人视频的时分,仍是需求注意下,对方或许不是真的。

WebRTC 从实战到未来!迎候风口,前端必学的技能

WebRTC 从实战到未来!迎候风口,前端必学的技能

WebRTC 从实战到未来!迎候风口,前端必学的技能

跑题了,咱们持续。

说完了切换摄像头,咱们再来看看怎么在支撑切换前后置摄像头的设备上怎么切换前后摄像头。咱们能够经过指定 facingMode 来完成,facingMode 有 4 个值,分别是 user、environment 和 left、right,分别对应前后摄像头和左右摄像头。

当需求强制运用前置摄像头时,能够运用 exact 关键字,例如 facingMode: { exact: ‘user’ },强制切换前后摄像头时,当摄像头不支撑时,会报一个 OverconstrainedError[无法满足要求的过错]

WebRTC 从实战到未来!迎候风口,前端必学的技能

// 切换前后摄像头
function switchCamera(val: number) {
  let constraints = {
    video: true, // 开启默许摄像头
    audio: true,
  }
  constraints.video = {
    // 强制切换前后摄像头时,当摄像头不支撑时,会报一个OverconstrainedError[无法满足要求的过错]
    facingMode: { exact: val === 1 ? 'user' : 'environment' },
    // 也能够这样当时后摄像头不支撑切换时,会持续运用当时摄像头,优点是不会报错
    // facingMode: val === 1 ? 'user' : 'environment',
  }
  navigator.mediaDevices
    .getUserMedia(constraints)
    .then((stream) => {
      ElMessage.success('切换成功')
      playLocalStream(stream)
    })
    .catch((err) => {
      ElMessage.error('你的设备不支撑切换前后摄像头')
    })
}
switchCamera(1) // 切换前置摄像头

WebRTC 从实战到未来!迎候风口,前端必学的技能

经过这个简略的摄影小运用,信任咱们现已知道了经过摄像头获取媒体流的大概流程以及一些常用的 API 了。

ok, continue ~

经过屏幕同享获取获取媒体流,完成录制等操作

在视频会议中,咱们常常会运用到屏幕同享,现已咱们常常会有录制屏幕等需求,市面上还有那么多需求付费的软件,咱们经过 WebRTC 合作一些相关 api 自己完成一个不是更好吗? 既省钱又学到了常识。

那么咱们怎么经过屏幕同享获取媒体流并完成录制呢?下面经过一个小 demo 来简略完成一下。

在 WebRTC 中,咱们能够经过 getDisplayMedia 来获取屏幕同享的媒体流,这个 API 与 getUserMedia 相似,可是它只能获取屏幕同享的媒体流。

// 获取屏幕同享的媒体流
async function shareScreen() {
  let localStream = await navigator.mediaDevices.getDisplayMedia({
    audio: true,
    video: true,
  })
  // 播映本地视频流
  playStream(localStream)
}
// 在视频标签中播映视频流
function playStream(stream: MediaStream) {
  const video = document.querySelector('#localVideo') as HTMLVideoElement
  video.srcObject = stream
}

履行 shareScreen 函数后,会弹出一个权限询问框,询问是否答应获取屏幕同享的媒体流。

然后你就能够同享你的整个屏幕,假如你又多个屏幕的话,你能够挑选其间一个进行同享

WebRTC 从实战到未来!迎候风口,前端必学的技能

然后你也能够挑选只同享你屏幕上的某个运用的窗口,不必忧虑你一边干嘛干嘛一边录制屏幕,它只会捕捉你挑选的运用窗口的内容。十分 nice。

WebRTC 从实战到未来!迎候风口,前端必学的技能

你乃至能够在你浏览器打开的各种页面中,挑选一个你想要同享的网页,当你页面各种切换时分,你的屏幕同享也只会显现你挑选的网页的内容。

同享前你能够随便选一个进行预览,然后能够挑选是否同享的时分包括页面中的音频,这样你获取的媒体流就会包括音频轨迹了。

WebRTC 从实战到未来!迎候风口,前端必学的技能

这儿我打开自己 github 的网页,然后点击屏幕同享,能够看到同享的只有自己的 github 页面了。不必忧虑会有什么古怪的东西乱入进来,十分适合视频会议或许在线教育等场景。

WebRTC 从实战到未来!迎候风口,前端必学的技能

说完获取屏幕媒体流,接下来咱们来看看怎么经过媒体流进行录制。

完整的 MIME 类型列表

这儿咱们运用 MediaRecorder 来进行录制,它是一个用于录制媒体流的 API,它能够将媒体流中的数据进行录制,然后将录制的数据保存成一个文件。

因为 MediaRecorder api 的对 mimeType 参数的支撑是有限的。所以咱们需求经过 MediaRecorder.isTypeSupported 来判别当时浏览器是否支撑咱们需求的 mimeType。

chrome 中 MediaRecorder 支撑的 mimeType 如下:

"video/webm"
"video/webm;codecs=vp8"
"video/webm;codecs=vp9"
"video/webm;codecs=h264"
"video/x-matroska;codecs=avc1"

WebRTC 从实战到未来!迎候风口,前端必学的技能

为了验证上述的内容,这儿我把一些常用的 mimeType 列出来,组装后经过 MediaRecorder.isTypeSupported 来判别是否支撑,最终放到下拉框中供用户依据自己的需求挑选适宜的 mimeType。

// 获取支撑的媒体类型
function getSupportedMimeTypes() {
  const media = 'video'
  // 常用的视频格式
  const types = [
    'webm',
    'mp4',
    'ogg',
    'mov',
    'avi',
    'wmv',
    'flv',
    'mkv',
    'ts',
    'x-matroska',
  ]
  // 常用的视频编码
  const codecs = ['vp9', 'vp9.0', 'vp8', 'vp8.0', 'avc1', 'av1', 'h265', 'h264']
  // 支撑的媒体类型
  const supported: string[] = []
  const isSupported = MediaRecorder.isTypeSupported
  // 遍历判别一切的媒体类型
  types.forEach((type: string) => {
    const mimeType = `${media}/${type}`
    codecs.forEach((codec: string) =>
      [
        `${mimeType};codecs=${codec}`,
        `${mimeType};codecs=${codec.toUpperCase()}`,
      ].forEach((variation) => {
        if (isSupported(variation)) supported.push(variation)
      }),
    )
    if (isSupported(mimeType)) supported.push(mimeType)
  })
  return supported
}
console.log(getSupportedMimeTypes())

能够看到这么多排列组合后,筛选出的支撑的 mimeType 也就只有"video/webm" "video/x-matroska" 两种。

WebRTC 从实战到未来!迎候风口,前端必学的技能

也能够经过这个网址media-mime-support 来检查当时浏览器所支撑的 mimeType 的情况。

现在最常用的一般都是 video/mp4。 截止到现在为止,最佳的 8 种视频格式为:mp4 ,webm ,mov ,avi ,mkv ,wmv ,avchd ,flv。而 webm 是 Google 专门为 web 端推出的一种视频格式。100% 开源,且 100%兼容 Google Chrome 浏览器和 Android 设备。假如你没有强烈的需求,也能够运用 webm 格式。

说了这么多,不支撑就不能录制成 mp4 了?
必定不是啊

都拿到 blob 了,能够经过 ffmpeg.js 来将 webm 格式转换成 mp4 格式,可是 ffmpeg.js 的体积比较大,太重了。这儿也能够经过一种 hack 的方法来完成,可是不靠谱,这种方法导出的 mp4 文件部分软件或许会识别不了,求稳的话最好仍是引荐运用咱们浏览器环境列出的支撑的 mimeType,或许用工具转一下)

// 录制媒体流
function startRecord() {
  const kbps = 1024
  const Mbps = kbps * kbps
  const options = {
    audioBitsPerSecond: 128000,
    videoBitsPerSecond: 2500000,
    mimeType: 'video/webm; codecs="vp8,opus"',
  }
  const mediaRecorder = new MediaRecorder(localStream, options)
  mediaRecorder.start()
  mediaRecorder.ondataavailable = (e) => {
    // 将录制的数据合并成一个 Blob 方针
    // const blob = new Blob([e.data], { type: e.data.type })
    // 重点是这个当地,咱们不要把获取到的 e.data.type设置成 blob 的 type,而是直接改成 mp4
    const blob = new Blob([e.data], { type: 'video/mp4' })
    downloadBlob(blob)
  }
  mediaRecorder.onstop = (e: Event) => {
    // 中止录制
  }
}
// 下载 Blob
function downloadBlob(blob: Blob) {
  // 将 Blob 方针转换成一个 URL 地址
  const url = URL.createObjectURL(blob)
  const a = document.createElement('a')
  // 设置 a 标签的 href 特点为刚刚生成的 URL 地址
  a.href = url
  // 设置 a 标签的 download 特点为文件名
  a.download = `${new Date().getTime()}.${blob.type.split('/')[1]}`
  // 模拟点击 a 标签
  a.click()
  // 释放 URL 地址
  URL.revokeObjectURL(url)
}

然后咱们就能够愉快的录制视频了。当然这儿仅仅用同享屏幕的方法来录制视频,假如你想要录制摄像头的视频,也是相同,拿到媒体流后,就能够直接录制了。

线上体会地址:

当然,既然都拿到了媒体流,那么咱们也能够将媒体流中的视频轨迹录制成 gif 图片,这样在一些场景下同享起来也会愈加便利。

因为 MediaRecorder api 不支撑将 mimeType 设置成 image/gif ,所以咱们需求凭借一个第三方库MediaStreamRecorder来完成。它的用法和 MediaRecorder 基本共同。我就不再赘述了。

最终有一个需求注意的当地,也是我在实践项目中遇到的问题。截止到现在为止,在运用 MediaRecorder 录制视频的时分,假如你的体系是 Windows 或许 Chrome OS,那么录制的视频没什么问题,可是在 Mac 和 Linux 上,录制摄像头和同享屏幕时,挑选网页的同享方法,所拿获得的媒体流是能够拿到视频轨迹和音频轨迹的,可是录制整个屏幕时,因为体系的约束,只能拿到视频的轨迹。好在一般录屏都不会有带音频的需求,等待后边能够支撑。

WebRTC 从实战到未来!迎候风口,前端必学的技能

完成视频的虚拟布景

在视频会议中,咱们常常会看到一些人在视频中的布景是虚拟的,这样能够让咱们更专心于对方的表情和言语,而不会被布景中的一些搅扰因素所分散注意力。那么咱们怎么完成这样的作用呢?

下面咱们先经过一个简略的 demo 来看看作用。

线上体会地址

主要原理是经过 canvas 将视频中的每一帧画到画布上,然后将画布中的像素逐一与设定的布景色(默许是绿色,你能够更换为任意符合你布景的色彩)进行核算,比较后的差值到达设定的阈值时,对其进行处理,将其更换为预先准备好的布景图的图画数据,最终将处理后的图画数据再画到虚拟布景画布上,经过虚拟布景画布拿到媒体流后给到 video 标签播映, 这样就完成了视频的虚拟布景作用。

下面咱们来看看详细的完成。

为保持巨细共同,这儿咱们统一设置画布和视频的宽高为 480*300。

<canvas id="backgroundImg" width="480" height="300"></canvas>
<video id="real-video" width="480" height="300" autoplay muted></video>
<video id="virtual-video" width="480" height="300" autoplay muted></video>

首要咱们需求准备好布景图,这儿我运用了一张图片,你也能够运用视频作为布景。

经过 canvas 将布景图画到画布上,然后经过 getImageData 办法拿到图画数据。

// 虚拟布景的 canvas中的图片数据
let backgroundImageData: ImageData
// 获取布景图画数据
function getBackgroundImageData() {
  const backgroundCanvas = document.querySelector(
    '#backgroundImg',
  ) as HTMLCanvasElement
  const backgroundCtx = backgroundCanvas.getContext('2d')!
  const img = new Image()
  img.src = ''
  img.onload = () => {
    backgroundCtx.drawImage(
      img,
      0,
      0,
      backgroundCanvas.width,
      backgroundCanvas.height,
    )
    backgroundImageData = backgroundCtx.getImageData(
      0,
      0,
      backgroundCanvas.width,
      backgroundCanvas.height,
    )
  }
}

然后咱们需求经过摄像头获取到视频流,仍是用之前几个 demo 中的办法。

// 获取本地音视频流
async function getLocalStream(options: MediaStreamConstraints) {
  const stream = await navigator.mediaDevices.getUserMedia(options)
  return stream
}
// 播映原始视频流
function playRealVideo(stream: MediaStream) {
  realVideo = document.querySelector('#real-video') as HTMLVideoElement
  realVideo.srcObject = stream
}

上述步骤完成后,咱们就能够经过创立 canvas 标签 先将实在的视频每隔 40ms 一次 画到画布上。

这样的话,画布就会不断的更新,能到达 25 帧的作用,这样能确保咱们的视频流是十分流通的。

一般来说,人眼舒适放松时可视帧数是每秒 24 帧,高度集中精力时不超越 30 帧。电影院里的 2D 电影一般是 24 帧的,3D 电影一般是 60 帧以上。

画到画布后,咱们也相应的要经过 getImageData 办法拿到实在视频的图画数据。

然后每一帧都要与设置好的布景色进行比较,比较后的差值到达设定的阈值的像素,就要扣除(替换为之前拿到的布景图的像素。

WebRTC 从实战到未来!迎候风口,前端必学的技能

WebRTC 从实战到未来!迎候风口,前端必学的技能

看到这儿,假如曾经看过我的文章,咱们必定很眼熟,这个核算色彩差的逻辑与我之前写的《我用 10000 张图片合成咱们美好的瞬间》用来做合成图的逻辑是相同的。

首要需求明白的一点便是,rgb 的色域是一个 3 维的空间,咱们能够经过核算两个点之间的间隔来判别两个色彩的差异。间隔越小,色彩差异越小。

而这只需求咱们中学时期学过的 欧式间隔 公式就能够了。

WebRTC 从实战到未来!迎候风口,前端必学的技能

咱们把它转化为色彩差的核算公式如下:

// 核算色彩差
function colorDiff(color1: number[], color2: number[]) {
  const r = color1[0] - color2[0]
  const g = color1[1] - color2[1]
  const b = color1[2] - color2[2]
  return Math.sqrt(r * r + g * g + b * b)
}

然后再将处理后的图画数据画到虚拟视频的画布上,再经过captureStreamapi 将画布转换为视频流,最终将视频流赋值给虚拟视频的 srcObject 特点。

代码如下:

const WIDTH = 480 // 视频宽度
const HEIGHT = 300 // 视频高度
// 将视频写到 canvas 中
function drawVideoToCanvas(realVideo: HTMLVideoElement) {
  realVideoCanvas = document.createElement('canvas') as HTMLCanvasElement
  realVideoCtx = realVideoCanvas.getContext('2d')!
  virtualVideoCanvas = document.createElement('canvas') as HTMLCanvasElement
  virtualVideoCtx = virtualVideoCanvas.getContext('2d')!
  realVideoCanvas.width = virtualVideoCanvas.width = WIDTH
  realVideoCanvas.height = virtualVideoCanvas.height = HEIGHT
  // 每隔 100ms 将实在的视频写到 canvas 中,并获取视频的图画数据
  setInterval(() => {
    realVideoCtx.drawImage(
      realVideo,
      0,
      0,
      realVideoCanvas.width,
      realVideoCanvas.height,
    )
    // 获取 realVideoCanvas 中的图画数据
    realVideoImageData = realVideoCtx.getImageData(
      0,
      0,
      realVideoCanvas.width,
      realVideoCanvas.height,
    )
    // 处理实在视频的图画数据,将其写到虚拟视频的 canvas 中
    processFrameDrawToVirtualVideo()
  }, 40)
  // 从 VirtualVideoCanvas 中获取视频流并在 virtualVideo 中播映
  getStreamFromVirtualVideoCanvas()
}
// 从 VirtualVideoCanvas 中获取视频流并在 virtualVideo 中播映
function getStreamFromVirtualVideoCanvas() {
  virtualVideo = document.querySelector('#virtual-video') as HTMLVideoElement
  const stream = virtualVideoCanvas.captureStream(30)
  virtualVideo.srcObject = stream
}

逐像素核算与要处理的方针色彩的差值,假如差值小于容差,则将该像素设置为布景图片中的对应像素

// 处理实在视频的图画数据,将其写到虚拟视频的 canvas 中
function processFrameDrawToVirtualVideo() {
  // 逐像素核算与要处理的方针色彩的差值,假如差值小于阈值,则将该像素设置为布景图片中的对应像素
  for (let i = 0; i < realVideoImageData.data.length; i += 4) {
    const r = realVideoImageData.data[i]
    const g = realVideoImageData.data[i + 1]
    const b = realVideoImageData.data[i + 2]
    const a = realVideoImageData.data[i + 3]
    const bgR = backgroundImageData.data[i]
    const bgG = backgroundImageData.data[i + 1]
    const bgB = backgroundImageData.data[i + 2]
    const bgA = backgroundImageData.data[i + 3]
    // 核算与布景色的差值
    const diff = colorDiff([r, g, b], backgroundColor)
    // 当差值小于设定的阈值,则将该像素设置为布景图片中的对应像素
    if (diff < allowance.value) {
      realVideoImageData.data[i] = bgR
      realVideoImageData.data[i + 1] = bgG
      realVideoImageData.data[i + 2] = bgB
      realVideoImageData.data[i + 3] = bgA
    }
  }
  // 将处理后的图画数据写到虚拟视频的 canvas 中
  virtualVideoCtx.putImageData(realVideoImageData, 0, 0)
}
// 核算色彩差异
function colorDiff(rgba1: number[], rgba2: number[]) {
  let d = 0
  for (let i = 0; i < rgba1.length; i++) {
    d += (rgba1[i] - rgba2[i]) ** 2
  }
  return Math.sqrt(d)
}

能够看到,其间backgroundColor(需求扣除的布景色)和allowance(容差值)两个变量是由外部操控的,这样咱们就能够在页面上经过滑动条或是其他的组件来动态改变容差,经过取色器来动态改变需求扣除的布景色。

WebRTC 从实战到未来!迎候风口,前端必学的技能

最终

至此,咱们就能够完成一个简略的布景替换的功用了。当然,这儿仅仅简略的完成了一个布景替换的功用,实践上,咱们还能够经过更多的技能手段来完成愈加杂乱的功用,比方:

现在仅仅针对纯色的布景进行了替换,假如杂乱的布景,咱们能够经过图画切割的方法来完成布景替换,比方:TensorFlow.js 中的 身体切割(BodyPix)。

WebRTC 从实战到未来!迎候风口,前端必学的技能

或许是说,关于视频中的人脸,咱们能够经过face-api.js来检测人脸,并将人脸替换为其他的图片,然后完成一个简略的换脸功用。关于视频中的人体,咱们能够经过BodyPix来检测人体,并将人体替换为其他的图片,然后完成一个简略的换装功用。等等…(后续我都在这个专栏中安排~)

能够见得,用 WebRTC 相关的常识来结合一些其他相关技能,能够完成十分多的风趣的项目,可玩性十分强。

这作为我专栏的第一篇,主要是想经过这篇文章来介绍一下 WebRTC 相关的常识,以及 WebRTC 相关的一些运用场景,期望能够帮助到咱们。

原本还想写下 1v1 视频聊天的完成,可是因为时刻关系,我把它放到第二篇来写吧,demo 我现已放到了 我的前端公园合集仓库中,这两天抽暇写完,咱们也能够 follow 一下我的 Github,谢谢咱们~

WebRTC 从实战到未来!迎候风口,前端必学的技能

我是通宵写完这篇文章的,所以我需求休息一下,哈哈。

最终的最终 ~

咱们喜欢这个专栏的话, 能够点点赞 ~ ,你的支撑便是我的动力