在针对 Web 进行开发时,WebRTC 标准提供了用于访问连接到计算机或智能手机的摄像头和麦克风的 API。这些设备通常称为“媒体设备”,可通过实现 MediaDevices
接口的 navigator.mediaDevices
对象使用 JavaScript 进行访问。在此对象中,我们可以枚举所有已连接的设备,监听设备更改(当设备连接或断开连接时),并打开设备以检索媒体流(见下文)。
最常见的用法是通过函数 getUserMedia()
,该函数会返回一个针对匹配的媒体设备解析为 MediaStream
的 promise。此函数接受单个 MediaStreamConstraints
对象,用于指定我们的要求。例如,如需简单地打开默认麦克风和摄像头,我们可以执行以下操作。
使用 Promise
const constraints = {
'video': true,
'audio': true
}
navigator.mediaDevices.getUserMedia(constraints)
.then(stream => {
console.log('Got MediaStream:', stream);
})
.catch(error => {
console.error('Error accessing media devices.', error);
});
使用 async/await
const openMediaDevices = async (constraints) => {
return await navigator.mediaDevices.getUserMedia(constraints);
}
try {
const stream = openMediaDevices({'video':true,'audio':true});
console.log('Got MediaStream:', stream);
} catch(error) {
console.error('Error accessing media devices.', error);
}
调用 getUserMedia()
将触发权限请求。如果用户接受该权限,系统会使用包含一个视频和一个音轨的 MediaStream
解析 promise。如果权限遭拒,系统会抛出 PermissionDeniedError
。如果没有连接任何匹配设备,系统会抛出 NotFoundError
。
有关 MediaDevices
接口的完整 API 参考文档,请参阅 MDN Web 文档。
查询媒体设备
在更复杂的应用中,我们很可能需要检查所有连接的摄像头和麦克风,并向用户提供适当的反馈。这可以通过调用函数 enumerateDevices()
来实现。这将返回一个解析为描述每个已知媒体设备的 MediaDevicesInfo
数组的 promise。我们可以使用它向用户显示一个界面,让用户选择他们喜欢的界面。每个 MediaDevicesInfo
都包含一个名为 kind
的属性,其值为 audioinput
、audiooutput
或 videoinput
,用于指示媒体设备的类型。
使用 Promise
function getConnectedDevices(type, callback) {
navigator.mediaDevices.enumerateDevices()
.then(devices => {
const filtered = devices.filter(device => device.kind === type);
callback(filtered);
});
}
getConnectedDevices('videoinput', cameras => console.log('Cameras found', cameras));
使用 async/await
async function getConnectedDevices(type) {
const devices = await navigator.mediaDevices.enumerateDevices();
return devices.filter(device => device.kind === type)
}
const videoCameras = getConnectedDevices('videoinput');
console.log('Cameras found:', videoCameras);
监听设备更改
大多数计算机都支持在运行时插入各种设备。它可能是通过 USB 连接的摄像头、蓝牙耳机或一组外部扬声器。为了适当地支持此功能,Web 应用应监听媒体设备的更改。为此,可以针对 devicechange
事件向 navigator.mediaDevices
添加监听器。
// Updates the select element with the provided set of cameras
function updateCameraList(cameras) {
const listElement = document.querySelector('select#availableCameras');
listElement.innerHTML = '';
cameras.map(camera => {
const cameraOption = document.createElement('option');
cameraOption.label = camera.label;
cameraOption.value = camera.deviceId;
}).forEach(cameraOption => listElement.add(cameraOption));
}
// Fetch an array of devices of a certain type
async function getConnectedDevices(type) {
const devices = await navigator.mediaDevices.enumerateDevices();
return devices.filter(device => device.kind === type)
}
// Get the initial set of cameras connected
const videoCameras = getConnectedDevices('videoinput');
updateCameraList(videoCameras);
// Listen for changes to media devices and update the list accordingly
navigator.mediaDevices.addEventListener('devicechange', event => {
const newCameraList = getConnectedDevices('video');
updateCameraList(newCameraList);
});
媒体限制
约束对象必须实现 MediaStreamConstraints
接口,我们将其作为参数传递给 getUserMedia()
,这样我们就能够打开符合特定要求的媒体设备。此要求可以非常宽泛(音频和/或视频),也可以非常具体(最低相机分辨率或确切的设备 ID)。对于使用 getUserMedia()
API 的应用,建议先检查现有设备,然后使用 deviceId
约束条件指定与确切设备匹配的约束条件。在可能的情况下,设备还将根据约束条件进行配置。我们可以在麦克风上启用回声消除,也可以为摄像头设置视频的特定或最小宽度和高度。
async function getConnectedDevices(type) {
const devices = await navigator.mediaDevices.enumerateDevices();
return devices.filter(device => device.kind === type)
}
// Open camera with at least minWidth and minHeight capabilities
async function openCamera(cameraId, minWidth, minHeight) {
const constraints = {
'audio': {'echoCancellation': true},
'video': {
'deviceId': cameraId,
'width': {'min': minWidth},
'height': {'min': minHeight}
}
}
return await navigator.mediaDevices.getUserMedia(constraints);
}
const cameras = getConnectedDevices('videoinput');
if (cameras && cameras.length > 0) {
// Open first available video camera with a resolution of 1280x720 pixels
const stream = openCamera(cameras[0].deviceId, 1280, 720);
}
有关 MediaStreamConstraints
接口的完整文档,请参阅 MDN Web 文档。
本地播放
打开媒体设备并且我们有可用的 MediaStream
后,我们可以将其分配给视频或音频元素,以在本地播放音频流。
async function playVideoFromCamera() {
try {
const constraints = {'video': true, 'audio': true};
const stream = await navigator.mediaDevices.getUserMedia(constraints);
const videoElement = document.querySelector('video#localVideo');
videoElement.srcObject = stream;
} catch(error) {
console.error('Error opening video camera.', error);
}
}
与 getUserMedia()
配合使用的典型视频元素所需的 HTML 通常具有 autoplay
和 playsinline
属性。autoplay
属性会导致分配给元素的新流自动播放。
playsinline
属性允许视频在某些移动浏览器中以内嵌方式播放,而不是仅以全屏模式播放。此外,我们还建议对直播使用 controls="false"
,除非用户能够暂停直播。
<html>
<head><title>Local video playback</title></head>
<body>
<video id="localVideo" autoplay playsinline controls="false"/>
</body>
</html>