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 default 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 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