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.
  • Use usePersistedDevicePreferences 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.

Microphone permissions

import { useCallStateHooks } from "@stream-io/video-react-sdk";

const { useMicrophoneState } = useCallStateHooks();
const { hasBrowserPermission } = 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: 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,
);

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

Speaker management

Browser support

Speaker selection isn't supported by all browsers. Check availability:

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

Use usePersistedDevicePreferences to save device choices via localStorage. Render in a lobby component before calling call.get(), call.getOrCreate(), or call.join():

import {
  StreamVideo,
  StreamCall,
  usePersistedDevicePreferences,
} from "@stream-io/video-react-sdk";

export const MyApp = () => {
  // ... client and call initialization
  return (
    <StreamVideo client={client}>
      <StreamCall call={call}>
        <MyCallShell />
      </StreamCall>
    </StreamVideo>
  );
};

const MyCallShell = () => {
  usePersistedDevicePreferences("my-custom-preference-key");
  return <div>...</div>;
};