import { RTCViewPipIOS } from "@stream-io/video-react-native-sdk";
<>
<RTCViewPipIOS />
<MyComponent />
</>;Picture in picture
Picture-in-picture (PiP) mode displays calls in a movable small window while using other apps.
iOS
Setup
CallContent activates PiP automatically when backgrounding during active calls. Disable with disablePictureInPicture prop.
For custom components, use RTCViewPipIOS:
Mirroring in PiP
PiP mirroring follows the same mirror behavior as participant rendering:
CallContent: setmirrorto force mirrored/unmirrored video in PiP.RTCViewPipIOS: setmirrorto force mirrored/unmirrored video when building custom PiP rendering.
For layout-level controls, see Call layouts.
<CallContent mirror={false} /><RTCViewPipIOS mirror={false} />If mirror is not provided, default behavior is used (local front camera mirrored, other camera tracks unmirrored). Screen share is never mirrored.
Current user camera
iOS requires multitasking camera access for background camera use. Enabled automatically with voip in UIBackgroundModes (iOS 18+) or the multitasking-camera-access entitlement.
Enabling
Enable local camera in PiP:
CallContent: SetiOSPiPIncludeLocalParticipantVideototrueRTCViewPipIOS: SetincludeLocalParticipantVideototrue
Then apply native changes:
Update AppDelegate
Add header after #import "AppDelegate.h":
#import <WebRTCModuleOptions.h>Add to didFinishLaunchingWithOptions:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
WebRTCModuleOptions *options = [WebRTCModuleOptions sharedInstance];
options.enableMultitaskingCameraAccess = YES;
// the rest
}Add to app.json plugins:
{
"plugins": [
[
"@stream-io/video-react-native-sdk",
{
"iOSEnableMultitaskingCameraAccess": true
}
]
]
}If Expo EAS build is not used, please do npx expo prebuild --clean to generate the native directories again after adding the config plugins.
Android
Setup
Changes to AndroidManifest
Add to MainActivity in AndroidManifest.xml:
<activity>
...
android:name=".MainActivity"
android:supportsPictureInPicture="true"
android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"
...
</activity>Changes to MainActivity
Add imports:
import android.content.res.Configuration
import com.streamvideo.reactnative.StreamVideoReactNative
import android.os.Build
import androidx.lifecycle.Lifecycleimport android.content.res.Configuration;
import com.streamvideo.reactnative.StreamVideoReactNative;
import android.os.Build;
import androidx.lifecycle.Lifecycle;Add functions:
fun onPictureInPictureModeChanged(isInPictureInPictureMode: Boolean, newConfig: Configuration) {
super.onPictureInPictureModeChanged(isInPictureInPictureMode)
if (isFinishing) {
return
}
if (lifecycle.currentState === Lifecycle.State.CREATED) {
// when user clicks on Close button of PIP
finishAndRemoveTask()
} else {
StreamVideoReactNative.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig)
}
}
override fun onUserLeaveHint() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O &&
Build.VERSION.SDK_INT < Build.VERSION_CODES.S &&
StreamVideoReactNative.canAutoEnterPictureInPictureMode) {
val config = resources.configuration
onPictureInPictureModeChanged(true, config)
}
}@Override
public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode, Configuration newConfig) {
super.onPictureInPictureModeChanged(isInPictureInPictureMode);
if (isFinishing()) {
return;
}
if (getLifecycle().getCurrentState() == Lifecycle.State.CREATED) {
// when user clicks on Close button of PIP
finishAndRemoveTask();
} else {
StreamVideoReactNative.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig);
}
}
@Override
protected void onUserLeaveHint() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O &&
Build.VERSION.SDK_INT < Build.VERSION_CODES.S &&
StreamVideoReactNative.Companion.getCanAutoEnterPictureInPictureMode()) {
Configuration config = getResources().getConfiguration();
onPictureInPictureModeChanged(true, config);
}
}Add to app.json plugins:
{
"plugins": [
[
"@stream-io/video-react-native-sdk",
{
"androidPictureInPicture": true
}
]
]
}If Expo EAS build is not used, please do npx expo prebuild --clean to generate the native directories again after adding the config plugins.
Keep call alive in the background
PiP triggers background mode on Android. Add a foreground service to keep calls alive. See Keeping Call Alive.
Entering PiP mode
CallContent activates PiP on home button press. Disable with disablePictureInPicture prop.
For custom components, use useAutoEnterPiPEffect:
import { useAutoEnterPiPEffect } from "@stream-io/video-react-native-sdk";
useAutoEnterPiPEffect();Choosing what to render on PiP mode
PiP windows are small - render only essential content. CallContent automatically shows only the dominant speaker.
For custom rendering, use useIsInPiPMode:
import { useIsInPiPMode } from "@stream-io/video-react-native-sdk";
const isInPiPMode = useIsInPiPMode();Use this boolean to conditionally render content during PiP mode.
When using CallContent, you can also set mirror to control whether the rendered participant video is mirrored in Android PiP:
<CallContent mirror={false} />