const video = document.createElement("video");
video.src = URL.createObjectURL(file);
video.loop = true;
video.muted = true;
const handle = call.camera.registerVirtualDevice({
label: file.name,
getUserMedia: async (constraints: MediaTrackConstraints) => {
await video.play();
return {
// @ts-expect-error `captureStream()` is not present in the standard TypeScript DOM typings.
stream: video.captureStream(),
stop: () => {
video.pause();
video.currentTime = 0;
},
};
},
});Virtual devices
Register any MediaStream as a camera or microphone using call.camera.registerVirtualDevice() or call.microphone.registerVirtualDevice(). The SDK lists the virtual device in listDevices(), lets the user select it like any real device, and publishes the stream you provide.
Common use cases:
- Publish a pre-recorded video file, a canvas, or a
<video>element as a camera. - Publish synthetic or mixed audio as a microphone. For example, Web Audio output, a tone generator, or music played alongside the user's voice.
- Replace the device with a processed stream you produce yourself, when the built-in Video & Audio filters pipeline is not a fit.
To transform the live camera or microphone (background blur, noise reduction, color grading), use Video & Audio filters instead. Virtual devices replace the source; filters transform it.
Register a virtual device
Pass a label and a getUserMedia function that receives the resolved constraints the SDK would otherwise pass to a real device, then returns the MediaStream to publish plus an optional stop callback that runs when the SDK releases the stream:
For cameras, constraints can include values such as width, height, and facingMode. For microphones, it can include audio constraints such as sampleRate or channelCount. Your virtual device can honor these values or ignore them.
In the example above, HTMLMediaElement.captureStream() is convenient for turning a local <video> element into a virtual camera, but it is not available in every browser. Check MDN documentation for HTMLMediaElement.captureStream() for compatibility and provide a fallback code path when needed
The same API exists on call.microphone for audio sources. Return a stream with at least one audio track.
Select the device
Pass handle.deviceId to select() to switch to the virtual device:
await call.camera.select(handle.deviceId);Virtual devices also appear in call.camera.listDevices() (and the useCameraState hook), so they show up in any device picker UI you build. Their deviceId starts with the VIRTUAL_DEVICE_PREFIX constant exported from @stream-io/video-client, which you can use to render virtual entries differently:
import { VIRTUAL_DEVICE_PREFIX } from "@stream-io/video-client";
const isVirtual = device.deviceId.startsWith(VIRTUAL_DEVICE_PREFIX);Unregister
Call handle.unregister() to remove the device. If it is currently selected, the SDK falls back to the default device and runs your stop callback.
await handle.unregister();