import React, { useCallback, useMemo } from "react";
import { Button, View } from "react-native";
import { RTCView } from "@stream-io/react-native-webrtc";
import {
CallingState,
useCall,
useCallStateHooks,
useLoopbackRecording,
} from "@stream-io/video-react-native-sdk";
const PreCallTest = () => {
const call = useCall();
const { useCallCallingState } = useCallStateHooks();
const callingState = useCallCallingState();
const { startRecording, stopRecording, recordingState, loopbackVideoStream } =
useLoopbackRecording();
const onStart = useCallback(async () => {
if (!call) return;
await call.join({ create: true, allowOwnTracksLoopback: true });
const uri = await startRecording();
call.leave();
if (uri) {
// `uri` is a `file://` URI of a local video on the device.
// Use it to play the recording back or to share it via the OS share sheet.
}
}, [call, startRecording]);
const isConnecting =
(callingState === CallingState.JOINING ||
callingState === CallingState.JOINED) &&
recordingState !== "recording";
const label = useMemo(() => {
if (recordingState === "recording") return "Stop recording";
if (isConnecting) return "Connecting…";
return "Start recording";
}, [recordingState, isConnecting]);
const onPress =
recordingState === "recording" ? () => stopRecording() : onStart;
return (
<View style={{ flex: 1 }}>
{loopbackVideoStream ? (
<RTCView
streamURL={loopbackVideoStream.toURL()}
objectFit="cover"
style={{ flex: 1 }}
/>
) : null}
<Button title={label} onPress={onPress} disabled={isConnecting} />
</View>
);
};Pre-call self-test
Introduction
Build a pre-call self-test screen where users record a short loopback of their own microphone, camera, and network path before joining a real call. Once recorded, they can watch the result back and optionally share the local video file.
This recipe uses the loopback recording API. The result is a two-screen flow: a recording screen and a playback / share screen.
Implementation
Consider using a dedicated call type (for example pre-call-test) and a recognisable call ID (for example prefixed with test_recording_) for the loopback call. That keeps self-tests cleanly separated from real call sessions in logs, analytics, and the Stream dashboard, and lets you scope permissions and defaults independently.
Step 1: Build the recording UI
Inside the recording screen, grab the active call with useCall() and drive the UI from useLoopbackRecording, using recordingState and the call's connecting state to reflect progress through join and record. When startRecording() resolves with a URI, you have a local video file you can play back or share via the OS share sheet; see the next step.
You can also render the live loopbackVideoStream via RTCView so the user sees the SFU echo immediately.
Step 2: Build the playback / share screen
The results screen takes the file URI as a prop and plays it back with react-native-video. Add a Share button to let users export the file via react-native-share.
import React, { useRef, useState } from "react";
import { Button, Pressable, StyleSheet, View } from "react-native";
import Video, { VideoRef } from "react-native-video";
import Share from "react-native-share";
export const TestRecordingResults = ({ uri }: { uri: string }) => {
const videoRef = useRef<VideoRef>(null);
const [paused, setPaused] = useState(true);
const handleShare = () => {
Share.open({
url: uri,
type: "video/mp4",
failOnCancel: false,
}).catch(() => {});
};
return (
<View style={{ flex: 1 }}>
<View style={{ flex: 1 }}>
<Video
ref={videoRef}
source={{ uri }}
paused={paused}
style={StyleSheet.absoluteFillObject}
resizeMode="cover"
/>
<Pressable
onPress={() => setPaused((p) => !p)}
style={StyleSheet.absoluteFillObject}
/>
</View>
<Button title="Share" onPress={handleShare} />
</View>
);
};Surfacing stats during the test
useCallStatsReport() works inside the loopback call too. Render a small overlay of latency, jitter, and bitrate while the user is recording so connectivity issues surface immediately. See the Call Stats Report guide.