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:

import { RTCViewPipIOS } from "@stream-io/video-react-native-sdk";

<>
  <RTCViewPipIOS />
  <MyComponent />
</>;

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: Set iOSPiPIncludeLocalParticipantVideo to true
  • RTCViewPipIOS: Set includeLocalParticipantVideo to true

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
}

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.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.