【从头到脚】撸一个多人视频聊天 — 前端 WebRTC 实战(一)

本文是 WebRTC 实战系列的第一篇,主要介绍了 WebRTC 的基础知识和如何实现一对一的本地及网络对等连接。通过 getUserMedia 获取媒体流,RTCPeerConnection 实现点对点通信,详细阐述了连接流程,包括信令交换、ICE 穿越技术。同时提供了一个本地和网络对等连接的实战案例,帮助读者理解 WebRTC 的工作原理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

从头到脚 】会作为一个系列文章来发布,它包括但不限于 WebRTC 多人视频,预计会有:

  • WebRTC 实战(一):也就是本期,主要是基础讲解以及一对一的本地对等连接,网络对等连接。
  • WebRTC 实战(二):主要讲解数据传输以及多端本地对等连接、网络对等连接。
  • WebRTC 实战(三):实现一个一对一的视频聊天项目,包括但不限于截图、录制等。
  • WebRTC + Canvas 实现一个共享画板项目。
  • 作者开源作品 ???Vchat — 一个社交聊天系统(vue + node + mongodb) 的系列文章

因为文章输出确实要耗费很大的精力,所以上面计划不一定是这个发布顺序,中间也会穿插发布其它方向的文章,如 Vue、JavaScript 或者其他学习的主题。在文章里,会把我自己遇到过的一些坑点重点提示大家注意,尽量让大家在学习过程中少走弯路。当然,我的也并不是标准答案,只是我个人的思路。如果大家有更好的方法,可以互相交流,也希望大家都能有所收获。

在这里也希望大家可以 关注 一波,你们的关注支持,也能激励我输出更好的文章。

先放个效果图,这期的目标是实现一个 1 V 1 的视频通话(笔记本摄像头不能用了,用的虚拟摄像头)。文章比较长,可以 mark 以后慢慢看。

文章末尾有 交流群公众号,希望大家支持,感谢?。

什么是 WebRTC ?

WebRTC 是由一家名为 Gobal IP Solutions,简称 GIPS 的瑞典公司开发的。Google 在 2011 年收购了 GIPS,并将其源代码开源。然后又与 IETF 和 W3C 的相关标准机构合作,以确保行业达成共识。其中:

  • Web Real-Time Communications (WEBRTC) W3C 组织:定义浏览器 API。
  • Real-Time Communication in Web-browsers (RTCWEB) IETF 标准组织:定义其所需的协议,数据,安全性等手段。

简单来说,WebRTC 是一个可以在 Web 应用程序中实现音频,视频和数据的实时通信的开源项目。在实时通信中,音视频的采集和处理是一个很复杂的过程。比如音视频流的编解码、降噪和回声消除等,但是在 WebRTC 中,这一切都交由浏览器的底层封装来完成。我们可以直接拿到优化后的媒体流,然后将其输出到本地屏幕和扬声器,或者转发给其对等端。

WebRTC 的音视频处理引擎:

所以,我们可以在不需要任何第三方插件的情况下,实现一个浏览器到浏览器的点对点(P2P)连接,从而进行音视频实时通信。当然,WebRTC 提供了一些 API 供我们使用,在实时音视频通信的过程中,我们主要用到以下三个:

  • getUserMedia:获取音频和视频流(MediaStream)
  • RTCPeerConnection:点对点通信
  • RTCDataChannel:数据通信

不过,虽然浏览器给我们解决了大部分音视频处理问题,但是从浏览器请求音频和视频时,我们还是需要特别注意流的大小和质量。因为即便硬件能够捕获高清质量流,CPU 和带宽也不一定可以跟上,这也是我们在建立多个对等连接时,不得不考虑的问题。

实现

接下来,我们通过分析上文提到的 API,来逐步弄懂 WebRTC 实时通信实现的流程。

getUserMedia

MediaStream

getUserMedia 这个 API 大家可能并不陌生,因为常见的 H5 录音等功能就需要用到它,主要就是用来获取设备的媒体流(即 MediaStream)。它可以接受一个约束对象 constraints 作为参数,用来指定需要获取到什么样的媒体流。

    navigator.mediaDevices.getUserMedia({ audio: true, video: true }) 
    // 参数表示需要同时获取到音频和视频
        .then(stream => {
          // 获取到优化后的媒体流
          let video = document.querySelector('#rtc');
          video.srcObject = stream;
        })
        .catch(err => {
          // 捕获错误
        });
复制代码

我们简单看一下获取到的 MediaStream。

可以看到它有很多属性,我们只需要了解一下就好,更多信息可以查看 MDN

* id [String]: 对当前的 MS 进行唯一标识。所以每次刷新浏览器或是重新获取 MS,id 都会变动。
* active [boolean]: 表示当前 MS 是否是活跃状态(就是是否可以播放)。
* onactive: 当 active 为 true 时,触发该事件。
复制代码

结合上图,我们顺便复习一下上期讲的原型和原型链。MediaStream 的 __proto__ 指向它的构造函数所对应的原型对象,在原型对象中又有一个 constructor 属性指向了它所对应的构造函数。也就是说 MediaStream 的构造函数是一个名为 MediaStream 的函数。可能说得有一点绕,对原型还不熟悉的同学,可以去看一下上期文章 JavaScript 原型和原型链及 canvas 验证码实践

这里也可以通过 getAudioTracks()、getVideoTracks() 来查看获取到的流的某些信息,更多信息查看 MDN

* kind: 是当前获取的媒体流类型(Audio/Video)。
* label: 是媒体设备,我这里用的是虚拟摄像头。
* muted: 表示媒体轨道是否静音。
复制代码
兼容性

继续来看 getUserMedia,navigator.mediaDevices.getUserMedia 是新版的 API,旧版的是 navigator.getUserMedia。为了避免兼容性问题,我们可以稍微处理一下(其实说到底,现在 WebRTC 的支持率还不算高,有需要的可以选择一些适配器,如 adapter.js)。

    // 判断是否有 navigator.mediaDevices,没有赋成空对象
    if (navigator.mediaDevices === undefined) {
        navigator.mediaDevices = {};
    }
    
    // 继续判断是否有 navigator.mediaDevices.getUserMedia,没有就采用 navigator.getUserMedia
    if (navigator.mediaDevices.getUserMedia) {
        navigator.mediaDevices.getUserMedia = function(prams) {
            let getUserMedia = navigator.webkitGetUserMedia || navigator.mozGetUserMedia;
            // 兼容获取
            if (!getUserMedia) {
                return Promise.reject(new Error('getUserMedia is not implemented in this browser'));
            }
            return new Promise(function(resolve, reject) {
                getUserMedia.call(navigator, prams, resolve, reject);
            });
        };
    }
    navigator.mediaDevices.getUserMedia(constraints)
        .then(stream => {
            let video = document.querySelector('#Rtc');
            if ('srcObject' in video) { // 判断是否支持 srcObject 属性
                video.srcObject = stream;
            } else {
                video.src = window.URL.createObjectURL(stream);
            }
            video.onloadedmetadata = function(e) {
                video.play();
            };
        })
        .catch((err) => { // 捕获错误
            console.error(err.name + ': ' + err.message);
        });
复制代码
constraints

对于 constraints 约束对象,我们可以用来指定一些和媒体流有关的属性。比如指定是否获取某种流:

    navigator.mediaDevices.g
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值