Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

adding pip #51

Open
wants to merge 2 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
2 changes: 1 addition & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[versions]
gradle = "8.5.2"
gradle = "8.7.2"
kotlin-gradle-plugin = "1.9.25"
ktx = "1.13.1"
lifecycle-compose = "2.8.5"
Expand Down
2 changes: 1 addition & 1 deletion gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#Mon Nov 20 16:01:06 CET 2023
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
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