Background Handling - React Native
Due to platform-specific restrictions on iOS and Android, essential functionalities like microphone and camera access are limited when the app moves to Background Mode.
-
iOS has strict resource management policies, preventing access to the camera in the background. However, it allows microphone access, which is crucial for maintaining media transmission.
-
Android 14 and above restrict background microphone and camera access and enforce strict battery optimization policies. These policies can aggressively kill background processes.
To ensure seamless media transmission in VideoSDK when the app is in background, you must address these platform-specific limitations by:
- Enable the required background modes on iOS.
- Configure permissions and use foreground services on Android.
Alternatively, if you prefer not to implement a foreground service, you can use Picture-in-Picture (PiP) mode on both iOS and Android. This allows the app to continue media playback in a small floating window, bypassing some of the background restrictions.
If these steps aren’t correctly implemented, the app will lose the ability to send or receive media when backgrounded, disrupting core communication features.
The sections below explain how to implement background support effectively on both platforms with VideoSDK.
iOS Background Handling
For iOS devices, you need to enable the necessary capabilities in Xcode to continue publishing and playing media while the app is in the background:
- Open your project in Xcode.
- Navigate to the target settings.
- Select the "Signing & Capabilities" tab.
- Click the "+" button to add capabilities.
- Add Background Modes.
Under Background Modes, enable the following options:
- Audio, AirPlay, and Picture in Picture
This will allow your app to handle audio and video in the background, ensuring that essential media features like calls continue uninterrupted.

Android Background Handling
On Android, background media capture is more restrictive. Challenges include:
- Limited microphone/camera access when the app is backgrounded (usually ~60 seconds by default).
- OEM-specific optimizations that aggressively kill background processes.
To overcome this, you must run a foreground service so that Android treats your app as an actively running process.
Update your AndroidManifest.xml
to declare permissions and the foreground service:
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Permissions -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MICROPHONE"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_CAMERA"/>
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
<!-- Foreground Service -->
<application>
<service android:name=".MyForegroundService"
android:foregroundServiceType="camera|microphone"
android:stopWithTask="true" />
</application>
</manifest>
Implement a foreground service that shows a persistent notification and keeps audio/video capture running:
package com.myapp
import android.app.*
import android.content.Intent
import android.os.Build
import android.os.IBinder
import androidx.core.app.NotificationCompat
import android.util.Log
class MyForegroundService : Service() {
companion object {
const val NOTIFICATION_ID = 1001
const val CHANNEL_ID = "MyAppChannelID"
private const val TAG = "MyForegroundService"
const val ACTION_START = "com.myapp.START_FOREGROUND_SERVICE"
const val ACTION_STOP = "com.myapp.STOP_FOREGROUND_SERVICE"
}
override fun onCreate() {
super.onCreate()
createNotificationChannel()
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
when (intent?.action) {
ACTION_START -> startForegroundService()
ACTION_STOP -> stopForegroundService()
else -> startForegroundService()
}
return START_STICKY
}
override fun onBind(intent: Intent?): IBinder? = null
private fun startForegroundService() {
try {
val notification = createNotification()
startForeground(NOTIFICATION_ID, notification)
} catch (e: SecurityException) {
Log.e(TAG, "Security exception while starting foreground service", e)
throw e
} catch (e: Exception) {
Log.e(TAG, "Error starting foreground service", e)
throw e
}
}
private fun stopForegroundService() {
stopForeground(true)
stopSelf()
}
private fun createNotification(): Notification {
val notificationIntent = Intent(this, MainActivity::class.java)
val pendingIntent = PendingIntent.getActivity(
this,
0,
notificationIntent,
PendingIntent.FLAG_IMMUTABLE
)
return NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle("MyApp Foreground Service")
.setContentText("Camera and microphone are active")
.setSmallIcon(R.mipmap.ic_launcher)
.setContentIntent(pendingIntent)
.setPriority(NotificationCompat.PRIORITY_LOW)
.setOngoing(true)
.build()
}
private fun createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel(
CHANNEL_ID,
"MyApp Foreground Service",
NotificationManager.IMPORTANCE_LOW
).apply {
description = "Channel for foreground service notifications"
setShowBadge(false)
enableLights(false)
enableVibration(false)
}
val notificationManager=getSystemService(NotificationManager::class.java)
notificationManager?.createNotificationChannel(channel)
}
}
}
Expose methods to control the service from React Native:
package com.myapp
import android.content.Intent
import android.util.Log
import com.facebook.react.bridge.*
import com.facebook.react.modules.core.DeviceEventManagerModule
class ForegroundServiceModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) {
companion object {
const val NAME = "ForegroundServiceModule"
private const val TAG = "ForegroundServiceModule"
}
override fun getName(): String = NAME
@ReactMethod
fun startService(promise: Promise) {
try {
val intent = Intent(reactApplicationContext, MyForegroundService::class.java).apply {
action = MyForegroundService.ACTION_START
}
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
reactApplicationContext.startForegroundService(intent)
} else {
reactApplicationContext.startService(intent)
}
promise.resolve(true)
} catch (e: Exception) {
Log.e(TAG, "Error starting foreground service", e)
promise.reject("SERVICE_ERROR", "Failed to start foreground service", e)
}
}
@ReactMethod
fun stopService(promise: Promise) {
try {
val intent = Intent(reactApplicationContext, MyForegroundService::class.java).apply {
action = MyForegroundService.ACTION_STOP
}
reactApplicationContext.stopService(intent)
promise.resolve(true)
} catch (e: Exception) {
Log.e(TAG, "Error stopping foreground service", e)
promise.reject("SERVICE_ERROR", "Failed to stop foreground service", e)
}
}
@ReactMethod
fun isServiceRunning(promise: Promise) {
try {
val notificationManager = reactApplicationContext.getSystemService(android.app.NotificationManager::class.java)
val activeNotifications = notificationManager?.activeNotifications
val isRunning = activeNotifications?.any { it.id == MyForegroundService.NOTIFICATION_ID } ?: false
promise.resolve(isRunning)
} catch (e: Exception) {
Log.e(TAG, "Error checking service status", e)
promise.reject("SERVICE_ERROR", "Failed to check service status", e)
}
}
}
package com.myapp
import com.facebook.react.ReactPackage
import com.facebook.react.bridge.NativeModule
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.uimanager.ViewManager
class ForegroundServiceMediaPackage : ReactPackage {
override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
return listOf(ForegroundServiceModule(reactContext))
}
override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
return emptyList()
}
}
Add this new package to your application's React Native setup:
import com.myapp.ForegroundServiceMediaPackage
class MainApplication : Application(), ReactApplication {
override val reactNativeHost: ReactNativeHost =
object : DefaultReactNativeHost(this) {
override fun getPackages(): List<ReactPackage> {
val packages = PackageList(this).packages.toMutableList()
packages.add(ForegroundServiceMediaPackage())
return packages
}
}
}
Finally, request permissions and start the service from React Native:
const { ForegroundServiceModule } = NativeModules;
const requestPermissions = async () => {
if (Platform.OS !== 'android') {
return true;
}
try {
console.log('Requesting runtime permissions...');
const permissions = [
PermissionsAndroid.PERMISSIONS.RECORD_AUDIO,
PermissionsAndroid.PERMISSIONS.CAMERA,
PermissionsAndroid.PERMISSIONS.POST_NOTIFICATIONS
];
const granted = await PermissionsAndroid.requestMultiple(permissions);
console.log('Permission results:', granted);
const allGranted = Object.values(granted).every(
permission => permission === PermissionsAndroid.RESULTS.GRANTED
);
if (allGranted) {
console.log('All permissions granted');
return true;
} else {
console.log('Some permissions denied:', granted);
return false;
}
} catch (err) {
console.error('Error requesting permissions:', err);
return false;
}
};
export default function App() {
useEffect(() => {
if (Platform.OS === 'android') {
if (ForegroundServiceModule) {
const initializeService = async () => {
const hasPermissions = await requestPermissions();
if (hasPermissions) {
try {
await ForegroundServiceModule.startService();
console.log('Foreground service started automatically');
} catch (e) {
console.error('Failed to start foreground service :', e);
}
}
};
setTimeout(initializeService, 1000);
} else {
console.error('ForegroundServiceModule is not available');
}
}
}, []);
}
In above code com.example.example
is our package name kindly replace with yours.
Got a Question? Ask us on discord