Precall Setup - React
Picture this: before diving into the depths of a video call, imagine giving your setup a quick check-up, like a tech-savvy doctor ensuring all systems are a go. That's essentially what a precall experience does- it’s like your extensive debug session before the main code execution—a crucial step in ensuring your app's performance is top-notch.
Why is it necessary?
Why invest time and effort into crafting a precall experience, you wonder? Well, picture this scenario: your users eagerly join a video call, only to encounter a myriad of technical difficulties—muted microphones, pixelated cameras, and laggy connections. Not exactly the smooth user experience you had in mind, right?
By integrating a robust precall process into your app, developers become the unsung heroes, preemptively addressing potential pitfalls and ensuring that users step into their video calls with confidence.
Step-by-Step Guide: Integrating Precall Feature
Step 1: Check Permissions
- Begin by ensuring that your application has the necessary permissions to access user devices such as cameras, microphones, and speakers.
- Utilize the
checkPermissions()method of theuseMediaDevicehook to verify if permissions are granted.
import { useMediaDevice } from "@videosdk.live/react-sdk";
const { checkPermissions } = useMediaDevice();
const checkMediaPermission = async () => {
//These methods return a Promise that resolve to a Map<string, boolean> object.
let checkAudioPermission;
try {
checkAudioPermission = await checkPermissions("audio"); //For getting audio permission
} catch (err) {
console.error("checkPermissions failed:", err);
}
let checkVideoPermission;
try {
checkVideoPermission = await checkPermissions("video"); //For getting video permission
} catch (err) {
console.error("checkPermissions failed:", err);
}
let checkAudioVideoPermission;
try {
checkAudioVideoPermission = await checkPermissions("audio_video"); //For getting both audio and video permissions
} catch (err) {
console.error("checkPermissions 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
*/
};
- When microphone and camera permissions are blocked, rendering device lists is not possible:
Step 2: Request Permissions (if necessary)
- If permissions are not granted, use the
requestPermission()method of theuseMediaDevicehook to prompt users to grant access to their devices.
In case permissions are blocked by the user, the browser's permission request dialogue cannot be re-rendered programmatically. In such cases, consider providing guidance to users on manually adjusting their browser settings.
const requestAudioVideoPermission = async () => {
try {
//These methods return a Promise that resolve to a Map<string, boolean> object.
let requestAudioPermission;
try {
requestAudioPermission = await requestPermission("audio"); //For Requesting Audio Permission
} catch (err) {
console.error("requestPermission failed:", err);
}
let requestVideoPermission;
try {
requestVideoPermission = await requestPermission("video"); //For Requesting Video Permission
} catch (err) {
console.error("requestPermission failed:", err);
}
let requestAudioVideoPermission;
try {
requestAudioVideoPermission = await requestPermission("audio_video"); //For Requesting Audio and Video Permissions
} catch (err) {
console.error("requestPermission failed:", err);
}
} catch (ex) {
console.log("Error in requestPermission ", ex);
}
};
- Requesting permissions if not already granted:

Step 3: Render Device List
- Once you have the necessary permissions, Fetch and render list of available camera, microphone, and speaker devices using the
getCameras(),getMicrophones(), andgetPlaybackDevices()methods of theuseMediaDevicehook respectively. - Enable users to select their preferred devices from these lists.
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);
}
//Method to get all available Microphones.
//It returns a Promise that is resolved with an array of MicrophoneDeviceInfo objects describing the audio input devices.
let mics;
try {
mics = await getMicrophones();
} catch (err) {
console.error("getMicrophones failed:", err);
}
//Method to get all available speakers.
//It returns a Promise that is resolved with an array of PlaybackDeviceInfo objects describing the playback devices.
let speakers;
try {
speakers = await getPlaybackDevices();
} catch (err) {
console.error("getPlaybackDevices failed:", err);
}
} catch (err) {
console.log("Error in getting audio or video devices", err);
}
};
- Displaying device lists once permissions are granted:
Step 4: Handle Device Changes
- Implement the
OnDeviceChangedcallback of theuseMediaDevicehook to dynamically re-render device lists whenever new devices are attached or removed from the system. - Ensure that users can seamlessly interact with newly connected devices without disruptions.
const {
...
} = useMediaDevice({ onDeviceChanged });
//Fetch camera, mic and speaker devices again using this function.
function onDeviceChanged(devices) {
console.log("Device Changed", devices)
}
- Dynamically updating device lists when new devices are connected or disconnected:
Step 5: Create Media Tracks
- Upon user selection of devices, 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.
import {
createCameraVideoTrack,
createMicrophoneAudioTrack,
} from "@videosdk.live/react-sdk";
//For Getting Audio Tracks
const getMediaTracks = async () => {
try {
//Returns a MediaStream object, containing the Audio Stream from the selected Mic Device.
const customAudioStream = await createMicrophoneAudioTrack({
// Here, selectedMicId should be the microphone id of the device selected by the user.
microphoneId: selectedMicId,
});
//To retrive audio tracks that will be displayed to the user from the stream.
const audioTracks = stream?.getAudioTracks();
const audioTrack = audioTracks.length ? audioTracks[0] : null;
} catch (error) {
console.log("Error in getting Audio Track", error);
}
//For Getting Video Tracks
try {
//Returns a MediaStream object, containing the Video Stream from the selected Webcam Device.
const customVideoStream = await createCameraVideoTrack({
// Here, selectedWebcamId should be the webcam id of the device selected by the user.
cameraId: selectedWebcamId,
encoderConfig: encoderConfig ? encoderConfig : "h540p_w960p",
optimizationMode: "motion",
multiStream: false,
});
//To retrive video tracks that will be displayed to the user from the stream.
const videoTracks = stream?.getVideoTracks();
const videoTrack = videoTracks.length ? videoTracks[0] : null;
} catch (error) {
console.log("Error in getting Video Track", error);
}
};
- Rendering Media Tracks when necessary permissions are available:

Step 6: Testing Microphone
- The process of testing microphone device provides valuable insights into microphone quality and ensures users can optimize their audio setup for clear communication.
- To facilitate this functionality, incorporate a recording feature that enables users to capture audio for a specified duration. After recording, users can playback the audio to evaluate microphone performance accurately.
- For implementing this functionality, you can refer to the official guide of MediaRecorder for comprehensive instructions and best practices.
Step 7: Testing Speakers
- Testing the speaker device allows users to assess audio playback clarity and fidelity, enabling them to fine-tune settings for optimal sound quality in calls and meetings.
- To facilitate effective speaker testing, integrate sound playback functionality into your application.
- This functionality empowers users to play a predefined audio sample, providing a precise evaluation of their speaker output quality.
const testSpeakers = () => {
//Here, you have to path of your desired test sound.
const test_sound_path = "test_sound_path";
//Create an audio tag using a test sound of your choice.
const audio = new Audio(test_sound_path);
try {
//Set the sinkId of the audio to the speaker's device Id, as selected by the user.
audio.setSinkId(selectedSpeakerDeviceId).then(() => {
audio.play();
});
} catch (error) {
console.log(error);
}
};
Step 8: Network Quality Assessment
The getNetworkStats() method has been removed in React SDK v0.10.0. Use the runPreCallTest() method instead.
- Run
runPreCallTest()as a pre-flight check before the user joins the meeting. - 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
MediaStreamyou have already created withcreateCameraVideoTrack(), such as the one rendered on your precall screen. - 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
MediaStreamyou have already created withcreateMicrophoneAudioTrack(). - 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. - Accepts the same parameters as createCameraVideoTrack() —
cameraId,encoderConfig,facingMode,optimizationMode,multiStream,bitrateModeandmaxLayer. - The track created from this configuration is returned live in the result, so you can hand it straight to
MeetingProvider. - This is an
OPTIONALparameter.
- Configuration used to acquire the camera track when
-
audioConfig:- Configuration used to acquire the microphone track when
audioTrackis not provided. - Accepts the same parameters as createMicrophoneAudioTrack() —
microphoneId,encoderConfigandnoiseConfig(echoCancellation,autoGainControl,noiseSuppression). - The track created from this configuration is returned live in the result, so you can hand it straight to
MeetingProvider. - 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 roughly once per second during sampling. It receives the same
{ uplink, downlink }object thatnetworkQualitycarries in the result. - It has to be of
Functiontype. - This is an
OPTIONALparameter.
- A callback invoked roughly once per second 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, PreCallTestError } from "@videosdk.live/react-sdk";
//Starts the pre-call test and returns a Promise that resolves with the test result.
//Here, customVideoStream and customAudioStream are the tracks created in Step 5.
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 MediaStream to use for the test.
videoTrack: customVideoStream,
//Microphone MediaStream to use for the test.
audioTrack: customAudioStream,
//Invoked roughly once per second with live network stats during sampling.
onStatsChange: (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:
// test.stop();
.stop() is exposed on the Promise that runPreCallTest() returns, not on the promises that .then() produces. Keep a reference to the original Promise, as in the example above, when you need to cancel the test.
Only one test can run at a time. Calling runPreCallTest() while another test is still in flight rejects with ERROR_PRECALL_TEST_ALREADY_RUNNING.
Result
The Promise resolves with an object containing:
aborted:truewhen the test was cancelled with.stop(), otherwisefalse.testDuration: Total test time in milliseconds.camera: Camera result. On success it carriesstatus: true, thetrackthat was tested as aMediaStream, 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 carrying a code and a message.
import { PreCallTestError } from "@videosdk.live/react-sdk";
runPreCallTest({ token: "<YOUR_AUTH_TOKEN>" }).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 meeting has already been initialized. Run the test before MeetingProvider initializes the meeting. |
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. |
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 | The permission prompt was denied or dismissed. |
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 cannot be produced. |
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 value you passed is not a MediaStream, or contains no track of that kind. |
ERROR_VIDEO_SOURCE_INITIATION_FAILED / ERROR_AUDIO_SOURCE_INITIATION_FAILED | Any other device-acquisition failure. |
Step 9: Passing States to Meeting
- Ensure that all relevant states, such as microphone and camera status (on/off), and selected devices, are passed into the meeting from the precall screen.
- This can be accomplished by passing these crucial states and media streams onto the VideoSDK
MeetingProvider. - By ensuring this integration, users can seamlessly transition from the precall setup to the actual meeting while preserving their preferred settings.
<MeetingProvider
config={
{
...
//Status of Mircophone Device as selected by the user (On/Off).
micEnabled: micOn,
//Status of Webcam Device as selected by the user (On/Off).
webcamEnabled: webcamOn,
//Use the camera track returned from runPreCallTest() in Step 8.
customCameraVideoTrack: result.camera.track,
//Use the microphone track returned from runPreCallTest() in Step 8.
customMicrophoneAudioTrack: result.microphone.track
}
} >
</MeetingProvider>
By following these step-by-step instructions, you can seamlessly integrate a precall feature into your application, empowering users to optimize their audio and video setup for a superior communication experience.
You can explore the complete implementation of the Precall feature in the official React JS SDK example available here.
API Reference
The API references for all the methods utilized in this guide are provided below.
Got a Question? Ask us on discord

