Implementing Picture in Picture Mode
Overview
Android supports PIP mode starting from android 8.0 (API level 26). Picture in Picture (PiP) is a special case of the multi-window UI which lets the user watch a video in a small window pinned to a corner of the screen while navigating between apps or browsing content on the main screen. To add PiP to your app, you need to register your activities that support PiP, switch your activity to PiP mode as needed, and make sure UI elements are hidden and video playback continues when the activity is in PiP mode. The PiP window appears in the topmost layer of the screen, in a corner chosen by the system.
How users can interact with the PiP window
It is possible to drag the PiP window to another location. Starting in Android 12, users can also:
- Single-tap the window to display a full-screen toggle, a close button, a settings button, and custom actions provided by your app (for example, play controls).
- Double-tap the window to toggle between the current PiP size and maximum PiP size.
- Resize the PiP window using pinch-to-zoom.
Your app controls when the current activity enters PiP mode. Here are some examples:
- An activity can enter PiP mode when the user taps the home button.
- Your app can move a video into PiP mode when the user navigates back from the video to browse other content.
Declare picture-in-picture support
Register your video activity in your manifest by setting android:supportsPictureInPicture
to true
. Also, specify that your activity handles layout configuration changes as by setting android:configChanges
as mentioned below so that your activity doesn't relaunch when layout changes occur during PiP mode transitions.
<activity
android:name=".PlayerActivity"
android:configChanges="keyboardHidden|orientation|screenSize|smallestScreenSize|screenLayout"
android:supportsPictureInPicture="true">
Switch your activity to picture-in-picture
To enter picture-in-picture mode, an activity must call enterPictureInPictureMode(). For example, the following code switches an activity to PiP mode when a the user clicks the home or recent button:
- Java
- Kotlin
@Override
protected void onUserLeaveHint() {
super.onUserLeaveHint();
// switch to PiP mode if the user presses the home or recent button,
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
enterPictureInPictureMode(new PictureInPictureParams.Builder().build());
}
}
override fun onUserLeaveHint() {
super.onUserLeaveHint()
// switch to PiP mode if the user presses the home or recent button,
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
enterPictureInPictureMode(PictureInPictureParams.Builder().build())
}
}
Handle UI during picture-in-picture
When the activity enters or exits picture-in-picture mode, the system calls Activity.onPictureInPictureModeChanged() or Fragment.onPictureInPictureModeChanged(). You should override these callbacks to redraw the activity's UI elements. In PiP mode your activity is shown in a small window. PiP mode does not allow users to interact with your app's UI elements, and small details may be difficult to see. If your app needs to provide custom actions for PiP, see Add controls in this document. Remove other UI elements before entering PiP, then restore them when you return to fullscreen:
- Java
- Kotlin
@Override
public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode) {
super.onPictureInPictureModeChanged(isInPictureInPictureMode);
// Hide the full-screen UI (controls, etc.) while in picture-in-picture mode for player with custom controls.
showControls(!isInPictureInPictureMode);
// Hide any non-player UI elements when entering PiP mode for a cleaner experience.
toggleOtherElementsVisibility(!isInPictureInPictureMode);
}
private void showControls(boolean show) {
if (show) {
playerControlView.show();
} else {
playerControlView.hide();
}
}
override fun onPictureInPictureModeChanged(isInPictureInPictureMode: Boolean) {
super.onPictureInPictureModeChanged(isInPictureInPictureMode)
// Hide the full-screen UI (controls, etc.) while in picture-in-picture mode for player with custom controls.
showControls(!isInPictureInPictureMode)
// Hide any non-player UI elements when entering PiP mode for a cleaner experience.
toggleOtherElementsVisibility(!isInPictureInPictureMode)
}
private fun showControls(show: Boolean) {
if (show) {
playerControlView.show()
} else {
playerControlView.hide()
}
}
Handle audio interference with another app (Required only for player with custom controls)
Video playback in the PiP window can cause audio interference with another app. To avoid this, request audio focus when you start playing the video and handle audio focus change notifications, as described in Managing Audio Focus. In PiP mode, pause or stop video playback if you receive a notification of audio focus loss.
- Java
- Kotlin
private AudioManager audioManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
}
private boolean requestAudioFocus(AudioManager audioManager) {
return AudioManager.AUDIOFOCUS_REQUEST_GRANTED == audioManager.requestAudioFocus(audioFocusChangeListener, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
}
@Override
public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
boolean playing = playbackState != VdoPlayer.STATE_IDLE && playbackState != VdoPlayer.STATE_ENDED && playWhenReady;
if (playing) {
// Request audio focus and notify other players to stop playback.
if (!requestAudioFocus(audioManager)) {
Log.i(TAG, "Audio focus not granted");
}
} else if (playbackState == VdoPlayer.STATE_ENDED) {
// Abandon audio focus when playback complete
audioManager.abandonAudioFocus(audioFocusChangeListener);
}
}
private final AudioManager.OnAudioFocusChangeListener audioFocusChangeListener = new AudioManager.OnAudioFocusChangeListener() {
@Override
public void onAudioFocusChange(int focusChange) {
switch (focusChange) {
case AudioManager.AUDIOFOCUS_GAIN:
// Resume playing
if (player != null) player.setPlayWhenReady(true);
break;
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
// Depending on your app, reduce audio to a minimum if you want to continue playing or pause
break;
case AudioManager.AUDIOFOCUS_LOSS:
// Pause playing
if (player != null) player.setPlayWhenReady(false);
break;
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
default:
// Stop playing
break;
}
}
};
private var audioManager: AudioManager? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
audioManager = getSystemService(Context.AUDIO_SERVICE) as AudioManager
}
private fun requestAudioFocus(audioManager: AudioManager): Boolean {
return AudioManager.AUDIOFOCUS_REQUEST_GRANTED == audioManager.requestAudioFocus(audioFocusChangeListener, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN)
}
override fun onPlayerStateChanged(playWhenReady: Boolean, playbackState: Int) {
val playing = playbackState != VdoPlayer.STATE_IDLE && playbackState != VdoPlayer.STATE_ENDED && playWhenReady
if (playing) {
// Request audio focus and notify other players to stop playback.
if (!requestAudioFocus(audioManager!!)) {
Log.i(TAG, "Audio focus not granted")
}
} else if (playbackState == VdoPlayer.STATE_ENDED) {
// Abandon audio focus when playback complete
audioManager?.abandonAudioFocus(audioFocusChangeListener)
}
}
private val audioFocusChangeListener = object : AudioManager.OnAudioFocusChangeListener {
override fun onAudioFocusChange(focusChange: Int) {
when (focusChange) {
AudioManager.AUDIOFOCUS_GAIN -> {
// Resume playing
player?.setPlayWhenReady(true)
}
AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK -> {
// Depending on your app, reduce audio to a minimum if you want to continue playing or pause
}
AudioManager.AUDIOFOCUS_LOSS -> {
// Pause playing
player?.setPlayWhenReady(false)
}
AudioManager.AUDIOFOCUS_LOSS_TRANSIENT,
else -> {
// Stop playing
}
}
}
}
For more details and best practices visit Picture-in-picture (PiP) support.