diff --git a/android/src/main/java/dev/zoriya/omni/EventMap.kt b/android/src/main/java/dev/zoriya/omni/EventMap.kt index d7e16ae..8aa44dd 100644 --- a/android/src/main/java/dev/zoriya/omni/EventMap.kt +++ b/android/src/main/java/dev/zoriya/omni/EventMap.kt @@ -10,9 +10,9 @@ import android.os.SystemClock import dev.jdtech.mpv.MPVLib class EventMap(private val player: MPVLib) : HybridOmniEventMapSpec(), MPVLib.EventObserver { + val onPrevListeners = mutableSetOf<() -> Unit>() + val onNextListeners = mutableSetOf<() -> Unit>() private val onEndListeners = mutableSetOf<() -> Unit>() - private val onPrevListeners = mutableSetOf<() -> Unit>() - private val onNextListeners = mutableSetOf<() -> Unit>() private val onErrorListeners = mutableSetOf<(type: String, message: String) -> Unit>() private val onAudioFocusChangeListeners = mutableSetOf<(status: String) -> Unit>() private val onVideoTrackChangeListeners = mutableSetOf<(track: Track) -> Unit>() @@ -32,7 +32,7 @@ class EventMap(private val player: MPVLib) : HybridOmniEventMapSpec(), MPVLib.Ev player.observeProperty("aid", MPVLib.MpvFormat.MPV_FORMAT_INT64) player.observeProperty("sid", MPVLib.MpvFormat.MPV_FORMAT_INT64) player.observeProperty("pause", MPVLib.MpvFormat.MPV_FORMAT_FLAG) - player.observeProperty("muted", MPVLib.MpvFormat.MPV_FORMAT_FLAG) + player.observeProperty("mute", MPVLib.MpvFormat.MPV_FORMAT_FLAG) player.observeProperty("eof-reached", MPVLib.MpvFormat.MPV_FORMAT_FLAG) player.observeProperty("core-idle", MPVLib.MpvFormat.MPV_FORMAT_FLAG) player.observeProperty("paused-for-cache", MPVLib.MpvFormat.MPV_FORMAT_FLAG) @@ -65,7 +65,12 @@ class EventMap(private val player: MPVLib) : HybridOmniEventMapSpec(), MPVLib.Ev val label = player.getPropertyString("$base/title") ?: player.getPropertyString("$base/codec") val language = player.getPropertyString("$base/lang") - return Track(id = trackId.toString(), label = label, language = language, selected = selected) + return Track( + id = trackId.toString(), + label = label, + language = language, + selected = selected + ) } return null } @@ -78,8 +83,10 @@ class EventMap(private val player: MPVLib) : HybridOmniEventMapSpec(), MPVLib.Ev isSeeking = false playerStatusListeners.forEach { it(PlayerStatus.LOADING) } } + MPVLib.MpvEvent.MPV_EVENT_FILE_LOADED -> playerStatusListeners.forEach { it(PlayerStatus.READYTOPLAY) } + MPVLib.MpvEvent.MPV_EVENT_SEEK -> isSeeking = true MPVLib.MpvEvent.MPV_EVENT_PLAYBACK_RESTART -> isSeeking = false MPVLib.MpvEvent.MPV_EVENT_END_FILE -> { @@ -93,6 +100,7 @@ class EventMap(private val player: MPVLib) : HybridOmniEventMapSpec(), MPVLib.Ev } playerStatusListeners.forEach { it(PlayerStatus.IDLE) } } + MPVLib.MpvEvent.MPV_EVENT_QUEUE_OVERFLOW -> onErrorListeners.forEach { it("queue_overflow", "mpv event queue overflow") } @@ -107,10 +115,12 @@ class EventMap(private val player: MPVLib) : HybridOmniEventMapSpec(), MPVLib.Ev val track = getTrackById("video", value) if (track != null) onVideoTrackChangeListeners.forEach { it(track) } } + "aid" -> { val track = getTrackById("audio", value) if (track != null) onAudioTrackChangeListeners.forEach { it(track) } } + "sid" -> { val track = getTrackById("sub", value) onSubtitleChangeListeners.forEach { it(track) } @@ -124,12 +134,16 @@ class EventMap(private val player: MPVLib) : HybridOmniEventMapSpec(), MPVLib.Ev lastTimePosDispatchMs = SystemClock.elapsedRealtime() stateListeners[NumberProperty.CURRENTTIME]?.forEach { it(value.coerceAtLeast(0.0)) } } + "demuxer-cache-time" -> stateListeners[NumberProperty.BUFFERED] ?.forEach { it(value.coerceAtLeast(0.0)) } + "duration" -> stateListeners[NumberProperty.DURATION] ?.forEach { it(value.coerceAtLeast(0.0)) } + "speed" -> stateListeners[NumberProperty.PLAYBACKRATE] ?.forEach { it(value) } + "volume" -> stateListeners[NumberProperty.VOLUME] ?.forEach { it((value / 100.0).coerceIn(0.0, 1.0)) } } @@ -142,8 +156,10 @@ class EventMap(private val player: MPVLib) : HybridOmniEventMapSpec(), MPVLib.Ev onAudioFocusChangeListeners.forEach { it(if (isPlaying) "playing" else "paused") } stateBoolListeners[BoolProperty.ISPLAYING]?.forEach { it(isPlaying) } } - "muted" -> + + "mute" -> stateBoolListeners[BoolProperty.MUTED]?.forEach { it(value) } + "eof-reached" -> eofReached = value "core-idle", "paused-for-cache" -> playerStatusListeners.forEach { it(computePlayerStatus()) } diff --git a/android/src/main/java/dev/zoriya/omni/OmniPackage.kt b/android/src/main/java/dev/zoriya/omni/OmniPackage.kt index ceeb2b6..203f546 100755 --- a/android/src/main/java/dev/zoriya/omni/OmniPackage.kt +++ b/android/src/main/java/dev/zoriya/omni/OmniPackage.kt @@ -12,8 +12,9 @@ import com.margelo.nitro.omni.views.*; public class OmniPackage : BaseReactPackage() { override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? = null - override fun getReactModuleInfoProvider(): ReactModuleInfoProvider = ReactModuleInfoProvider { emptyMap() } - + override fun getReactModuleInfoProvider(): ReactModuleInfoProvider = + ReactModuleInfoProvider { emptyMap() } + override fun createViewManagers(reactContext: ReactApplicationContext): List> { val viewManagers = ArrayList>() viewManagers.add(HybridOmniViewManager()) diff --git a/android/src/main/java/dev/zoriya/omni/OmniPlayer.kt b/android/src/main/java/dev/zoriya/omni/OmniPlayer.kt index d8e0d55..2c38deb 100644 --- a/android/src/main/java/dev/zoriya/omni/OmniPlayer.kt +++ b/android/src/main/java/dev/zoriya/omni/OmniPlayer.kt @@ -1,5 +1,6 @@ package dev.zoriya.omni +import android.util.Log import com.margelo.nitro.NitroModules import com.margelo.nitro.omni.Source import com.margelo.nitro.omni.HybridOmniPlayerSpec @@ -15,7 +16,7 @@ class OmniPlayer : HybridOmniPlayerSpec() { override val eventMap = EventMap(player) init { - player.setOptionString("vo", "gpu") + player.setOptionString("vo", "null") player.setOptionString("gpu-context", "android") player.setOptionString("opengl-es", "yes") player.setOptionString("hwdec", "mediacodec-copy") @@ -65,43 +66,48 @@ class OmniPlayer : HybridOmniPlayerSpec() { fun setSurface(surface: android.view.Surface?) { if (surface == null) { + player.setOptionString("vo", "null") + player.setOptionString("force-window", "no") player.detachSurface() } else { player.attachSurface(surface) + player.setOptionString("force-window", "yes") + player.setOptionString("vo", "gpu-next") } } override val hasPrev get() = source.metadata?.hasPrev ?: false override val hasNext get() = source.metadata?.hasNext ?: false - override val status: PlayerStatus - get() { - val idle = player.getPropertyBoolean("core-idle") ?: false - val loading = player.getPropertyBoolean("paused-for-cache") ?: false - return when { - idle -> PlayerStatus.IDLE - loading -> PlayerStatus.LOADING - else -> PlayerStatus.READYTOPLAY - } - } + override val status: PlayerStatus + get() { + val idle = player.getPropertyBoolean("core-idle") ?: false + val loading = player.getPropertyBoolean("paused-for-cache") ?: false + return when { + idle -> PlayerStatus.IDLE + loading -> PlayerStatus.LOADING + else -> PlayerStatus.READYTOPLAY + } + } override val isPlaying get() = !(player.getPropertyBoolean("pause") ?: true) override var currentTime - get() = (player.getPropertyDouble("time-pos") ?: 0.0).coerceAtLeast(0.0) - set(value) { - player.command(arrayOf("seek", value.coerceAtLeast(0.0).toString(), "absolute")) - } - override val buffered get() = (player.getPropertyDouble("demuxer-cache-time") ?: 0.0).coerceAtLeast(0.0) + get() = (player.getPropertyDouble("time-pos") ?: 0.0).coerceAtLeast(0.0) + set(value) { + player.command(arrayOf("seek", value.coerceAtLeast(0.0).toString(), "absolute")) + } + override val buffered + get() = (player.getPropertyDouble("demuxer-cache-time") ?: 0.0).coerceAtLeast(0.0) override val duration get() = (player.getPropertyDouble("duration") ?: 0.0).coerceAtLeast(0.0) override var playbackRate get() = player.getPropertyDouble("speed") ?: 1.0 set(value) { player.setPropertyDouble("speed", value.coerceAtLeast(0.0)) - } + } override var muted - get() = player.getPropertyBoolean("muted") ?: false + get() = player.getPropertyBoolean("mute") ?: false set(value) { - player.setPropertyBoolean("muted", value) + player.setPropertyBoolean("mute", value) } override var volume get() = (player.getPropertyDouble("volume") ?: 100.0).coerceIn(0.0, 100.0) / 100.0 @@ -128,32 +134,32 @@ class OmniPlayer : HybridOmniPlayerSpec() { } override fun playPrev() { - // App-level action; no default implementation in VLC player. + eventMap.onPrevListeners.forEach { it() } } override fun playNext() { - // App-level action; no default implementation in VLC player. + eventMap.onNextListeners.forEach { it() } } - override fun selectVideo(video: Track) { - val id = video.id.toIntOrNull() ?: return - player.setPropertyInt("vid", id) - } + override fun selectVideo(video: Track) { + val id = video.id.toIntOrNull() ?: return + player.setPropertyInt("vid", id) + } - override fun selectAudio(audio: Track) { - val id = audio.id.toIntOrNull() ?: return - player.setPropertyInt("aid", id) - } + override fun selectAudio(audio: Track) { + val id = audio.id.toIntOrNull() ?: return + player.setPropertyInt("aid", id) + } - override fun selectSubtitle(subtitle: Track?) { - when (subtitle) { - null -> player.setPropertyString("sid", "no") - else -> { - val id = subtitle.id.toIntOrNull() ?: return - player.setPropertyInt("sid", id) - } - } - } + override fun selectSubtitle(subtitle: Track?) { + when (subtitle) { + null -> player.setPropertyString("sid", "no") + else -> { + val id = subtitle.id.toIntOrNull() ?: return + player.setPropertyInt("sid", id) + } + } + } override fun selectRendition(rendition: Rendition?) { // Not supported by libVLC MediaPlayer track APIs. diff --git a/android/src/main/java/dev/zoriya/omni/OmniPlayerFactory.kt b/android/src/main/java/dev/zoriya/omni/OmniPlayerFactory.kt index 3459ec2..3efadf1 100755 --- a/android/src/main/java/dev/zoriya/omni/OmniPlayerFactory.kt +++ b/android/src/main/java/dev/zoriya/omni/OmniPlayerFactory.kt @@ -5,7 +5,7 @@ import com.margelo.nitro.omni.HybridOmniPlayerFactorySpec import com.margelo.nitro.omni.HybridOmniPlayerSpec import com.margelo.nitro.omni.Source -class OmniPlayerFactory(val context: ThemedReactContext): HybridOmniPlayerFactorySpec() { +class OmniPlayerFactory(val context: ThemedReactContext) : HybridOmniPlayerFactorySpec() { override fun createPlayer(props: Source): HybridOmniPlayerSpec { return OmniPlayer().apply { source = props diff --git a/android/src/main/java/dev/zoriya/omni/OmniView.kt b/android/src/main/java/dev/zoriya/omni/OmniView.kt index 1aa44c3..2e6d341 100755 --- a/android/src/main/java/dev/zoriya/omni/OmniView.kt +++ b/android/src/main/java/dev/zoriya/omni/OmniView.kt @@ -26,7 +26,12 @@ class OmniView(val context: ThemedReactContext) : HybridOmniViewSpec() { boundPlayer?.setSurface(holder.surface) } - override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) { + override fun surfaceChanged( + holder: SurfaceHolder, + format: Int, + width: Int, + height: Int + ) { surfaceReady = true boundPlayer?.setSurface(holder.surface) } diff --git a/src/events.tsx b/src/events.tsx index b26f4fa..3b53bfd 100644 --- a/src/events.tsx +++ b/src/events.tsx @@ -5,8 +5,7 @@ import type { OmniEvents } from "./types/events"; import type { OmniPlayerState } from "./types/player"; function capitalize(str: T): Capitalize { - return (str.charAt(0).toUpperCase() + - str.slice(1).toLowerCase()) as Capitalize; + return (str.charAt(0).toUpperCase() + str.slice(1)) as Capitalize; } export const useEvent = (