SDKs
Video Calls in Expo Apps with Development Builds
Run VoltKit RTC calls in an Expo app: a development build with the config plugin, permissions, EAS profiles, Android first, iOS in preview. Not Expo Go.
An Expo app makes VoltKit RTC calls with the React Native SDK, @voltkit-rtc/react-native, in a development build: your own build of the app, with the native code that WebRTC needs, and Expo's fast refresh and tools as before. The SDK's config plugin writes the Android permissions, the call service and the iOS usage strings into the build, so you edit no native file. Expo Go cannot run it: see Why Expo Go cannot work. Android is tested; iOS is a preview.
What you need
- Expo SDK 58 or newer. VoltKit tests with Expo SDK 58 (
expo58.0.19, React Native 0.88.0). expo-dev-client, which turns your build into a development build.- For local builds, Android Studio's SDK and an Android phone with USB debugging, or the Android emulator. For builds in the cloud, an Expo account and EAS Build.
- For iOS (preview), a Mac with Xcode.
Install
npx expo install @voltkit-rtc/react-native @voltkit-rtc/react @voltkit-rtc/client react-native-webrtc expo-dev-client
@voltkit-rtc/client, @voltkit-rtc/react and react-native-webrtc are the SDK's peer dependencies, so the app holds one copy of each. Do not add @config-plugins/react-native-webrtc as well: the SDK's own plugin sets up react-native-webrtc, without the overlay permission that plugin adds.
Configure the plugin
Add the SDK to the plugins in app.json (or app.config.ts):
{
"expo": {
"plugins": [
[
"@voltkit-rtc/react-native",
{
"cameraPermission": "Your camera is shown to the people in your call.",
"microphonePermission": "Your microphone is heard by the people in your call."
}
]
]
}
}
| Option | Default | What it does |
|---|---|---|
cameraPermission | the app's own Info.plist text if it has one, else Allow $(PRODUCT_NAME) to use your camera in calls. | The iOS camera prompt's text (NSCameraUsageDescription) |
microphonePermission | the app's own Info.plist text if it has one, else Allow $(PRODUCT_NAME) to use your microphone in calls. | The iOS microphone prompt's text (NSMicrophoneUsageDescription) |
android.camera | true | Declares CAMERA, with the camera, its autofocus and the front camera marked as not required (uses-feature android:required="false"), so Google Play does not hide the app from devices without them; the microphone is marked the same way, with RECORD_AUDIO. false for an app that never publishes video |
android.callService | true | Declares the call service with FOREGROUND_SERVICE, FOREGROUND_SERVICE_MICROPHONE, FOREGROUND_SERVICE_MEDIA_PLAYBACK and POST_NOTIFICATIONS, so calls go on in the background. false declares no foreground service, and Google Play then needs no declaration, but a call drops a few seconds after the app leaves the screen |
android.callServiceCamera | false | Adds the camera type and FOREGROUND_SERVICE_CAMERA, for an app that keeps the camera on in the background (background={{ pauseCamera: false }} on the provider) |
android.screenShare | false | Turns on react-native-webrtc's screen-capture service for screen sharing, with FOREGROUND_SERVICE_MEDIA_PROJECTION, and adds the mediaProjection type to the call service, which takes it only while a share is live. That screen-capture service is a foreground service with a notification of its own, so even with the call service off it brings FOREGROUND_SERVICE and POST_NOTIFICATIONS. Stays off, with a prebuild warning, if blockedPermissions blocks FOREGROUND_SERVICE or FOREGROUND_SERVICE_MEDIA_PROJECTION |
android.audioMode | "communication" | "media" for an app whose users only listen: see Audio routing |
android.hardwareEchoCancellation | true | The phone's own echo canceller and noise suppressor, where it has them |
ios.backgroundAudio | true | Adds audio to UIBackgroundModes, so a call keeps its sound in the background |
ios.voip | false | Adds voip to UIBackgroundModes, for an app that integrates CallKit itself |
On Android the plugin declares RECORD_AUDIO, MODIFY_AUDIO_SETTINGS, INTERNET and ACCESS_NETWORK_STATE, CAMERA unless you turn it off, the call service and its permissions unless you turn it off, and the in.voltkit.rtc.* meta-data the SDK reads when the app starts. For the permissions it adds, it marks as not required the device features Google Play would otherwise read as required, <uses-feature android:required="false" /> for android.hardware.camera, android.hardware.camera.autofocus, android.hardware.camera.front and android.hardware.microphone; the app's own uses-feature entries are left as they are. It never adds a Bluetooth permission, which call audio does not need, or SYSTEM_ALERT_WINDOW. On iOS it writes the two usage strings and the background modes. Running it again gives the same result.
Your blockedPermissions win over the plugin: a permission the app blocks is never added, and a call-service type the block leaves unusable is not declared, so an app that blocks RECORD_AUDIO gets a mediaPlayback-only service. When no call service can run at all, because FOREGROUND_SERVICE is blocked or both the microphone and the mediaPlayback type are, the plugin declares none and prebuild warns; screen sharing goes the same way, off with a warning, when a block leaves react-native-webrtc's screen-capture service unable to run.
Expo adds a few permissions to every Android app by default that a call app does not need, SYSTEM_ALERT_WINDOW, VIBRATE and the storage permissions among them. Leave them out with blockedPermissions:
{
"expo": {
"android": {
"blockedPermissions": [
"android.permission.SYSTEM_ALERT_WINDOW",
"android.permission.VIBRATE",
"android.permission.READ_EXTERNAL_STORAGE",
"android.permission.WRITE_EXTERNAL_STORAGE"
]
}
}
}
Build and run
npx expo run:android
This generates the Android project from app.json, builds the app with its native code and installs it on the connected phone or emulator. From then on, npx expo start serves your JavaScript to that build, with fast refresh, as Expo Go would. Build again whenever native code changes: after installing or upgrading the SDK or react-native-webrtc, and after changing the plugin's options, with npx expo prebuild --clean first so the Android project is generated anew. Without --clean, the plugin updates the meta-data and the call service in place, but the permissions and uses-feature entries an earlier run wrote stay, since Expo never removes them: turning android.camera off, for example, leaves CAMERA in the manifest until a clean prebuild.
EAS Build
To build in the cloud instead, add profiles to eas.json:
{
"build": {
"development": { "developmentClient": true, "distribution": "internal" },
"preview": { "distribution": "internal" },
"production": {}
}
}
eas build --platform android --profile development
Install the build on the phone, then run npx expo start. The production profile builds the release app for Google Play.
Permissions, the call service and Google Play
- Permissions at run time.
publish()asks for the microphone and camera before it opens them;requestMediaPermissionsasks earlier. A refusal rejects withmedia-unavailableand the permission's status: see Permissions. On Android 13 and later, ask for notifications too, so the call's notification shows. - The call service starts when a call connects, while the app is visible, and keeps the call's network, microphone and sound going in the background and with the screen off. See Calls in the background.
- Google Play. An app that declares foreground service types declares each one in Play Console, with a description and a short video. The plugin's defaults declare
microphoneandmediaPlayback; withandroid.screenSharethe app also declaresmediaProjection, and withandroid.callServiceCamera,camera: what to write.
Release builds
Release builds are what VoltKit tests: R8-minified, with Hermes. The SDK carries its own R8 rules, including the one react-native-webrtc 155.0.1 needs, so expo-build-properties needs no extra ProGuard rules for it. If your app sets minSdkVersion below 24, raise it, since react-native-webrtc needs Android 7.0:
{
"expo": {
"plugins": [
["expo-build-properties", { "android": { "minSdkVersion": 24, "enableMinifyInReleaseBuilds": true } }]
]
}
}
iOS (preview)
npx expo run:ios on a Mac builds the iOS app; the plugin writes the usage strings and the audio background mode. VoltKit has not compiled or run the SDK on iOS. Audio routing, the call service and screen sharing are Android only. See iOS preview.
Why Expo Go cannot work
Expo Go is one app, built by Expo, with the native modules Expo ships. react-native-webrtc and this SDK bring native code of their own, which only a build of your app contains. In Expo Go the SDK does not crash: it reports media-unavailable with data.reason set to "webrtc-missing", and a message that says it needs a development build. A development build keeps what Expo Go gives you, fast refresh, the developer menu and npx expo start, with your app's own native code.
Troubleshooting
- "needs a development build": the app is running in Expo Go, or in a development build made before the SDK was installed. Build it again.
- A plugin option has no effect: the Android project was generated before the change. Run
npx expo prebuild --clean, then build again. - Prebuild warns
» android: @voltkit-rtc/react-native android.callService: VoltkitCallService is not declared …or» android: @voltkit-rtc/react-native android.screenShare: screen sharing is off …: ablockedPermissionsconflict. The call service, or react-native-webrtc's screen-capture service, needs a permission your app blocks, which the warning names, so the plugin left it out rather than declare a service that cannot run. Unblock that permission, or turn the option off to silence the warning. @voltkit-rtc/react-native: the Expo config plugin's build, plugin/build, is missing: the app uses a checkout of the SDK, not the published package, which ships the build. Runnpm run build:plugininreact-native-sdk, then prebuild again.- Expo cannot find the plugin: install
@voltkit-rtc/react-nativein the app's own folder, then build again. - The call drops in the background: the call service is off, or could not start. See The call drops about 6 seconds after the app goes to the background.
- Everything else: the React Native SDK's troubleshooting.
Last updated . Something unclear or wrong? Tell us.