How to implement Floating Window (PIP) mode in Jetpack Compose?
Last Updated :
22 Jun, 2025
In a video player app, whenever the app is closed (paused) while the video is playing, the video player turns into a small window floating over the screen. This is called as a Floating Window, or Picture in Picture (PIP) mode. In this article, we will learn how to implement that PIP mode in Jetpack Compose.
Note: PiP mode is supported on Android 8.0 (API level 26) and higher.
What is PiP Mode?
Picture-in-Picture (PiP) mode is a special type of multi-window mode that allows users to continue watching video content in a small, resizable floating window while performing other tasks in the background. This feature is especially useful for multitasking such as replying to messages, browsing the web, or navigating apps without interrupting the video playback.
Features of PiP Mode
The PiP mode has several features which includes the following:
- Seamless transition between full screen and pip mode.
- Touch and drag functionality to reposition the window.
- Playback controls (play, pause, skip) optionally available in the pip interface.
- Responsive resizing based on screen space and orientation.
Step by Step Implementation
Step 1: Create a new project
To create a new project in the Android Studio, please refer to How to Create a new Project in Android Studio with Jetpack Compose.
Step 2: Create a Broadcast Receiver
First, let's create a Broadcast Receiver to receive UI events from action buttons while the screen is in PIP mode. Navigate to app > kotlin+java > {package-name}, right click on the folder and select, New > Kotlin Class/File and set the file name as PipReceiver.kt.
PipReceiver.kt:
Kotlin
package com.geeksforgeeks.demo
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
/**
* PipReceiver listens for broadcast actions triggered from Picture-in-Picture (PiP) mode.
* Specifically, it responds to the custom play/pause action added to the PiP controls.
*/
class PipReceiver : BroadcastReceiver() {
/**
* Called when the broadcast associated with the PiP action is received.
* This triggers playback to toggle (play or pause) via the VideoController.
*/
override fun onReceive(context: Context, intent: Intent) {
VideoController.togglePlayPause() // Toggle video playback state
}
}
Step 3: Working with manifest file
Navigate to app > manifests > AndroidManifest.xml and make the following changes as follows. First, we need to register the broadcast receiver under the application tag similar to how we register an activity.
<receiver android:name=".PipReceiver"/>
Finally, inside the activity scope of MainActivity or the activity you will be using, we need to allow support for Picture-In-Picture Mode and add config changes.
<activity
android:name=".MainActivity"
android:exported="true"
android:supportsPictureInPicture="true"
android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"
android:theme="@style/Theme.Demo">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
Step 4: Create a Video Playback Manager
Create a Kotlin class file VideoController.kt as similar to previous steps. This file provides centralized methods to start/pause the video.It maintains a WeakReference to the VideoView to avoid memory leaks. As a singleton object, it allows toggling video playback from outside the UI (e.g., PiP actions).
VideoController.kt:
Kotlin
package com.geeksforgeeks.demo
import android.widget.VideoView
import java.lang.ref.WeakReference
/**
* VideoController is a singleton object that provides centralized control over video playback.
* It allows external components (like the PiP Receiver) to toggle playback without direct access to the VideoView.
*/
object VideoController {
// A weak reference to the VideoView to avoid memory leaks
private var videoViewRef: WeakReference<VideoView>? = null
/**
* Registers the VideoView instance for external control.
* Stores it as a weak reference to prevent memory leaks.
*/
fun setVideoView(videoView: VideoView) {
videoViewRef = WeakReference(videoView)
}
/**
* Toggles the video playback state.
* If the video is playing, it pauses it; otherwise, it starts playback.
*/
fun togglePlayPause() {
videoViewRef?.get()?.let { videoView ->
if (videoView.isPlaying) {
videoView.pause()
} else {
videoView.start()
}
}
}
}
Step 5: Create a manager class for Picture-In-Picture Mode
This class can handle everything related to entering PiP mode, including setting bounds, aspect ratio, and actions. We will creating a custom RemoteAction (like a play/pause button) visible in the PiP window. Create a Kotlin Class named PipManager.kt and make the following changes as follows.
PipManager.kt
package com.geeksforgeeks.demo
import android.app.PendingIntent
import android.app.PictureInPictureParams
import android.app.RemoteAction
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.graphics.Rect
import android.graphics.drawable.Icon
import android.net.Uri
import android.os.Build
import android.util.Rational
import androidx.annotation.RequiresApi
/**
* PipManager handles all Picture-in-Picture (PiP) related functionality,
* such as entering PiP mode and setting up custom actions.
*/
class PipManager(private val context: Context) {
// Holds the bounds of the video on the screen, used to hint PiP position
private var videoBounds: Rect = Rect()
companion object {
// Static video URL used by the player
val VIDEO_URL: Uri =
Uri.parse("https://www.sample-videos.com/video321/mp4/720/big_buck_bunny_720p_30mb.mp4")
}
// Check if PiP is supported on the current device
private val isPipSupported: Boolean
get() = context.packageManager.hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE)
/**
* Updates the bounds of the video view. These bounds are used as a source hint
* for positioning the PiP window when it appears.
*/
fun updateBounds(bounds: Rect) {
videoBounds = bounds
}
/**
* Enters Picture-in-Picture mode if supported by the device and OS version.
* Configures the PiP parameters such as aspect ratio, source rect hint,
* and adds a custom action (play/pause button).
*/
fun enterPipModeIfSupported() {
// Exit early if PiP is not supported or the API level is below 26
if (!isPipSupported || Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return
// Cast context to Activity (required for PiP mode)
val activity = context as? android.app.Activity ?: return
// Build PiP parameters
val pipParams = PictureInPictureParams.Builder()
.setSourceRectHint(videoBounds) // Hint for initial PiP window placement
.setAspectRatio(Rational(16, 9)) // Set fixed aspect ratio
.setActions(listOf(buildRemoteAction())) // Add custom PiP action
.build()
// Request system to enter PiP mode
activity.enterPictureInPictureMode(pipParams)
}
/**
* Creates a custom RemoteAction (e.g. Play/Pause button) that appears in the PiP window.
* The action sends a broadcast, which is received by PipReceiver to toggle playback.
*/
@RequiresApi(Build.VERSION_CODES.O)
private fun buildRemoteAction(): RemoteAction {
// Create an Intent to broadcast when the PiP action is clicked
val intent = Intent(context, PipReceiver::class.java)
// Wrap the intent in a PendingIntent (required for RemoteAction)
val pendingIntent = PendingIntent.getBroadcast(
context,
0,
intent,
PendingIntent.FLAG_IMMUTABLE
)
// Create and return the RemoteAction with an icon, title, description, and intent
return RemoteAction(
Icon.createWithResource(context, R.drawable.play_pause), // Icon shown in PiP
"Play/Pause", // Title
"Toggle playback", // Description
pendingIntent // Action intent
)
}
}
play_pause.xml
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="32dp"
android:height="32dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:pathData="M200,648v-336l240,168 -240,168ZM520,640v-320h80v320h-80ZM680,640v-320h80v320h-80Z"
android:fillColor="#e8eaed"/>
</vector>
Step 6: Create a composable for Video Player
Create a new Kotlin file with the name VideoPlayerScreen.kt and create a new composable inside the file which displays the video using VideoView inside a Compose layout via AndroidView. It starts playback automatically, registers the view with VideoController for external control, and reports its screen bounds to PipManager for proper Picture-in-Picture positioning. This composable bridges traditional views with modern Compose UI while enabling PiP support.
VideoPlayerScreen.kt:
Kotlin
package com.geeksforgeeks.demo
import android.net.Uri
import android.widget.VideoView
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.toAndroidRect
import androidx.compose.ui.layout.boundsInWindow
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.viewinterop.AndroidView
/**
* Composable function that displays a VideoView inside a Compose UI using AndroidView interop.
*
* @param videoUrl The URL of the video to be played.
* @param onBoundsChanged Callback to report the video’s on-screen position for PiP purposes.
*/
@Composable
fun VideoPlayerScreen(
videoUrl: Uri,
onBoundsChanged: (android.graphics.Rect) -> Unit
) {
AndroidView(
factory = { context ->
// Creates a VideoView and configures it to play the given video
VideoView(context).apply {
setVideoURI(videoUrl) // Set the video source
start() // Start playback automatically
VideoController.setVideoView(this) // Register with VideoController for external control (e.g., play/pause from PiP)
}
},
modifier = Modifier
.fillMaxWidth() // Makes the video view fill the width of the parent
.onGloballyPositioned {
// Captures the position and size of the VideoView on the screen
// Converts Compose Rect to Android Rect for compatibility with PiP APIs
onBoundsChanged(it.boundsInWindow().toAndroidRect())
}
)
}
Step 7: Working with MainActivity.kt
In this file, we will define the VideoPlayerScreen composable with video url and layout bounds from the PipManager. We have overridden the onUserLeaveHint() to detect when the app is paused to turn on the PiP mode.
MainActivity.kt:
Kotlin
package com.geeksforgeeks.demo
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.material3.MaterialTheme
/**
* MainActivity is the entry point of the app and hosts the Jetpack Compose UI.
* It also integrates Picture-in-Picture (PiP) functionality using PipManager.
*/
class MainActivity : ComponentActivity() {
// Manages Picture-in-Picture logic such as entering PiP mode and updating bounds
private lateinit var pipManager: PipManager
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Initialize PipManager with the current Activity context
pipManager = PipManager(this)
// Set the Compose UI content
setContent {
MaterialTheme {
// Display the video player and provide a callback to report its screen bounds
VideoPlayerScreen(
videoUrl = PipManager.VIDEO_URL, // Static video URL
onBoundsChanged = { pipManager.updateBounds(it) } // Report video bounds to PipManager
)
}
}
}
/**
* Called when the user is about to leave the activity (e.g., pressing the Home button).
* This is the trigger point for entering Picture-in-Picture mode.
*/
override fun onUserLeaveHint() {
super.onUserLeaveHint()
pipManager.enterPipModeIfSupported() // Attempt to enter PiP mode if supported
}
}
Output:
Refer to the following github repo to get the entire code: PIP_Mode_Jetpack_Compose
Similar Reads
Implement Vertical and Horizontal Draggable Modifier in Android using Jetpack Compose In Jetpack Compose, a Draggable modifier is a modifier similar to scrollable, which is the high-level entry point to drag gestures in a single orientation and report the drag distance in pixels. Draggable modifier can be implemented on top of any element. Users can hold the element state and drag it
3 min read
Popup Window in Android using Jetpack Compose Pop Up Window is seen in most of the applications to show the pop-up messages within the applications to the user. These pop-ups are used to display an offer image, alerts, and other important information within the android application. In this article, we will take a look at How to implement Pop Up
5 min read
How to implement Picture in Picture (PIP) in Android? In this article, it is explained how to implement a Picture in Picture (PIP) in an Android application. We have seen in many apps such as Google Maps while using navigation that when we close the app, there is a floating screen that appears at the bottom right of the screen as shown in the image bel
3 min read
How to Add Margin in Android using Jetpack Compose? In Android, Padding is used to offset the content of the view by a specific number of pixels from either direction, i.e., padding from left, right, top and bottom. Using Padding, we can create multiple borders to a view by applying a combination of multiple padding and border. Â So in this article,
2 min read
Horizontal ListView in Android using Jetpack Compose Many Android Applications present data in the form of horizontally scrollable lists. This list can be scrolled horizontally. In this article, we will take a look at How to Implement Horizontal ListView in Android Applications using Jetpack Compose. Note: If you are seeking Java code for Jetpack Com
6 min read