diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 33139e7..6c1eac1 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -18,7 +18,8 @@ tools:targetApi="31"> diff --git a/ui/src/main/java/com/theoplayer/android/ui/PipHandler.kt b/ui/src/main/java/com/theoplayer/android/ui/PipHandler.kt new file mode 100644 index 0000000..589075e --- /dev/null +++ b/ui/src/main/java/com/theoplayer/android/ui/PipHandler.kt @@ -0,0 +1,136 @@ +package com.theoplayer.android.ui + +import android.app.PictureInPictureParams +import android.content.Context +import android.content.ContextWrapper +import android.os.Build +import android.util.Log +import android.util.Printer +import android.view.View +import android.view.ViewGroup +import androidx.activity.ComponentActivity +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.onGloballyPositioned +import androidx.compose.ui.platform.LocalContext +import androidx.core.app.PictureInPictureModeChangedInfo +import androidx.core.util.Consumer + +internal class PipHandler( + private val view: View, + private val context: Context, +) { + + private var previousViewParent: ViewGroup? = null + private var previousViewIndex: Int = 0 + private var previousViewLayoutParams: ViewGroup.LayoutParams? = null + + val activity: ComponentActivity + get() = context.findActivity() + + + @Composable + fun PipFUllScreenHandler() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + LaunchedEffect(activity) { + val observer = Consumer { info -> + if (info.isInPictureInPictureMode) { + enterPipFullScreen(context) + } else { + exitPipFullScreen(context) + } + } + activity.addOnPictureInPictureModeChangedListener(observer) + } + } else { + exitPipFullScreen(context) + } + } + + private fun Context.findActivity(): ComponentActivity { + var context = this + while (context is ContextWrapper) { + if (context is ComponentActivity) return context + context = context.baseContext + } + throw IllegalStateException("Picture in picture should be called in the context of an Activity") + } + + private fun enterPipFullScreen(context: Context) { + val activity = context.findActivity() + val rootView = activity.findViewById(android.R.id.content) + (view.parent as? ViewGroup?)?.let { parent -> + previousViewParent = parent + previousViewIndex = parent.indexOfChild(view) + previousViewLayoutParams = view.layoutParams + parent.removeView(view) + rootView.post { + rootView.addView( + view, + ViewGroup.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT + ) + ) + } + } + } + + private fun exitPipFullScreen(context: Context) { + val activity = context.findActivity() + val rootView = activity.findViewById(android.R.id.content) + previousViewParent?.let { parent -> + rootView.removeView(view) + parent.post { + parent.addView(view, previousViewIndex, previousViewLayoutParams) + view.layout(view.left, view.top, view.right, view.bottom) + } + } + previousViewParent = null + previousViewIndex = 0 + } + + +} + + +@Composable +internal fun Modifier.installPip(handler: PipHandler, player: Player): Modifier { + val context = LocalContext.current + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + handler.PipFUllScreenHandler() + this.then( + Modifier + .onGloballyPositioned { + val builder = PictureInPictureParams.Builder() + builder.setAutoEnterEnabled(player.paused.not()) + handler.activity.setPictureInPictureParams(builder.build()) + } + ) + } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + handler.PipFUllScreenHandler() + DisposableEffect(context) { + val onUserLeaveBehavior: () -> Unit = { + if (player.paused.not()) + handler.activity + .enterPictureInPictureMode(PictureInPictureParams.Builder().build()) + } + handler.activity.addOnUserLeaveHintListener( + onUserLeaveBehavior + ) + onDispose { + handler.activity.removeOnUserLeaveHintListener( + onUserLeaveBehavior + ) + } + } + this + } else + this +} \ No newline at end of file diff --git a/ui/src/main/java/com/theoplayer/android/ui/UIController.kt b/ui/src/main/java/com/theoplayer/android/ui/UIController.kt index 7385215..4966db1 100644 --- a/ui/src/main/java/com/theoplayer/android/ui/UIController.kt +++ b/ui/src/main/java/com/theoplayer/android/ui/UIController.kt @@ -1,6 +1,7 @@ package com.theoplayer.android.ui import android.app.Activity +import android.view.View import android.view.ViewGroup import androidx.compose.animation.AnimatedContent import androidx.compose.animation.AnimatedVisibility @@ -331,11 +332,24 @@ private fun PlayerContainer( player: Player, ui: @Composable () -> Unit ) { + val isPipEnabled = + true //todo: change it in a way that if user has activated pip using config, it becomes true. + val pipHandler = + player.theoplayerView?.findViewById(com.theoplayer.android.R.id.theo_player_container) + ?.let { + PipHandler(view = it, context = LocalContext.current) + } val theoplayerView = player.theoplayerView val containerModifier = Modifier .background(Color.Black) .then(modifier) .playerAspectRatio(player) + .then( + pipHandler.takeIf { isPipEnabled && pipHandler != null }?.let { + Modifier.installPip(it, player = player) + } ?: Modifier + ) + if (theoplayerView == null) { Box( modifier = containerModifier @@ -535,7 +549,7 @@ internal fun setupTHEOplayerView(theoplayerView: THEOplayerView): THEOplayerView } } - Lifecycle.Event.ON_PAUSE -> { + Lifecycle.Event.ON_STOP -> { wasPlayingAd = theoplayerView.player.ads.isPlaying theoplayerView.onPause() }