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 ;
-};
+});