// Static factory methods for common media sources. // // All methods return a MediaStream so they can be passed directly to // StreamManager.addStream(label, stream). export const MediaSources = { // Single camera (video only by default). async camera(constraints = {}) { return navigator.mediaDevices.getUserMedia({ video: constraints.video ?? true, audio: constraints.audio ?? false }); }, // Microphone (audio only). async microphone(constraints = {}) { return navigator.mediaDevices.getUserMedia({ audio: constraints.audio ?? true, video: false }); }, // Camera + microphone together. async cameraAndMic(constraints = {}) { return navigator.mediaDevices.getUserMedia({ video: constraints.video ?? true, audio: constraints.audio ?? true }); }, // Screen / window / tab sharing via getDisplayMedia. async screen(constraints = {}) { return navigator.mediaDevices.getDisplayMedia({ video: constraints.video ?? { cursor: 'always' }, audio: constraints.audio ?? false }); }, // Enumerate available media devices. async devices() { const all = await navigator.mediaDevices.enumerateDevices(); return { cameras: all.filter(d => d.kind === 'videoinput'), microphones: all.filter(d => d.kind === 'audioinput'), speakers: all.filter(d => d.kind === 'audiooutput') }; }, // Play a pre-decoded AudioBuffer as a continuous MediaStream. // Useful for replacing the live microphone with an audio file. // Returns the MediaStream; the AudioContext is attached as ._ctx for cleanup. fromAudioBuffer(audioBuffer, { loop = false } = {}) { const ctx = new AudioContext(); const src = ctx.createBufferSource(); src.buffer = audioBuffer; src.loop = loop; const dest = ctx.createMediaStreamDestination(); src.connect(dest); src.start(); const stream = dest.stream; stream._ctx = ctx; // caller can close() to free resources stream._src = src; return stream; }, // Capture a or OffscreenCanvas as a video MediaStream. fromCanvas(canvas, fps = 30) { if (typeof canvas.captureStream === 'function') { return canvas.captureStream(fps); } if (typeof canvas.transferControlToOffscreen === 'function') { throw new Error( 'fromCanvas: pass the OffscreenCanvas, not the original canvas, ' + 'or use fromOffscreenCanvas()' ); } throw new Error('fromCanvas: captureStream() not supported on this element'); }, // Create an AudioContext-based mixing bus and capture its output as a // MediaStream. Useful for compositing multiple audio sources before sending. // Returns { ctx, dest, stream } — add nodes to ctx and connect to dest. createAudioMix() { const ctx = new AudioContext(); const dest = ctx.createMediaStreamDestination(); return { ctx, dest, stream: dest.stream }; }, // Fetch and decode an audio file URL into an AudioBuffer. async loadAudioFile(url) { const ctx = new AudioContext(); const resp = await fetch(url); const ab = await resp.arrayBuffer(); const buf = await ctx.decodeAudioData(ab); await ctx.close(); return buf; // pass to fromAudioBuffer() } };