SDKs
React Native SDK reference: hooks and errors
Every export of @voltkit-rtc/react-native: VoltkitProvider, hooks, VideoTrack and AudioTrack, audio routing, the call service, the Expo plugin and error codes.
The complete surface of @voltkit-rtc/react-native 0.1.0. For how to use it, start with the React Native SDK guide and its call screen; React Native with Expo covers the config plugin. The client underneath is @voltkit-rtc/client, whose methods, events and options are on the JavaScript SDK page.
Errors
Every failure is an RpcError from @voltkit-rtc/client, with the same codes as every VoltKit SDK.
| Member | |
|---|---|
code | The registry code: switch on it, never on the message |
message | For logs; it may be reworded between releases. It starts with the code |
data | What the SDK or the server attached, typed unknown: read it after checking the code, as below |
isTerminalError(code) and errorInfo(code) from @voltkit-rtc/client read the shared registry: whether a join refused with the code may be retried with the same token, and whether retrying later can help.
These are the codes the React Native SDK raises itself. Everything else, from the service and the client, is on Errors.
| Code | data | When | What to do |
|---|---|---|---|
media-unavailable | { permission, status } | The camera ("camera") or microphone ("microphone") is not granted: publish() asked and the user refused, or a capture the SDK makes later (a reconnect, a switch), which only checks, found it missing. status is "denied" (the system may ask again) or "blocked" (only the app's settings can grant it). No device was opened | Explain why the app needs it; for blocked, offer Linking.openSettings() |
media-unavailable | { reason: "webrtc-missing" } | react-native-webrtc's native module is not in the app: Expo Go, or an app not built again after installing it. The provider makes no client and reports it through onError; createClient throws it. The message says it needs a development build | Build the app again; use a development build |
media-unavailable | { reason: "native-missing", feature } | The SDK's own native module is not in the app (Android): background calls, audio routing and native timers are off, and feature names what asked for them | Build the app again |
media-unavailable | { reason } | The camera or microphone could not open (another app holds it, or it failed): reason is the capture error's name, or capture-failed | Offer audio only, or try again |
media-unavailable | { feature: "callService" } | The call service could not start: the manifest lacks the service or one of its permissions, or the app was in the background. Reported through onError; the call goes on | Add what the message names; start calls from a visible screen |
media-unavailable | { platform: "ios", feature } | An Android-only call on iOS: setSpeakerphoneOn, selectAudioOutput, switchDevice("audiooutput", ...) | Leave the control out on iOS |
media-unavailable | switchCamera() or switchDevice("videoinput", ...) with no camera published, or startRecording(); also a failure inside the audio router on Android (selectAudioOutput, useAudioOutput().select, switchDevice("audiooutput", ...) or switchDevice("audioinput", ...), and the call audio the provider holds, which reaches onError), with the native side's words in the message | Publish a camera first; recording needs a browser; for the router, try again or leave the output as it is | |
camera-failed | A camera switch failed; the old camera stays on air | Try again | |
bad-params | An id no camera, microphone or output has, or an output chosen in media audio mode | Use an id from useDevices() or useAudioOutput() | |
screen-share-service-required | startScreenShare() in a build without the screen-capture service | Turn on the plugin's android.screenShare, or add the meta-data, and build again | |
screen-share-denied | The user cancelled Android's screen-capture consent | Nothing; offer the button again | |
screen-share-unsupported | startScreenShare() on iOS | Leave the control out on iOS | |
screen-share-active | A share is already running or starting | Wait for it, or stop it first | |
screen-share-failed | The screen capture could not start; the message says why | Try again | |
music-unavailable | publishMusic(): in-app music needs a browser | Leave it out | |
not-connected | A useLocalMedia() call with no connected client | Wait for connected |
import { RpcError } from "@voltkit-rtc/client";
type MediaProblem = {
permission?: "camera" | "microphone";
status?: "denied" | "blocked";
reason?: string;
feature?: string;
platform?: string;
};
/** Words for a media-unavailable error, or null for any other error. */
export function mediaProblem(err: unknown): string | null {
if (!(err instanceof RpcError) || err.code !== "media-unavailable") return null;
const data = (err.data ?? {}) as MediaProblem;
if (data.permission) return data.status === "blocked" ? `Allow the ${data.permission} in Settings.` : `The ${data.permission} is needed.`;
if (data.reason === "webrtc-missing") return "This build cannot make calls. Install the latest version of the app.";
if (data.feature === "callService") return "Calls may stop when you leave the app.";
return "The camera or microphone is not available.";
}
API reference
VoltkitProvider
<VoltkitProvider> holds one StreamClient for the components below it: it makes it with the options of reactNativeClientOptions, connects it, holds the phone's call audio and the call service while it is connected, and closes it on unmount. It is the React SDK's provider underneath, so every re-exported hook works below it.
| Prop | Type | Default | |
|---|---|---|---|
wsUrl | string | The room's WebSocket URL, from your backend | |
token | string | A room token from your backend. A new one for the same session goes to updateToken; one for another room or identity makes a new client | |
connect | boolean | true | Connect the client when it is idle |
onConnected | (result: JoinResult) => void | After every successful join, reconnects included | |
onError | (error: Error) => void | Every error event, a failed connect, a refused token, a call service that could not start, and missing native code | |
onClosed | (reason: ClosedReason) => void | The session ended for good | |
client | StreamClient | null | A client you made with createClient and own; never closed by the provider | |
clientOptions | ClientOptions | Laid over React Native's own options when the client is made | |
audio | AudioSessionOptions | false | false leaves the audio mode, focus and routing to your app | |
audio.speakerphone | boolean | true | The loudspeaker (true) or the earpiece (false) when no headset is connected, unless setSpeakerphoneOn chose |
audio.manage | boolean | true | false is the same as audio={false} |
background.pauseCamera | boolean | true | Switch the camera off while the app is in the background, and back on when it returns |
background.pauseMic | boolean | false | The same for the microphone; leave it off for calls |
callService | boolean | CallServiceText | true | Start the call service when the call connects, if the manifest declares it. CallServiceText sets the notification: title (default "Call in progress"), text ("Tap to return to the call") and leaveLabel ("Leave") |
children | ReactNode |
The client it makes has the options reactNativeClientOptions({ background, clientOptions }) returns. Where react-native-webrtc's native code is missing, it makes no client: onError gets media-unavailable (webrtc-missing) and the hooks keep their defaults.
Hooks from the React SDK
Re-exported unchanged from @voltkit-rtc/react 0.1.1; the React SDK's hooks describe each.
| Hook | Returns |
|---|---|
useVoltkitClient() | The provider's StreamClient, or null |
useRoom() | RoomState: client, info, state, error, connect(), leave() |
useConnectionState() | ConnectionState |
useParticipants() | ParticipantInfo[] |
useRemoteTracks(filter?) | RemoteTrack[]; the filter takes identity, kind and source |
useNetworkQuality() | NetworkQuality | null |
useWalletLow() | WalletLowState: active, remainingSeconds, notice |
useVoltkitEvent(event, handler) | Nothing: calls handler for each event while mounted |
useAudioLevels() | AudioLevel[], every 500 ms on React Native |
useActiveSpeakers() | Who is speaking, most active first |
useIsSpeaking(identity) | boolean |
useStreamFallback(producerId) | "bandwidth", "audio-protection" or null |
usePublishFallback() | boolean |
useMediaWarnings() | The camera and microphone warnings in force, such as mic-silent and mic-interrupted |
useCpuConstrained() | boolean |
useDevices(kind?) | MediaDevice[]: cameras with facing, microphones, and on Android the outputs with type |
useLocalMedia
useLocalMedia(): LocalMedia: the React SDK's useLocalMedia, with two additions.
| State | |
|---|---|
stream | Your camera and microphone as published (a MediaStreamLike: react-native-webrtc's stream, by shape), or null |
publishing | Anything is published |
micEnabled, camEnabled | Switched on |
audioQuality, videoQuality, videoFrameRate | What is sent, after the project's limits |
screenSharing, screenStream | The screen being shared |
permissions | What the project lets this client publish |
facingMode | "user" (front) or "environment" (back): the camera in use; null without a camera |
| Call | |
|---|---|
publish(options?) | Ask for the permissions, then open the camera and microphone and publish them. Options: the client's publish options, audio, video and facingMode |
unpublish() | Stop publishing everything and release the devices |
setMicEnabled(on), setCamEnabled(on) | Switch one off or on |
setAudioQuality(quality), setVideoQuality(quality), setVideoFrameRate(frameRate) | Change what is sent while live; each resolves to what is used |
switchCamera(): Promise<boolean> | Front and back on the same track and producer; resolves true when the front camera is now in use. media-unavailable before a camera is published |
startScreenShare(options?), stopScreenShare() | Android screen sharing, after the system's consent |
VideoTrack
<VideoTrack> shows one video track in react-native-webrtc's RTCView.
| Prop | Type | Default | |
|---|---|---|---|
track | MediaStreamTrackLike | null | undefined | Required. The video: a remote track's track, or useLocalMedia().stream?.getVideoTracks()[0]; nothing while null. Typed by shape (react-native-webrtc's track; a remote track's track fits), so no DOM library is needed | |
producerId | string | A remote video's id: adaptive video from this view's size and visibility. Leave it out for your own camera | |
mirror | boolean | false | Flip horizontally |
objectFit | "cover" | "contain" | "cover" | The view filled and cropped, or the whole picture with bars |
zOrder | number | Which overlapping video view is drawn on top: 0 for a full-screen video, 1 for a preview over it | |
visible | boolean | true | false pauses a remote video at the server while the view cannot be seen |
onDimensionsChange | (size: { width: number; height: number }) => void | The first frame arrived, or the size changed | |
style | StyleProp<ViewStyle> | ||
testID | string |
One native stream per track, shared by its views and released before the connection it came on closes.
AudioTrack
<AudioTrack> renders nothing: remote sound plays without it.
| Prop | Type | Default | |
|---|---|---|---|
track | MediaStreamTrackLike | null | undefined | Required. A remote audio track; nothing while null | |
volume | number | 0 to 1, clamped, this track only: 1 is the track as received, and unset leaves it as it is | |
muted | boolean | false | Silence this track on this phone; restored when false or on unmount |
useAudioOutput, setSpeakerphoneOn and selectAudioOutput
useAudioOutput(): AudioOutput returns where call audio plays and the calls that change it. Android only.
| Field | |
|---|---|
supported | Outputs can be chosen: Android with the SDK's native module, in the communication audio mode. False on iOS and in the media mode |
outputs | MediaDevice[]: the loudspeaker, the earpiece, and the headsets connected now, each with deviceId, label and type (speaker, earpiece, wiredHeadset, bluetooth, usb, other) |
current | The output in use, or null while no client holds the audio |
speakerphone | The loudspeaker is in use, or, while no call holds the audio, preferred |
select(deviceId): Promise<void> | Play call audio on that output while it stays connected, even over a headset. bad-params for an output that is not there, and in the media mode |
setSpeakerphoneOn(on): Promise<void> | The loudspeaker or the earpiece when no headset is connected; a headset still wins |
setSpeakerphoneOn(on): Promise<void> and selectAudioOutput(deviceId): Promise<void> are the same calls as functions. They work before any client exists, and the choice holds for the calls joined later. They reject with media-unavailable on iOS (data.platform "ios") and where the SDK's native module is missing.
useCallService
useCallService() returns the call service's state, for a settings screen or a warning.
| Field | |
|---|---|
declared | The app's manifest declares VoltkitCallService |
running | It runs now |
types | The foreground service types it runs with: microphone, mediaPlayback, camera, mediaProjection; empty when stopped |
The provider starts it when a call connects, if declared and callService is not false, and stops it when the call ends. Its notification has a Leave button, which ends the call, and swiping the app away ends the call too.
requestMediaPermissions, checkMediaPermissions and usePermissions
PermissionKind is "camera", "microphone" or "notifications" (the call's notification, Android 13 and later), and PermissionStatus is "granted", "denied" (the dialog may still show) or "blocked" (only the app's settings can grant it).
requestMediaPermissions(kinds): Promise<PermissionStatuses> | Show the system's dialogs for those not granted yet, and resolve with each one's status. Never rejects |
checkMediaPermissions(kinds): Promise<PermissionStatuses> | Each one's status now, without asking. Never rejects |
usePermissions(kinds): Permissions | statuses (null until first checked, and checked again whenever the app comes back to the front), granted (every one granted), request() and refresh() |
publish() asks for the camera and microphone by itself; these are for asking earlier, on a screen before the call. The SDK's own captures afterwards, such as a reconnect, only check and never ask: a dialog must not open in the background. On Android they use PermissionsAndroid; on iOS, react-native-webrtc's permission requests, where a refusal is blocked from then on. Below Android 13 there is no notification permission to ask for.
createClient and reactNativeClientOptions
createClient(wsUrl: string, token: string, options?: CreateClientOptions): StreamClient makes a StreamClient for React Native, not yet connected. CreateClientOptions is { background?: BackgroundOptions; clientOptions?: ClientOptions }, where BackgroundOptions is { pauseCamera?: boolean; pauseMic?: boolean } (defaults true and false). Pass the client to the provider's client prop, or call connect() yourself, and close() it when you are done: the provider never closes a client it was given. It throws media-unavailable (webrtc-missing) where react-native-webrtc's native module is missing. Never make one with new StreamClient(...) alone, which would look for a browser.
reactNativeClientOptions(options?: CreateClientOptions): ClientOptions returns the options such a client is made with: handlerName: "ReactNative106", platform: reactNativePlatform(), pauseCameraInBackground and pauseMicInBackground from background, and then your clientOptions, which win.
reactNativePlatform
reactNativePlatform(): ClientPlatform is the platform the client runs on in React Native (ClientOptions.platform of @voltkit-rtc/client 0.9.12): capture, devices and audio routing, streams and their release order, the app's visibility from the process lifecycle, the network, and native timers. One object per process, shared by every client. createClient and the provider pass it for you.
preflight
preflight(wsUrl: string, token: string, options?: ReactNativePreflightOptions): Promise<PreflightReport> is the pre-call check: it joins with a client of its own, publishes for a few seconds (5 s unless durationMs says otherwise) and leaves. React Native cannot make a test tone or picture, so without media it publishes the microphone when its permission is already granted, and otherwise tests the join alone; it never asks for a permission. The options are the client's PreflightOptions (media, durationMs, clientOptions) and background. It never rejects: read ok, then steps, path, rttMs, lossPct, uploadKbps, level and warnings. Give it a token for a test room of its own.
registerVoltkitGlobals
registerVoltkitGlobals(): boolean registers react-native-webrtc's globals, which mediasoup-client needs. Importing the package calls it, and calling it again does nothing. It returns false, without throwing, where react-native-webrtc's native module is missing.
SDK_VERSION
SDK_VERSION is this package's version, "0.1.0": what getDiagnostics() reports, beside react-native-webrtc's version and the Android version. When the app's native code was built from another version, the SDK warns in the log to build the app again.
Types
One import covers a component. This package's own: VoltkitProviderProps, AudioSessionOptions, CallServiceText, CallServiceState, BackgroundOptions, CreateClientOptions, ReactNativePreflightOptions, LocalMedia, LocalPublishOptions, VideoTrackProps, AudioTrackProps, MediaStreamLike, MediaStreamTrackLike, MediaTrackSettingsLike (react-native-webrtc's stream, track and track settings by shape: what useLocalMedia().stream and the track props are, with no DOM library needed), AudioOutput, PermissionKind, PermissionStatus, PermissionStatuses and Permissions. The React SDK's: RoomState, RemoteTrackFilter and WalletLowState. The client's: StreamClient, ClientOptions, ClientPlatform, ClosedReason, ConnectionState, ErrorCode, JoinResult, MediaDevice, AudioDeviceType, CameraFacing, DeviceKind, ParticipantInfo, PublishOptions, PreflightReport, RemoteTrack, Role, ChatMessage and the rest. They are types only: import values such as RpcError, ChatClient and isTerminalError from @voltkit-rtc/client.
The Expo config plugin
"@voltkit-rtc/react-native" in app.json's plugins, with the options cameraPermission, microphonePermission, android.camera, android.callService, android.callServiceCamera, android.screenShare, android.audioMode, android.hardwareEchoCancellation, ios.backgroundAudio and ios.voip. Defaults and what each writes: Configure the plugin.
Android manifest
What an app without Expo writes by hand, all in android/app/src/main/AndroidManifest.xml:
| Entry | |
|---|---|
RECORD_AUDIO, CAMERA | The microphone, and the camera if you publish video |
<uses-feature android:name="android.hardware.microphone" android:required="false" />, and the same for android.hardware.camera, android.hardware.camera.autofocus and android.hardware.camera.front with CAMERA | Google Play reads those permissions as hardware the app cannot do without and hides it from devices without them, unless the manifest marks them as not required |
FOREGROUND_SERVICE, FOREGROUND_SERVICE_MICROPHONE, FOREGROUND_SERVICE_MEDIA_PLAYBACK, POST_NOTIFICATIONS | The call service |
<service android:name="com.voltkit.rtc.reactnative.VoltkitCallService" android:exported="false" android:foregroundServiceType="microphone|mediaPlayback" /> | The call service; add camera or mediaProjection to the types, with their permissions, to keep the camera in the background or share the screen |
in.voltkit.rtc.AUDIO_MODE | Meta-data: communication (the default) or media |
in.voltkit.rtc.HW_AEC | Meta-data: the phone's own echo canceller, true (the default) or false |
in.voltkit.rtc.SCREEN_SHARE | Meta-data: react-native-webrtc's screen-capture service, false (the default) or true, with FOREGROUND_SERVICE_MEDIA_PROJECTION |
INTERNET, ACCESS_NETWORK_STATE, MODIFY_AUDIO_SETTINGS and the startup provider come with the SDK's own manifest. The full blocks are in the guide: Calls in the background and Audio routing.
Last updated . Something unclear or wrong? Tell us.