From f1112bbc8f674c54efb9de5d6472dd385c68964a Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Mon, 20 Apr 2026 12:13:42 +0200 Subject: [PATCH] Fix omni-view --- .../main/java/dev/zoriya/omni/OmniPlayer.kt | 11 +- .../src/main/java/dev/zoriya/omni/OmniView.kt | 84 ++++++---- example/App.tsx | 143 ++++++++---------- src/view.tsx | 11 +- 4 files changed, 134 insertions(+), 115 deletions(-) diff --git a/android/src/main/java/dev/zoriya/omni/OmniPlayer.kt b/android/src/main/java/dev/zoriya/omni/OmniPlayer.kt index 2c38deb..769b270 100644 --- a/android/src/main/java/dev/zoriya/omni/OmniPlayer.kt +++ b/android/src/main/java/dev/zoriya/omni/OmniPlayer.kt @@ -16,7 +16,10 @@ class OmniPlayer : HybridOmniPlayerSpec() { override val eventMap = EventMap(player) init { - player.setOptionString("vo", "null") + // `vo` is effectively fixed after init in libmpv. + // Initializing with `null` keeps audio working but prevents video output forever. + player.setOptionString("vo", "gpu-next") + player.setOptionString("force-window", "yes") player.setOptionString("gpu-context", "android") player.setOptionString("opengl-es", "yes") player.setOptionString("hwdec", "mediacodec-copy") @@ -46,6 +49,7 @@ class OmniPlayer : HybridOmniPlayerSpec() { } override var source: Source by deferredObservable { _, _, new -> + Log.e("omni", "Chaning source") player.command(arrayOf("stop")) val src = new.src.firstOrNull() ?: return@deferredObservable @@ -76,6 +80,11 @@ class OmniPlayer : HybridOmniPlayerSpec() { } } + fun setSurfaceSize(width: Int, height: Int) { + if (width <= 0 || height <= 0) return + player.setPropertyString("android-surface-size", "${width}x${height}") + } + override val hasPrev get() = source.metadata?.hasPrev ?: false override val hasNext get() = source.metadata?.hasNext ?: false override val status: PlayerStatus diff --git a/android/src/main/java/dev/zoriya/omni/OmniView.kt b/android/src/main/java/dev/zoriya/omni/OmniView.kt index 2e6d341..a3182c2 100755 --- a/android/src/main/java/dev/zoriya/omni/OmniView.kt +++ b/android/src/main/java/dev/zoriya/omni/OmniView.kt @@ -1,61 +1,57 @@ package dev.zoriya.omni +import android.util.Log import android.view.SurfaceHolder import android.view.SurfaceView import android.view.View +import android.widget.FrameLayout import com.facebook.react.uimanager.ThemedReactContext import com.margelo.nitro.omni.HybridOmniPlayerSpec import com.margelo.nitro.omni.HybridOmniViewSpec -class OmniView(val context: ThemedReactContext) : HybridOmniViewSpec() { - private val surfaceView = SurfaceView(context) +class OmniView(val context: ThemedReactContext) : HybridOmniViewSpec(), SurfaceHolder.Callback { + override val view = FrameLayout(context) + + private val surfaceView = SurfaceView(context).apply { + layoutParams = FrameLayout.LayoutParams( + FrameLayout.LayoutParams.MATCH_PARENT, + FrameLayout.LayoutParams.MATCH_PARENT + ) + holder.addCallback(this@OmniView) + view.addView(this) + } private var surfaceReady = false private var boundPlayer: OmniPlayer? = null - override val view: View = surfaceView - override lateinit var player: HybridOmniPlayerSpec override var autoplay: Boolean? = true override var showNotification: Boolean? = true override var autoPip: Boolean? = true - init { - surfaceView.holder.addCallback(object : SurfaceHolder.Callback { - override fun surfaceCreated(holder: SurfaceHolder) { - surfaceReady = true - boundPlayer?.setSurface(holder.surface) - } - - override fun surfaceChanged( - holder: SurfaceHolder, - format: Int, - width: Int, - height: Int - ) { - surfaceReady = true - boundPlayer?.setSurface(holder.surface) - } - - override fun surfaceDestroyed(holder: SurfaceHolder) { - surfaceReady = false - boundPlayer?.setSurface(null) - } - }) - } - override fun afterUpdate() { + Log.e("omniView", "After update called") if (!::player.isInitialized) { - throw IllegalStateException("Player is null in OmniView") + Log.w("omniView", "Skipping update because player is not set yet") + return } val omniPlayer = player as? OmniPlayer - ?: throw IllegalStateException("Player is not an OmniPlayer in OmniView") + ?: run { + Log.w( + "omniView", + "Skipping update because player has unexpected type: ${player::class.java.name}" + ) + return + } - if (boundPlayer !== omniPlayer) { - boundPlayer?.setSurface(null) - boundPlayer = omniPlayer + if (boundPlayer === omniPlayer) { + return; } + Log.e("omniView", "Resetting old player") + boundPlayer?.setSurface(null) + boundPlayer = omniPlayer + if (surfaceReady) { omniPlayer.setSurface(surfaceView.holder.surface) } @@ -66,10 +62,32 @@ class OmniView(val context: ThemedReactContext) : HybridOmniViewSpec() { } override fun onDropView() { + Log.e("omniView", "omni-view dropped") if (!::player.isInitialized) return val omniPlayer = player as? OmniPlayer ?: return omniPlayer.setSurface(null) boundPlayer = null } + + override fun surfaceCreated(holder: SurfaceHolder) { + surfaceReady = true + Log.e("omniView", "surface created") + boundPlayer?.setSurface(holder.surface) + } + + override fun surfaceChanged( + holder: SurfaceHolder, + format: Int, + width: Int, + height: Int + ) { + boundPlayer?.setSurfaceSize(width, height) + } + + override fun surfaceDestroyed(holder: SurfaceHolder) { + Log.e("omniView", "surface destroyed") + surfaceReady = false + boundPlayer?.setSurface(null) + } } diff --git a/example/App.tsx b/example/App.tsx index f3eab58..7736a14 100644 --- a/example/App.tsx +++ b/example/App.tsx @@ -1,13 +1,6 @@ import { useCallback, useMemo, useState } from "react"; import type React from "react"; -import { - Pressable, - SafeAreaView, - ScrollView, - StyleSheet, - Text, - View, -} from "react-native"; +import { Pressable, ScrollView, StyleSheet, Text, View } from "react-native"; import { OmniProvider, OmniView, @@ -129,72 +122,73 @@ function PlayerExample({ }; return ( - - - react-native-omni - {trackLabel} + + react-native-omni + {trackLabel} - - - + - - - {isPlaying ? "Pause" : "Play"} - - player.seekBy(-10)}> - -10s - - player.seekBy(10)}> - +10s - - - - - player.playPrev()}> - Prev - - player.playNext()}> - Next - - - {muted ? "Unmute" : "Mute"} - - - - - changeVolume(-0.1)}> - Vol - - - changeVolume(0.1)}> - Vol + - - - {playbackRate.toFixed(2)}x - - - - - Status: {status} - - Time: {formatTime(currentTime)} / {formatTime(duration)} - - Volume: {(volume * 100).toFixed(0)}% - - - - {logs.length === 0 ? ( - Event log will appear here. - ) : ( - logs.map((entry, index) => ( - - {entry} - - )) - )} - + + + {isPlaying ? "Pause" : "Play"} + + player.seekBy(-10)}> + -10s + + player.seekBy(10)}> + +10s + - + + + player.playPrev()}> + Prev + + player.playNext()}> + Next + + + {muted ? "Unmute" : "Mute"} + + + + + changeVolume(-0.1)}> + Vol - + + changeVolume(0.1)}> + Vol + + + + {playbackRate.toFixed(2)}x + + + + + Status: {status} + + Time: {formatTime(currentTime)} / {formatTime(duration)} + + + Volume: {(volume * 100).toFixed(0)}% + + + + + {logs.length === 0 ? ( + Event log will appear here. + ) : ( + logs.map((entry, index) => ( + + {entry} + + )) + )} + + ); } @@ -239,13 +233,11 @@ function App(): React.JSX.Element { } const styles = StyleSheet.create({ - safeArea: { - flex: 1, - backgroundColor: "#0b1020", - }, container: { flex: 1, + backgroundColor: "#0b1020", paddingHorizontal: 16, + marginTop: 64, paddingVertical: 12, gap: 12, }, @@ -263,7 +255,6 @@ const styles = StyleSheet.create({ aspectRatio: 16 / 9, borderRadius: 14, overflow: "hidden", - backgroundColor: "#000000", }, row: { flexDirection: "row", diff --git a/src/view.tsx b/src/view.tsx index 8ea8519..1657bf5 100644 --- a/src/view.tsx +++ b/src/view.tsx @@ -7,13 +7,14 @@ import { usePlayer } from "./provider"; import type { OmniPlayer } from "./specs/omni-player.nitro"; import type { Props } from "./specs/omni-view.nitro"; import type { OmniViewProps } from "./types/view"; +import type { ViewStyle } from "react-native"; +import { memo } from "react"; -const NativeView = getHostComponent( - "OmniView", - () => OmniConfig, +const NativeView = memo( + getHostComponent("OmniView", () => OmniConfig), ); -export const OmniView = (props: OmniViewProps) => { +export const OmniView = memo((props: OmniViewProps & { style: ViewStyle }) => { const player = usePlayer() as OmniPlayer; return ; -}; +});