declare global { interface HTMLCanvasElement { captureStream(frameRate?: number): MediaStream; } } function dummyTrack(): MediaStreamTrack { const canvas = document.createElement('canvas'); //, { width: 0, height: 0}) canvas.width = canvas.height = 2; // Doesn't work when 1 (?!) const ctx = canvas.getContext('2d'); ctx?.fillRect(0, 0, canvas.width, canvas.height); requestAnimationFrame(function draw() { ctx?.fillRect(0, 0, canvas.width, canvas.height); requestAnimationFrame(draw); }); // Also works. Probably it should be done once connected. //setTimeout(() => { ctx?.fillRect(0,0, canvas.width, canvas.height) }, 4000) return canvas.captureStream(60).getTracks()[0]; } export function RequestLocalStream(): Promise { return navigator.mediaDevices.getUserMedia({ audio: true }).then((aStream) => { const aTrack = aStream.getAudioTracks()[0]; if (!aTrack) { throw new Error('No audio tracks provided'); } return new _LocalStream(aTrack); }); } class _LocalStream { private mediaRequested: boolean = false; readonly stream: MediaStream; private readonly vdTrack: MediaStreamTrack; constructor(aTrack: MediaStreamTrack) { this.vdTrack = dummyTrack(); this.stream = new MediaStream([aTrack, this.vdTrack]); } toggleVideo(): Promise { if (!this.mediaRequested) { return navigator.mediaDevices .getUserMedia({ video: true }) .then((vStream) => { const vTrack = vStream.getVideoTracks()[0]; if (!vTrack) { throw new Error('No video track provided'); } this.stream.addTrack(vTrack); this.stream.removeTrack(this.vdTrack); this.mediaRequested = true; if (this.onVideoTrackCb) { this.onVideoTrackCb(vTrack); } return true; }) .catch((e) => { // TODO: log console.error(e); return false; }); } let enabled = true; this.stream.getVideoTracks().forEach((track) => { track.enabled = enabled = enabled && !track.enabled; }); return Promise.resolve(enabled); } toggleAudio(): boolean { let enabled = true; this.stream.getAudioTracks().forEach((track) => { track.enabled = enabled = enabled && !track.enabled; }); return enabled; } private onVideoTrackCb: ((t: MediaStreamTrack) => void) | null = null; onVideoTrack(cb: (t: MediaStreamTrack) => void) { this.onVideoTrackCb = cb; } stop() { this.stream.getTracks().forEach((t) => t.stop()); } } export type LocalStream = InstanceType;