diff --git a/frontend/app/components/Session_/ScreenRecorder/ScreenRecorder.tsx b/frontend/app/components/Session_/ScreenRecorder/ScreenRecorder.tsx new file mode 100644 index 000000000..9f8d239fb --- /dev/null +++ b/frontend/app/components/Session_/ScreenRecorder/ScreenRecorder.tsx @@ -0,0 +1,56 @@ +import React from 'react'; +import { screenRecorder } from 'App/utils/screenRecorder'; +import { Tooltip } from 'react-tippy' + +let stopRecorderCb: () => void + +/** + * "edge" || "edg/" chromium based edge (dev or canary) + * "chrome" && window.chrome chrome + * "opr" && (!!window.opr || !!window.opera) opera + * "trident" ie + * "firefox" firefox + * "safari" safari + */ +function isSupported() { + const agent = window.navigator.userAgent.toLowerCase() + + if (agent.includes("edge") || agent.includes("edg/")) return true + // @ts-ignore + if (agent.includes("chrome") && !!window.chrome) return true + + return false +} + +function ScreenRecorder() { + const [isRecording, setRecording] = React.useState(false); + + const toggleRecording = async () => { + console.log(isRecording); + if (isRecording) { + stopRecorderCb?.(); + setRecording(false); + } else { + const stop = await screenRecorder(); + stopRecorderCb = stop; + setRecording(true); + } + }; + + const isSupportedBrowser = isSupported() + if (!isSupportedBrowser) return ( +
+ {/* @ts-ignore */} + +
Record
+
+
+ ) + return ( +
+
{isRecording ? 'STOP' : 'RECORD'}
+
+ ); +} + +export default ScreenRecorder diff --git a/frontend/app/utils.ts b/frontend/app/utils/index.ts similarity index 100% rename from frontend/app/utils.ts rename to frontend/app/utils/index.ts diff --git a/frontend/app/utils/screenRecorder.ts b/frontend/app/utils/screenRecorder.ts new file mode 100644 index 000000000..47acf6bca --- /dev/null +++ b/frontend/app/utils/screenRecorder.ts @@ -0,0 +1,91 @@ +const FILE_TYPE = 'video/webm'; +const FRAME_RATE = 30; + +function createFileRecorder (stream: MediaStream, mimeType: string) { + let ended = false; + let recordedChunks: BlobPart[] = []; + const SAVE_INTERVAL_MS = 200; + const mediaRecorder = new MediaRecorder(stream); + + mediaRecorder.ondataavailable = function (e) { + if (e.data.size > 0) { + recordedChunks.push(e.data); + } + }; + + function onEnd() { + if (ended) return; + + ended = true; + saveFile(recordedChunks, mimeType); + recordedChunks = []; + }; + + // sometimes we get race condition or the onstop won't trigger at all, + // this is why we have to make it twice to make sure that stream is saved + // plus we want to be able to handle both, native and custom button clicks + mediaRecorder.stream.getTracks().forEach(track => track.onended = onEnd); + mediaRecorder.onstop = () => { + onEnd(); + mediaRecorder.stream.getTracks().forEach(track => track.stop()); + } + mediaRecorder.start(SAVE_INTERVAL_MS); + + return mediaRecorder; +} + +function saveFile(recordedChunks: BlobPart[], mimeType: string) { + const blob = new Blob(recordedChunks, { + type: mimeType, + }); + const filename = +new Date() + '.' + mimeType.split('/')[1]; + const downloadLink = document.createElement('a'); + downloadLink.href = URL.createObjectURL(blob); + downloadLink.download = filename; + + document.body.appendChild(downloadLink); + downloadLink.click(); + + URL.revokeObjectURL(downloadLink.href); // clear from memory + document.body.removeChild(downloadLink); +} + +async function recordScreen() { + return await navigator.mediaDevices.getDisplayMedia({ + audio: true, + video: { frameRate: FRAME_RATE }, + // potential chrome hack + // @ts-ignore + preferCurrentTab: true, + }); +}; + +/** +* Creates a screen recorder that sends the media stream to MediaRecorder +* which then saves the stream to a file. +* +* Supported Browsers: +* +* Windows: Chrome v91+, Edge v90+ - FULL SUPPORT; +* *Nix: Chrome v91+, Edge v90+ - LIMITED SUPPORT - (audio only captured from current tab) +* +* @returns a promise that resolves to a function that stops the recording +*/ +export async function screenRecorder() { + try { + const stream = await recordScreen(); + const mediaRecorder = createFileRecorder(stream, FILE_TYPE); + + return () => mediaRecorder.stop(); + } catch (e) { + console.log(e) + } +} + +// @ts-ignore +window.recordSceenSample = screenRecorder; + +// NOT SUPPORTED: +// macOS: chrome and edge only support capturing current tab's audio +// windows: chrome and edge supports all audio +// other: not supported