Skip to content

Instantly share code, notes, and snippets.

@gmotzespina
Last active August 26, 2025 15:13
Show Gist options
  • Select an option

  • Save gmotzespina/875cdae47969919156a3a94281ff1171 to your computer and use it in GitHub Desktop.

Select an option

Save gmotzespina/875cdae47969919156a3a94281ff1171 to your computer and use it in GitHub Desktop.
Screen Recorder
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