mirror of
https://github.com/zoriya/react-native-omni.git
synced 2026-08-05 05:36:17 +00:00
Implement pip
This commit is contained in:
@@ -268,12 +268,7 @@ class OmniPlayerService : MediaSessionService() {
|
||||
lateinit var player: Player
|
||||
lateinit var mediaSession: MediaSession
|
||||
|
||||
init {
|
||||
Log.e("omni", "service inited")
|
||||
}
|
||||
|
||||
override fun onCreate() {
|
||||
Log.e("omni", "service created")
|
||||
super.onCreate()
|
||||
player = OmniPlayer.notificationPlayer ?: throw Error("No player available")
|
||||
val launchIntent = packageManager.getLaunchIntentForPackage(packageName)?.apply {
|
||||
|
||||
@@ -1,15 +1,42 @@
|
||||
package dev.zoriya.omni
|
||||
|
||||
import android.app.PictureInPictureParams
|
||||
import android.os.Build
|
||||
import android.util.Log
|
||||
import android.util.Rational
|
||||
import android.view.SurfaceHolder
|
||||
import android.view.SurfaceView
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.FrameLayout
|
||||
import androidx.core.view.isVisible
|
||||
import com.facebook.react.bridge.LifecycleEventListener
|
||||
import com.facebook.react.uimanager.ThemedReactContext
|
||||
import com.margelo.nitro.omni.HybridOmniPlayerSpec
|
||||
import com.margelo.nitro.omni.HybridOmniViewSpec
|
||||
|
||||
class OmniView(val context: ThemedReactContext) : HybridOmniViewSpec(), SurfaceHolder.Callback {
|
||||
override val view = FrameLayout(context)
|
||||
class OmniView(val context: ThemedReactContext) :
|
||||
HybridOmniViewSpec(),
|
||||
SurfaceHolder.Callback,
|
||||
LifecycleEventListener {
|
||||
override val view = object : FrameLayout(context) {
|
||||
// React Native manages layout via Yoga and swallows requestLayout() calls from native
|
||||
// views. When children are added/removed (e.g. during PIP transitions), addView triggers
|
||||
// requestLayout() but it never results in a measure+layout pass. Override to force one.
|
||||
// see: https://github.com/facebook/react-native/blob/d19afc73f5048f81656d0b4424232ce6d69a6368/ReactAndroid/src/main/java/com/facebook/react/views/toolbar/ReactToolbar.java#L166
|
||||
private val layoutRunnable = Runnable {
|
||||
measure(
|
||||
MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
|
||||
MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)
|
||||
)
|
||||
layout(left, top, right, bottom)
|
||||
}
|
||||
|
||||
override fun requestLayout() {
|
||||
super.requestLayout()
|
||||
post(layoutRunnable)
|
||||
}
|
||||
}
|
||||
|
||||
private val surfaceView = SurfaceView(context).apply {
|
||||
layoutParams = FrameLayout.LayoutParams(
|
||||
@@ -21,11 +48,18 @@ class OmniView(val context: ThemedReactContext) : HybridOmniViewSpec(), SurfaceH
|
||||
}
|
||||
private var surfaceReady = false
|
||||
private var boundPlayer: OmniPlayer? = null
|
||||
private var rootContent: ViewGroup? = null
|
||||
private var rootContentViews: List<View> = emptyList()
|
||||
private var movedSurfaceToRootForPip = false
|
||||
|
||||
override lateinit var player: HybridOmniPlayerSpec
|
||||
override var autoplay: Boolean? = true
|
||||
override var autoPip: Boolean? = true
|
||||
|
||||
init {
|
||||
context.addLifecycleEventListener(this)
|
||||
}
|
||||
|
||||
override fun afterUpdate() {
|
||||
Log.e("omniView", "After update called")
|
||||
if (!::player.isInitialized) {
|
||||
@@ -51,6 +85,9 @@ class OmniView(val context: ThemedReactContext) : HybridOmniViewSpec(), SurfaceH
|
||||
}
|
||||
|
||||
override fun onDropView() {
|
||||
restoreUiAfterPip()
|
||||
context.removeLifecycleEventListener(this)
|
||||
|
||||
if (!::player.isInitialized) return
|
||||
|
||||
val omniPlayer = player as? OmniPlayer ?: return
|
||||
@@ -58,6 +95,95 @@ class OmniView(val context: ThemedReactContext) : HybridOmniViewSpec(), SurfaceH
|
||||
boundPlayer = null
|
||||
}
|
||||
|
||||
override fun onHostResume() {
|
||||
Log.e("omni", "resume")
|
||||
restoreUiAfterPip()
|
||||
}
|
||||
|
||||
override fun onHostPause() {
|
||||
Log.e("omni", "pause")
|
||||
if (autoPip != true) {
|
||||
return
|
||||
}
|
||||
|
||||
val activity = context.currentActivity ?: return
|
||||
if (activity.isFinishing || activity.isDestroyed || activity.isInPictureInPictureMode) {
|
||||
return
|
||||
}
|
||||
|
||||
val omniPlayer = boundPlayer ?: return
|
||||
if (!omniPlayer.isPlaying) {
|
||||
return
|
||||
}
|
||||
|
||||
val aspectRatio = if (surfaceView.width > 0 && surfaceView.height > 0) {
|
||||
Rational(surfaceView.width, surfaceView.height)
|
||||
} else {
|
||||
Rational(16, 9)
|
||||
}
|
||||
|
||||
val params = PictureInPictureParams.Builder()
|
||||
.setAspectRatio(aspectRatio)
|
||||
.build()
|
||||
isolateUiForPip(activity)
|
||||
val enteredPip = activity.enterPictureInPictureMode(params)
|
||||
if (!enteredPip) {
|
||||
restoreUiAfterPip()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onHostDestroy() {
|
||||
restoreUiAfterPip()
|
||||
}
|
||||
|
||||
private fun isolateUiForPip(activity: android.app.Activity) {
|
||||
if (movedSurfaceToRootForPip) {
|
||||
return
|
||||
}
|
||||
|
||||
val root = activity.window.decorView.findViewById<ViewGroup>(android.R.id.content) ?: return
|
||||
rootContent = root
|
||||
|
||||
rootContentViews = (0 until root.childCount)
|
||||
.map { root.getChildAt(it) }
|
||||
.filter { child -> child.isVisible }
|
||||
rootContentViews.forEach { child ->
|
||||
child.visibility = View.GONE
|
||||
}
|
||||
|
||||
view.removeView(surfaceView)
|
||||
root.addView(
|
||||
surfaceView,
|
||||
FrameLayout.LayoutParams(
|
||||
FrameLayout.LayoutParams.MATCH_PARENT,
|
||||
FrameLayout.LayoutParams.MATCH_PARENT
|
||||
)
|
||||
)
|
||||
movedSurfaceToRootForPip = true
|
||||
}
|
||||
|
||||
private fun restoreUiAfterPip() {
|
||||
if (!movedSurfaceToRootForPip) {
|
||||
return
|
||||
}
|
||||
|
||||
val root = rootContent ?: return
|
||||
root.removeView(surfaceView)
|
||||
rootContent = null
|
||||
|
||||
rootContentViews.forEach { child ->
|
||||
child.visibility = View.VISIBLE
|
||||
}
|
||||
rootContentViews = emptyList()
|
||||
|
||||
view.addView(surfaceView, FrameLayout.LayoutParams(
|
||||
FrameLayout.LayoutParams.MATCH_PARENT,
|
||||
FrameLayout.LayoutParams.MATCH_PARENT
|
||||
))
|
||||
|
||||
movedSurfaceToRootForPip = false
|
||||
}
|
||||
|
||||
override fun surfaceCreated(holder: SurfaceHolder) {
|
||||
surfaceReady = true
|
||||
boundPlayer?.setSurface(holder)
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
android:label="@string/app_name"
|
||||
android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize|uiMode"
|
||||
android:launchMode="singleTask"
|
||||
android:resizeableActivity="true"
|
||||
android:supportsPictureInPicture="true"
|
||||
android:windowSoftInputMode="adjustResize"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
|
||||
Reference in New Issue
Block a user