How-to Add Picture in Picture

Enable picture-in-picture for Android devices

Picture-in-Picture (PiP) was introduced with Android 8.0 (API level 26). This feature allows you to support a floating video frame that persists even when the user sends the application to the background or switches to another application.

Refer to the Interactive Player sample application to learn more about implementing the code needed for PiP feature in a fully functioning Android App.

👍

Getting Started with Android SDK

If you haven't already, begin by following the Getting Started tutorials to become familiar with the concepts to create an application that can publish and/or subscribe using the Android SDK.

1. Add a Centralized PipViewModel

Create a ViewModel called PipViewModel that manages the state of whether PiP mode is enabled or not. This ViewModel should expose a StateFlow or a Flow that emits the current state of PiP mode.

Here's an example implementation using Hilt for dependency injection:

@HiltViewModel
class PipViewModel @Inject constructor() : ViewModel() {
    private val _isPipEnabled = MutableStateFlow(false)
    val isPipEnabled = _isPipEnabled.asStateFlow()

    fun enablePip(enable: Boolean) {
        viewModelScope.launch {
            _isPipEnabled.emit(enable)
        }
    }
}

2. Enable/Disable PiP mode from different screens

In the screens where you want to enable or disable PiP mode, inject the PipViewModel and call the enablePip method with the desired state.

@Composable
fun ExampleScreen(pipViewModel: PipViewModel) {
    // Disable PiP mode in this screen
    pipViewModel.enablePip(false)
    // ...
}

3. Add a utility function to enable PiP mode

Create a utility @Composable function that listens to the isPipEnabled flow from the PipViewModel and enters or exits PiP mode accordingly.

@Composable
fun EnablePipMode(enablePipMode: Boolean, content: @Composable () -> Unit) {
    val currentShouldEnterPipMode = rememberUpdatedState(newValue = enablePipMode)
    val context = LocalContext.current

    DisposableEffect(context) {
        val onUserLeaveBehavior: () -> Unit = {
            if (currentShouldEnterPipMode.value) {
                context.findActivity()
                    .enterPictureInPictureMode(PictureInPictureParams.Builder().build())
            }
        }

        context.findActivity().addOnUserLeaveHintListener(onUserLeaveBehavior)

        onDispose {
            context.findActivity().removeOnUserLeaveHintListener(onUserLeaveBehavior)
        }
    }

    content()
}

4. Add a utility function to check if the app is in PiP mode

Create a utility @Composable function that checks if the app is currently in PiP mode or not. This function can be used in different composables to show or hide views based on the PiP mode.

@Composable
fun rememberIsInPipMode(): Boolean {
    val activity = LocalContext.current.findActivity()
    var pipMode by remember { mutableStateOf(activity.isInPictureInPictureMode) }

    DisposableEffect(activity) {
        val observer = Consumer<PictureInPictureModeChangedInfo> { info ->
            pipMode = info.isInPictureInPictureMode
        }

        activity.addOnPictureInPictureModeChangedListener(observer)

        onDispose {
            activity.removeOnPictureInPictureModeChangedListener(observer)
        }
    }

    return pipMode
}

5. Setup your Activity to use PiP

In your AndroidManifest.xml, add the following attributes to your Activity to enable PiP mode:

<activity
    android:name=".MainActivity"
    android:configChanges="orientation|screenLayout|screenSize|smallestScreenSize"
    android:supportsPictureInPicture="true" />

6. Use the utility functions in your root composable

In your root composable (e.g., MainScreen), use the EnablePipMode and rememberIsInPipMode functions to manage the PiP mode and show/hide views accordingly.

@Composable
fun MainScreen(appViewModel: PipViewModel) {
    val currentShouldEnterPipMode by appViewModel.isPipEnabled.collectAsStateWithLifecycle()
    val inPipMode = rememberIsInPipMode()

    EnablePipMode(enablePipMode = currentShouldEnterPipMode) {
        // All app screens composable views
        if (!inPipMode) {
            // Show/hide your views
        }
    }
}

By following these steps, you should be able to add Picture-In-Picture playback functionality to your application. The PipViewModel manages the state of PiP mode, and you can enable or disable it from different screens. The utility functions handle entering and exiting PiP mode, as well as checking if the app is currently in PiP mode.