读完全文,可以掌握在浏览器端如何创建和使用媒体流,在文末尾有代码库可以直接下载运行。
注意: 使用以下这些
API
是有前提条件的哦。必须是在安全源
访问,调用API
才没有任何阻碍的。那什么是安全源
呢?看下面思维导图(更详细的看:chrome官方文档),且记住这句话:安全源
是至少匹配以下( Scheme 、 Host 、 Port )模式之一的源。

举个简单的例子:你本地开发用
HTTP
请求地址获取摄像头API
没有问题,但是你的同事用他的电脑访问你电脑IP
对应的项目地址时,摄像头调用失败,为什么呢?因为在他的浏览器中,你的项目访问地址非
HTTPS
,在非HTTPS
的情况下,如果IP
不是localhost
或127.0.0.1
,都不属于安全源
。
getUserMedia
api: navigator.mediaDevices.getUserMedia
用处: 获取用户层面的媒体,当你的计算机通过 USB
或者其他网络形式接入了 N 多个 摄像头或虚拟设备时,都是可以通过这个 API
获取到的。 当然不仅仅是视频设备,还包括音频设备和虚拟音频设备。 获取媒体设备是最简单的操作,它还可以控制获取到媒体的分辨率,以及其他的以一些可选项
简单使用:直接调用不使用任何参数,则获取的就是 PC 的默认摄像头和麦克风
复杂使用:比如你的电脑上自带麦克风,同时你连接了蓝牙耳机和有线耳机,那么在视频通话过程中,你如何主动选择使用哪个呢?也就是说, 在用摄像头或者麦克风之前,我们先要解决如何从 N 个摄像头或者麦克风中选择我们想要的。解决思路如下:
- 获取当前设备所有的摄像头和麦克风信息
- 从所有设备信息中遍历筛选出我们想要的设备
- 将我们想要使用的设备以某种参数形式传递给浏览器
api
- 浏览器
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也是足够的。
运行结果图:
另外, 推荐一下我的另一个开源项目:问卷平台
使用的技术栈为react + ts + echarts + 高德地图 + webrtc 目前正在持续开发中。有想要学习的小伙伴可以加入进来,一起交流学习
下一章节会讲如何创建会话流和信令服务器的搭建。