mirror of
https://github.com/zoriya/react-native-omni.git
synced 2026-08-16 02:44:51 +00:00
Fix omni-view
This commit is contained in:
@@ -16,7 +16,10 @@ class OmniPlayer : HybridOmniPlayerSpec() {
|
|||||||
override val eventMap = EventMap(player)
|
override val eventMap = EventMap(player)
|
||||||
|
|
||||||
init {
|
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("gpu-context", "android")
|
||||||
player.setOptionString("opengl-es", "yes")
|
player.setOptionString("opengl-es", "yes")
|
||||||
player.setOptionString("hwdec", "mediacodec-copy")
|
player.setOptionString("hwdec", "mediacodec-copy")
|
||||||
@@ -46,6 +49,7 @@ class OmniPlayer : HybridOmniPlayerSpec() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override var source: Source by deferredObservable { _, _, new ->
|
override var source: Source by deferredObservable { _, _, new ->
|
||||||
|
Log.e("omni", "Chaning source")
|
||||||
player.command(arrayOf("stop"))
|
player.command(arrayOf("stop"))
|
||||||
|
|
||||||
val src = new.src.firstOrNull() ?: return@deferredObservable
|
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 hasPrev get() = source.metadata?.hasPrev ?: false
|
||||||
override val hasNext get() = source.metadata?.hasNext ?: false
|
override val hasNext get() = source.metadata?.hasNext ?: false
|
||||||
override val status: PlayerStatus
|
override val status: PlayerStatus
|
||||||
|
|||||||
@@ -1,61 +1,57 @@
|
|||||||
package dev.zoriya.omni
|
package dev.zoriya.omni
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
import android.view.SurfaceHolder
|
import android.view.SurfaceHolder
|
||||||
import android.view.SurfaceView
|
import android.view.SurfaceView
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import android.widget.FrameLayout
|
||||||
import com.facebook.react.uimanager.ThemedReactContext
|
import com.facebook.react.uimanager.ThemedReactContext
|
||||||
import com.margelo.nitro.omni.HybridOmniPlayerSpec
|
import com.margelo.nitro.omni.HybridOmniPlayerSpec
|
||||||
import com.margelo.nitro.omni.HybridOmniViewSpec
|
import com.margelo.nitro.omni.HybridOmniViewSpec
|
||||||
|
|
||||||
class OmniView(val context: ThemedReactContext) : HybridOmniViewSpec() {
|
class OmniView(val context: ThemedReactContext) : HybridOmniViewSpec(), SurfaceHolder.Callback {
|
||||||
private val surfaceView = SurfaceView(context)
|
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 surfaceReady = false
|
||||||
private var boundPlayer: OmniPlayer? = null
|
private var boundPlayer: OmniPlayer? = null
|
||||||
|
|
||||||
override val view: View = surfaceView
|
|
||||||
|
|
||||||
override lateinit var player: HybridOmniPlayerSpec
|
override lateinit var player: HybridOmniPlayerSpec
|
||||||
override var autoplay: Boolean? = true
|
override var autoplay: Boolean? = true
|
||||||
override var showNotification: Boolean? = true
|
override var showNotification: Boolean? = true
|
||||||
override var autoPip: 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() {
|
override fun afterUpdate() {
|
||||||
|
Log.e("omniView", "After update called")
|
||||||
if (!::player.isInitialized) {
|
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
|
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) {
|
if (boundPlayer === omniPlayer) {
|
||||||
boundPlayer?.setSurface(null)
|
return;
|
||||||
boundPlayer = omniPlayer
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Log.e("omniView", "Resetting old player")
|
||||||
|
boundPlayer?.setSurface(null)
|
||||||
|
boundPlayer = omniPlayer
|
||||||
|
|
||||||
if (surfaceReady) {
|
if (surfaceReady) {
|
||||||
omniPlayer.setSurface(surfaceView.holder.surface)
|
omniPlayer.setSurface(surfaceView.holder.surface)
|
||||||
}
|
}
|
||||||
@@ -66,10 +62,32 @@ class OmniView(val context: ThemedReactContext) : HybridOmniViewSpec() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onDropView() {
|
override fun onDropView() {
|
||||||
|
Log.e("omniView", "omni-view dropped")
|
||||||
if (!::player.isInitialized) return
|
if (!::player.isInitialized) return
|
||||||
|
|
||||||
val omniPlayer = player as? OmniPlayer ?: return
|
val omniPlayer = player as? OmniPlayer ?: return
|
||||||
omniPlayer.setSurface(null)
|
omniPlayer.setSurface(null)
|
||||||
boundPlayer = 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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+67
-76
@@ -1,13 +1,6 @@
|
|||||||
import { useCallback, useMemo, useState } from "react";
|
import { useCallback, useMemo, useState } from "react";
|
||||||
import type React from "react";
|
import type React from "react";
|
||||||
import {
|
import { Pressable, ScrollView, StyleSheet, Text, View } from "react-native";
|
||||||
Pressable,
|
|
||||||
SafeAreaView,
|
|
||||||
ScrollView,
|
|
||||||
StyleSheet,
|
|
||||||
Text,
|
|
||||||
View,
|
|
||||||
} from "react-native";
|
|
||||||
import {
|
import {
|
||||||
OmniProvider,
|
OmniProvider,
|
||||||
OmniView,
|
OmniView,
|
||||||
@@ -129,72 +122,73 @@ function PlayerExample({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SafeAreaView style={styles.safeArea}>
|
<View style={styles.container}>
|
||||||
<View style={styles.container}>
|
<Text style={styles.heading}>react-native-omni</Text>
|
||||||
<Text style={styles.heading}>react-native-omni</Text>
|
<Text style={styles.subheading}>{trackLabel}</Text>
|
||||||
<Text style={styles.subheading}>{trackLabel}</Text>
|
|
||||||
|
|
||||||
<View style={styles.video}>
|
<OmniView style={styles.video} autoplay={true} showNotification={true} />
|
||||||
<OmniView autoplay={true} showNotification={true} />
|
|
||||||
</View>
|
|
||||||
|
|
||||||
<View style={styles.row}>
|
<View style={styles.row}>
|
||||||
<Pressable style={styles.button} onPress={togglePlayback}>
|
<Pressable style={styles.button} onPress={togglePlayback}>
|
||||||
<Text style={styles.buttonText}>{isPlaying ? "Pause" : "Play"}</Text>
|
<Text style={styles.buttonText}>{isPlaying ? "Pause" : "Play"}</Text>
|
||||||
</Pressable>
|
</Pressable>
|
||||||
<Pressable style={styles.button} onPress={() => player.seekBy(-10)}>
|
<Pressable style={styles.button} onPress={() => player.seekBy(-10)}>
|
||||||
<Text style={styles.buttonText}>-10s</Text>
|
<Text style={styles.buttonText}>-10s</Text>
|
||||||
</Pressable>
|
</Pressable>
|
||||||
<Pressable style={styles.button} onPress={() => player.seekBy(10)}>
|
<Pressable style={styles.button} onPress={() => player.seekBy(10)}>
|
||||||
<Text style={styles.buttonText}>+10s</Text>
|
<Text style={styles.buttonText}>+10s</Text>
|
||||||
</Pressable>
|
</Pressable>
|
||||||
</View>
|
|
||||||
|
|
||||||
<View style={styles.row}>
|
|
||||||
<Pressable style={styles.button} onPress={() => player.playPrev()}>
|
|
||||||
<Text style={styles.buttonText}>Prev</Text>
|
|
||||||
</Pressable>
|
|
||||||
<Pressable style={styles.button} onPress={() => player.playNext()}>
|
|
||||||
<Text style={styles.buttonText}>Next</Text>
|
|
||||||
</Pressable>
|
|
||||||
<Pressable style={styles.button} onPress={toggleMute}>
|
|
||||||
<Text style={styles.buttonText}>{muted ? "Unmute" : "Mute"}</Text>
|
|
||||||
</Pressable>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
<View style={styles.row}>
|
|
||||||
<Pressable style={styles.button} onPress={() => changeVolume(-0.1)}>
|
|
||||||
<Text style={styles.buttonText}>Vol -</Text>
|
|
||||||
</Pressable>
|
|
||||||
<Pressable style={styles.button} onPress={() => changeVolume(0.1)}>
|
|
||||||
<Text style={styles.buttonText}>Vol +</Text>
|
|
||||||
</Pressable>
|
|
||||||
<Pressable style={styles.button} onPress={cyclePlaybackRate}>
|
|
||||||
<Text style={styles.buttonText}>{playbackRate.toFixed(2)}x</Text>
|
|
||||||
</Pressable>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
<View style={styles.statsCard}>
|
|
||||||
<Text style={styles.statText}>Status: {status}</Text>
|
|
||||||
<Text style={styles.statText}>
|
|
||||||
Time: {formatTime(currentTime)} / {formatTime(duration)}
|
|
||||||
</Text>
|
|
||||||
<Text style={styles.statText}>Volume: {(volume * 100).toFixed(0)}%</Text>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
<ScrollView style={styles.logCard} contentContainerStyle={styles.logContent}>
|
|
||||||
{logs.length === 0 ? (
|
|
||||||
<Text style={styles.logText}>Event log will appear here.</Text>
|
|
||||||
) : (
|
|
||||||
logs.map((entry, index) => (
|
|
||||||
<Text key={`${entry}-${index}`} style={styles.logText}>
|
|
||||||
{entry}
|
|
||||||
</Text>
|
|
||||||
))
|
|
||||||
)}
|
|
||||||
</ScrollView>
|
|
||||||
</View>
|
</View>
|
||||||
</SafeAreaView>
|
|
||||||
|
<View style={styles.row}>
|
||||||
|
<Pressable style={styles.button} onPress={() => player.playPrev()}>
|
||||||
|
<Text style={styles.buttonText}>Prev</Text>
|
||||||
|
</Pressable>
|
||||||
|
<Pressable style={styles.button} onPress={() => player.playNext()}>
|
||||||
|
<Text style={styles.buttonText}>Next</Text>
|
||||||
|
</Pressable>
|
||||||
|
<Pressable style={styles.button} onPress={toggleMute}>
|
||||||
|
<Text style={styles.buttonText}>{muted ? "Unmute" : "Mute"}</Text>
|
||||||
|
</Pressable>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={styles.row}>
|
||||||
|
<Pressable style={styles.button} onPress={() => changeVolume(-0.1)}>
|
||||||
|
<Text style={styles.buttonText}>Vol -</Text>
|
||||||
|
</Pressable>
|
||||||
|
<Pressable style={styles.button} onPress={() => changeVolume(0.1)}>
|
||||||
|
<Text style={styles.buttonText}>Vol +</Text>
|
||||||
|
</Pressable>
|
||||||
|
<Pressable style={styles.button} onPress={cyclePlaybackRate}>
|
||||||
|
<Text style={styles.buttonText}>{playbackRate.toFixed(2)}x</Text>
|
||||||
|
</Pressable>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={styles.statsCard}>
|
||||||
|
<Text style={styles.statText}>Status: {status}</Text>
|
||||||
|
<Text style={styles.statText}>
|
||||||
|
Time: {formatTime(currentTime)} / {formatTime(duration)}
|
||||||
|
</Text>
|
||||||
|
<Text style={styles.statText}>
|
||||||
|
Volume: {(volume * 100).toFixed(0)}%
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<ScrollView
|
||||||
|
style={styles.logCard}
|
||||||
|
contentContainerStyle={styles.logContent}
|
||||||
|
>
|
||||||
|
{logs.length === 0 ? (
|
||||||
|
<Text style={styles.logText}>Event log will appear here.</Text>
|
||||||
|
) : (
|
||||||
|
logs.map((entry, index) => (
|
||||||
|
<Text key={`${entry}-${index}`} style={styles.logText}>
|
||||||
|
{entry}
|
||||||
|
</Text>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</ScrollView>
|
||||||
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -239,13 +233,11 @@ function App(): React.JSX.Element {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
safeArea: {
|
|
||||||
flex: 1,
|
|
||||||
backgroundColor: "#0b1020",
|
|
||||||
},
|
|
||||||
container: {
|
container: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
|
backgroundColor: "#0b1020",
|
||||||
paddingHorizontal: 16,
|
paddingHorizontal: 16,
|
||||||
|
marginTop: 64,
|
||||||
paddingVertical: 12,
|
paddingVertical: 12,
|
||||||
gap: 12,
|
gap: 12,
|
||||||
},
|
},
|
||||||
@@ -263,7 +255,6 @@ const styles = StyleSheet.create({
|
|||||||
aspectRatio: 16 / 9,
|
aspectRatio: 16 / 9,
|
||||||
borderRadius: 14,
|
borderRadius: 14,
|
||||||
overflow: "hidden",
|
overflow: "hidden",
|
||||||
backgroundColor: "#000000",
|
|
||||||
},
|
},
|
||||||
row: {
|
row: {
|
||||||
flexDirection: "row",
|
flexDirection: "row",
|
||||||
|
|||||||
+6
-5
@@ -7,13 +7,14 @@ import { usePlayer } from "./provider";
|
|||||||
import type { OmniPlayer } from "./specs/omni-player.nitro";
|
import type { OmniPlayer } from "./specs/omni-player.nitro";
|
||||||
import type { Props } from "./specs/omni-view.nitro";
|
import type { Props } from "./specs/omni-view.nitro";
|
||||||
import type { OmniViewProps } from "./types/view";
|
import type { OmniViewProps } from "./types/view";
|
||||||
|
import type { ViewStyle } from "react-native";
|
||||||
|
import { memo } from "react";
|
||||||
|
|
||||||
const NativeView = getHostComponent<Props, HybridViewMethods>(
|
const NativeView = memo(
|
||||||
"OmniView",
|
getHostComponent<Props, HybridViewMethods>("OmniView", () => OmniConfig),
|
||||||
() => OmniConfig,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
export const OmniView = (props: OmniViewProps) => {
|
export const OmniView = memo((props: OmniViewProps & { style: ViewStyle }) => {
|
||||||
const player = usePlayer() as OmniPlayer;
|
const player = usePlayer() as OmniPlayer;
|
||||||
return <NativeView player={player} {...props} />;
|
return <NativeView player={player} {...props} />;
|
||||||
};
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user