webrtc基础api学习

读完全文,可以掌握在浏览器端如何创建和使用媒体流,在文末尾有代码库可以直接下载运行。

注意: 使用以下这些API是有前提条件的哦。必须是在安全源访问,调用API才没有任何阻碍的。那什么是安全源呢?看下面思维导图(更详细的看:chrome官方文档),且记住这句话:安全源 是至少匹配以下( Scheme Host Port )模式之一的源。

举个简单的例子:你本地开发用HTTP请求地址获取摄像头API没有问题,但是你的同事用他的电脑访问你电脑IP对应的项目地址时,摄像头调用失败,为什么呢?

因为在他的浏览器中,你的项目访问地址非HTTPS,在非HTTPS的情况下,如果IP不是localhost127.0.0.1,都不属于安全源

getUserMedia

api: navigator.mediaDevices.getUserMedia

用处: 获取用户层面的媒体,当你的计算机通过 USB 或者其他网络形式接入了 N 多个 摄像头或虚拟设备时,都是可以通过这个 API 获取到的。 当然不仅仅是视频设备,还包括音频设备和虚拟音频设备。 获取媒体设备是最简单的操作,它还可以控制获取到媒体的分辨率,以及其他的以一些可选项

简单使用:直接调用不使用任何参数,则获取的就是 PC 的默认摄像头和麦克风

复杂使用:比如你的电脑上自带麦克风,同时你连接了蓝牙耳机和有线耳机,那么在视频通话过程中,你如何主动选择使用哪个呢?也就是说, 在用摄像头或者麦克风之前,我们先要解决如何从 N 个摄像头或者麦克风中选择我们想要的。解决思路如下:

  1. 获取当前设备所有的摄像头和麦克风信息
  2. 从所有设备信息中遍历筛选出我们想要的设备
  3. 将我们想要使用的设备以某种参数形式传递给浏览器api
  4. 浏览器api去执行任务

对于第3点中的设备,这里要补充一些基础知识。设备有三大类型,每个类型都有固定的字段,比如 ID、kind、label ,而其中用于区分它们的就是kind字段中的固定值最核心的字段就是 ID

那么具体是如何使用的呢?

看看下面这段代码,简单看看,后面会详细介绍

js 复制代码
function handleError(error) {
    alert("摄像头无法正常使用,请检查是否占用或缺失")
    console.error('navigator.MediaDevices.getUserMedia error: ', error.message, error.name);
}

function initInnerLocalDevice(){
        const that  = this
        var localDevice = {
            audioIn:[],
            videoIn: [],
            audioOut: []

        }
        let constraints = {video:true, audio: true}
        if (!navigator.mediaDevices || !navigator.mediaDevices.enumerateDevices) {
            console.log("浏览器不支持获取媒体设备");
            return;
        }
        navigator.mediaDevices.getUserMedia(constraints)
            .then(function(stream) {
                stream.getTracks().forEach(trick => {
                    trick.stop()
                })
                // List cameras and microphones.
                navigator.mediaDevices.enumerateDevices()
                    .then(function(devices) {
                        devices.forEach(function(device) {
                            let obj = {id:device.deviceId, kind:device.kind, label:device.label}
                            if(device.kind === 'audioinput'){
                                if(localDevice.audioIn.filter(e=>e.id === device.deviceId).length === 0){
                                    localDevice.audioIn.push(obj)
                                }
                            }if(device.kind === 'audiooutput'){
                                if(localDevice.audioOut.filter(e=>e.id === device.deviceId).length === 0){
                                    localDevice.audioOut.push(obj)
                                }
                            }else if(device.kind === 'videoinput' ){
                                if(localDevice.videoIn.filter(e=>e.id === device.deviceId).length === 0){
                                    localDevice.videoIn.push(obj)
                                }
                            }
                        });
                    })
                    .catch(handleError);

            })
            .catch(handleError);
    }

这个代码片段的主要作用就是获取用户设备上所有的摄像头和麦克风信息,起关键作用的是enumerateDevices函数。但是在调用这个关键函数之前,先调用了getUserMedia函数,getUserMedia函数是用户在访问服务时直接调用用户摄像头,此时如果用户授权且同意使用设备摄像头、麦克风,那么enumerateDevices函数就能获取设备信息了,在这里getUserMedia函数可以理解为获取摄像头或者麦克风权限集合的探路函数

可以看到我电脑上打印出来的信息

可以看到,每个设备里都是ID、kind、label 这三个字段。那么,当我们选择了某一个设备后,如何利用设备信息去选中对应的摄像头、麦克风呢?这里就涉及到了getUserMedia的约束参数constraints

媒体约束 constraints

在具体讲解约束参数 constraints 之前,大家先看下面这段示例代码:

js 复制代码
let constraints = {video:true, audio: true} 
--
    function handleError(error) {
        console.error('navigator.MediaDevices.getUserMedia error: ', error.message, error.name);
    }
--
    /**
     * 获取设备 stream
     * @param constraints
     * @returns {Promise<MediaStream>}
     */
    async function getLocalUserMedia(constraints){
        return await navigator.mediaDevices.getUserMedia(constraints)
    }
--
    let stream = await this.getLocalUserMedia(constraints).catch(handleError);
console.log(stream)

上面的代码为给getUserMedia方法传入constraints参数来获取获取计算机摄像头和麦克风的媒体流。当然,这是在电脑本身默认有媒体流的情况下才好用,当电脑没有媒体流或者有多个媒体流时就会出现问题。那么如何才能兼容其他两种情况呢?这就需要知道constraints的具体用法了

  • 同时获取视频和音频输入

使用下面约束, 如果遇到计算机没有摄像头的话,你调用上述代码的过程中就会报错。因此我们在调用之前可以通过enumerateDevices返回结果主动判断有无视频输入源,没有的话,可以动态将这个参数中的 video设置为false

js 复制代码
{ audio: true, video: true }
  • 获取指定分辨率

在会议宽带足够且流媒体传输合理的情况下,无需考虑服务端压力,而需考虑客户端用户摄像头的分辨率范围,通常我们会设置一个分辨率区间。

下面展示的①约束是请求一个 1920×1080 分辨率的视频,但是还提到 min 参数,将 320×240 作为最小分辨率,因为并不是所有的网络摄像头都可以支持 1920×1080 。当请求包含一个 ideal(应用最理想的)值时,这个值有着更高的权重,意味着浏览器会先尝试找到最接近指定理想值的设定或者摄像头(如果设备拥有不止一个摄像头)。

但是,在多人会议简单架构场景中,在不改变会议稳定性的情况下,为了让更多的客户端加入,我们通常会把高分辨率主动降低到低分辨率,约束特定摄像头获取指定分辨率如下面②配置。

js 复制代码
--------------------①:1--------------------------
{
    audio: true,
    video: {
        width: { min: 320, ideal: 1280, max: 1920 },
        height: { min: 240, ideal: 720, max: 1080 }
    }
}
--------------------②:2--------------------------
{
    audio: true,
    video: { width: 720, height: 480}
}
  • 指定视频轨道约束:获取移动设备的前置或者后置摄像头

facingMode属性。可接受的值有:user(前置摄像头)、environment(后置摄像头);需要注意的是,这个属性仅移动端可用 ,当我们的会议项目通过 h5 在移动端打开时,我们可以动态设置这个属性从而达到切换前后摄像头的场景。

js 复制代码
{ audio: true, video: { facingMode: "user" } }
{ audio: true, video: { facingMode: { exact: "environment" } } }
  • 指定帧速率frameRate

帧速率(可以理解为FPS)不仅对视频质量,还对带宽有着影响,所以在我们通话过程中,如果判定网络状况不好,那么可以限制帧速率。

视频是通过一定速率的连续多张图像形成的,比如每秒 24 张图片才会形成一个基础流畅的视频,因此帧速率对于实时通话的质量也有影响。

js 复制代码
const constraints = {
    audio: true,
    video: {
        width:1920,
        height:1080,
        frameRate: { ideal: 10, max: 15 }
    }
};

实际上,通过FPS我们可以引申出来一些场合,在特定场合选择特定的FPS搭配前面的分辨率配置,以提高我们会议系统的质量,比如:

  • 屏幕分享过程中,我们应当很重视高分辨率而不是帧速率,稍微卡点也没关系;

  • 在普通会议过程中,我们应当重视的是画面的流畅,即帧速率而不是高分辨率;

  • 在开会人数多但宽带又受限的情况下,我们重视的同样是会议的流程性,同样低分辨率更适合宽带受限的多人会议;

  • 使用特定的网络摄像头或者麦克风

这里就是将之前获取到的设备id传入即可

js 复制代码
/**
 * 获取指定媒体设备id对应的媒体流
 * @param videoId
 * @param audioId
 * @returns {Promise<void>}
 */
async function getTargetIdStream(videoId,audioId){
    const constraints = {
        audio: {deviceId: audioId ? {exact: audioId} : undefined},
        video: {
            deviceId: videoId ? {exact: videoId} : undefined,
            width:1920,
            height:1080,
            frameRate: { ideal: 10, max: 15 }
        }
    };
    if (window.stream) {
        window.stream.getTracks().forEach(track => {
            track.stop();
        });
    }
    //被调用方法前面有,此处不再重复
    let stream = await this.getLocalUserMedia(constraints).catch(handleError);

}

到这里,貌似只讲了一个apinavigator.mediaDevices.getUserMedia和它的参数constraints。但使用基本的媒体流,只要这个api也是足够的。

代码地址:gitee.com/yoboom/webr...

运行结果图:

另外, 推荐一下我的另一个开源项目:问卷平台

使用的技术栈为react + ts + echarts + 高德地图 + webrtc 目前正在持续开发中。有想要学习的小伙伴可以加入进来,一起交流学习

下一章节会讲如何创建会话流和信令服务器的搭建。

相关推荐
daols883 小时前
vue vxe-table 自适应列宽,根据内容自适应宽度的2种使用方式
vue.js·vxe-table
小小小小宇3 小时前
虚拟列表兼容老DOM操作
前端
悦悦子a啊3 小时前
Python之--基本知识
开发语言·前端·python
安全系统学习4 小时前
系统安全之大模型案例分析
前端·安全·web安全·网络安全·xss
涛哥码咖4 小时前
chrome安装AXURE插件后无效
前端·chrome·axure
OEC小胖胖4 小时前
告别 undefined is not a function:TypeScript 前端开发优势与实践指南
前端·javascript·typescript·web
行云&流水5 小时前
Vue3 Lifecycle Hooks
前端·javascript·vue.js
Sally璐璐5 小时前
零基础学HTML和CSS:网页设计入门
前端·css
老虎06275 小时前
JavaWeb(苍穹外卖)--学习笔记04(前端:HTML,CSS,JavaScript)
前端·javascript·css·笔记·学习·html
三水气象台5 小时前
用户中心Vue3网页开发(1.0版)
javascript·css·vue.js·typescript·前端框架·html·anti-design-vue