監(jiān)聽開始事件
- EventTarget.addEventListener() 方法將指定的監(jiān)聽器注冊到 EventTarget 上,當該對象觸發(fā)指定的事件時,指定的回調函數(shù)就會被執(zhí)行。 事件目標可以是一個文檔上的元素 Element,Document和Window或者任何其他支持事件的對象 (比如 XMLHttpRequest)。
- addEventListener()的工作原理是將實現(xiàn)EventListener的函數(shù)或對象添加到調用它的EventTarget上的指定事件類型的事件偵聽器列表中。
document.querySelector('button#start').addEventListener('click', async () => {
document.querySelector('button#start').disabled = true;
const constraints = {
audio: {},
video: {
width: 1280, height: 720
}
};
await init(constraints);
});
獲取音視頻軌道
- MediaDevices.getUserMedia() 會提示用戶給予使用媒體輸入的許可,媒體輸入會產(chǎn)生一個MediaStream,里面包含了請求的媒體類型的軌道。此流可以包含一個視頻軌道(來自硬件或者虛擬視頻源,比如相機、視頻采集設備和屏幕共享服務等等)、一個音頻軌道(同樣來自硬件或虛擬音頻源,比如麥克風、A/D轉換器等等),也可能是其它軌道類型。
- 它返回一個 Promise 對象,成功后會resolve回調一個 MediaStream 對象。若用戶拒絕了使用權限,或者需要的媒體源不可用,promise會reject回調一個 PermissionDeniedError 或者 NotFoundError 。
async function init(constraints) {
try {
const stream = await navigator.mediaDevices.getUserMedia(constraints);
handleSuccess(stream);
} catch (e) {
console.error('navigator.getUserMedia error:', e);
}
}
- htmlMediaElement 接口的 srcObject 屬性設定或返回一個對象,這個對象提供了一個與HTMLMediaElement關聯(lián)的媒體源,這個對象通常是 MediaStream ,但根據(jù)規(guī)范可以是 MediaSource, Blob 或者 File。
function handleSuccess(stream) {
recordButton.disabled = false;
window.stream = stream;
const gumVideo = document.querySelector('video#gum');
gumVideo.srcObject = stream;
}
錄制媒體流
- MediaRecorder() 構造函數(shù)會創(chuàng)建一個對指定的 MediaStream 進行錄制的 MediaRecorder 對象
- MediaRecorder.ondataavailable 事件處理程序API處理dataavailable事件,在響應運行代碼Blob數(shù)據(jù)被提供使用。
- dataavailable當MediaRecorder將媒體數(shù)據(jù)傳遞到您的應用程序以供使用時,將觸發(fā)該事件。數(shù)據(jù)在包含數(shù)據(jù)的Blob對象中提供。這在四種情況下發(fā)生:
- 媒體流結束時,所有尚未傳遞到ondataavailable處理程序的媒體數(shù)據(jù)都將在單個Blob中傳遞。
- 當調用MediaRecorder.stop() (en-US)時,自記錄開始或dataavailable事件最后一次發(fā)生以來已捕 獲的所有媒體數(shù)據(jù)都將傳遞到Blob中;此后,捕獲結束。
- 調用MediaRecorder.requestData() (en-US) dataavailable時,將傳遞自記錄開始或事件最后一次發(fā)生以來捕獲的所有媒體數(shù)據(jù);然后Blob創(chuàng)建一個新文件,并將媒體捕獲繼續(xù)到該blob中。
- 如果將timeslice屬性傳遞到開始媒體捕獲的MediaRecorder.start() (en-US)方法中,dataavailable則每timeslice毫秒觸發(fā)一次事件。這意味著每個Blob都有特定的持續(xù)時間(最后一個Blob除外,后者可能更短,因為它將是自上次事件以來剩下的所有東西)。
let mediaRecorder;
const recordButton = document.querySelector('button#record');
recordButton.addEventListener('click', () => {
if (recordButton.textContent === '開始記錄') {
startRecording();
} else {
stopRecording();
recordButton.textContent = '開始記錄';
playButton.disabled = false;
}
});
function startRecording() {
recordedBlobs = [];
try {
mediaRecorder = new MediaRecorder(window.stream);
} catch (e) {
console.error('創(chuàng)建MediaRecorder時異常:', e);
}
recordButton.textContent = '停止記錄';
playButton.disabled = true;
mediaRecorder.ondataavailable = handleDataAvailable;
mediaRecorder.start();
}
function stopRecording() {
mediaRecorder.stop();
}
function handleDataAvailable(event) {
if (event.data && event.data.size > 0) {
recordedBlobs.push(event.data);
}
}
播放媒體流
- URL.createObjectURL() 靜態(tài)方法會創(chuàng)建一個 DOMString,其中包含一個表示參數(shù)中給出的對象的URL。這個 URL 的生命周期和創(chuàng)建它的窗口中的 document 綁定。這個新的URL 對象表示指定的 File 對象或 Blob 對象。
let recordedBlobs;
const recordedVideo = document.querySelector('video#recorded');
const playButton = document.querySelector('button#play');
playButton.addEventListener('click', () => {
const superBuffer = new Blob(recordedBlobs, { type: 'video/webm' });
recordedVideo.src = null;
recordedVideo.srcObject = null;
recordedVideo.src = window.URL.createObjectURL(superBuffer);
recordedVideo.controls = true;
recordedVideo.play();
});
HTML
<link rel="stylesheet" href="./index.css">
<video id="gum" autoplay></video>
<video id="recorded"></video>
<div>
<button id="start">開始</button>
<button id="record" disabled>開始記錄</button>
<button id="play" disabled>Play</button>
</div>
<script src="./index.js"></script>
CSS
button {
margin: 0 3px 10px 0;
padding-left: 2px;
padding-right: 2px;
width: 99px;
}
button:last-of-type {
margin: 0;
}
video {
vertical-align: top;
--width: 25vw;
width: var(--width);
height: calc(var(--width) * 0.5625);
}
video:last-of-type {
margin: 0 0 20px 0;
}
video#gumVideo {
margin: 0 20px 20px 0;
}
JAVAScript
let mediaRecorder;
let recordedBlobs;
const recordedVideo = document.querySelector('video#recorded');
const recordButton = document.querySelector('button#record');
recordButton.addEventListener('click', () => {
if (recordButton.textContent === '開始記錄') {
startRecording();
} else {
stopRecording();
recordButton.textContent = '開始記錄';
playButton.disabled = false;
}
});
const playButton = document.querySelector('button#play');
playButton.addEventListener('click', () => {
const superBuffer = new Blob(recordedBlobs, { type: 'video/webm' });
recordedVideo.src = null;
recordedVideo.srcObject = null;
recordedVideo.src = window.URL.createObjectURL(superBuffer);
recordedVideo.controls = true;
recordedVideo.play();
});
function handleDataAvailable(event) {
if (event.data && event.data.size > 0) {
recordedBlobs.push(event.data);
}
}
function startRecording() {
recordedBlobs = [];
try {
mediaRecorder = new MediaRecorder(window.stream);
} catch (e) {
console.error('創(chuàng)建MediaRecorder時異常:', e);
}
recordButton.textContent = '停止記錄';
playButton.disabled = true;
mediaRecorder.ondataavailable = handleDataAvailable;
mediaRecorder.start();
}
function stopRecording() {
mediaRecorder.stop();
}
function handleSuccess(stream) {
recordButton.disabled = false;
window.stream = stream;
const gumVideo = document.querySelector('video#gum');
gumVideo.srcObject = stream;
}
async function init(constraints) {
try {
const stream = await navigator.mediaDevices.getUserMedia(constraints);
handleSuccess(stream);
} catch (e) {
console.error('navigator.getUserMedia error:', e);
}
}
document.querySelector('button#start').addEventListener('click', async () => {
document.querySelector('button#start').disabled = true;
const constraints = {
audio: {},
video: {
width: 1280, height: 720
}
};
await init(constraints);
});