WebRTC 在访问设备摄像头和设备麦克风以及在浏览器中流式传输视频或音频媒体方面非常流行。但在许多情况下,我们可能需要记录流媒体以供将来使用或供用户使用(例如用户可能想要下载流媒体等)。在这种情况下,我们可以使用 MediaRecorder API 来记录媒体流。
在本文中,我们将使用纯 JavaScript 及其 MediaRecorder API 创建一个基本的 Video and Audio Recorder 网站。
项目描述:我们正在建设的网站将有——
所以,让我们首先设置我们简单的 HTML 页面——
Video & Audio Recorder
Video & Audio Recorder
Record Video
Click the "Start video Recording"
button to start recording
Record Audio
Click the "Start Recording"
button to start recording
|
输出:
如果你仔细查看index.html,你会发现 video 和 audio 标签没有给出任何来源,我们稍后会使用 JavaScript 添加来源。现在,我们有一个选择选项,让用户可以选择他们想要录制的媒体类型。“vid-recorder” div 元素中的第一个 video 元素将包含网络摄像头流,评论中的 video 元素将包含录制的视频。请注意,只有最后一个视频元素具有“控件”属性,因为第一个视频元素将包含流并且不需要任何控件。
在“aud-recorder” div 中,我们有两个按钮来开始和停止录音,注释的音频元素将包含录制的音频。
现在,让我们向 HTML 页面添加一些 CSS —
body {
text-align: center;
color: green;
font-size: 1.2em;
}
.display-none {
display: none;
}
.recording {
color: red;
background-color: rgb(241 211 211);
padding: 5px;
margin: 6px auto;
width: fit-content;
}
video {
background-color: black;
display: block;
margin: 6px auto;
width: 420px;
height: 240px;
}
audio {
display: block;
margin: 6px auto;
}
a {
color: green;
}
|
输出:
现在,我们已将“display-none”类添加到“vid-recorder”和“aud-recorder” div。因为我们想根据用户的选择显示正确的记录器。
现在,让我们使用 JavaScript 实现仅显示用户选择的记录器的逻辑——
const mediaSelector = document.getElementById("media");
let selectedMedia = null;
// Handler function to handle the "change" event
// when the user selects some option
mediaSelector.addEventListener("change", (e) => {
selectedMedia = e.target.value;
document.getElementById(
`${selectedMedia}-recorder`).style.display = "block";
document.getElementById(
`${otherRecorder(selectedMedia)}-recorder`)
.style.display = "none";
});
function otherRecorder(selectedMedia) {
return selectedMedia === "vid" ? "aud" : "vid";
}
|
输出:当用户选择“视频”时,会显示以下录像机——
同样,当用户选择“音频”选项时,录音机会显示——
上面的代码只显示用户选择的记录器,即音频或视频。我们为 mediaSelector 元素添加了一个“change”事件监听器,当 select 元素的值发生变化时,它会发出一个“change”事件,该事件由给定的回调函数处理。回调函数将所选媒体记录器的 CSS “display”属性更改为“block”,将其他媒体记录器更改为“none”。
访问网络摄像头和麦克风: WebRTC getUserMedia API 允许您访问设备摄像头和麦克风。getUserMedia() 方法返回一个 Promise,它根据给定的规范解析为包含媒体内容(媒体轨道流)的 MediaStream。getUserMedia() 方法采用 MediaStreamConstraints 对象作为参数,该对象定义结果媒体流应匹配的所有约束。
const mediaStreamConstraints = {
audio: true,
video: true
};
// The above MediaStreamConstraints object
// specifies that the resulting media must have
// both the video and audio media content or tracks.
// The mediaStreamConstraints object is passed to
// the getUserMedia method
navigator.mediaDevices.getUserMedia( MediaStreamConstraints )
.then( resultingMediaStream => {
// Code to use the received media stream
});
当调用 getUserMedia 方法时,浏览器会提示用户请求使用设备摄像头和麦克风的权限。如果用户允许,则 getUserMedia 返回的承诺将解析为结果媒体流,否则将引发NotAllowedError异常。在上面的代码中,接收到的媒体流包含视频和音频媒体数据。
因此,将以下代码行添加到 index.js 文件中:
const mediaSelector =
document.getElementById("media");
// Added code
const webCamContainer = document
.getElementById('web-cam-container');
let selectedMedia = null;
/* Previous code
...
Added code */
const audioMediaConstraints = {
audio: true,
video: false
};
const videoMediaConstraints = {
// or you can set audio to false
// to record only video
audio: true,
video: true
};
function startRecording(thisButton, otherButton) {
navigator.mediaDevices.getUserMedia(
selectedMedia === "vid" ?
videoMediaConstraints :
audioMediaConstraints)
.then(mediaStream => {
// Use the mediaStream in
// your application
// Make the mediaStream global
window.mediaStream = mediaStream;
if (selectedMedia === 'vid') {
// Remember to use the "srcObject"
// attribute since the "src" attribute
// doesn't support media stream as a value
webCamContainer.srcObject = mediaStream;
}
document.getElementById(
`${selectedMedia}-record-status`)
.innerText = "Recording";
thisButton.disabled = true;
otherButton.disabled = false;
});
}
function stopRecording(thisButton, otherButton) {
// Stop all the tracks in the received
// media stream i.e. close the camera
// and microphone
window.mediaStream.getTracks().forEach(track => {
track.stop();
});
document.getElementById(
`${selectedMedia}-record-status`)
.innerText = "Recording done!";
thisButton.disabled = true;
otherButton.disabled = false;
}
|
所述的startRecording函数调用navigator.mediaDevices.getUserMedia()方法来访问设备的摄像头和麦克风,禁用“开始记录”键,并使得“停止记录”按钮。而stopRecording函数通过调用媒体流使用的每个媒体轨道的“stop()”方法关闭摄像头和麦克风,禁用“停止录制”按钮,并启用“开始录制”按钮。
实现录音机:到目前为止,我们只访问了网络摄像头和麦克风,但没有做任何事情来录制媒体。
要录制媒体流,我们首先需要使用 MediaRecorder 构造函数创建 MediaRecorder 的实例(用于录制媒体流的接口)。
MediaRecorder 构造函数有两个参数——
句法:
const mediaRecorder = new MediaRecorder(
stream, { mimeType: "audio/webm" });
上面的代码行创建了一个新的 MediaRecorder 实例,用于记录给定的流并将其存储为音频 WebM 文件。
所以,修改你的 index.js 文件:
/* Previous code
... */
function startRecording(thisButton, otherButton) {
navigator.mediaDevices.getUserMedia(
selectedMedia === "vid" ?
videoMediaConstraints :
audioMediaConstraints)
.then(mediaStream => {
/* New code */
// Create a new MediaRecorder
// instance that records the
// received mediaStream
const mediaRecorder =
new MediaRecorder(mediaStream);
// Make the mediaStream global
window.mediaStream = mediaStream;
// Make the mediaRecorder global
// New line of code
window.mediaRecorder = mediaRecorder;
if (selectedMedia === 'vid') {
// Remember to use the srcObject
// attribute since the src attribute
// doesn't support media stream as a value
webCamContainer.srcObject = mediaStream;
}
document.getElementById(
`${selectedMedia}-record-status`)
.innerText = "Recording";
thisButton.disabled = true;
otherButton.disabled = false;
});
}
/* Remaining code
...*/
|
当startRecording() 函数被调用时,它会创建一个 MediaRecorder 实例来记录接收到的 mediaStream。现在,我们需要使用创建的 MediaRecorder 实例。MediaRecorder 提供了一些我们可以在这里使用的有用方法——
同样,MediaRecorder 也提供了一些有用的 Event Handlers ——
mediaRecorder.ondataavailable = ( event ) => { const recordedData = event.data; }
mediaRecorder.onerror = ( event ) => {
console.log(event.error);
}
现在,我们需要使用其中一些方法和事件处理程序来使我们的项目工作。
const mediaSelector = document.getElementById("media");
const webCamContainer =
document.getElementById("web-cam-container");
let selectedMedia = null;
// This array stores the recorded media data
let chunks = [];
// Handler function to handle the "change" event
// when the user selects some option
mediaSelector.addEventListener("change", (e) => {
// Takes the current value of the mediaSeletor
selectedMedia = e.target.value;
document.getElementById(
`${selectedMedia}-recorder`)
.style.display = "block";
document.getElementById(
`${otherRecorderContainer(
selectedMedia)}-recorder`)
.style.display = "none";
});
function otherRecorderContainer(
selectedMedia) {
return selectedMedia === "vid" ?
"aud" : "vid";
}
// This constraints object tells
// the browser to include only
// the audio Media Track
const audioMediaConstraints = {
audio: true,
video: false,
};
// This constraints object tells
// the browser to include
// both the audio and video
// Media Tracks
const videoMediaConstraints = {
// or you can set audio to
// false to record
// only video
audio: true,
video: true,
};
// When the user clicks the "Start
// Recording" button this function
// gets invoked
function startRecording(
thisButton, otherButton) {
// Access the camera and microphone
navigator.mediaDevices.getUserMedia(
selectedMedia === "vid" ?
videoMediaConstraints :
audioMediaConstraints)
.then((mediaStream) => {
// Create a new MediaRecorder instance
const mediaRecorder =
new MediaRecorder(mediaStream);
//Make the mediaStream global
window.mediaStream = mediaStream;
//Make the mediaRecorder global
window.mediaRecorder = mediaRecorder;
mediaRecorder.start();
// Whenever (here when the recorder
// stops recording) data is available
// the MediaRecorder emits a "dataavailable"
// event with the recorded media data.
mediaRecorder.ondataavailable = (e) => {
// Push the recorded media data to
// the chunks array
chunks.push(e.data);
};
// When the MediaRecorder stops
// recording, it emits "stop"
// event
mediaRecorder.onstop = () => {
/* A Blob is a File like object.
In fact, the File interface is
based on Blob. File inherits the
Blob interface and expands it to
support the files on the user's
systemThe Blob constructor takes
the chunk of media data as the
first parameter and constructs
a Blob of the type given as the
second parameter*/
const blob = new Blob(
chunks, {
type: selectedMedia === "vid" ?
"video/mp4" : "audio/mpeg"
});
chunks = [];
// Create a video or audio element
// that stores the recorded media
const recordedMedia = document.createElement(
selectedMedia === "vid" ? "video" : "audio");
recordedMedia.controls = true;
// You can not directly set the blob as
// the source of the video or audio element
// Instead, you need to create a URL for blob
// using URL.createObjectURL() method.
const recordedMediaURL = URL.createObjectURL(blob);
// Now you can use the created URL as the
// source of the video or audio element
recordedMedia.src = recordedMediaURL;
// Create a download button that lets the
// user download the recorded media
const downloadButton = document.createElement("a");
// Set the download attribute to true so that
// when the user clicks the link the recorded
// media is automatically gets downloaded.
downloadButton.download = "Recorded-Media";
downloadButton.href = recordedMediaURL;
downloadButton.innerText = "Download it!";
downloadButton.onclick = () => {
/* After download revoke the created URL
using URL.revokeObjectURL() method to
avoid possible memory leak. Though,
the browser automatically revokes the
created URL when the document is unloaded,
but still it is good to revoke the created
URLs */
URL.revokeObjectURL(recordedMedia);
};
document.getElementById(
`${selectedMedia}-recorder`).append(
recordedMedia, downloadButton);
};
if (selectedMedia === "vid") {
// Remember to use the srcObject
// attribute since the src attribute
// doesn't support media stream as a value
webCamContainer.srcObject = mediaStream;
}
document.getElementById(
`${selectedMedia}-record-status`)
.innerText = "Recording";
thisButton.disabled = true;
otherButton.disabled = false;
});
}
function stopRecording(thisButton, otherButton) {
// Stop the recording
window.mediaRecorder.stop();
// Stop all the tracks in the
// received media stream
window.mediaStream.getTracks()
.forEach((track) => {
track.stop();
});
document.getElementById(
`${selectedMedia}-record-status`)
.innerText = "Recording done!";
thisButton.disabled = true;
otherButton.disabled = false;
}
|
输出:
假设,用户选择录音机——
现在,如果用户点击开始录制按钮,那么——
当点击“停止录制”按钮时——
它显示录制的音频并提供下载录制音频的链接。
那么,startRecording() 函数有什么作用呢?
window.mediaStream = mediaStream;
window.mediaRecorder = mediaRecorder;
mediaRecorder.start();
stopRecording() 函数有什么作用?