昨天我們用 MediaDevices 打開麥克風/攝影機,並用 MediaRecorder 把「裝置來源」(ex: 麥克風/攝影機)的聲音錄下來。
今天把場景從「裝置」換成「顯示面」:用 getDisplayMedia 讓使用者選擇 全螢幕/視窗/分頁,再交給 MediaRecorder 錄製成影片。換句話說,我們要做的就是——「錄製我的螢幕」(畫面+可用時的來源音)。 💻 ⏺️ 🎙️
getDisplayMedia:讓使用者從「整個螢幕、單一視窗或單一分頁」中選一個顯示面,並回傳一條 MediaStream。
這條串流一定包含畫面(video track),而是否包含音訊(audio track) 則視瀏覽器與來源而定(例如「分頁」常可勾選分享分頁音訊;「整個螢幕」未必能拿到系統音)。
MediaRecorder:把任何 MediaStream 編碼成一段段 Blob chunks,最後組成檔案(可下載或上傳)。
兩者配合:用 getDisplayMedia()
產生的串流 → 交給 MediaRecorder
→ 就得到「錄製我的螢幕」!🎉
// 取得螢幕串流(一定有 video;是否有 audio 取決於來源/瀏覽器)
const displayStream = await navigator.mediaDevices.getDisplayMedia({
// 目標幀率(每秒影格數)為 30。全程顯示游標
video: { frameRate: 30, cursor: 'always' },
// 視瀏覽器與選擇來源而定,可能拿到分頁音訊或無音訊
audio: true,
});
// 使用者在瀏覽器 UI 中按「停止共用」→ video track 會觸發 ended
const [vTrack] = displayStream.getVideoTracks();
vTrack.addEventListener('ended', () => {
stopShare();
});
// 程式主動關閉分享(唯一做法:把所有 track 停掉)
function stopShare() {
// 停止 video/audio,釋放資源
displayStream.getTracks().forEach(t => t.stop());
preview.srcObject = null;
}
video track
會觸發 ended
。// 建立錄影器,告訴瀏覽器用什麼容器/編碼輸出
const recorder = new MediaRecorder(stream, { mimeType: someMime });
// 每次可用資料就會觸發一次 dataavailable
recorder.ondataavailable = (e) => chunks.push(e.data);
// onstop 會在 recorder.stop() 之後被呼叫
recorder.onstop = () => {
const blob = new Blob(chunks, { type: recorder.mimeType });
const url = URL.createObjectURL(blob);
// 下載 / 上傳 / 預覽
};
// 每秒丟一個 dataavailable,分段輸出
recorder.start(1000);
mimeType
需以 MediaRecorder.isTypeSupported()
探測,常見優先序:
video/webm;codecs=vp9,opus
video/webm;codecs=vp8,opus
video/webm
video/mp4
(部分瀏覽器)start(timeslice)
會週期性觸發 dataavailable
;有些瀏覽器只在 stop()
時吐最後一包。
最小可用範例,能錄畫面,若瀏覽器允許也會帶到來源音訊。
<button id="btnStart">Start</button>
<button id="btnStop" disabled>Stop</button>
<video id="preview" playsinline muted autoplay></video>
<script type="module">
const $ = s => document.querySelector(s);
// 介面元素:開始/停止按鈕與預覽視窗
const btnStart = $('#btnStart');
const btnStop = $('#btnStop');
const video = $('#preview');
// 錄製相關的狀態:MediaRecorder、資料片段、螢幕串流
let rec, chunks = [], stream;
// 點「Start」→ 取得螢幕串流並開始錄製
btnStart.onclick = async () => {
btnStart.disabled = true; // 避免重複點擊
stream = await navigator.mediaDevices.getDisplayMedia({
video: true, // 讓瀏覽器挑可行的解析度/幀率
audio: true // 若來源支援(如分頁)則帶到來源音
});
video.srcObject = stream; // 讓使用者看到自己正在分享的畫面
chunks = []; // 每次開始錄都重置片段
rec = new MediaRecorder(stream); // 讓瀏覽器自動挑選可用的 mimeType
rec.ondataavailable = (e) => {
if (e.data.size) chunks.push(e.data); // 收集每段資料(有些瀏覽器只在 stop 時吐最後一段)
};
rec.onstop = () => {
// 組成最終 Blob 並下載
const blob = new Blob(chunks, { type: rec.mimeType || 'video/webm' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `screen-${Date.now()}.webm`; // 用時間戳命名
a.click();
setTimeout(() => URL.revokeObjectURL(url), 10_000); // 稍後回收 URL
};
rec.start(); // 最簡:不分段(timeslice),錄到 stop 為止
btnStop.disabled = false; // 啟用「Stop」
};
// 點「Stop」→ 停止錄製與來源,清理預覽與按鈕狀態
btnStop.onclick = () => {
rec?.stop(); // 觸發 onstop → 產生檔案
stream?.getTracks().forEach(t => t.stop()); // 停止螢幕分享(視訊/音訊軌)
video.srcObject = null; // 關閉預覽
btnStart.disabled = false;
btnStop.disabled = true;
};
</script>
多數瀏覽器在
MediaRecorder
只會採用 一條音軌。若你同時想錄到 螢幕來源音(例如分頁播放的影片聲)+ 麥克風旁白,最穩的方式是 用 Web Audio 把兩條音源混成一條,再與影片軌合流。
<!-- 最小可用:單鍵「螢幕 + 麥克風」混音錄影 -->
<button id="btn">Record Screen + Mic</button>
<video id="v" playsinline muted autoplay></video>
<a id="dl"></a>
<script type="module">
const btn = document.getElementById('btn');
const v = document.getElementById('v');
const dl = document.getElementById('dl');
// 這些會在開始/停止之間被重複使用
let rec, chunks = [];
let displayStream, micStream, mixedStream;
let audioCtx, destNode;
btn.onclick = async () => {
// 若未錄或已停止 → 開始;否則 → 停止
if (!rec || rec.state === 'inactive') {
// 1) 取得螢幕來源(可能含分頁/系統音,視瀏覽器/來源而定)
displayStream = await navigator.mediaDevices.getDisplayMedia({
video: true,
audio: true
});
// 2) 取得麥克風(開常見的語音處理有助降噪/回授)
micStream = await navigator.mediaDevices.getUserMedia({
audio: { echoCancellation: true, noiseSuppression: true, autoGainControl: true }
});
// 預覽畫面(靜音避免回授)
v.srcObject = displayStream;
// 3) 用 Web Audio 把「來源音」與「麥克風」混成**單一音軌**
const AC = window.AudioContext || window.webkitAudioContext;
audioCtx = new AC();
destNode = audioCtx.createMediaStreamDestination();
const add = (track) => {
if (!track) return; // 有些情況螢幕來源可能沒有音軌
const src = audioCtx.createMediaStreamSource(new MediaStream([track]));
src.connect(destNode);
};
add(displayStream.getAudioTracks()[0]);
add(micStream.getAudioTracks()[0]);
// 4) 合成「影片軌 + 單一混音軌」為新的串流
mixedStream = new MediaStream([
...displayStream.getVideoTracks(),
...destNode.stream.getAudioTracks()
]);
// 5) 交給 MediaRecorder 錄製(最簡:不分段)
chunks = [];
rec = new MediaRecorder(mixedStream); // 讓瀏覽器自選可用格式
rec.ondataavailable = (e) => e.data.size && chunks.push(e.data);
rec.onstop = () => {
// 組檔並提供下載
const type = rec.mimeType || (chunks[0] && chunks[0].type) || 'video/webm';
const blob = new Blob(chunks, { type });
const url = URL.createObjectURL(blob);
dl.href = url;
dl.download = `screen-mix-${Date.now()}.webm`;
dl.textContent = '下載錄影檔';
// 稍後回收 URL
setTimeout(() => URL.revokeObjectURL(url), 60_000);
// 完整清理
cleanup();
};
// 使用者若在瀏覽器 UI 停止分享,也要跟著停止錄影
const [vTrack] = mixedStream.getVideoTracks();
vTrack.addEventListener('ended', () => {
if (rec && rec.state !== 'inactive') rec.stop();
});
rec.start();
btn.textContent = 'Stop';
} else {
// 結束錄製(會觸發 onstop → 組檔下載)
rec.stop();
}
};
function cleanup() {
displayStream?.getTracks().forEach(t => t.stop());
micStream?.getTracks().forEach(t => t.stop());
v.srcObject = null;
// 關掉音訊處理
try { audioCtx?.close(); } catch {}
audioCtx = null; destNode = null; mixedStream = null;
displayStream = null; micStream = null; rec = null; chunks = [];
btn.textContent = 'Record Screen + Mic';
}
</script>
注意:
getDisplayMedia
是否含「來源音」取決於瀏覽器與你選的來源(整個螢幕 / 視窗 / 分頁)。「分頁」通常較容易錄到分頁音;「整個螢幕 / 視窗」不一定能拿到「系統音」。getDisplayMedia
會被擋。<video>
建議 muted
,避免「螢幕分頁音 → 預覽播放 → 再被抓到」造成回授。MediaRecorder.isTypeSupported()
探測;WebM 是網頁上最常見的輸出。部分瀏覽器支援 MP4(H.264/AAC),但跨瀏覽器一致性較差。stop()
時才給最後一塊。MediaRecorder
只吃到第一條音軌;因此錄「來源音 + 麥克風」要先用 Web Audio 合併。快速對照:
Day 10 錄的是「裝置」(ex: mic/cam,getUserMedia
);Day 11 錄的是「顯示面」(整個螢幕 / 視窗 / 分頁,getDisplayMedia
)。
兩者都把拿到的 MediaStream 交給 MediaRecorder,就能變成檔案。
只錄音(語音備忘/Podcast 片段) 🎙️getUserMedia({ audio: true })
→ MediaRecorder
最簡單、最穩。
錄螢幕(可含來源音)(產品 Demo/投影片講解) 💻 ⏺️ 🎙️getDisplayMedia({ video: true, audio: true })
→ MediaRecorder
來源若是「分頁」通常較容易帶到分頁音。
錄螢幕 + 麥克風旁白(教學影片/操作解說) 💻 ⏺️ 🎙️ 🎤getDisplayMedia(...)
+ getUserMedia({ audio: true })
→ Web Audio 混成單一音軌 → MediaRecorder
避免多音軌相容性差,跨瀏覽器更穩。
一句話總結:
getUserMedia
給你裝置來源,getDisplayMedia
給你螢幕來源;把「來源」交給MediaRecorder
,就得到檔案。
上面用簡單程式碼示範了「螢幕擷取 + 錄影」的基本流程。
想直接體驗完整互動,請看這個線上範例(本文截圖也來自這裡):
👉 歡迎追蹤這個系列,我會從 Canvas 開始,一步步帶你認識更多 Web API 🎯