Last active
August 26, 2025 15:13
-
-
Save gmotzespina/875cdae47969919156a3a94281ff1171 to your computer and use it in GitHub Desktop.
Screen Recorder
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
| import { | |
| AddStreamOptions, | |
| ConstructorOptions, | |
| VideoStreamMerger | |
| } from "video-stream-merger"; | |
| import { RecordRTCPromisesHandler } from "recordrtc"; | |
| import { ScreenShare } from "@components/ScreenShareOption"; | |
| // This just renames it to StreamSettings because it gives you a better idea of what it is. | |
| // eslint-disable-next-line @typescript-eslint/no-empty-interface | |
| interface StreamSettings extends ConstructorOptions {} | |
| const STREAM_SETTINGS: StreamSettings = { | |
| width: 1920, | |
| height: 1080, | |
| fps: 30, | |
| clearRect: true, | |
| audioContext: new AudioContext() | |
| }; | |
| const WEBCAM_STREAM_SIZE = { | |
| width: 320 * 1.2, | |
| height: 185 * 1.2 | |
| }; | |
| const WEBCAM_STREAM_SETTINGS: AddStreamOptions = { | |
| index: 0, | |
| x: STREAM_SETTINGS.width - WEBCAM_STREAM_SIZE.width, | |
| y: STREAM_SETTINGS.height - WEBCAM_STREAM_SIZE.height, | |
| width: WEBCAM_STREAM_SIZE.width, | |
| height: WEBCAM_STREAM_SIZE.height, | |
| mute: false, | |
| muted: false, | |
| draw: (ctx, frame, done) => { | |
| const x = STREAM_SETTINGS.width - WEBCAM_STREAM_SIZE.width; | |
| const y = STREAM_SETTINGS.height - WEBCAM_STREAM_SIZE.height; | |
| ctx.save(); | |
| ctx.beginPath(); | |
| ctx.arc( | |
| x + WEBCAM_STREAM_SIZE.width / 2, | |
| y + WEBCAM_STREAM_SIZE.height / 2, | |
| WEBCAM_STREAM_SIZE.height / 2, | |
| 0, | |
| Math.PI * 2, | |
| true | |
| ); | |
| ctx.closePath(); | |
| ctx.clip(); | |
| ctx.drawImage( | |
| frame, | |
| x, | |
| y, | |
| WEBCAM_STREAM_SIZE.width, | |
| WEBCAM_STREAM_SIZE.height | |
| ); | |
| ctx.restore(); | |
| done(); | |
| }, | |
| // eslint-disable-next-line @typescript-eslint/no-empty-function | |
| audioEffect: (sourceNode, destinationNode) => {} | |
| }; | |
| const SCREEN_SHARE_STREAM_SETTINGS: Pick< | |
| AddStreamOptions, | |
| "index" | "x" | "y" | "width" | "height" | "mute" | "muted" | |
| > = { | |
| index: 0, | |
| x: 0, | |
| y: 0, | |
| width: STREAM_SETTINGS.width, | |
| height: STREAM_SETTINGS.height, | |
| mute: true, | |
| muted: true | |
| }; | |
| export function stopScreenShare(stream?: MediaStream): void { | |
| stream?.getTracks().map((track) => track.stop()); | |
| } | |
| export function initializeRercording( | |
| stream: MediaStream | |
| ): RecordRTCPromisesHandler { | |
| const recorder = new RecordRTCPromisesHandler(stream, { | |
| mimeType: "video/mp4", | |
| type: "video" | |
| }); | |
| recorder.startRecording(); | |
| return recorder; | |
| } | |
| export function isSharingScreen(selectedScreenShareOption: ScreenShare) { | |
| return selectedScreenShareOption !== ScreenShare.NO_SCREEN; | |
| } | |
| export function mergeScreenAndWebcamStreams( | |
| screenStream: MediaStream, | |
| webcamStream: MediaStream | |
| ): MediaStream { | |
| const merger: VideoStreamMerger = new VideoStreamMerger(STREAM_SETTINGS); | |
| // Add the screen capture. Position it to fill the whole stream (default). | |
| merger.addStream( | |
| screenStream, | |
| SCREEN_SHARE_STREAM_SETTINGS as AddStreamOptions // We don't need to implement the draw and audioEffect functions for the screen share. | |
| ); | |
| // Add the webcam stream. Position it on the bottom left and resize it to 100x100. | |
| merger.addStream(webcamStream, WEBCAM_STREAM_SETTINGS); | |
| // Start the merging. Calling this makes the result available. | |
| merger.start(); | |
| // We now have a merged MediaStream! | |
| const result = merger.result; | |
| if (!result) { | |
| throw new Error("Streams could not be merged"); | |
| } | |
| return result; | |
| } | |
| export async function getVideoDevices(): Promise<MediaDeviceInfo[]> { | |
| const mediaDevices = await navigator.mediaDevices.enumerateDevices(); | |
| return mediaDevices.filter((device) => device.kind === "videoinput"); | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment