Background calls
Both Android and iOS support calls running in the background, but they use different approaches:
- Android: Uses foreground services to keep the app active in the background
- iOS: Uses CallKit integration to maintain VoIP calls in the background
Below is configuration required to make it work:
- Expo
- Bare workflow
You need to modify app.json
file and add our plugin:
{ "expo": { ... "plugins": { ... [ "@fishjam-cloud/react-native-client", { "android": { "enableForegroundService": true }, "ios": { "enableVoIPBackgroundMode": true } } ], ... } } }
Android Configuration
You need to add the following service to AndroidManifest.xml
:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"> ... <application ...> ... <service android:name="io.fishjam.reactnative.FishjamForegroundService" android:foregroundServiceType="camera|microphone|mediaProjection"/> </application> </manifest>
iOS Configuration
You need to add VoIP background mode in Info.plist
:
<key>UIBackgroundModes</key> <array> <string>voip</string> </array>
Usage
- Android
- iOS
You can use useForegroundService
hook to handle how foreground service behaves on Android.
If you want to use enableCamera
or enableMicrophone
,
user must first grant permission for this resource. useForegroundService
will check if permission is
granted and only then allow to start a service.
import {
useForegroundService ,useCamera ,useMicrophone , } from "@fishjam-cloud/react-native-client"; const {isCameraOn } =useCamera (); const {isMicrophoneOn } =useMicrophone ();useForegroundService ({channelId : "io.fishjam.example.fishjamchat.foregroundservice.channel",channelName : "Fishjam Chat Notifications",notificationTitle : "Your video call is ongoing",notificationContent : "Tap to return to the call.",enableCamera :isCameraOn ,enableMicrophone :isMicrophoneOn , });
On iOS, background calls are achieved through CallKit integration. You can use the CallKit hooks to manage VoIP calls that continue running in the background.
Manual CallKit Management
Use the useCallKit
hook for fine-grained control over CallKit sessions:
import {
useCallKit } from "@fishjam-cloud/react-native-client"; const {startCallKitSession ,endCallKitSession } =useCallKit (); // Start CallKit session when joining a room consthandleJoinRoom = async () => { awaitstartCallKitSession ({displayName : "John Doe",isVideo : true, }); // ... join room logic }; // End CallKit session when leaving consthandleLeaveRoom = async () => { awaitendCallKitSession (); // ... leave room logic };
Automatic CallKit Management
Use the useCallKitService
hook for automatic session lifecycle management:
import
React from "react"; import {useCallKitService } from "@fishjam-cloud/react-native-client"; import {View } from "react-native"; functionCallScreen ({username }: {username : string }) { // CallKit session automatically starts when component mounts // and ends when component unmountsuseCallKitService ({displayName :username ,isVideo : true, }); return <View >...</View >; }
Listening to CallKit Events
Use the useCallKitEvent
hook to respond to user interactions with the native CallKit interface:
import {
useCallKitEvent ,useCamera ,useMicrophone ,useConnection , } from "@fishjam-cloud/react-native-client"; const {toggleCamera } =useCamera (); const {startMicrophone ,stopMicrophone } =useMicrophone (); const {leaveRoom } =useConnection (); // Listen for mute toggle from CallKit UIuseCallKitEvent ("muted", (isMuted : boolean) => { if (isMuted ) {stopMicrophone (); } else {startMicrophone (); } }); // Listen for hold state changesuseCallKitEvent ("held", (isOnHold : boolean) => {console .log ("Call hold state:",isOnHold ); // Handle hold state in your app }); // Listen for call end from CallKit UIuseCallKitEvent ("ended", () => { // Handle call terminationleaveRoom (); });