Skip to main content
Version: 4.x.x

Precall Setup - iOS

A precall screen lets users check their camera, microphone and network before they join a video call.

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 and microphones.
  • Utilize the getAudioPermissionStatus() and getVideoPermissionStatus() methods of the VideoSDK class to verify if permissions are granted.
let audioPermission = VideoSDK.getAudioPermissionStatus() // audio permission status
let videoPermission = VideoSDK.getVideoPermissionStatus() // video permission status

Request Permissions (if necessary)​

  • If permission status is notDetermined, use the getAudioPermission() and getVideoPermission() methods of the VideoSDK class to prompt users to grant access to their devices.

  • Calling the above functions will automatically handle the notDetermined state and prompt the user to grant permission.

  • Both functions return right away, before the user answers. If you need the answer, for example before you create media tracks, call await AVCaptureDevice.requestAccess(for: .video) or await AVCaptureDevice.requestAccess(for: .audio) yourself.

class StartViewController: UIViewController {

override func viewDidLoad() {
super.viewDidLoad()

// UI Setup
setupUI()

// prompt user to grant permission when the app starts
VideoSDK.getVideoPermission()
VideoSDK.getAudioPermission()
}

func setupUI() {
// your UI code
}
}
  • If the permissions are denied or restricted after approving, you need to prompt user to manually approve the permissions from the Settings. AuthorisationDelegate was removed in 4.0.0, so open Settings from your own method. Below is an example on how you can do it.
class StartViewController: UIViewController {
// settings button
@IBOutlet weak var openSetting: UIButton!

override func viewDidLoad() {
super.viewDidLoad()

// UI Setup
setupUI()

// prompt user to grant permission when the app starts
VideoSDK.getVideoPermission()
VideoSDK.getAudioPermission()
}

func setupUI() {
// your UI code
settingsButton()
}
//sets the visibility of settings button
func settingsButton() {
let audioStatus = VideoSDK.getAudioPermissionStatus()
let videoStatus = VideoSDK.getVideoPermissionStatus()

if (audioStatus == .authorized && videoStatus == .authorized) {
openSetting.isHidden = true
}
else {
openSetting.isHidden = false
}
}
// settings button tapped
@IBAction func openSettingsButtonTapped(_ sender: Any) {
handlePermission()
}
}

extension StartViewController {
// opens the app's page in the Settings app
func handlePermission() {
guard let settingsURL = URL(string: UIApplication.openSettingsURLString) else { return }
if UIApplication.shared.canOpenURL(settingsURL) {
UIApplication.shared.open(settingsURL, options: [:], completionHandler: nil)
}
}
}

Get Device Lists​

  • Once you have the necessary permissions, fetch the lists of available cameras and audio devices using the getCameras() and getMics() methods of the VideoSDK class respectively.
  • Each entry has a label to show in a picker and a deviceId to pass back to the SDK. getMics() lists the Speaker output first, then each microphone.
let cameras = VideoSDK.getCameras() // [CameraDeviceInfo]
let audioDevices = VideoSDK.getMics() // [MicrophoneDeviceInfo]

Create Media Tracks​

  • Create media tracks for the selected microphone and camera using the createMicrophoneAudioTrack() and createCameraVideoTrack() methods.
  • Ensure that these tracks originate from the user-selected devices for accurate testing. Pass the deviceId of the camera and microphone the user picked from getCameras() and getMics() as cameraId and microphoneId.
  • These methods never ask for permission. Until access is granted, they throw ERROR_CAMERA_ACCESS_DENIED_OR_DISMISSED or ERROR_MICROPHONE_ACCESS_DENIED_OR_DISMISSED.
  • Both methods are async throws, so call them with try await from a Task or an async function. They wait up to 2 seconds for the device to open. If the device fails after that, the track ends and its onEnded handler receives the error.
var videoMediaTrack: CustomStreamTrack?
var audioMediaTrack: CustomStreamTrack?

do {
// Create custom Video Track
videoMediaTrack = try await VideoSDK.createCameraVideoTrack(
// This will accept the enum value of CustomCameraTrackConfig which contains resolution (height x width) of video you want to capture.
encoderConfig: .h720p_w1280p, // .h540p_w960p | .h720p_w1280p ... // Default : .h720p_w1280p

// It will specify whether to use front or back camera for the video track.
facingMode: .front, // .back, Default : .front

// It will specify whether to send multiple resolution layers or a single layer.
multiStream: true, // false, Default : true

// The deviceId of the camera the user selected from getCameras(). When set, it is used instead of facingMode.
cameraId: selectedCamera.deviceId, // Default : nil

// The video codec. H264, VP9 and AV1 need multiStream: false.
codec: .VP8 // .H264 | .VP9 | .AV1, Default: .VP8
)

// Create custom Audio Track
audioMediaTrack = try await VideoSDK.createMicrophoneAudioTrack(
// The deviceId of the microphone the user selected from getMics().
microphoneId: selectedMicrophone.deviceId, // Default : nil
noiseConfig: NoiseConfig(
noiseSuppression: true,
echoCancellation: true,
autoGainControl: true,
highPassFilter: true
)
)
} catch {
// For example ERROR_CAMERA_ACCESS_DENIED_OR_DISMISSED when camera access is not granted
print("Failed to create media tracks: \(error)")
}

Network Quality Assessment​

  • Run runPreCallTest() as a pre-flight check before the user joins the room.
  • It checks that the camera and microphone work. Then it measures how well the network carries a call, for uplink and downlink, audio and video. It returns a quality score and the stats behind it.

Parameters​

VideoSDK.runPreCallTest(_:onStatsChange:) takes a PreCallTestOptions value and an optional stats callback.

  • token:

    • The authentication token used to authorize the test.
    • It has to be of String type.
    • This is a REQUIRED parameter.
  • samplingDuration:

    • Controls how long (in milliseconds) the network-stats phase runs.
    • It has to be of Int type, between 10000 and 120000.
    • This is an OPTIONAL parameter.
    • Default: 15000 ms
  • audioOnly:

    • Skips the camera test and runs the pre-call test using audio only; the result's camera field is then nil.
    • It has to be of Bool type.
    • This is an OPTIONAL parameter.
    • Default: false
  • videoTrack:

    • A camera CustomStreamTrack you have already created with createCameraVideoTrack(), such as the one rendered on your precall screen.
    • The SDK never stops a track you pass in. It stays yours to manage after the test.
    • This is an OPTIONAL parameter.
  • audioTrack:

    • A microphone CustomStreamTrack you have already created with createMicrophoneAudioTrack().
    • The SDK never stops a track you pass in. It stays yours to manage after the test.
    • This is an OPTIONAL parameter.
  • videoConfig:

    • A PreCallVideoConfig used to acquire the camera track when videoTrack is not provided.
    • Accepts cameraId, encoderConfig, facingMode, multiStream, bitrateMode and maxLayer.
    • The track created from this configuration is returned live in the result, so you can hand it straight to the room.
    • This is an OPTIONAL parameter.
  • audioConfig:

    • A PreCallAudioConfig used to acquire the microphone track when audioTrack is not provided.
    • Accepts microphoneId, encoderConfig and noiseConfig.
    • The track created from this configuration is returned live in the result, so you can hand it straight to the room.
    • This is an OPTIONAL parameter.
  • onStatsChange:

    • A closure called on the main actor each time the stats update while sampling runs. It receives the latest stats as a PreCallNetworkQuality, the same type as the result's networkQuality.
    • This is an OPTIONAL parameter.
caution

Each of these rules rejects the test with ERROR_PRECALL_INVALID_CONFIG, and the specific cause is preserved in the error's message:

  • samplingDuration has to be inside the 10000–120000 ms range.
  • Pass videoTrack or videoConfig, not both. Pass audioTrack or audioConfig, not both.
  • Do not combine audioOnly: true with videoTrack or videoConfig.
note
  • Only one test can run at a time, and a test cannot run while a room is initialized. Run it before createRoom(): after that, the test is refused with ERROR_PRECALL_AFTER_INIT until the room is left.
  • Camera and microphone permission must already be granted, because the test does not ask for it.
  • If your precall screen already shows tracks, pass them as videoTrack and audioTrack. The SDK has one camera capture and one microphone capture, so when the test opens a device itself, the track you created earlier for that device ends.
  • Cancelling the calling Task stops the test, like stopPreCallTest(). A stopped test still returns a result, with aborted set to true.

Example​

import VideoSDKRTC

Task {
do {
let result = try await VideoSDK.runPreCallTest(
// Test the tracks your precall screen already shows
PreCallTestOptions(
token: token,
samplingDuration: 15_000,
videoTrack: videoMediaTrack,
audioTrack: audioMediaTrack
)
) { stats in
// Called on the main actor each time the stats update while sampling
print("uplink quality:", stats.uplink.quality)
}

print("camera ok:", result.camera?.status ?? false)
print("microphone ok:", result.microphone?.status ?? false)
print("downlink quality:", result.networkQuality?.downlink.quality ?? 0)
} catch let error as PreCallTestError {
print(error.code, error.message)
} catch {
print("Failed to run pre-call test: \(error)")
}
}

To stop a running test early:

Task {
await VideoSDK.stopPreCallTest()
}

Result​

runPreCallTest() returns a PreCallTestResult:

PropertyTypeDescription
abortedBooltrue when the test was stopped before it finished.
testDurationIntHow long the test actually ran, in milliseconds.
cameraPreCallMediaReport?The camera check. nil when audioOnly is true.
microphonePreCallMediaReport?The microphone check.
networkQualityPreCallNetworkQuality?The uplink and downlink measurements.

PreCallMediaReport carries status, track, captureResolution, fps, codec and error. When the SDK opened the device for you (you did not pass videoTrack or audioTrack), track is the live track. Use it for the room instead of creating another one. It stays open after the test, so call its stop() if you do not use it.

PreCallNetworkQuality carries audioOnly plus uplink and downlink, each a PreCallDirectionQuality:

PropertyTypeDescription
qualityIntThe score for that direction.
factors[String]What limited the score.
audioPreCallAudioQuality?quality, rtt, bitrate, packetLoss, jitter, bytesSent, bytesReceived.
videoPreCallVideoQuality?The audio fields plus fps, resolution, qualityLimitationReason, framesDropped, freezeCount, framesDroppedRatio and totalFreezesDuration.

Errors​

Failures throw PreCallTestError, which carries code, name and message. Note that code is a String here, not an integer.

CodeWhen
ERROR_PRECALL_INVALID_TOKENThe token is empty.
ERROR_PRECALL_INVALID_CONFIGOne of the option rules above was broken.
ERROR_PRECALL_AFTER_INITA room was already initialized.
ERROR_PRECALL_TEST_ALREADY_RUNNINGAnother test is still running.
ERROR_PRECALL_MEDIA_CHECK_FAILEDNeither the camera nor the microphone could be opened.
ERROR_PRECALL_TEST_FAILEDThe network test could not be completed, for example because the server refused the token.

If only one device fails its check, the test does not throw. That device's report has status set to false and carries the error. The test throws the device's own error, such as ERROR_INVALID_CUSTOM_VIDEO_TRACK or ERROR_MICROPHONE_ACCESS_DENIED_OR_DISMISSED, when a track you passed cannot be used or the microphone fails in an audio-only test.

Passing States to Room​

  • Ensure that all relevant states, such as microphone and camera status (on/off) are passed into the room from the precall screen.
  • Pass these states and media tracks to createRoom.
  • Users then keep their precall settings when they join the room.
do {
room = try VideoSDK.createRoom(
roomId: "abcd-efgh-xyzw",
token: token,
participantName: "John Doe", // optional
micEnabled: micStatus,
camEnabled: camStatus,
participantId: "JD", // optional
mode: .SEND_AND_RECV, //optional (default mode is SEND_AND_RECV)
customCameraTrack: videoMediaTrack, //optional
customMicrophoneTrack: audioMediaTrack //optional
)
} catch {
print("Failed to create the room: \(error)")
}

API Reference​

The API references for all the methods utilized in this guide are provided below.

Got a Question? Ask us on discord