99 lines
3.5 KiB
JavaScript
99 lines
3.5 KiB
JavaScript
// 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 <canvas> 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()
|
|
}
|
|
};
|