Last active
April 21, 2025 00:51
-
-
Save curegit/b6c84eff634b84708a3bbeb5a7c72d07 to your computer and use it in GitHub Desktop.
Download a video via Media Capture and Streams API
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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