Picture In Picture - Android Studio - Compose
How to add Picture In Picture Mode in the Android app?
Android 8.0 (API level 26) allows activities to launch in the picture-in-picture (PIP) mode. PIP is a special type of multi-window mode mostly used for video playback. It 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. The PIP window appears in the top layer of the screen in a corner chosen by the system.
Step 1: Create a new project OR Open your project
Step 2: Create another activity named PIPActivity
Step 3: Add the following properties to the PIPActivity in AndroidManifest
<activity android:name=".PIPActivity"
android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"
android:launchMode="singleTask"
android:resizeableActivity="true"
android:supportsPictureInPicture="true"/>
<activity android:name=".PIPActivity" android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation" android:launchMode="singleTask" android:resizeableActivity="true" android:supportsPictureInPicture="true"/>
Step 3: Code
AndroidMenifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.blogspot.atifsoftwares.pictureinpicture">
<!-- internet permission -->
<uses-permission android:name="android.permission.INTERNET" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.PictureInPicture">
<activity android:name=".PIPActivity"
android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"
android:launchMode="singleTask"
android:resizeableActivity="true"
android:supportsPictureInPicture="true"/>
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.blogspot.atifsoftwares.pictureinpicture"> <!-- internet permission --> <uses-permission android:name="android.permission.INTERNET" /> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/Theme.PictureInPicture"> <activity android:name=".PIPActivity" android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation" android:launchMode="singleTask" android:resizeableActivity="true" android:supportsPictureInPicture="true"/> <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>
MainActivity.kt
package com.technifysoft.myapplication import android.content.Context import android.content.Intent import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.material3.Button import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.technifysoft.myapplication.ui.theme.MyApplicationTheme class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) enableEdgeToEdge() setContent { VideoSelectionScreen() } } } // video urls private const val videoUrlOne = "https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4" private const val videoUrlTwo = "https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/WeAreGoingOnBullrun.mp4" private const val videoUrlThree = "https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/VolkswagenGTIReview.mp4" @OptIn(ExperimentalMaterial3Api::class) @Composable fun VideoSelectionScreen() { val context = LocalContext.current Scaffold( topBar = { TopAppBar(title = { Text("Select a Video") }) } ) { padding -> Column( modifier = Modifier .padding(padding) .padding(16.dp) .fillMaxSize(), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally ) { VideoButton("Video One") { playVideo(context, videoUrlOne) } Spacer(modifier = Modifier.height(12.dp)) VideoButton("Video Two") { playVideo(context, videoUrlTwo) } Spacer(modifier = Modifier.height(12.dp)) VideoButton("Video Three") { playVideo(context, videoUrlThree) } } } } @Composable fun VideoButton(text: String, onClick: () -> Unit) { Button( onClick = onClick, modifier = Modifier .fillMaxWidth(0.9f) .height(55.dp), shape = MaterialTheme.shapes.medium ) { Text(text) } } private fun playVideo(context: Context, videoUrl: String?) { //Intent to start activity, with video url val intent = Intent(context, PIPActivity::class.java) .apply { putExtra("videoURL", videoUrl) } context.startActivity(intent) } /** * GreetingPreview is a composable function for previewing the MainUI in Android Studio. * It is annotated with @Preview to enable live preview. * */ @Preview(showBackground = true) @Composable private fun GreetingPreview() { MyApplicationTheme { VideoSelectionScreen() } }
PIPActivity.kt
package com.technifysoft.myapplication import android.app.PictureInPictureParams import android.content.Intent import android.content.res.Configuration import android.os.Build import android.os.Bundle import android.util.Log import android.util.Rational import android.widget.MediaController import android.widget.VideoView import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.material3.Button import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.viewinterop.AndroidView import androidx.core.net.toUri import com.technifysoft.myapplication.ui.theme.MyApplicationTheme class PIPActivity : ComponentActivity() { private var videoUrl: String? = null private var videoView: VideoView? = null private var pipParams: PictureInPictureParams.Builder? = null private val TAG = "PIP_TAG" override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) enableEdgeToEdge() videoUrl = intent.getStringExtra("videoURL") if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { pipParams = PictureInPictureParams.Builder() } setContent { MaterialTheme { // ✅ Place Composable content inside a proper @Composable block VideoPlayerScreen( videoUrl = videoUrl, onEnterPip = { enterPipMode() }, onVideoViewReady = { videoView = it } ) } } } private fun enterPipMode() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && videoView != null) { val aspectRatio = Rational(videoView!!.width, videoView!!.height) pipParams?.setAspectRatio(aspectRatio) enterPictureInPictureMode(pipParams!!.build()) Log.d(TAG, "enterPipMode: Entered PIP mode") } else { Log.d(TAG, "enterPipMode: PIP not supported or VideoView null") } } override fun onUserLeaveHint() { super.onUserLeaveHint() if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && !isInPictureInPictureMode) { Log.d(TAG, "onUserLeaveHint: Entering PIP mode") enterPipMode() } } override fun onPictureInPictureModeChanged(isInPictureInPictureMode: Boolean, newConfig: Configuration) { super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig) Log.d(TAG, "PIP Mode Changed: $isInPictureInPictureMode") } override fun onNewIntent(intent: Intent) { super.onNewIntent(intent) videoUrl = intent.getStringExtra("videoURL") videoUrl?.let { videoView?.setVideoURI(it.toUri()) videoView?.start() } } override fun onStop() { super.onStop() videoView?.stopPlayback() } } @Composable fun VideoPlayerScreen( videoUrl: String?, onEnterPip: () -> Unit, onVideoViewReady: (VideoView) -> Unit ) { val context = LocalContext.current Column( modifier = Modifier .fillMaxSize() .padding(16.dp), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally ) { // ✅ Wrap AndroidView inside Box or Column — not directly inside @UiComposable slots Box( modifier = Modifier .fillMaxWidth() .aspectRatio(16 / 9f) ) { AndroidView( factory = { ctx -> val videoView = VideoView(ctx) val mediaController = MediaController(ctx) mediaController.setAnchorView(videoView) videoView.setMediaController(mediaController) videoUrl?.let { val uri = it.toUri() videoView.setVideoURI(uri) videoView.setOnPreparedListener { mp -> Log.d("PIP_TAG", "Video Prepared, Playing...") mp.start() } } onVideoViewReady(videoView) videoView }, modifier = Modifier.fillMaxSize() ) } Spacer(modifier = Modifier.height(24.dp)) Button( onClick = onEnterPip, modifier = Modifier .fillMaxWidth(0.8f) .height(50.dp) ) { Text("Enter Picture-in-Picture Mode") } } } /** * GreetingPreview is a composable function for previewing the MainUI in Android Studio. * It is annotated with @Preview to enable live preview. * */ @Preview(showBackground = true) @Composable private fun GreetingPreview() { MyApplicationTheme { //VideoPlayerScreen() } }
Comments
Post a Comment