Skip to content

Instantly share code, notes, and snippets.

@curegit
Last active April 21, 2025 00:51
Show Gist options
  • Select an option

  • Save curegit/b6c84eff634b84708a3bbeb5a7c72d07 to your computer and use it in GitHub Desktop.

Select an option

Save curegit/b6c84eff634b84708a3bbeb5a7c72d07 to your computer and use it in GitHub Desktop.
Download a video via Media Capture and Streams API
async function newRecorder(
target = "stream",
querySelector = "video",
frameRate = 60,
systemAudio = false,
flushMinutes = 10,
) {
let stream = null;
let beforeUnloadListener = null;
switch (target) {
/* mediaStream (video/canvas) */
case null:
case "":
case "stream":
stream = (
querySelector instanceof Element || querySelector instanceof Document
? querySelector
: document.querySelector(querySelector)
).captureStream(frameRate);
break;
/* mediaStream (desktop/tabs) */
case "display":
case "tab":
stream = await navigator.mediaDevices.getDisplayMedia({
video: true,
audio: true,
systemAudio: systemAudio ? "include" : "exclude",
});
break;
/* mediaStream (user devices) */
case "user":
case "camera":
stream = await navigator.mediaDevices.getUserMedia({
video: true,
audio: true,
});
break;
}
if (!stream) {
throw new Error("Wrong parameter or unexpected error!");
}
function initRecorder(stream) {
const mediaRecorder = new MediaRecorder(stream, {
mimeType: MediaRecorder.isTypeSupported("video/webm; codecs=vp9")
? "video/webm; codecs=vp9"
: "video/webm",
});
let currentChunks = [];
let allChunks = [currentChunks];
let flushInterval = null;
const setupBeforeUnloadHandler = () => {
beforeUnloadListener = (e) => {
e.preventDefault();
e.returnValue = "Recording is in progress. Closing the page will lose the recording.";
return e.returnValue;
};
window.addEventListener("beforeunload", beforeUnloadListener);
};
const removeBeforeUnloadHandler = () => {
if (beforeUnloadListener) {
window.removeEventListener("beforeunload", beforeUnloadListener);
beforeUnloadListener = null;
}
};
mediaRecorder.addEventListener("dataavailable", (e) => {
if (e.data.size > 0) {
currentChunks.push(e.data);
}
});
mediaRecorder.addEventListener("start", () => {
setupBeforeUnloadHandler();
if (flushMinutes > 0) {
flushInterval = setInterval(flush, flushMinutes * 60 * 1000);
}
});
mediaRecorder.addEventListener("pause", () => {
if (flushInterval) {
clearInterval(flushInterval);
flushInterval = null;
}
});
mediaRecorder.addEventListener("resume", () => {
if (flushMinutes > 0) {
flushInterval = setInterval(flush, flushMinutes * 60 * 1000);
}
});
mediaRecorder.addEventListener("stop", () => {
if (currentChunks.length > 0) {
downloadWebmChunks(currentChunks);
currentChunks = [];
allChunks.push(currentChunks);
}
if (flushInterval) {
clearInterval(flushInterval);
flushInterval = null;
}
removeBeforeUnloadHandler();
});
const pause = () => {
if (mediaRecorder.state === "recording") {
mediaRecorder.pause();
}
};
const resume = () => {
if (mediaRecorder.state === "paused") {
mediaRecorder.resume();
}
};
const split = () => {
if (mediaRecorder.state === "recording") {
mediaRecorder.stop();
mediaRecorder.start();
if (flushInterval) {
clearInterval(flushInterval);
flushInterval = null;
}
if (flushMinutes > 0) {
flushInterval = setInterval(flush, flushMinutes * 60 * 1000);
}
}
};
const flush = () => {
if (mediaRecorder.state === "recording") {
mediaRecorder.requestData();
}
};
const downloadCurrentFlushed = () => {
if (currentChunks.length > 0) {
downloadWebmChunks(currentChunks);
}
};
return {
chunks: allChunks,
mediaRecorder,
start: () => mediaRecorder.start(),
pause,
resume,
flush,
split,
stop: () => mediaRecorder.stop(),
getState: () => mediaRecorder.state,
getCurrentChunks: () => currentChunks,
downloadCurrentFlushed,
};
}
function downloadWebmChunks(chunks) {
const blob = new Blob(chunks, { type: chunks[0].type });
downloadWebm(blob);
}
function downloadWebm(blob) {
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = `video-${now()}.webm`;
a.click();
}
function now() {
const now = new Date();
const year = now.getFullYear();
const month = `${now.getMonth() + 1}`.padStart(2, "0");
const date = `${now.getDate()}`.padStart(2, "0");
const hour = `${now.getHours()}`.padStart(2, "0");
const minute = `${now.getMinutes()}`.padStart(2, "0");
return `${year}${month}${date}-${hour}${minute}`;
}
return { ...initRecorder(stream), downloadWebmChunks, downloadWebm };
}
// Usage example 1: video stream
var recorder = await newRecorder("stream", "video", 60);
recorder.start();
// Usage example 2: screen sharing
var recorder = await newRecorder("display");
recorder.start();
// when you want to stop recording
recorder.stop();
// when you want to start new segment (very small gap between segments may occur)
recorder.split();
// when you want to pause recording
recorder.pause();
// when you want to resume recording
recorder.resume();
// when you want to devide internal chunks
recorder.flush();
// when you want to download current already flushed chunks
recorder.downloadCurrentFlushed();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment