Pre-call test recording

The loopback recording API lets you record a short self-test of the local participant's media as it travels through Stream's SFU. The local video file is written to the device — nothing is uploaded.

This is different from server-side recording, which records the full call on Stream's infrastructure. Use the loopback API for local, pre-call diagnostics.

When to use it

  • Pre-call device check — verify the user's microphone, camera, and network path end-to-end before they join a real call.
  • Diagnostics builds — capture a short reference clip when users report audio or video quality issues, so you have a reproducible artifact to inspect.

How it works

The hook records your published media after it has made a full round trip through Stream's servers — capturing what other participants would actually see and hear, not just your local camera and microphone. Any problem along that path shows up in the recording.

Recordings are written to a Stream-managed directory inside your app's private storage (iOS temporary storage, Android cache). Files stay there until you remove them — clearing the directory is your responsibility, and the API exposes clearRecordings() for that.

Requirements

The component must be rendered inside <StreamCall>, and the call must be joined with allowOwnTracksLoopback: true.

No other participants can be present. The hook will not start recording when the call is shared.

API reference

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

const {
  startRecording,
  stopRecording,
  clearRecordings,
  getRecordings,
  recordingState,
  loopbackVideoStream,
  loopbackAudioStream,
} = useLoopbackRecording();
FieldDescription
startRecording(options?)Starts a recording. Returns a promise that resolves with the file:// URI of the produced video file once recording finishes, or null if no file was produced (for example, you stopped the recording before any data arrived). Pass { includeVideo: false } for an audio-only recording, or { maxDurationMs } to override the default 10-second cap (clamped to a 5-second minimum and a 2-minute maximum).
stopRecording()Stops the recording early. During 'awaiting-streams' it aborts the pending wait; during 'recording' it finalises the file.
clearRecordings()Recursively deletes every file under the SDK's local recordings directory.
getRecordings()Returns every file:// URI in that directory, sorted most-recent first.
recordingStateOne of 'idle', 'awaiting-streams', or 'recording'.
loopbackVideoStreamThe video MediaStream echoed back by the SFU, when present. Render it with RTCView to give the user live feedback while recording.
loopbackAudioStreamThe audio MediaStream echoed back by the SFU, when present.

Minimal example

import React, { useCallback, useEffect, useMemo } from "react";
import { Button, View } from "react-native";
import {
  Call,
  StreamCall,
  useLoopbackRecording,
  useStreamVideoClient,
} from "@stream-io/video-react-native-sdk";

export function PreCallTest() {
  const client = useStreamVideoClient();
  const call = useMemo<Call | undefined>(() => {
    if (!client) return undefined;
    return client.call("default", "self-test-" + Date.now());
  }, [client]);

  useEffect(() => {
    if (!call) return;
    call.getOrCreate();
    return () => {
      call.leave();
    };
  }, [call]);

  if (!call) return null;

  return (
    <StreamCall call={call}>
      <RecordButton call={call} />
    </StreamCall>
  );
}

function RecordButton({ call }: { call: Call }) {
  const { startRecording, recordingState } = useLoopbackRecording();

  const onPress = useCallback(async () => {
    await call.join({ create: true, allowOwnTracksLoopback: true });
    const uri = await startRecording();
    call.leave();
    if (uri) {
      // You now have a local video file on disk. Play it back or share it.
      console.log("Recording saved at", uri);
    }
  }, [call, startRecording]);

  return (
    <View>
      <Button title={recordingState} onPress={onPress} />
    </View>
  );
}

Lifecycle and limits

  • Recordings auto-stop after 10 seconds by default. Override via startRecording({ maxDurationMs }) — values outside the [5 seconds, 2 minutes] range snap to the nearest bound.
  • startRecording() waits up to 10 seconds for the SFU to echo the loopback tracks back. If the wait times out the promise rejects.
  • The recording auto-stops and finalises if the call leaves or the screen unmounts mid-recording — the file is still produced.

See also