feat(ui): allow to record session screen
This commit is contained in:
parent
738fc3af1e
commit
d071d6d2cd
3 changed files with 147 additions and 0 deletions
|
|
@ -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 (
|
||||
<div className="p-3">
|
||||
{/* @ts-ignore */}
|
||||
<Tooltip title="Supported browsers: Chrome v91+; Edge v90+">
|
||||
<div className="p-1 text-disabled-text cursor-not-allowed">Record</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
)
|
||||
return (
|
||||
<div onClick={toggleRecording} className="p-3">
|
||||
<div className="p-1 font-semibold cursor-pointer hover:text-main">{isRecording ? 'STOP' : 'RECORD'}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default ScreenRecorder
|
||||
91
frontend/app/utils/screenRecorder.ts
Normal file
91
frontend/app/utils/screenRecorder.ts
Normal file
|
|
@ -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
|
||||
Loading…
Add table
Reference in a new issue