Skip to content

Commit

Permalink
add pip
Browse files Browse the repository at this point in the history
  • Loading branch information
m-derakhshan committed Nov 12, 2024
1 parent 1ddc58e commit 027fed9
Show file tree
Hide file tree
Showing 3 changed files with 153 additions and 2 deletions.
3 changes: 2 additions & 1 deletion app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@
tools:targetApi="31">
<activity
android:name=".MainActivity"
android:configChanges="orientation|screenSize|screenLayout|keyboardHidden"
android:supportsPictureInPicture="true"
android:configChanges="orientation|screenLayout|keyboardHidden|screenSize|smallestScreenSize"
android:exported="true"
android:label="@string/app_name"
android:theme="@style/Theme.THEOplayerAndroidUI">
Expand Down
136 changes: 136 additions & 0 deletions ui/src/main/java/com/theoplayer/android/ui/PipHandler.kt
Original file line number Diff line number Diff line change
@@ -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<PictureInPictureModeChangedInfo> { 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<ViewGroup>(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<ViewGroup>(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
}
16 changes: 15 additions & 1 deletion ui/src/main/java/com/theoplayer/android/ui/UIController.kt
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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<View>(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
Expand Down Expand Up @@ -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()
}
Expand Down

0 comments on commit 027fed9

Please sign in to comment.