Camera & Microphone
The SDK simplifies WebRTC complexity (MediaStream, MediaDeviceInfo, etc.) through APIs on the call instance and utility hooks.
Best Practices
- Check
hasBrowserPermissionbefore enabling camera/microphone - users get one chance to grant access. - Configure device persistence on
StreamVideoClient(devicePersistence) to remember device selections across sessions. - Always await
enable(),disable(), andtoggle()calls; the SDK handles race conditions. - Use
useDeviceList()for device listings - it includes a "Default" fallback option. - Implement a lobby preview so users can verify their setup before joining.
Camera management
Access the camera through the call.camera object or the useCameraState hook:
const call = useCall();
const camera = call.camera;
// or using hooks
import { useCallStateHooks } from "@stream-io/video-react-sdk";
const { useCameraState } = useCallStateHooks();
const { camera } = useCameraState();
await camera.enable();Call settings
The default state of the camera is determined by the call type settings:
import { useCallStateHooks } from "@stream-io/video-react-sdk";
const { useCallSettings } = useCallStateHooks();
const settings = useCallSettings();
console.log(settings?.video.camera_default_on);Start-stop camera
import { useCallStateHooks } from "@stream-io/video-react-sdk";
const { useCameraState } = useCallStateHooks();
const { camera, isMute } = useCameraState();
console.log(`Camera is ${isMute ? "off" : "on"}`);
await camera.toggle();
// or, alternatively
await camera.enable();
await camera.disable();Always await these calls. The SDK handles race conditions (last call wins). Use optimisticIsMute for immediate UI feedback.
List and select devices
import { useCallStateHooks, useDeviceList } from "@stream-io/video-react-sdk";
const { useCameraState } = useCallStateHooks();
const { camera, selectedDevice, devices } = useCameraState();
const { deviceList, selectedDeviceInfo } = useDeviceList(
devices,
selectedDevice,
);
console.log("current camera device:", selectedDeviceInfo);
console.log("available devices:", deviceList);
const preferredDevice = deviceList.find((d) => d.label === "My Camera");
await camera.select(preferredDevice.deviceId);useDeviceList() includes a "Default" fallback when no device is selected:
const { devices, selectedDevice } = useCameraState();
const { deviceList, selectedDeviceInfo, selectedIndex } = useDeviceList(
devices,
selectedDevice,
);
// `deviceList` contains useful properties, like `deviceList[0].isSelected`.
// `selectedDeviceInfo` is always defined, but can point to the "Default" device.Camera permissions
import { useCallStateHooks } from "@stream-io/video-react-sdk";
const { useCameraState } = useCallStateHooks();
const { hasBrowserPermission, isPromptingPermission } = useCameraState();
if (hasBrowserPermission) {
console.log("User has granted camera permissions!");
} else {
console.log("User has denied or not granted camera permissions!");
}
if (isPromptingPermission) {
// Useful to show certain UI while the prompt is pending
console.log("Waiting for user to respond to the browser permission prompt");
}Lobby preview
import { useCallStateHooks } from "@stream-io/video-react-sdk";
const { useCameraState } = useCallStateHooks();
const { camera, mediaStream } = useCameraState();
// will turn on the camera
await camera.enable();
// play the video preview
<video srcObject={mediaStream} autoPlay muted />;Or use the VideoPreview component.
Access to the Camera's MediaStream
Access mediaStream for custom use cases (local recording, etc.):
import { useCallStateHooks } from "@stream-io/video-react-sdk";
const { useCameraState } = useCallStateHooks();
const { mediaStream } = useCameraState();
const [videoTrack] = mediaStream.getVideoTracks();
console.log("Video track", videoTrack);Microphone management
Access the microphone through call.microphone or useMicrophoneState:
const call = useCall();
const microphone = call.microphone;
// or using hooks
import { useCallStateHooks } from "@stream-io/video-react-sdk";
const { useMicrophoneState } = useCallStateHooks();
const { microphone } = useMicrophoneState();Call settings
The default state of the microphone is determined by the call settings:
import { useCallStateHooks } from "@stream-io/video-react-sdk";
const { useCallSettings } = useCallStateHooks();
const settings = useCallSettings();
console.log(settings?.audio.mic_default_on);Start-stop microphone
import { useCallStateHooks } from "@stream-io/video-react-sdk";
const { useMicrophoneState } = useCallStateHooks();
const { microphone, isMute } = useMicrophoneState();
console.log(`Microphone is ${isMute ? "off" : "on"}`);
await microphone.toggle();
// or, alternatively
await microphone.enable();
await microphone.disable();Always await these calls. The SDK handles race conditions (last call wins). Use optimisticIsMute for immediate UI feedback.
List and select devices
import { useCallStateHooks, useDeviceList } from "@stream-io/video-react-sdk";
const { useMicrophoneState } = useCallStateHooks();
const { microphone, selectedDevice, devices } = useMicrophoneState();
const { deviceList, selectedDeviceInfo } = useDeviceList(
devices,
selectedDevice,
);
console.log("current microphone device:", selectedDeviceInfo);
console.log("available devices:", deviceList);
const preferredDevice = deviceList.find((d) => d.label === "My Mic");
await microphone.select(preferredDevice.deviceId);useDeviceList() includes a "Default" fallback when no device is selected:
const { devices, selectedDevice } = useMicrophoneState();
const { deviceList, selectedDeviceInfo, selectedIndex } = useDeviceList(
devices,
selectedDevice,
);
// `deviceList` contains useful properties, like `deviceList[0].isSelected`.
// `selectedDeviceInfo` is always defined, but can point to the "Default" device.Test a microphone
You can run a quick, on-demand capture test for a specific microphone.
import { useCallStateHooks } from "@stream-io/video-react-sdk";
const { useMicrophoneState } = useCallStateHooks();
const { microphone, selectedDevice } = useMicrophoneState();
const capturesAudio = await microphone.performTest(selectedDevice);
if (!capturesAudio) {
console.log("Microphone did not capture audio. Check device selection.");
}Microphone permissions
import { useCallStateHooks } from "@stream-io/video-react-sdk";
const { useMicrophoneState } = useCallStateHooks();
const { hasBrowserPermission, isPromptingPermission } = useMicrophoneState();
if (hasBrowserPermission) {
console.log("User has granted microphone permissions!");
} else {
console.log("User has denied or not granted microphone permissions!");
}
if (isPromptingPermission) {
// Useful to show certain UI while the prompt is pending
console.log("Waiting for user to respond to the browser permission prompt");
}Speaking while muted detection
Detects when users speak while muted. Enabled by default (unless user lacks audio permission).
import { useCallStateHooks } from "@stream-io/video-react-sdk";
const { useMicrophoneState } = useCallStateHooks();
const { isSpeakingWhileMuted, microphone } = useMicrophoneState();
if (isSpeakingWhileMuted) {
// your custom logic comes here
console.log("You are speaking while muted!");
}
// to disable this feature completely:
await microphone.disableSpeakingWhileMutedNotification();
// to enable it back:
await microphone.enableSpeakingWhileMutedNotification();Access to the Microphone's MediaStream
Access mediaStream for custom use cases (local recording, etc.):
import { useCallStateHooks } from "@stream-io/video-react-sdk";
const { useMicrophoneState } = useCallStateHooks();
const { mediaStream } = useMicrophoneState();
const [audioTrack] = mediaStream.getAudioTracks();
console.log("Audio track", audioTrack);Hi-Fi and Stereo Audio
Configure high-fidelity stereo capture. Available modes:
VOICE_STANDARD_UNSPECIFIED: standard quality mono audio, suitable for voice calls, default modeVOICE_HIGH_QUALITY: high-quality mono audio, suitable for podcastsMUSIC_HIGH_QUALITY: high-quality stereo audio, suitable for music calls
Update with setAudioBitrateProfile:
import { useCallStateHooks, SfuModels } from "@stream-io/video-react-sdk";
const { useMicrophoneState } = useCallStateHooks();
const { microphone } = useMicrophoneState();
await microphone.setAudioBitrateProfile(
SfuModels.AudioBitrateProfile.MUSIC_HIGH_QUALITY,
);MUSIC_HIGH_QUALITY disables echo cancellation and noise suppression. Users may experience echo without headphones.
Noise Cancellation
Check our Noise Cancellation guide.
Camera and Microphone AI/ML Filters
Apply filters to media streams:
- Background blur/replacement
- Video filters (color correction, face detection)
- Audio filters (noise reduction)
import { useCallStateHooks } from "@stream-io/video-react-sdk";
const { useCameraState, useMicrophoneState } = useCallStateHooks();
const { camera } = useCameraState();
const { microphone } = useMicrophoneState();
// apply a custom video filter
const { unregister: unregisterMyVideoFilter } = camera.registerFilter(
function myVideoFilter(inputMediaStream: MediaStream) {
// initialize the video filter, do some magic and
// return the modified media stream
return {
output: mediaStreamWithFilterApplied,
stop: () => {
// optional cleanup function
},
};
},
);
// apply a custom audio filter
const { unregister: unregisterMyAudioFilter } = microphone.registerFilter(
function myAudioFilter(inputMediaStream: MediaStream) {
// initialize the audio filter, do some magic and
// return the modified media stream
return {
output: mediaStreamWithFilterApplied,
stop: () => {
// optional cleanup function
},
};
},
);
// unregister the filters
unregisterMyVideoFilter();
unregisterMyAudioFilter();Filters can be registered/unregistered anytime. They chain in registration order.
For async filters, return output as a promise:
camera.registerFilter(function myVideoFilter(inputMediaStream: MediaStream) {
// note that output is returned synchronously!
return {
output: new Promise((resolve) => resolve(mediaStreamWithFilterApplied)),
stop: () => {
// optional cleanup function
},
};
});Await registered to know when the filter is active:
const { registered } = camera.registerFilter(
(inputMediaStream: MediaStream) => {
// your video filter
},
);
await registered;
// at this point, the filter is registeredThe SDK sends the filtered MediaStream to remote participants.
More: Apply Video Filters
Virtual devices
You can register a custom MediaStream source as if it were a real camera or microphone. The SDK lists it in listDevices(), lets you select it like any real device, and publishes the stream you provide. Use it for video-file playback as a camera, synthetic audio sources, or end-to-end tests with deterministic media.
See Virtual devices for the full guide.
Speaker management
Browser support
Speaker selection isn't supported by all browsers. It's also disabled on WebKit-based browsers (Safari, all iOS browsers and WebViews), where switching the output device can distort or silence remote audio. There, audio plays on the system's default output, and users change it in the operating system's sound settings. Check availability and hide your speaker picker when it's false:
import { useCallStateHooks } from "@stream-io/video-react-sdk";
const { useSpeakerState } = useCallStateHooks();
const { isDeviceSelectionSupported } = useSpeakerState();
console.log("is speaker selection supported:", isDeviceSelectionSupported);List and select devices
import { useCallStateHooks } from "@stream-io/video-react-sdk";
const { useSpeakerState } = useCallStateHooks();
const { speaker, selectedDevice, devices } = useSpeakerState();
console.log("current mic-id:", selectedDevice);
console.log("available devices:", devices);
const preferredDevice = devices.find((d) => d.label === "My Speakers");
await speaker.select(preferredDevice.deviceId);Set master output volume
import { useCallStateHooks } from "@stream-io/video-react-sdk";
const { useSpeakerState } = useCallStateHooks();
const { speaker } = useSpeakerState();
speaker.setVolume(0.5); // 0.5 is 50% of the maximum volumeSet participant volume
import {
useCallStateHooks,
StreamVideoParticipant,
} from "@stream-io/video-react-sdk";
let p: StreamVideoParticipant;
const { useSpeakerState } = useCallStateHooks();
const { speaker } = useSpeakerState();
// will set the volume of the participant to 50%
speaker.setParticipantVolume(p.sessionId, 0.5);
// will mute the participant
speaker.setParticipantVolume(p.sessionId, 0);
// will reset the volume to the default value
speaker.setParticipantVolume(p.sessionId, undefined);Persisting user's device preferences
By default, device persistence is enabled when localStorage is available.
To opt out, or configure it further, adjust the appropriate options.devicePersistence flags.
import {
StreamVideo,
StreamCall,
StreamVideoClient,
} from "@stream-io/video-react-sdk";
export const MyApp = () => {
const client = StreamVideoClient.getOrCreateInstance({
apiKey,
user,
token,
options: {
devicePersistence: {
enabled: true, // false to disable it
storageKey: "@stream-io/device-preferences", // to override the default
},
},
});
return (
<StreamVideo client={client}>
<StreamCall call={call}>{/* Your UI */}</StreamCall>
</StreamVideo>
);
};