Platform docs
Auth, users, webhooks & more
Info:
This is beta documentation for Stream Video React SDK v2. For the latest stable version, see the latest version (v1).

Camera & Microphone

The SDK simplifies WebRTC complexity (MediaStream, MediaDeviceInfo, etc.) through APIs on the call instance and utility hooks.

Best Practices

  • Check hasBrowserPermission before 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(), and toggle() 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 mode
  • VOICE_HIGH_QUALITY: high-quality mono audio, suitable for podcasts
  • MUSIC_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,
);
Warning:

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 registered

The 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 volume

Set 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>
  );
};