1.44.0

The lobby camera preview is now driven by a native capturer directly — no WebRTC track is created before the call is joined. If you built a custom lobby using the old cookbook recipe, your preview will render blank after upgrading. The built-in <Lobby> component is unaffected.

🔨 What changed?

Everything continues to work at and after join — the change only affects the pre-join (lobby) window.

💡 How to migrate?

If you use the built-in <Lobby> component

No changes required. The SDK's <Lobby> uses the new LobbyCameraPreview internally.

If you built a custom lobby

Two things to update.

1. Replace the video preview

Drop RTCView + call.camera.state.mediaStream and use LobbyCameraPreview from the SDK — it renders the platform camera preview and handles permission, direction, target resolution, and mirror mode for you.

Before:

import { RTCView } from "@stream-io/react-native-webrtc";
import { useCall, useCallStateHooks } from "@stream-io/video-react-native-sdk";

export const LocalVideoRenderer = () => {
  const call = useCall();
  const localVideoStream = call?.camera.state.mediaStream;
  const { useCameraState } = useCallStateHooks();
  const { status: cameraStatus } = useCameraState();

  return cameraStatus === "enabled" ? (
    <RTCView
      streamURL={localVideoStream?.toURL()}
      objectFit="cover"
      style={StyleSheet.absoluteFill}
    />
  ) : null;
};

After:

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

export const LocalVideoRenderer = () => {
  const { useCameraState } = useCallStateHooks();
  const { optimisticIsMute: cameraIsMuted } = useCameraState();

  return !cameraIsMuted ? <LobbyCameraPreview objectFit="cover" /> : null;
};

2. Read optimisticIsMute, not isMute or status

Toggle-button UIs and any conditional rendering that depends on the intended camera / microphone state must use optimisticIsMute from useCameraState() / useMicrophoneState(). isMute and status remain undefined until the call is joined.

const { isMute: cameraMuted } = useCameraState();
const { isMute: microphoneMuted } = useMicrophoneState();
const { optimisticIsMute: cameraMuted } = useCameraState();
const { optimisticIsMute: microphoneMuted } = useMicrophoneState();

See the Lobby Preview cookbook for the full updated recipe.

If you built a pre-call self-test screen

The Pre-Call Self-Test cookbook previously rendered nothing in the video area before recording started (loopbackVideoStream ? <RTCView /> : null). Swap the fallback branch for LobbyCameraPreview so users can verify their camera setup before hitting Start recording.

Before:

return (
  <View style={{ flex: 1 }}>
    {loopbackVideoStream ? (
      <RTCView
        streamURL={loopbackVideoStream.toURL()}
        objectFit="cover"
        style={{ flex: 1 }}
      />
    ) : null}
    <Button title={label} onPress={onPress} disabled={isConnecting} />
  </View>
);

After:

return (
  <View style={{ flex: 1 }}>
    {loopbackVideoStream ? (
      <RTCView
        streamURL={loopbackVideoStream.toURL()}
        objectFit="cover"
        style={{ flex: 1 }}
      />
    ) : (
      <LobbyCameraPreview objectFit="cover" />
    )}
    <Button title={label} onPress={onPress} disabled={isConnecting} />
  </View>
);

Remember to add LobbyCameraPreview to your imports from @stream-io/video-react-native-sdk.