androidx.core.pip
The PiP Jetpack library addresses several challenges in Android's Picture-in-Picture (PiP) mode:
-
OS Fragmentation: The library handles differences in PiP API calls across Android versions, such as
Activity#enterPictureInPictureModebefore Android S andPictureInPictureParams#isAutoEnterEnabledafter. -
Incorrect PiP Parameters: It provides a unified solution for setting correct
PictureInPictureParams, especially for playback, to ensure smooth animations (e.g., source rect hint). -
Unified PiP State Callbacks: The library consolidates
Activity#onPictureInPictureModeChangedandActivity#onPictureInPictureUiStateChangedinto a single, unified callback interface viaPictureInPictureDelegate.OnPictureInPictureEventListenerfor simplified state management.
Furthermore, all new PiP features will be delivered through the Jetpack library, ensuring that library adopters can access these features with minimal to no effort.
Usage of the library
This library depends on the latest androidx.core library, and it's recommended to use the ComponentActivity from the latest androidx.activity as well
-
androidx.core:1.18.0-rc01+
-
androidx.activity:1.13.0-rc01+ (optional, highly recommended)
The code snippets below would assume the application references both.
Navigation and Video Call applications
For these usages
-
Application does not need to specify the
sourceRectHint -
The
seamlessResizeEnabledflag is set toFALSEfor non-video playback content; for smoother crossfading animation. -
Typically, does not listen on
ENTER_ANIMATION_STARTandENTER_ANIMATION_ENDevents
// Pseudo code in Kotlin
class NavigationActivity :
ComponentActivity(), PictureInPictureDelegate.OnPictureInPictureEventListener {
private lateinit var pictureInPictureImpl: BasicPictureInPicture
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
pictureInPictureImpl = BasicPictureInPicture(this)
pictureInPictureImpl.addOnPictureInPictureEventListener(getMainExecutor(), this)
}
override fun onPictureInPictureEvent(
event: PictureInPictureDelegate.Event,
newConfig: Configuration?
) {
when (event) {
PictureInPictureDelegate.Event.ENTERED -> {
/* Change to PiP layout*/
}
PictureInPictureDelegate.Event.STASHED -> {
/* Optional: PiP is now in stashed state */
}
PictureInPictureDelegate.Event.UNSTASHED -> {
/* Optional: PiP is now in unstashed state */
}
PictureInPictureDelegate.Event.EXITED -> {
/* Change to full-screen layout*/
}
}
}
private fun onNavigationStateChanged(isInActiveNavigation: Boolean) {
pictureInPictureImpl.apply {
setEnabled(isInActiveNavigation)
setAspectRatio(desiredAspectRatio)
setActions(actions)
}
}
}Video Playback applications
For the video playback usage
-
Application can specify the player view, and the library can continuously track the view bounds as
sourceRectHint -
The
seamlessResizeEnabledflag is set toTRUE -
It's highly recommended to listen on
ENTER_ANIMATION_STARTevent to hide the overlays upon the video to achieve a cleaner entering PiP animation
// Pseudo code in Kotlin
class VideoPlaybackActivity :
ComponentActivity(), PictureInPictureDelegate.OnPictureInPictureEventListener {
private lateinit var pictureInPictureImpl: VideoPlaybackPictureInPicture
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
pictureInPictureImpl = VideoPlaybackPictureInPicture(this)
pictureInPictureImpl.addOnPictureInPictureEventListener(getMainExecutor(), this)
}
override fun onDestroy() {
super.onDestroy()
pictureInPictureImpl.close()
}
override fun onPictureInPictureEvent(
event: PictureInPictureDelegate.Event,
newConfig: Configuration?
) {
when (event) {
PictureInPictureDelegate.Event.ENTER_ANIMATION_START -> {
/* Optional: hide overlays that are hidden in PiP mode. */
}
PictureInPictureDelegate.Event.ENTER_ANIMATION_END -> {
/* Optional: the animation to enter PiP ends */
}
PictureInPictureDelegate.Event.ENTERED -> {
/* Change to PiP layout*/
}
PictureInPictureDelegate.Event.STASHED -> {
/* Optional: PiP is now in stashed state */
}
PictureInPictureDelegate.Event.UNSTASHED -> {
/* Optional: PiP is now in unstashed state */
}
PictureInPictureDelegate.Event.EXITED -> {
/* Change to full-screen layout*/
}
}
}
private fun onPlaybackStateChanged(isPlaying: Boolean) {
pictureInPictureImpl.apply {
setEnabled(isPlaying)
setPlayerView(if (isPlaying) playerView else null)
setAspectRatio(videoAspectRatio)
setActions(actions)
}
}
}Migration from platform APIs
-
Keep the AndroidManifest requirements on the activity.
Example
<activity android:name="VideoActivity"
android:supportsPictureInPicture="true"
android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation">
</activity> -
Include the required dependencies.
Example
dependencies {
implementation("androidx.core:core:1.18.0-rc01")
implementation("androidx.activity:activity:1.13.0-rc01")
implementation("androidx.core:core-pip:1.0.0-beta01")
} -
Select the template that best fits your use case
-
BasicPictureInPicture: Best for Navigation and Video Call apps where seamless resize is not typically supported, and no source rect hint is needed.
-
VideoPlaybackPictureInPicture: Designed for video playback. It automatically tracks player view bounds for the source rect hint and enables seamless resize by default.
-
Entering PiP
-
Legacy implementation
-
Apps must differentiate by API level: Manually call
enterPictureInPictureinOnUserLeaveHint(< Android S) or usesetAutoEnabled(boolean)(>= Android S). -
Jetpack implementation
-
Developers no longer need to manage API-specific logic for PiP. You can eliminate previous
onUserLeaveHintcode and instead triggersetEnabled(boolean)on your template instance whenever the PiP eligibility status changes. -
Receiving PiP Callbacks
-
Legacy implementation
-
Relies on separate callbacks:
onPictureInPictureModeChangedfor layout toggling andonPictureInPictureUiStateChangedfor animation/stashing states -
Jetpack implementation
-
Uses a unified event-based callback via
addOnPictureInPictureEventListener, coveringENTERED,EXITED,STASHED,UNSTASHEDand etc. in one place. -
Providing actions in the PiP menu
-
Legacy implementation
-
Updating
PictureInPictureParamsmanually whenever actions change. -
Jetpack implementation
-
Updating via
setActions(list)on your template instance whenever actions change.
Interfaces
PictureInPictureDelegate.OnPictureInPictureEventListener |
Unified listener interface for |
Classes
BasicPictureInPicture |
Basic Picture-in-Picture implementation. |
PictureInPictureDelegate |
A delegate class to help set up PiP (Picture-in-Picture) functionalities on behalf of the given |
PictureInPictureDelegate.Event |
Represents the PiP event emitted from the system. |
VideoPlaybackPictureInPicture |
Picture-in-Picture implementation optimized for Video Playback applications. |