现在,许多浏览器都能够访问用户的视频和音频输入。不过,具体取决于浏览器,它可能是完全动态的嵌入式体验,也可能委托给用户设备上的其他应用。
从简单开始,逐步深入
最简单的方法就是直接向用户索要预录制的文件。为此,请创建一个简单的文件输入元素,并添加一个 accept
过滤器(表示我们只能接受视频文件)和一个 capture
属性(表示我们希望直接从相机获取视频)。
<input type="file" accept="video/*" capture />
此方法适用于所有平台。在桌面设备上,系统会提示用户从文件系统上传文件(忽略 capture
属性)。在 iOS 上的 Safari 中,它会打开相机应用,以便您录制视频,然后将其发回给网页;在 Android 上,它会让用户选择使用哪个应用录制视频,然后将其发回给网页。
许多移动设备都具有多个摄像头。如果您有偏好设置,则可以将 capture
属性设置为 user
(如果您希望摄像头朝向用户),或设置为 environment
(如果您希望摄像头朝向外面)。
<input type="file" accept="video/*" capture="user" />
<input type="file" accept="video/*" capture="environment" />
请注意,这只是一个提示 - 如果浏览器不支持该选项,或者您请求的摄像头类型不可用,浏览器可能会选择其他摄像头。
用户录制完毕并返回到网站后,您需要通过某种方式获取文件数据。您可以通过将 onchange
事件附加到输入元素,然后读取事件对象的 files
属性来快速访问。
<input type="file" accept="video/*" capture="camera" id="recorder" />
<video id="player" controls></video>
<script>
var recorder = document.getElementById('recorder');
var player = document.getElementById('player');
recorder.addEventListener('change', function (e) {
var file = e.target.files[0];
// Do something with the video file.
player.src = URL.createObjectURL(file);
});
</script>
获得文件访问权限后,您可以对其执行任何操作。例如,您可以:
- 将其直接附加到
<video>
元素,以便您播放 - 将其下载到用户的设备上
- 通过附加到
XMLHttpRequest
将其上传到服务器 - 将帧绘制到画布并对其应用滤镜
虽然使用输入元素方法访问视频数据很常见,但它是最不受欢迎的选项。我们非常希望获取相机访问权限,并直接在网页中提供良好的体验。
以互动方式访问摄像头
新型浏览器可以直接与相机通信,这让我们能够打造与网页完全集成的体验,用户无需离开浏览器。
获取相机访问权限
我们可以使用 WebRTC 规范中名为 getUserMedia()
的 API 直接访问摄像头。getUserMedia()
会提示用户授予对已连接的麦克风和摄像头的访问权限。
如果成功,该 API 将返回一个 Stream
,其中包含来自摄像头或麦克风的数据,然后我们可以将其附加到 <video>
元素、附加到 WebRTC 流,或使用 MediaRecorder
API 进行保存。
如需从摄像头获取数据,只需在传递给 getUserMedia()
API 的 constraints 对象中设置 video: true
<video id="player" controls></video>
<script>
var player = document.getElementById('player');
var handleSuccess = function (stream) {
player.srcObject = stream;
};
navigator.mediaDevices
.getUserMedia({audio: true, video: true})
.then(handleSuccess);
</script>
如果您想选择特定摄像头,可以先枚举可用的摄像头。
navigator.mediaDevices.enumerateDevices().then((devices) => {
devices = devices.filter((d) => d.kind === 'videoinput');
});
然后,您可以在调用 getUserMedia
时传递要使用的 deviceId。
navigator.mediaDevices.getUserMedia({
audio: true,
video: {
deviceId: devices[0].deviceId,
},
});
这本身没什么用。我们只能获取视频数据并进行回放。
访问相机的原始数据
如需访问相机的原始视频数据,您可以将每个帧绘制到 <canvas>
中,然后直接操控像素。
对于 2D 画布,您可以使用上下文的 drawImage
方法将 <video>
元素的当前帧绘制到画布中。
context.drawImage(myVideoElement, 0, 0);
借助 WebGL 画布,您可以使用 <video>
元素作为纹理的来源。
gl.texImage2D(
gl.TEXTURE_2D,
0,
gl.RGBA,
gl.RGBA,
gl.UNSIGNED_BYTE,
myVideoElement,
);
请注意,无论是哪种情况,此操作都会使用正在播放的视频的当前帧。如需处理多个帧,您需要每次将视频重新绘制到画布上。
如需详细了解,请参阅我们关于对图片和视频应用实时特效的文章。
保存摄像头中的数据
如需保存来自相机的数据,最简单的方法是使用 MediaRecorder
API。
MediaRecorder
API 将获取由 getUserMedia
创建的数据流,然后逐步将数据流中的数据保存到首选目标位置。
<a id="download">Download</a>
<button id="stop">Stop</button>
<script>
let shouldStop = false;
let stopped = false;
const downloadLink = document.getElementById('download');
const stopButton = document.getElementById('stop');
stopButton.addEventListener('click', function() {
shouldStop = true;
})
var handleSuccess = function(stream) {
const options = {mimeType: 'video/webm'};
const recordedChunks = [];
const mediaRecorder = new MediaRecorder(stream, options);
mediaRecorder.addEventListener('dataavailable', function(e) {
if (e.data.size > 0) {
recordedChunks.push(e.data);
}
if(shouldStop === true && stopped === false) {
mediaRecorder.stop();
stopped = true;
}
});
mediaRecorder.addEventListener('stop', function() {
downloadLink.href = URL.createObjectURL(new Blob(recordedChunks));
downloadLink.download = 'acetest.webm';
});
mediaRecorder.start();
};
navigator.mediaDevices.getUserMedia({ audio: true, video: true })
.then(handleSuccess);
</script>
在本例中,我们将数据直接保存到一个数组中,稍后可以将其转换为 Blob
,然后将其保存到 Web 服务器或直接保存到用户设备上的存储空间中。
以负责任的方式使用相机功能
如果用户之前未向您的网站授予相机访问权限,那么在您调用 getUserMedia
时,浏览器会提示用户向您的网站授予相机权限。
用户不喜欢看到系统提示他们访问机器上强大的设备,他们会经常屏蔽此类请求,或者如果不了解系统创建此类提示的情境,则会忽略此类提示。最佳做法是仅在首次需要时请求访问相机。用户授予访问权限后,系统不会再次询问;但是,如果用户拒绝授予访问权限,您将无法再次获得访问权限,以便向用户请求权限。
使用 Permissions API 检查您是否已拥有访问权限
getUserMedia
API 无法让您知道自己是否已获得对摄像头的访问权限。这会给您带来一个问题,为了提供一个漂亮的界面来让用户授予您相机访问权限,您必须请求相机访问权限。
在某些浏览器中,可以使用 Permission API 解决此问题。借助 navigator.permission
API,您无需再次提示即可查询访问特定 API 的能力状态。
如需查询您是否有权访问用户的相机,您可以将 {name: 'camera'}
传入查询方法,该方法将返回以下任一值:
granted
- 用户之前已向您授予相机访问权限;prompt
- 用户尚未向您授予访问权限,当您调用getUserMedia
时,系统会提示您;denied
- 系统或用户已明确禁止访问相机,因此您将无法访问相机。
现在,您可以快速检查,看看是否需要更改界面以适应用户需要执行的操作。
navigator.permissions.query({name: 'camera'}).then(function (result) {
if (result.state == 'granted') {
} else if (result.state == 'prompt') {
} else if (result.state == 'denied') {
}
result.onchange = function () {};
});