Precall Setup - React Native
A precall screen lets users check their camera, microphone and network before they join a video call.
This page uses the following asynchronous methods. Refer to their API reference for the errors each Promise may reject with, and handle these rejections appropriately based on your use case.
Why is it necessary?
Without a precall check, users can join a call with a muted microphone, a camera that does not work or a poor connection.
A precall step in your app catches these problems before the user joins.
Using PreCall Functions
Check Permissions
- Begin by ensuring that your application has the necessary permissions to access user devices such as cameras, microphones
- Utilize the
checkPermission()andcheckBlueToothPermission()methods of theuseMediaDevicehook to verify if permissions are granted.
import { Constants, useMediaDevice } from "@videosdk.live/react-native-sdk";
const { checkPermission, checkBlueToothPermission } = useMediaDevice();
const checkMediaPermission = async () => {
//checkPermission() returns a Promise that resolves to a Map<string, boolean> object.
const checkAudioPermission = await checkPermission(Constants.permission.AUDIO); //For getting audio permission
const checkVideoPermission = await checkPermission(Constants.permission.VIDEO); //For getting video permission
const checkAudioVideoPermission = await checkPermission(Constants.permission.AUDIO_AND_VIDEO); //For getting both audio and video permissions
// Applicable only to Android; not supported on iOS
let checkBTPermission;
try {
checkBTPermission = await checkBlueToothPermission(); // For getting bluetooth permission
} catch (err) {
console.error("checkBlueToothPermission failed:", err);
}
// Output: Map object for both audio and video permission:
/*
Map(2)
0 : {"audio" => true}
key: "audio"
value: true
1 : {"video" => true}
key: "video"
value: true
*/
};
Request Permissions (if necessary)
- If permissions are not granted, use the
requestPermission()andrequestBluetoothPermission()methods of theuseMediaDevicehook to prompt users to grant access to their devices. - Request access here, before you create media tracks, run the pre-call test or join the room.
createCameraVideoTrack(),createMicrophoneAudioTrack()andrunPreCallTest()never ask the user for access, on Android or iOS, and on Android the room does not ask either. requestPermission()does not throw when the user refuses access. Check the returned values instead: a permission that was not granted isfalse.
import { Platform } from "react-native";
import { Constants, useMediaDevice } from "@videosdk.live/react-native-sdk";
const { requestPermission, requestBluetoothPermission } = useMediaDevice();
const requestAudioVideoPermission = async () => {
try {
//requestPermission() returns a Promise that resolves to a Map<string, boolean> object.
const requestAudioPermission = await requestPermission(Constants.permission.AUDIO); //For Requesting Audio Permission
const requestVideoPermission = await requestPermission(Constants.permission.VIDEO); //For Requesting Video Permission
const requestAudioVideoPermission = await requestPermission(Constants.permission.AUDIO_AND_VIDEO); //For Requesting Audio and Video Permissions
if (
!requestAudioVideoPermission.get(Constants.permission.AUDIO) ||
!requestAudioVideoPermission.get(Constants.permission.VIDEO)
) {
// The user refused access: explain why it is needed, or open the app settings
}
// Applicable only to Android; not required for iOS
if (Platform.OS === "android") {
const requestBTPermission = await requestBluetoothPermission(); //For requesting Bluetooth Permission.
}
} catch (ex) {
console.log("Error in requestPermission ", ex);
}
};
Render Device List
- Once you have the necessary permissions, fetch and render the list of available cameras, microphones and all devices using the
getCameras(),getMics()andgetDevices()methods of theuseMediaDevicehook. They work before the room is joined.
import { useMediaDevice } from "@videosdk.live/react-native-sdk";
const { getCameras, getMics, getDevices } = useMediaDevice();
const getMediaDevices = async () => {
try {
//Method to get all available webcams.
//It returns a Promise that is resolved with an array of CameraDeviceInfo objects describing the video input devices.
let webcams;
try {
webcams = await getCameras();
} catch (err) {
console.error("getCameras failed:", err);
}
console.log("List of Cameras:", webcams);
//Method to get all available audio devices.
//It returns a Promise that is resolved with an array of MicrophoneDeviceInfo objects describing the audio devices.
//On Android, each entry is an audio route, such as SPEAKER_PHONE or BLUETOOTH.
let mics;
try {
mics = await getMics();
} catch (err) {
console.error("getMics failed:", err);
}
console.log("List of Microphone:", mics);
//Method to get all available audio devices and cameras.
//It returns the audio devices from getMics(), followed by the cameras from getCameras()
let devices;
try {
devices = await getDevices();
} catch (err) {
console.error("getDevices failed:", err);
}
console.log("List of Devices:", devices);
} catch (err) {
console.log("Error in getting audio or video devices", err);
}
};
Handle Device Changes
- Pass an
onAudioDeviceChangedcallback to theuseMediaDevicehook to re-render device lists whenever audio devices are attached or removed. - The callback receives a
Mapof the available audio devices, keyed bydeviceId. - Microphone permission has to be granted, otherwise the callback does not receive the list.
import { useMediaDevice } from "@videosdk.live/react-native-sdk";
function onAudioDeviceChanged(devices) {
console.log("Available audio devices", Array.from(devices.values()));
}
const { getMics } = useMediaDevice({ onAudioDeviceChanged });
Create Media Tracks
- Create media tracks for the selected microphone and camera using the
createMicrophoneAudioTrack()andcreateCameraVideoTrack()methods. - Ensure that these tracks originate from the user-selected devices for accurate testing.
- Hold both streams in your component state, so the pre-call test and the
RoomProviderbelow can use them. - Both methods open the device as soon as you call them, so you can preview the camera before the room. Render the camera track with
RTCView, using the URL from itstoURL()method. - Request camera and microphone access first, as shown in Request Permissions. Without it,
createCameraVideoTrack()rejects with3017ERROR_CAMERA_ACCESS_DENIED_OR_DISMISSEDandcreateMicrophoneAudioTrack()with3018ERROR_MICROPHONE_ACCESS_DENIED_OR_DISMISSED. - Call
stop()on a track that you do not pass to the room, for example when the user leaves the precall screen, to close the device.
import { useState } from "react";
import {
createCameraVideoTrack,
createMicrophoneAudioTrack,
RTCView,
} from "@videosdk.live/react-native-sdk";
const [customTrack, setCustomTrack] = useState(null);
const [customVideoTrack, setCustomVideoTrack] = useState(null);
const getMediaTracks = async () => {
//For Getting Audio Tracks
try {
//Opens the selected Mic Device and returns a CustomStreamTrack for it.
const audioTrack = await createMicrophoneAudioTrack({
encoderConfig: "speech_standard",
noiseConfig: {
noiseSuppression: true,
echoCancellation: true,
autoGainControl: true,
},
});
setCustomTrack(audioTrack);
} catch (error) {
console.log("Error in getting Audio Track", error);
}
//For Getting Video Tracks
try {
//Opens the selected Webcam Device and returns a CustomStreamTrack for it.
const videoTrack = await createCameraVideoTrack({
encoderConfig: "h720p_w1280p",
facingMode: "front",
});
setCustomVideoTrack(videoTrack);
} catch (error) {
console.log("Error in getting Video Track", error);
}
};
//To show the camera preview to the user on your precall screen.
return customVideoTrack ? (
<RTCView
streamURL={customVideoTrack.toURL()}
objectFit="cover"
setMirror={true}
style={{ flex: 1 }}
/>
) : null;
Network Quality Assessment
- Run
runPreCallTest()as a pre-flight check before the user joins the room. - It verifies that the camera and microphone work, then measures how well the network carries a real call — uplink and downlink, audio and video — and returns a quality score along with the raw stats behind it.
Parameters
The runPreCallTest() method accepts the following parameters:
-
token:- The authentication token used to authorize the test.
- It has to be of
Stringtype. - This is a REQUIRED parameter.
-
samplingDuration:- Controls how long (in milliseconds) the network-stats phase runs.
- It has to be of
Numbertype, between10000and120000. - This is an
OPTIONALparameter. - Default:
15000ms
-
videoTrack:- A camera
CustomStreamTrackyou have already created withcreateCameraVideoTrack(), such as the one rendered on your precall screen. - If you already show a camera preview, pass that track here, so that the test does not open the camera a second time.
- A track you pass in is never stopped for you, whatever the outcome — it stays yours to manage after the test.
- This is an
OPTIONALparameter.
- A camera
-
audioTrack:- A microphone
CustomStreamTrackyou have already created withcreateMicrophoneAudioTrack(). - If you have already opened the microphone, pass that track here, so that the test does not open the microphone a second time.
- A track you pass in is never stopped for you, whatever the outcome — it stays yours to manage after the test.
- This is an
OPTIONALparameter.
- A microphone
-
videoConfig:- Configuration used to acquire the camera track when
videoTrackis not provided. If you pass neithervideoTracknorvideoConfig, the test opens the camera with default settings. - Accepts the same parameters as createCameraVideoTrack() —
cameraId,encoderConfig,facingMode,multiStream,bitrateModeandmaxLayer. - The camera track the test opens is returned live in the result as
camera.track, so you can hand it straight toRoomProvider. If you do not use it, callstop()on it to close the camera. - This is an
OPTIONALparameter.
- Configuration used to acquire the camera track when
-
audioConfig:- Configuration used to acquire the microphone track when
audioTrackis not provided. If you pass neitheraudioTracknoraudioConfig, the test opens the microphone with default settings. - Accepts the same parameters as createMicrophoneAudioTrack() —
microphoneId,encoderConfigandnoiseConfig(echoCancellation,autoGainControl,noiseSuppression). - The microphone track the test opens is returned live in the result as
microphone.track, so you can hand it straight toRoomProvider. If you do not use it, callstop()on it to close the microphone. - This is an
OPTIONALparameter.
- Configuration used to acquire the microphone track when
-
audioOnly:- Skips the camera test and runs the pre-call test using audio only; the result's
camerafield is thennull. - It has to be of
Booleantype. - This is an
OPTIONALparameter. - Default:
false
- Skips the camera test and runs the pre-call test using audio only; the result's
-
onStatsChange:- A callback invoked each time the stats update during sampling. It receives the same
{ audioOnly, uplink, downlink }object thatnetworkQualitycarries in the result. - Pass it as the second argument of
runPreCallTest(). - It has to be of
Functiontype. - This is an
OPTIONALparameter.
- A callback invoked each time the stats update during sampling. It receives the same
Each of these rules rejects the test with ERROR_PRECALL_INVALID_CONFIG, and the specific cause is preserved in err.message as a trailing detail:
samplingDurationhas to be a finite number inside the 10000–120000 ms range.- Do not pass
videoTrackandvideoConfigtogether, and do not passaudioTrackandaudioConfigtogether — only one option from each pair is allowed. - Do not combine
audioOnly: truewithvideoTrackorvideoConfig. - The
encoderConfigyou pass has to be one of the supported profiles.
Example
import {
runPreCallTest,
stopPreCallTest,
PreCallTestError,
} from "@videosdk.live/react-native-sdk";
//Starts the pre-call test and returns a Promise that resolves with the test result.
//Here, customTrack and customVideoTrack are the tracks created in the previous step.
const test = runPreCallTest({
//Authentication token.
token: "<YOUR_AUTH_TOKEN>",
//Duration (in ms) for which the network-stats phase runs.
samplingDuration: 15000,
//Set to true to skip the camera test entirely.
audioOnly: false,
//Camera track to use for the test.
videoTrack: customVideoTrack,
//Microphone track to use for the test.
audioTrack: customTrack,
},
//Invoked with live network stats each time they update during sampling.
(stats) => {
console.log("Live stats:", stats);
});
test
.then((result) => {
console.log("Pre-call test result:", result);
})
.catch((err) => {
if (err instanceof PreCallTestError) {
console.log("Pre-call test failed:", err.code, err.message);
}
});
//To cancel the test before it completes:
// await test.stop();
// or
// await stopPreCallTest();
stopPreCallTest() stops the test that is running. The Promise that runPreCallTest() returns also has a stop() method that stops that one test. The Promise then resolves with aborted: true. stopPreCallTest() does nothing when no test is running, and stop() does nothing once its test has finished.
Only one test can run at a time. Calling runPreCallTest() while another test is still in flight rejects with ERROR_PRECALL_TEST_ALREADY_RUNNING. Joining a room stops a test that is still running.
Result
The Promise resolves with an object containing:
aborted:truewhen the test was cancelled withstopPreCallTest(), otherwisefalse.testDuration: Total test time in milliseconds.camera: Camera result. On success it carriesstatus: true, thetrackthat was tested as aCustomStreamTrack, itscaptureResolutionandfps, and the negotiatedcodec. When the camera could not be acquired it carriesstatus: falseand anerrorobject with thecodeandmessage. It isnullin audio-only mode and when the test was cancelled before the media check finished.microphone: Microphone result, with the same success and failure shapes ascameraapart fromcaptureResolutionandfps. It isnullwhen the test was cancelled before the media check finished.networkQuality: An object withuplinkanddownlink. It isnullwhenever the test was cancelled.
Each of networkQuality.uplink and networkQuality.downlink contains:
quality: Overall score from1(BAD) to5(EXCELLENT), taken as the lower of theaudioandvideosub-scores. It is0when no sub-score could be computed.factors: An array of strings explaining the score —rtt,packetLoss,jitter, the encoder'squalityLimitationReason(bandwidth,cpuorother) on uplink, andfreeze,framesDroppedandaudioConcealmenton downlink.audio: Audio metrics with their ownqualitysub-score. It isnullwhen the microphone could not be acquired.video: Video metrics with their ownqualitysub-score. It isnullin audio-only mode and when the camera could not be acquired.
Both audio and video carry rtt, bitrate, packetLoss and jitter; video adds fps and resolution. The rest differs by direction: uplink reports what was sent (bytesSent, plus qualityLimitationReason on video), while downlink reports what was received (bytesReceived, plus framesDropped, framesDroppedRatio, freezeCount and totalFreezesDuration on video).
Errors
Every failure rejects with a PreCallTestError, which extends Error and carries a code and a message.
import { runPreCallTest, PreCallTestError } from "@videosdk.live/react-native-sdk";
//Here, customTrack and customVideoTrack are the tracks created in the Create Media Tracks step.
runPreCallTest({
token: "<YOUR_AUTH_TOKEN>",
videoTrack: customVideoTrack,
audioTrack: customTrack,
}).catch((err) => {
if (err instanceof PreCallTestError) {
console.log(err.code, err.message);
}
});
| Code | When it fires |
|---|---|
ERROR_PRECALL_INVALID_TOKEN | token is missing, empty, or not a string. |
ERROR_PRECALL_INVALID_CONFIG | samplingDuration is not a finite number or falls outside the 10000–120000 ms range; videoTrack and videoConfig (or audioTrack and audioConfig) were passed together; audioOnly: true was combined with videoTrack or videoConfig; or an encoderConfig is not a known profile. |
ERROR_PRECALL_TEST_ALREADY_RUNNING | Another pre-call test is still in flight. Stop it before starting a new one. |
ERROR_PRECALL_AFTER_INIT | A room has already started joining. Run the test before you call join() or render a RoomProvider with joinWithoutUserInteraction, or after the room is left. |
ERROR_PRECALL_MEDIA_CHECK_FAILED | Neither the camera nor the microphone could be acquired. |
ERROR_PRECALL_TEST_FAILED | The pre-call network test could not be completed, for example because the server rejected the token. |
ERROR_CAMERA_NOT_FOUND / ERROR_MICROPHONE_NOT_FOUND | No such device is available. |
ERROR_CAMERA_ACCESS_DENIED_OR_DISMISSED / ERROR_MICROPHONE_ACCESS_DENIED_OR_DISMISSED | Camera or microphone access has not been granted. The test never asks the user, so call requestPermission() before you run it. |
ERROR_CAMERA_IN_USE / ERROR_MICROPHONE_IN_USE | The device is held by another application. |
ERROR_CAMERA_CONSTRAINT_NOT_SATISFIED / ERROR_MICROPHONE_CONSTRAINT_NOT_SATISFIED | The requested resolution, frame rate, sample rate, or channel count is not supported by the device. |
ERROR_WEBCAM_TRACK_ENDED / ERROR_MICROPHONE_TRACK_ENDED | The track is not live — either a track you supplied has already ended, or a newly created one came back not live. |
ERROR_INVALID_CUSTOM_VIDEO_TRACK / ERROR_INVALID_CUSTOM_AUDIO_TRACK | The track you passed was already stopped, or contains no track of that kind. |
ERROR_VIDEO_SOURCE_INITIATION_FAILED / ERROR_AUDIO_SOURCE_INITIATION_FAILED | Any other device-acquisition failure. |
ERROR_BRIDGE_UNEXPECTED | An unexpected failure that none of the codes above describes. |
The message on the error carries the human-readable text for the code, with the specific cause appended after an em dash where one applies.
Passing States to Room
- Ensure that all relevant states, such as microphone and camera status (on/off), and selected devices, are passed into the room from the precall screen.
- Pass these states and media tracks to
RoomProvider. - Users then move from the precall screen into the room with the settings they chose.
<RoomProvider
config={
{
roomId,
//Status of Microphone Device as selected by the user (On/Off).
micEnabled: micOn,
//Status of Webcam Device as selected by the user (On/Off).
camEnabled: webcamOn,
//customVideoTrack is the video track of the user's selected Webcam device, created in the Create Media Tracks step.
customCameraTrack: customVideoTrack,
//customTrack is the audio track of the user's selected Microphone device, created in the Create Media Tracks step.
customMicrophoneTrack: customTrack
}
}
token={token}
//The user already chose to join on this precall screen, so there is no
//join button inside RoomProvider. Let the SDK join for you, and do not
//call join() from a mount effect: it runs before RoomProvider has
//configured the token, and fails with 4002 INVALID_TOKEN.
//This is a prop on RoomProvider, not a key inside config.
joinWithoutUserInteraction={true}
>
<RoomView />
</RoomProvider>
API Reference
The API references for all the methods utilized in this guide are provided below.
Got a Question? Ask us on discord

