diff --git a/android/src/main/java/dev/zoriya/omni/EventMap.kt b/android/src/main/java/dev/zoriya/omni/EventMap.kt index 1092470..858bc70 100644 --- a/android/src/main/java/dev/zoriya/omni/EventMap.kt +++ b/android/src/main/java/dev/zoriya/omni/EventMap.kt @@ -1,6 +1,6 @@ package dev.zoriya.omni -import android.os.SystemClock +import android.annotation.SuppressLint import androidx.media3.common.C import androidx.media3.common.C.TRACK_TYPE_AUDIO import androidx.media3.common.C.TRACK_TYPE_TEXT @@ -16,6 +16,7 @@ import androidx.media3.common.Player.STATE_ENDED import androidx.media3.common.Player.STATE_IDLE import androidx.media3.common.Player.STATE_READY import androidx.media3.common.Tracks +import androidx.media3.common.VideoSize import com.margelo.nitro.omni.BoolProperty import com.margelo.nitro.omni.HybridOmniEventMapSpec import com.margelo.nitro.omni.NumberProperty @@ -23,6 +24,7 @@ import com.margelo.nitro.omni.PlayerStatus import com.margelo.nitro.omni.Rendition import com.margelo.nitro.omni.Track +@SuppressLint("UnsafeOptInUsageError") class EventMap(private val player: Player) : HybridOmniEventMapSpec(), Player.Listener { private val onPrevListeners = mutableSetOf<() -> Unit>() private val onNextListeners = mutableSetOf<() -> Unit>() @@ -36,8 +38,10 @@ class EventMap(private val player: Player) : HybridOmniEventMapSpec(), Player.Li private val stateListeners = mutableMapOf Unit>>() private val stateBoolListeners = mutableMapOf Unit>>() private val playerStatusListeners = mutableSetOf<(PlayerStatus) -> Unit>() - private var lastTimePosDispatchMs = 0L + private var lastMediaItemIndex = 0 + private var lastRendition: Rendition? = null + private var lastIsAutoQuality: Boolean? = null init { player.addListener(this) @@ -61,6 +65,53 @@ class EventMap(private val player: Player) : HybridOmniEventMapSpec(), Player.Li return null } + private fun getCurrentRendition(): Rendition? { + val group = player.currentTracks.groups.firstOrNull { + it.isSelected && it.type == TRACK_TYPE_VIDEO + } ?: return null + + val isAuto = player.trackSelectionParameters.overrides.none { + it.key.type == TRACK_TYPE_VIDEO + } + + val currentIndex = when { + isAuto -> { + if (player.videoSize.width > 0 && player.videoSize.height > 0) { + (0 until group.length).firstOrNull { i -> + val format = group.getTrackFormat(i) + format.width == player.videoSize.width && format.height == player.videoSize.height + } + } else null + } + else -> (0 until group.length).firstOrNull { group.isTrackSelected(it) } + } ?: return null + + val format = group.getTrackFormat(currentIndex) + return Rendition( + id = currentIndex.toString(), + width = format.width.toDouble().coerceAtLeast(0.0), + height = format.height.toDouble().coerceAtLeast(0.0), + bitrate = format.bitrate.toDouble().coerceAtLeast(0.0), + selected = true + ) + } + + private fun emitIsAutoQualityChange() { + val isAuto = player.trackSelectionParameters.overrides.none { + it.key.type == C.TRACK_TYPE_VIDEO + } + if (isAuto == lastIsAutoQuality) return + lastIsAutoQuality = isAuto + stateBoolListeners[BoolProperty.ISAUTOQUALITY]?.forEach { it(isAuto) } + } + + private fun emitRenditionChange() { + val rendition = getCurrentRendition() ?: return + if (rendition == lastRendition) return + lastRendition = rendition + onRenditionChangeListeners.forEach { it(rendition) } + } + override fun onPlaybackStateChanged(playbackState: Int) { val state = when (player.playbackState) { STATE_IDLE -> PlayerStatus.IDLE @@ -108,6 +159,13 @@ class EventMap(private val player: Player) : HybridOmniEventMapSpec(), Player.Li ) ) } + emitIsAutoQualityChange() + emitRenditionChange() + } + + override fun onVideoSizeChanged(videoSize: VideoSize) { + emitIsAutoQualityChange() + emitRenditionChange() } override fun onPlayerError(error: PlaybackException) { @@ -135,11 +193,9 @@ class EventMap(private val player: Player) : HybridOmniEventMapSpec(), Player.Li newPosition: Player.PositionInfo, reason: Int ) { - emitCoreState(forceCurrentTime = true) - } - - private fun emitCoreState(forceCurrentTime: Boolean) { - emitCurrentTime(forceCurrentTime) + stateListeners[NumberProperty.CURRENTTIME]?.forEach { + it((player.currentPosition.toDouble() / 1000.0).coerceAtLeast(0.0)) + } stateListeners[NumberProperty.BUFFERED]?.forEach { it((player.totalBufferedDuration.toDouble() / 1000.0).coerceAtLeast(0.0)) } @@ -149,15 +205,6 @@ class EventMap(private val player: Player) : HybridOmniEventMapSpec(), Player.Li } } - private fun emitCurrentTime(force: Boolean) { - val now = SystemClock.elapsedRealtime() - if (!force && now - lastTimePosDispatchMs < 1000L) return - lastTimePosDispatchMs = now - stateListeners[NumberProperty.CURRENTTIME]?.forEach { - it((player.currentPosition.toDouble() / 1000.0).coerceAtLeast(0.0)) - } - } - override fun addStateListener(key: NumberProperty, cb: (value: Double) -> Unit) { stateListeners.getOrPut(key) { mutableSetOf() }.add(cb) } diff --git a/android/src/main/java/dev/zoriya/omni/MpvPlayer.kt b/android/src/main/java/dev/zoriya/omni/MpvPlayer.kt index 0cf8997..f1aa048 100644 --- a/android/src/main/java/dev/zoriya/omni/MpvPlayer.kt +++ b/android/src/main/java/dev/zoriya/omni/MpvPlayer.kt @@ -19,6 +19,7 @@ import androidx.media3.common.C.TRACK_TYPE_UNKNOWN import androidx.media3.common.C.TRACK_TYPE_VIDEO import androidx.media3.common.DeviceInfo import androidx.media3.common.Format +import androidx.media3.common.Format.NO_VALUE import androidx.media3.common.MediaItem import androidx.media3.common.MediaMetadata import androidx.media3.common.PlaybackException @@ -337,12 +338,15 @@ class MpvPlayer(ctx: Context) : BasePlayer(), MPVLib.EventObserver { val label = mpv.getPropertyString("$base/title") val language = mpv.getPropertyString("$base/lang") val codec = mpv.getPropertyString("$base/codec") + val bitrate = mpv.getPropertyInt("$base/hls-bitrate") + ?: mpv.getPropertyInt("$base/demux-bitrate") val format = Format.Builder() .setId(id.toString()) .setLabel(label) .setLanguage(language) .setCodecs(codec) + .setAverageBitrate(bitrate ?: NO_VALUE) .build() grouped.getOrPut>(type) { mutableListOf() } @@ -612,13 +616,15 @@ class MpvPlayer(ctx: Context) : BasePlayer(), MPVLib.EventObserver { EVENT_TIMELINE_CHANGED, EVENT_MEDIA_METADATA_CHANGED, EVENT_PLAYBACK_STATE_CHANGED, - EVENT_IS_PLAYING_CHANGED + EVENT_IS_PLAYING_CHANGED, + EVENT_TRACKS_CHANGED ) ) { it.onTimelineChanged(currentTimeline, TIMELINE_CHANGE_REASON_SOURCE_UPDATE) it.onMediaMetadataChanged(mediaMetadata) it.onPlaybackStateChanged(STATE_READY) it.onIsPlayingChanged(playWhenReady) + it.onTracksChanged(getCurrentTracks()) } MPVLib.MpvEvent.MPV_EVENT_SEEK, diff --git a/android/src/main/java/dev/zoriya/omni/OmniPlayer.kt b/android/src/main/java/dev/zoriya/omni/OmniPlayer.kt index 8db6638..25cc191 100644 --- a/android/src/main/java/dev/zoriya/omni/OmniPlayer.kt +++ b/android/src/main/java/dev/zoriya/omni/OmniPlayer.kt @@ -1,10 +1,8 @@ package dev.zoriya.omni -import android.R import android.annotation.SuppressLint import android.app.PendingIntent import android.content.Intent -import android.media.metrics.TrackChangeEvent.TRACK_TYPE_VIDEO import android.util.Log import android.view.SurfaceHolder import androidx.media3.common.C @@ -33,8 +31,8 @@ import dev.zoriya.omni.utils.ThreadHelper.runOnMainThreadSync class OmniPlayer : HybridOmniPlayerSpec() { private val ctx = NitroModules.applicationContext ?: throw Error("No Context available!") val player: Player = runOnMainThreadSync { -// ExoPlayer.Builder(ctx).build() - MpvPlayer(ctx) + // ExoPlayer.Builder(ctx).build() + MpvPlayer(ctx) } override val eventMap = EventMap(player) @@ -66,7 +64,7 @@ class OmniPlayer : HybridOmniPlayerSpec() { get() = currentSource ?: throw IllegalStateException("source should be initialized before get") set(value) { - Log.e("omni", "update source") + Log.i("omni", "update source") currentSource = value val src = value.src.firstOrNull() if (src == null) { @@ -204,6 +202,12 @@ class OmniPlayer : HybridOmniPlayerSpec() { override val subtitles by mainThreadProperty { tracksByType(C.TRACK_TYPE_TEXT) } override val rendition by mainThreadProperty { getRenditions() } + override var isAutoQuality by mainThreadProperty { + player.trackSelectionParameters.overrides.none { + it.key.type == C.TRACK_TYPE_VIDEO + } + } + override fun play() { runOnMainThreadSync { player.play() } } @@ -286,16 +290,28 @@ class OmniPlayer : HybridOmniPlayerSpec() { player.currentTracks.groups.firstOrNull { it.isSelected && it.type == C.TRACK_TYPE_VIDEO } ?: return emptyArray() + val currentIndex = when { + isAutoQuality -> { + if (player.videoSize.width > 0 && player.videoSize.height > 0) { + (0 until group.length).firstOrNull { i -> + val format = group.getTrackFormat(i) + format.width == player.videoSize.width && format.height == player.videoSize.height + } + } else null + } + else -> (0 until group.length).firstOrNull { group.isTrackSelected(it) } + } + val result = ArrayList() for (i in 0 until group.length) { val format = group.getTrackFormat(i) result.add( Rendition( - id = format.id ?: group.mediaTrackGroup.id, + id = i.toString(), width = format.width.toDouble().coerceAtLeast(0.0), height = format.height.toDouble().coerceAtLeast(0.0), bitrate = format.bitrate.toDouble().coerceAtLeast(0.0), - selected = group.isTrackSelected(i) + selected = i == currentIndex ) ) } @@ -316,17 +332,10 @@ class OmniPlayer : HybridOmniPlayerSpec() { player.currentTracks.groups.find { it.isSelected && it.type == C.TRACK_TYPE_VIDEO } ?: return@runOnMainThreadSync - for (i in 0 until group.length) { - val format = group.getTrackFormat(i) - val formatId = format.id ?: group.mediaTrackGroup.id - if (formatId == rendition.id) continue - - player.trackSelectionParameters = player.trackSelectionParameters - .buildUpon() - .setOverrideForType(TrackSelectionOverride(group.mediaTrackGroup, i)) - .build() - return@runOnMainThreadSync - } + player.trackSelectionParameters = player.trackSelectionParameters + .buildUpon() + .setOverrideForType(TrackSelectionOverride(group.mediaTrackGroup, rendition.id.toInt())) + .build() } } @@ -361,7 +370,7 @@ class OmniPlayerService : MediaSessionService() { .build() setMediaNotificationProvider(DefaultMediaNotificationProvider.Builder(this).build().apply { - setSmallIcon(applicationInfo.icon.takeIf { it != 0 } ?: R.drawable.ic_media_play) + setSmallIcon(applicationInfo.icon.takeIf { it != 0 } ?: android.R.drawable.ic_media_play) }) addSession(mediaSession) setShowNotificationForIdlePlayer(SHOW_NOTIFICATION_FOR_IDLE_PLAYER_ALWAYS) diff --git a/example/App.tsx b/example/App.tsx index 908843f..385dd3d 100644 --- a/example/App.tsx +++ b/example/App.tsx @@ -1,4 +1,4 @@ -import { memo, useCallback, useEffect, useMemo, useState } from "react"; +import { useCallback, useEffect, useMemo, useState } from "react"; import type React from "react"; import { Pressable, ScrollView, StyleSheet, Text, View } from "react-native"; import { @@ -10,6 +10,12 @@ import { } from "react-native-omni"; const PLAYLIST = [ + { + title: "elephants dram", + artist: "multi audio", + album: "Adaptive", + uri: "https://playertest.longtailvideo.com/adaptive/elephants_dream_v4/index.m3u8", + }, { title: "Big Buck Bunny (HLS)", artist: "Blender Foundation", @@ -60,6 +66,7 @@ function PlayerExample({ const muted = usePlayerState("muted"); const volume = usePlayerState("volume"); + const isAutoQuality = usePlayerState("isAutoQuality"); const [logs, setLogs] = useState([]); const [tracks, setTracks] = useState(() => ({ videos: [...player.videos], @@ -205,11 +212,6 @@ function PlayerExample({ refreshTracks(); }; - const selectRendition = (rendition?: (typeof tracks.renditions)[number]) => { - player.selectRendition(rendition); - refreshTracks(); - }; - return ( react-native-omni @@ -369,16 +371,14 @@ function PlayerExample({ rendition.selected) && - styles.selectedTrackButton, + isAutoQuality && styles.selectedTrackButton, ]} - onPress={() => selectRendition(undefined)} + onPress={() => player.selectRendition(undefined)} > rendition.selected) && - styles.selectedTrackButtonText, + isAutoQuality && styles.selectedTrackButtonText, ]} > Auto @@ -394,7 +394,7 @@ function PlayerExample({ styles.trackButton, rendition.selected && styles.selectedTrackButton, ]} - onPress={() => selectRendition(rendition)} + onPress={() => player.selectRendition(rendition)} > getStaticField("MUTED"); return clazz->getStaticFieldValue(fieldMUTED); + case BoolProperty::ISAUTOQUALITY: + static const auto fieldISAUTOQUALITY = clazz->getStaticField("ISAUTOQUALITY"); + return clazz->getStaticFieldValue(fieldISAUTOQUALITY); default: std::string stringValue = std::to_string(static_cast(value)); throw std::invalid_argument("Invalid enum value (" + stringValue + "!"); diff --git a/nitrogen/generated/android/c++/JHybridOmniPlayerSpec.cpp b/nitrogen/generated/android/c++/JHybridOmniPlayerSpec.cpp index 41cc4f2..938d503 100644 --- a/nitrogen/generated/android/c++/JHybridOmniPlayerSpec.cpp +++ b/nitrogen/generated/android/c++/JHybridOmniPlayerSpec.cpp @@ -225,6 +225,11 @@ namespace margelo::nitro::omni { static const auto method = _javaPart->javaClassStatic()->getMethod("setMuted"); method(_javaPart, muted); } + bool JHybridOmniPlayerSpec::getIsAutoQuality() { + static const auto method = _javaPart->javaClassStatic()->getMethod("isAutoQuality"); + auto __result = method(_javaPart); + return static_cast(__result); + } // Methods void JHybridOmniPlayerSpec::play() { diff --git a/nitrogen/generated/android/c++/JHybridOmniPlayerSpec.hpp b/nitrogen/generated/android/c++/JHybridOmniPlayerSpec.hpp index b089565..48f0209 100644 --- a/nitrogen/generated/android/c++/JHybridOmniPlayerSpec.hpp +++ b/nitrogen/generated/android/c++/JHybridOmniPlayerSpec.hpp @@ -73,6 +73,7 @@ namespace margelo::nitro::omni { void setVolume(double volume) override; bool getMuted() override; void setMuted(bool muted) override; + bool getIsAutoQuality() override; public: // Methods diff --git a/nitrogen/generated/android/c++/JNumberProperty.hpp b/nitrogen/generated/android/c++/JNumberProperty.hpp index 62dc028..e39332b 100644 --- a/nitrogen/generated/android/c++/JNumberProperty.hpp +++ b/nitrogen/generated/android/c++/JNumberProperty.hpp @@ -57,6 +57,9 @@ namespace margelo::nitro::omni { case NumberProperty::VOLUME: static const auto fieldVOLUME = clazz->getStaticField("VOLUME"); return clazz->getStaticFieldValue(fieldVOLUME); + case NumberProperty::ISAUTOQUALITY: + static const auto fieldISAUTOQUALITY = clazz->getStaticField("ISAUTOQUALITY"); + return clazz->getStaticFieldValue(fieldISAUTOQUALITY); default: std::string stringValue = std::to_string(static_cast(value)); throw std::invalid_argument("Invalid enum value (" + stringValue + "!"); diff --git a/nitrogen/generated/android/kotlin/com/margelo/nitro/omni/BoolProperty.kt b/nitrogen/generated/android/kotlin/com/margelo/nitro/omni/BoolProperty.kt index e4c3e62..c0adede 100644 --- a/nitrogen/generated/android/kotlin/com/margelo/nitro/omni/BoolProperty.kt +++ b/nitrogen/generated/android/kotlin/com/margelo/nitro/omni/BoolProperty.kt @@ -17,7 +17,8 @@ import com.facebook.proguard.annotations.DoNotStrip @Keep enum class BoolProperty(@DoNotStrip @Keep val value: Int) { ISPLAYING(0), - MUTED(1); + MUTED(1), + ISAUTOQUALITY(2); companion object } diff --git a/nitrogen/generated/android/kotlin/com/margelo/nitro/omni/HybridOmniPlayerSpec.kt b/nitrogen/generated/android/kotlin/com/margelo/nitro/omni/HybridOmniPlayerSpec.kt index 9120e9c..85188b5 100644 --- a/nitrogen/generated/android/kotlin/com/margelo/nitro/omni/HybridOmniPlayerSpec.kt +++ b/nitrogen/generated/android/kotlin/com/margelo/nitro/omni/HybridOmniPlayerSpec.kt @@ -104,6 +104,10 @@ abstract class HybridOmniPlayerSpec: HybridObject() { @set:DoNotStrip @set:Keep abstract var muted: Boolean + + @get:DoNotStrip + @get:Keep + abstract val isAutoQuality: Boolean // Methods @DoNotStrip diff --git a/nitrogen/generated/android/kotlin/com/margelo/nitro/omni/NumberProperty.kt b/nitrogen/generated/android/kotlin/com/margelo/nitro/omni/NumberProperty.kt index 5b771cc..96f95c3 100644 --- a/nitrogen/generated/android/kotlin/com/margelo/nitro/omni/NumberProperty.kt +++ b/nitrogen/generated/android/kotlin/com/margelo/nitro/omni/NumberProperty.kt @@ -20,7 +20,8 @@ enum class NumberProperty(@DoNotStrip @Keep val value: Int) { BUFFERED(1), DURATION(2), PLAYBACKRATE(3), - VOLUME(4); + VOLUME(4), + ISAUTOQUALITY(5); companion object } diff --git a/nitrogen/generated/shared/c++/BoolProperty.hpp b/nitrogen/generated/shared/c++/BoolProperty.hpp index 83b2a23..3bbfca5 100644 --- a/nitrogen/generated/shared/c++/BoolProperty.hpp +++ b/nitrogen/generated/shared/c++/BoolProperty.hpp @@ -31,6 +31,7 @@ namespace margelo::nitro::omni { enum class BoolProperty { ISPLAYING SWIFT_NAME(isplaying) = 0, MUTED SWIFT_NAME(muted) = 1, + ISAUTOQUALITY SWIFT_NAME(isautoquality) = 2, } CLOSED_ENUM; } // namespace margelo::nitro::omni @@ -45,6 +46,7 @@ namespace margelo::nitro { switch (hashString(unionValue.c_str(), unionValue.size())) { case hashString("isPlaying"): return margelo::nitro::omni::BoolProperty::ISPLAYING; case hashString("muted"): return margelo::nitro::omni::BoolProperty::MUTED; + case hashString("isAutoQuality"): return margelo::nitro::omni::BoolProperty::ISAUTOQUALITY; default: [[unlikely]] throw std::invalid_argument("Cannot convert \"" + unionValue + "\" to enum BoolProperty - invalid value!"); } @@ -53,6 +55,7 @@ namespace margelo::nitro { switch (arg) { case margelo::nitro::omni::BoolProperty::ISPLAYING: return JSIConverter::toJSI(runtime, "isPlaying"); case margelo::nitro::omni::BoolProperty::MUTED: return JSIConverter::toJSI(runtime, "muted"); + case margelo::nitro::omni::BoolProperty::ISAUTOQUALITY: return JSIConverter::toJSI(runtime, "isAutoQuality"); default: [[unlikely]] throw std::invalid_argument("Cannot convert BoolProperty to JS - invalid value: " + std::to_string(static_cast(arg)) + "!"); @@ -66,6 +69,7 @@ namespace margelo::nitro { switch (hashString(unionValue.c_str(), unionValue.size())) { case hashString("isPlaying"): case hashString("muted"): + case hashString("isAutoQuality"): return true; default: return false; diff --git a/nitrogen/generated/shared/c++/HybridOmniPlayerSpec.cpp b/nitrogen/generated/shared/c++/HybridOmniPlayerSpec.cpp index 52877ca..c9773eb 100644 --- a/nitrogen/generated/shared/c++/HybridOmniPlayerSpec.cpp +++ b/nitrogen/generated/shared/c++/HybridOmniPlayerSpec.cpp @@ -37,6 +37,7 @@ namespace margelo::nitro::omni { prototype.registerHybridSetter("volume", &HybridOmniPlayerSpec::setVolume); prototype.registerHybridGetter("muted", &HybridOmniPlayerSpec::getMuted); prototype.registerHybridSetter("muted", &HybridOmniPlayerSpec::setMuted); + prototype.registerHybridGetter("isAutoQuality", &HybridOmniPlayerSpec::getIsAutoQuality); prototype.registerHybridMethod("play", &HybridOmniPlayerSpec::play); prototype.registerHybridMethod("pause", &HybridOmniPlayerSpec::pause); prototype.registerHybridMethod("seekBy", &HybridOmniPlayerSpec::seekBy); diff --git a/nitrogen/generated/shared/c++/HybridOmniPlayerSpec.hpp b/nitrogen/generated/shared/c++/HybridOmniPlayerSpec.hpp index 4204561..4d75613 100644 --- a/nitrogen/generated/shared/c++/HybridOmniPlayerSpec.hpp +++ b/nitrogen/generated/shared/c++/HybridOmniPlayerSpec.hpp @@ -83,6 +83,7 @@ namespace margelo::nitro::omni { virtual void setVolume(double volume) = 0; virtual bool getMuted() = 0; virtual void setMuted(bool muted) = 0; + virtual bool getIsAutoQuality() = 0; public: // Methods diff --git a/nitrogen/generated/shared/c++/NumberProperty.hpp b/nitrogen/generated/shared/c++/NumberProperty.hpp index 1544d49..39116b5 100644 --- a/nitrogen/generated/shared/c++/NumberProperty.hpp +++ b/nitrogen/generated/shared/c++/NumberProperty.hpp @@ -34,6 +34,7 @@ namespace margelo::nitro::omni { DURATION SWIFT_NAME(duration) = 2, PLAYBACKRATE SWIFT_NAME(playbackrate) = 3, VOLUME SWIFT_NAME(volume) = 4, + ISAUTOQUALITY SWIFT_NAME(isautoquality) = 5, } CLOSED_ENUM; } // namespace margelo::nitro::omni @@ -51,6 +52,7 @@ namespace margelo::nitro { case hashString("duration"): return margelo::nitro::omni::NumberProperty::DURATION; case hashString("playbackRate"): return margelo::nitro::omni::NumberProperty::PLAYBACKRATE; case hashString("volume"): return margelo::nitro::omni::NumberProperty::VOLUME; + case hashString("isAutoQuality"): return margelo::nitro::omni::NumberProperty::ISAUTOQUALITY; default: [[unlikely]] throw std::invalid_argument("Cannot convert \"" + unionValue + "\" to enum NumberProperty - invalid value!"); } @@ -62,6 +64,7 @@ namespace margelo::nitro { case margelo::nitro::omni::NumberProperty::DURATION: return JSIConverter::toJSI(runtime, "duration"); case margelo::nitro::omni::NumberProperty::PLAYBACKRATE: return JSIConverter::toJSI(runtime, "playbackRate"); case margelo::nitro::omni::NumberProperty::VOLUME: return JSIConverter::toJSI(runtime, "volume"); + case margelo::nitro::omni::NumberProperty::ISAUTOQUALITY: return JSIConverter::toJSI(runtime, "isAutoQuality"); default: [[unlikely]] throw std::invalid_argument("Cannot convert NumberProperty to JS - invalid value: " + std::to_string(static_cast(arg)) + "!"); @@ -78,6 +81,7 @@ namespace margelo::nitro { case hashString("duration"): case hashString("playbackRate"): case hashString("volume"): + case hashString("isAutoQuality"): return true; default: return false; diff --git a/src/events.tsx b/src/events.tsx index 54caf8e..5674113 100644 --- a/src/events.tsx +++ b/src/events.tsx @@ -46,6 +46,7 @@ export function usePlayerState( return () => em.removeStateListener(key, setState); case "isPlaying": case "muted": + case "isAutoQuality": em.addStateBoolListener(key, setState); return () => em.removeStateBoolListener(key, setState); case "status": diff --git a/src/specs/omni-player.nitro.ts b/src/specs/omni-player.nitro.ts index 1f29a64..c40d7d7 100644 --- a/src/specs/omni-player.nitro.ts +++ b/src/specs/omni-player.nitro.ts @@ -11,7 +11,7 @@ export type NumberProperty = Exclude< keyof OmniPlayerState, "status" | "isPlaying" | "muted" >; -export type BoolProperty = "isPlaying" | "muted"; +export type BoolProperty = "isPlaying" | "muted" | "isAutoQuality"; export interface OmniEventMap extends HybridObject<{ android: "kotlin" }> { addStateListener(key: NumberProperty, cb: (value: number) => void): void; diff --git a/src/types/player.ts b/src/types/player.ts index 3660b75..64a8ed1 100644 --- a/src/types/player.ts +++ b/src/types/player.ts @@ -34,21 +34,22 @@ export interface OmniPlayerState { // between 0 and 1 volume: number; muted: boolean; + readonly isAutoQuality: boolean } export type PlayerStatus = "idle" | "loading" | "readyToPlay" | "error"; export interface Track { - id: string; - label?: string; - language?: string; - selected: boolean; + readonly id: string; + readonly label?: string; + readonly language?: string; + readonly selected: boolean; } export interface Rendition { - id: string; - width: number; - height: number; - bitrate: number; - selected: boolean; + readonly id: string; + readonly width: number; + readonly height: number; + readonly bitrate: number; + readonly selected: boolean; }