diff --git a/README.md b/README.md index 55282bb..7c038c6 100644 --- a/README.md +++ b/README.md @@ -132,9 +132,9 @@ component that uses ``, `usePlayer`, `usePlayerState`, or `useEvent`. | Prop | Type | Default | Description | | ------------------ | --------- | ------- | ---------------------------------------------------- | -| `source` | `Source` | — | The media to play. | +| `source` | `Source` | x | The media to play. | | `showNotification` | `boolean` | `false` | Create a media session (OS controls/notification). | -| `children` | `ReactNode` | — | Your player UI. | +| `children` | `ReactNode` | x | Your player UI. | Updating `source` swaps the media in place (and seeks to `source.startTime` if set) without recreating the player. @@ -152,7 +152,7 @@ plus: ### `usePlayer()` -Returns the `OmniPlayer` instance — an imperative handle for controlling +Returns the `OmniPlayer` instance: an imperative handle for controlling playback and reading/writing state. **Playback controls** @@ -199,11 +199,11 @@ player.selectAudio(track); player.subtitles; // Track[] player.selectSubtitle(track); // pass undefined to turn subtitles off -player.rendition; // Rendition[] +player.renditions; // Rendition[] player.selectRendition(rendition); // pass undefined for automatic quality ``` -> Prefer `usePlayerState`/`useEvent` for values that change over time — reading +> Prefer `usePlayerState` for values that change over time. Reading > them directly off `player` gives you a one-time snapshot and won't re-render. ### `usePlayerState(key, refresh?)` @@ -216,6 +216,10 @@ const status = usePlayerState("status"); const isPlaying = usePlayerState("isPlaying"); const duration = usePlayerState("duration"); const currentTime = usePlayerState("currentTime", 1); // poll every 1s + +const audios = usePlayerState("audios"); // Track[] +const subtitles = usePlayerState("subtitles"); // Track[] +const renditions = usePlayerState("renditions"); // Rendition[] ``` The optional `refresh` argument (seconds) sets a polling interval. @@ -250,6 +254,10 @@ useEvent("renditionChange", (rendition) => {/* … */}); | `subtitleChange` | `(track?) => void` | The active subtitle changed (`undefined` = off). | | `renditionChange` | `(rendition) => void` | The active quality/rendition changed. | +The track/rendition change events fire when the _active_ selection changes; +`usePlayerState("audios" | "subtitles" | "renditions" | "videos")` exposes the +full lists as reactive state. Use whichever fits. + ## Example app A full example (playlist navigation, track/rendition selectors, an event log, diff --git a/android/src/main/java/dev/zoriya/omni/EventMap.kt b/android/src/main/java/dev/zoriya/omni/EventMap.kt index 1317253..a9ec1e0 100644 --- a/android/src/main/java/dev/zoriya/omni/EventMap.kt +++ b/android/src/main/java/dev/zoriya/omni/EventMap.kt @@ -26,9 +26,22 @@ import com.margelo.nitro.omni.PlayerStatus import com.margelo.nitro.omni.Rendition import com.margelo.nitro.omni.Source import com.margelo.nitro.omni.Track +import com.margelo.nitro.omni.TrackProperty + +/** + * Source of the current track/rendition lists. Implemented by [OmniPlayer] so + * state emissions reuse the same computation (incl. cast metadata recovery) as + * its readonly getters. + */ +interface TrackProvider { + val videos: Array + val audios: Array + val subtitles: Array + val renditions: Array +} @SuppressLint("UnsafeOptInUsageError") -class EventMap() : HybridOmniEventMapSpec(), Player.Listener { +class EventMap(private val tracks: TrackProvider) : HybridOmniEventMapSpec(), Player.Listener { private val onPrevListeners = mutableSetOf<() -> Unit>() private val onNextListeners = mutableSetOf<() -> Unit>() private val onEndListeners = mutableSetOf<() -> Unit>() @@ -38,6 +51,8 @@ class EventMap() : HybridOmniEventMapSpec(), Player.Listener { private val onAudioTrackChangeListeners = mutableSetOf<(track: Track) -> Unit>() private val onSubtitleChangeListeners = mutableSetOf<(track: Track?) -> Unit>() private val onRenditionChangeListeners = mutableSetOf<(rendition: Rendition) -> Unit>() + private val tracksListeners = mutableMapOf) -> Unit>>() + private val renditionsListeners = mutableSetOf<(Array) -> Unit>() private val stateListeners = mutableMapOf Unit>>() private val stateBoolListeners = mutableMapOf Unit>>() private val playerStatusListeners = mutableSetOf<(PlayerStatus) -> Unit>() @@ -46,6 +61,7 @@ class EventMap() : HybridOmniEventMapSpec(), Player.Listener { private var lastMediaItemIndex = 0 private var lastRendition: Rendition? = null + private var lastRenditions: Array? = null private var lastIsAutoQuality: Boolean? = null // swapped on cast start/end @@ -59,6 +75,7 @@ class EventMap() : HybridOmniEventMapSpec(), Player.Listener { value.addListener(this) lastMediaItemIndex = value.currentMediaItemIndex lastRendition = null + lastRenditions = null lastIsAutoQuality = null // immediatly send all the state @@ -158,6 +175,21 @@ class EventMap() : HybridOmniEventMapSpec(), Player.Listener { stateBoolListeners[BoolProperty.ISAUTOQUALITY]?.forEach { it(isAuto) } } + private fun emitTracks() { + tracksListeners[TrackProperty.VIDEOS]?.let { cbs -> + val videos = tracks.videos + cbs.forEach { it(videos) } + } + tracksListeners[TrackProperty.AUDIOS]?.let { cbs -> + val audios = tracks.audios + cbs.forEach { it(audios) } + } + tracksListeners[TrackProperty.SUBTITLES]?.let { cbs -> + val subtitles = tracks.subtitles + cbs.forEach { it(subtitles) } + } + } + private fun emitRenditionChange() { val rendition = getCurrentRendition() ?: return if (rendition == lastRendition) return @@ -165,6 +197,12 @@ class EventMap() : HybridOmniEventMapSpec(), Player.Listener { onRenditionChangeListeners.forEach { it(rendition) } } + private fun emitRenditions() { + if (renditionsListeners.isEmpty()) return + val renditions = tracks.renditions + renditionsListeners.forEach { it(renditions) } + } + override fun onPlaybackStateChanged(playbackState: Int) { val state = when (player.playbackState) { STATE_IDLE -> PlayerStatus.IDLE @@ -227,20 +265,19 @@ class EventMap() : HybridOmniEventMapSpec(), Player.Listener { selectedTrack(TRACK_TYPE_AUDIO)?.let { track -> onAudioTrackChangeListeners.forEach { it(track) } } - onSubtitleChangeListeners.forEach { - it( - selectedTrack( - TRACK_TYPE_TEXT - ) - ) + selectedTrack(TRACK_TYPE_TEXT)?.let { track -> + onSubtitleChangeListeners.forEach { it(track) } } + emitTracks() emitIsAutoQualityChange() emitRenditionChange() + emitRenditions() } override fun onVideoSizeChanged(videoSize: VideoSize) { emitIsAutoQualityChange() emitRenditionChange() + emitRenditions() } override fun onPlayerError(error: PlaybackException) { @@ -397,6 +434,22 @@ class EventMap() : HybridOmniEventMapSpec(), Player.Listener { onRenditionChangeListeners.remove(cb) } + override fun addTracksListener(key: TrackProperty, cb: (value: Array) -> Unit) { + tracksListeners.getOrPut(key) { mutableSetOf() }.add(cb) + } + + override fun removeTracksListener(key: TrackProperty, cb: (value: Array) -> Unit) { + tracksListeners[key]?.remove(cb) + } + + override fun addRenditionsListener(cb: (value: Array) -> Unit) { + renditionsListeners.add(cb) + } + + override fun removeRenditionsListener(cb: (value: Array) -> Unit) { + renditionsListeners.remove(cb) + } + override fun dispose() { player.removeListener(this) super.dispose() diff --git a/android/src/main/java/dev/zoriya/omni/OmniPlayer.kt b/android/src/main/java/dev/zoriya/omni/OmniPlayer.kt index 0800a00..02f82af 100644 --- a/android/src/main/java/dev/zoriya/omni/OmniPlayer.kt +++ b/android/src/main/java/dev/zoriya/omni/OmniPlayer.kt @@ -51,7 +51,7 @@ import org.json.JSONObject class OmniPlayer( private val backend: AndroidBackend = AndroidBackend.VLC, castOptions: CastOptions? = null, -) : HybridOmniPlayerSpec() { +) : HybridOmniPlayerSpec(), TrackProvider { private val ctx = NitroModules.applicationContext ?: throw Error("No Context available!") // exoplayer only, used to specify request headers. @@ -72,7 +72,7 @@ class OmniPlayer( AndroidBackend.VLC -> VlcPlayer(ctx) } } - override val eventMap = EventMap() + override val eventMap = EventMap(this) private var castContext: CastContext? = null private val castStateListener = @@ -295,7 +295,39 @@ class OmniPlayer( override val videos by mainThreadProperty { tracksByType(C.TRACK_TYPE_VIDEO) } override val audios by mainThreadProperty { tracksByType(C.TRACK_TYPE_AUDIO) } override val subtitles by mainThreadProperty { tracksByType(C.TRACK_TYPE_TEXT) } - override val rendition by mainThreadProperty { getRenditions() } + override val renditions by mainThreadProperty { + val group = + 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 = i.toString(), + width = format.width.toDouble().coerceAtLeast(0.0), + height = format.height.toDouble().coerceAtLeast(0.0), + bitrate = format.bitrate.toDouble().coerceAtLeast(0.0), + selected = i == currentIndex + ) + ) + } + return result.toTypedArray() + } override var isAutoQuality by mainThreadProperty { player.trackSelectionParameters.overrides.none { @@ -441,37 +473,6 @@ class OmniPlayer( } private fun getRenditions(): Array { - val group = - 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 = i.toString(), - width = format.width.toDouble().coerceAtLeast(0.0), - height = format.height.toDouble().coerceAtLeast(0.0), - bitrate = format.bitrate.toDouble().coerceAtLeast(0.0), - selected = i == currentIndex - ) - ) - } - return result.toTypedArray() } override fun selectRendition(rendition: Rendition?) { diff --git a/example/App.tsx b/example/App.tsx index d474b54..f97ca6e 100644 --- a/example/App.tsx +++ b/example/App.tsx @@ -81,25 +81,12 @@ function PlayerExample({ const castStatus = usePlayerState("castStatus"); const [logs, setLogs] = useState<{ id: number; message: string }[]>([]); const logId = useRef(0); - const [tracks, setTracks] = useState(() => ({ - videos: [...player.videos], - audios: [...player.audios], - subtitles: [...player.subtitles], - renditions: [...player.rendition], - })); - - const refreshTracks = useCallback(() => { - setTracks({ - videos: [...player.videos], - audios: [...player.audios], - subtitles: [...player.subtitles], - renditions: [...player.rendition], - }); - }, [player]); - - useEffect(() => { - refreshTracks(); - }, [refreshTracks]); + const tracks = { + videos: usePlayerState("videos"), + audios: usePlayerState("audios"), + subtitles: usePlayerState("subtitles"), + renditions: usePlayerState("renditions"), + }; const pushLog = useCallback((message: string) => { setLogs((prev) => { @@ -145,47 +132,6 @@ function PlayerExample({ handleNext(); }, [handleNext, pushLog]), ); - useEvent( - "videoTrackChange", - useCallback( - (track) => { - pushLog(`Video track: ${track.label ?? track.id}`); - refreshTracks(); - }, - [pushLog, refreshTracks], - ), - ); - useEvent( - "audioTrackChange", - useCallback( - (track) => { - pushLog(`Audio track: ${track.label ?? track.id}`); - refreshTracks(); - }, - [pushLog, refreshTracks], - ), - ); - useEvent( - "subtitleChange", - useCallback( - (track) => { - pushLog(`Subtitle: ${track?.label ?? track?.id ?? "off"}`); - refreshTracks(); - }, - [pushLog, refreshTracks], - ), - ); - useEvent( - "renditionChange", - useCallback( - (rendition) => { - pushLog(`Rendition: ${rendition.width}x${rendition.height}`); - refreshTracks(); - }, - [pushLog, refreshTracks], - ), - ); - const switchBackend = (target: AndroidBackend) => { if (target === backend) return; // Pause before tearing down: the native media-session guard refuses a @@ -221,17 +167,14 @@ function PlayerExample({ const selectVideo = (video: (typeof tracks.videos)[number]) => { player.selectVideo(video); - refreshTracks(); }; const selectAudio = (audio: (typeof tracks.audios)[number]) => { player.selectAudio(audio); - refreshTracks(); }; const selectSubtitle = (subtitle?: (typeof tracks.subtitles)[number]) => { player.selectSubtitle(subtitle); - refreshTracks(); }; return ( @@ -596,7 +539,7 @@ function App(): React.JSX.Element { key={backend} source={hasSource ? source : undefined} backend={{ android: backend }} - cast={{receiverApplicationId: "D8FB0FC1"}} + cast={{ receiverApplicationId: "D8FB0FC1" }} showNotification > +#include + +#include "Rendition.hpp" +#include +#include +#include +#include "JRendition.hpp" +#include + +namespace margelo::nitro::omni { + + using namespace facebook; + + /** + * Represents the Java/Kotlin callback `(value: Array) -> Unit`. + * This can be passed around between C++ and Java/Kotlin. + */ + struct JFunc_void_std__vector_Rendition_: public jni::JavaClass { + public: + static constexpr auto kJavaDescriptor = "Lcom/margelo/nitro/omni/Func_void_std__vector_Rendition_;"; + + public: + /** + * Invokes the function this `JFunc_void_std__vector_Rendition_` instance holds through JNI. + */ + void invoke(const std::vector& value) const { + static const auto method = javaClassStatic()->getMethod> /* value */)>("invoke"); + method(self(), [&](auto&& __input) { + size_t __size = __input.size(); + jni::local_ref> __array = jni::JArrayClass::newArray(__size); + for (size_t __i = 0; __i < __size; __i++) { + const auto& __element = __input[__i]; + auto __elementJni = JRendition::fromCpp(__element); + __array->setElement(__i, *__elementJni); + } + return __array; + }(value)); + } + }; + + /** + * An implementation of Func_void_std__vector_Rendition_ that is backed by a C++ implementation (using `std::function<...>`) + */ + class JFunc_void_std__vector_Rendition__cxx final: public jni::HybridClass { + public: + static jni::local_ref fromCpp(const std::function& /* value */)>& func) { + return JFunc_void_std__vector_Rendition__cxx::newObjectCxxArgs(func); + } + + public: + /** + * Invokes the C++ `std::function<...>` this `JFunc_void_std__vector_Rendition__cxx` instance holds. + */ + void invoke_cxx(jni::alias_ref> value) { + _func([&](auto&& __input) { + size_t __size = __input->size(); + std::vector __vector; + __vector.reserve(__size); + for (size_t __i = 0; __i < __size; __i++) { + auto __element = __input->getElement(__i); + __vector.push_back(__element->toCpp()); + } + return __vector; + }(value)); + } + + public: + [[nodiscard]] + inline const std::function& /* value */)>& getFunction() const { + return _func; + } + + public: + static constexpr auto kJavaDescriptor = "Lcom/margelo/nitro/omni/Func_void_std__vector_Rendition__cxx;"; + static void registerNatives() { + registerHybrid({makeNativeMethod("invoke_cxx", JFunc_void_std__vector_Rendition__cxx::invoke_cxx)}); + } + + private: + explicit JFunc_void_std__vector_Rendition__cxx(const std::function& /* value */)>& func): _func(func) { } + + private: + friend HybridBase; + std::function& /* value */)> _func; + }; + +} // namespace margelo::nitro::omni diff --git a/nitrogen/generated/android/c++/JFunc_void_std__vector_Track_.hpp b/nitrogen/generated/android/c++/JFunc_void_std__vector_Track_.hpp new file mode 100644 index 0000000..66e1af8 --- /dev/null +++ b/nitrogen/generated/android/c++/JFunc_void_std__vector_Track_.hpp @@ -0,0 +1,98 @@ +/// +/// JFunc_void_std__vector_Track_.hpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © Marc Rousavy @ Margelo +/// + +#pragma once + +#include +#include + +#include "Track.hpp" +#include +#include +#include +#include "JTrack.hpp" +#include +#include + +namespace margelo::nitro::omni { + + using namespace facebook; + + /** + * Represents the Java/Kotlin callback `(value: Array) -> Unit`. + * This can be passed around between C++ and Java/Kotlin. + */ + struct JFunc_void_std__vector_Track_: public jni::JavaClass { + public: + static constexpr auto kJavaDescriptor = "Lcom/margelo/nitro/omni/Func_void_std__vector_Track_;"; + + public: + /** + * Invokes the function this `JFunc_void_std__vector_Track_` instance holds through JNI. + */ + void invoke(const std::vector& value) const { + static const auto method = javaClassStatic()->getMethod> /* value */)>("invoke"); + method(self(), [&](auto&& __input) { + size_t __size = __input.size(); + jni::local_ref> __array = jni::JArrayClass::newArray(__size); + for (size_t __i = 0; __i < __size; __i++) { + const auto& __element = __input[__i]; + auto __elementJni = JTrack::fromCpp(__element); + __array->setElement(__i, *__elementJni); + } + return __array; + }(value)); + } + }; + + /** + * An implementation of Func_void_std__vector_Track_ that is backed by a C++ implementation (using `std::function<...>`) + */ + class JFunc_void_std__vector_Track__cxx final: public jni::HybridClass { + public: + static jni::local_ref fromCpp(const std::function& /* value */)>& func) { + return JFunc_void_std__vector_Track__cxx::newObjectCxxArgs(func); + } + + public: + /** + * Invokes the C++ `std::function<...>` this `JFunc_void_std__vector_Track__cxx` instance holds. + */ + void invoke_cxx(jni::alias_ref> value) { + _func([&](auto&& __input) { + size_t __size = __input->size(); + std::vector __vector; + __vector.reserve(__size); + for (size_t __i = 0; __i < __size; __i++) { + auto __element = __input->getElement(__i); + __vector.push_back(__element->toCpp()); + } + return __vector; + }(value)); + } + + public: + [[nodiscard]] + inline const std::function& /* value */)>& getFunction() const { + return _func; + } + + public: + static constexpr auto kJavaDescriptor = "Lcom/margelo/nitro/omni/Func_void_std__vector_Track__cxx;"; + static void registerNatives() { + registerHybrid({makeNativeMethod("invoke_cxx", JFunc_void_std__vector_Track__cxx::invoke_cxx)}); + } + + private: + explicit JFunc_void_std__vector_Track__cxx(const std::function& /* value */)>& func): _func(func) { } + + private: + friend HybridBase; + std::function& /* value */)> _func; + }; + +} // namespace margelo::nitro::omni diff --git a/nitrogen/generated/android/c++/JHybridOmniEventMapSpec.cpp b/nitrogen/generated/android/c++/JHybridOmniEventMapSpec.cpp index 378c1bb..7a1d149 100644 --- a/nitrogen/generated/android/c++/JHybridOmniEventMapSpec.cpp +++ b/nitrogen/generated/android/c++/JHybridOmniEventMapSpec.cpp @@ -25,6 +25,8 @@ namespace margelo::nitro::omni { struct Subtitle; } namespace margelo::nitro::omni { struct Metadata; } // Forward declaration of `MixAudioMode` to properly resolve imports. namespace margelo::nitro::omni { enum class MixAudioMode; } +// Forward declaration of `TrackProperty` to properly resolve imports. +namespace margelo::nitro::omni { enum class TrackProperty; } // Forward declaration of `Track` to properly resolve imports. namespace margelo::nitro::omni { struct Track; } // Forward declaration of `Rendition` to properly resolve imports. @@ -59,16 +61,20 @@ namespace margelo::nitro::omni { struct Rendition; } #include "JMetadata.hpp" #include "MixAudioMode.hpp" #include "JMixAudioMode.hpp" +#include "TrackProperty.hpp" +#include "JTrackProperty.hpp" +#include "Track.hpp" +#include "JFunc_void_std__vector_Track_.hpp" +#include "JTrack.hpp" +#include "Rendition.hpp" +#include "JFunc_void_std__vector_Rendition_.hpp" +#include "JRendition.hpp" #include "JFunc_void.hpp" #include "JFunc_void_std__string_std__string.hpp" #include "JFunc_void_std__string.hpp" -#include "Track.hpp" #include "JFunc_void_Track.hpp" -#include "JTrack.hpp" #include "JFunc_void_std__optional_Track_.hpp" -#include "Rendition.hpp" #include "JFunc_void_Rendition.hpp" -#include "JRendition.hpp" namespace margelo::nitro::omni { @@ -143,6 +149,22 @@ namespace margelo::nitro::omni { static const auto method = _javaPart->javaClassStatic()->getMethod /* cb */)>("removeSourceListener_cxx"); method(_javaPart, JFunc_void_std__optional_Source__cxx::fromCpp(cb)); } + void JHybridOmniEventMapSpec::addTracksListener(TrackProperty key, const std::function& /* value */)>& cb) { + static const auto method = _javaPart->javaClassStatic()->getMethod /* key */, jni::alias_ref /* cb */)>("addTracksListener_cxx"); + method(_javaPart, JTrackProperty::fromCpp(key), JFunc_void_std__vector_Track__cxx::fromCpp(cb)); + } + void JHybridOmniEventMapSpec::removeTracksListener(TrackProperty key, const std::function& /* value */)>& cb) { + static const auto method = _javaPart->javaClassStatic()->getMethod /* key */, jni::alias_ref /* cb */)>("removeTracksListener_cxx"); + method(_javaPart, JTrackProperty::fromCpp(key), JFunc_void_std__vector_Track__cxx::fromCpp(cb)); + } + void JHybridOmniEventMapSpec::addRenditionsListener(const std::function& /* value */)>& cb) { + static const auto method = _javaPart->javaClassStatic()->getMethod /* cb */)>("addRenditionsListener_cxx"); + method(_javaPart, JFunc_void_std__vector_Rendition__cxx::fromCpp(cb)); + } + void JHybridOmniEventMapSpec::removeRenditionsListener(const std::function& /* value */)>& cb) { + static const auto method = _javaPart->javaClassStatic()->getMethod /* cb */)>("removeRenditionsListener_cxx"); + method(_javaPart, JFunc_void_std__vector_Rendition__cxx::fromCpp(cb)); + } void JHybridOmniEventMapSpec::addOnEndListener(const std::function& cb) { static const auto method = _javaPart->javaClassStatic()->getMethod /* cb */)>("addOnEndListener_cxx"); method(_javaPart, JFunc_void_cxx::fromCpp(cb)); diff --git a/nitrogen/generated/android/c++/JHybridOmniEventMapSpec.hpp b/nitrogen/generated/android/c++/JHybridOmniEventMapSpec.hpp index 0734823..4b8baed 100644 --- a/nitrogen/generated/android/c++/JHybridOmniEventMapSpec.hpp +++ b/nitrogen/generated/android/c++/JHybridOmniEventMapSpec.hpp @@ -64,6 +64,10 @@ namespace margelo::nitro::omni { void removeCastStatusListener(const std::function& cb) override; void addSourceListener(const std::function& /* value */)>& cb) override; void removeSourceListener(const std::function& /* value */)>& cb) override; + void addTracksListener(TrackProperty key, const std::function& /* value */)>& cb) override; + void removeTracksListener(TrackProperty key, const std::function& /* value */)>& cb) override; + void addRenditionsListener(const std::function& /* value */)>& cb) override; + void removeRenditionsListener(const std::function& /* value */)>& cb) override; void addOnEndListener(const std::function& cb) override; void removeOnEndListener(const std::function& cb) override; void addOnPrevListener(const std::function& cb) override; diff --git a/nitrogen/generated/android/c++/JHybridOmniPlayerSpec.cpp b/nitrogen/generated/android/c++/JHybridOmniPlayerSpec.cpp index 9f8e535..6dcbc12 100644 --- a/nitrogen/generated/android/c++/JHybridOmniPlayerSpec.cpp +++ b/nitrogen/generated/android/c++/JHybridOmniPlayerSpec.cpp @@ -9,10 +9,6 @@ // Forward declaration of `HybridOmniEventMapSpec` to properly resolve imports. namespace margelo::nitro::omni { class HybridOmniEventMapSpec; } -// Forward declaration of `Track` to properly resolve imports. -namespace margelo::nitro::omni { struct Track; } -// Forward declaration of `Rendition` to properly resolve imports. -namespace margelo::nitro::omni { struct Rendition; } // Forward declaration of `Source` to properly resolve imports. namespace margelo::nitro::omni { struct Source; } // Forward declaration of `VideoSrc` to properly resolve imports. @@ -27,23 +23,23 @@ namespace margelo::nitro::omni { enum class MixAudioMode; } namespace margelo::nitro::omni { enum class PlayerStatus; } // Forward declaration of `CastStatus` to properly resolve imports. namespace margelo::nitro::omni { enum class CastStatus; } +// Forward declaration of `Track` to properly resolve imports. +namespace margelo::nitro::omni { struct Track; } +// Forward declaration of `Rendition` to properly resolve imports. +namespace margelo::nitro::omni { struct Rendition; } #include #include "HybridOmniEventMapSpec.hpp" #include "JHybridOmniEventMapSpec.hpp" #include -#include "Track.hpp" -#include -#include "JTrack.hpp" -#include -#include "Rendition.hpp" -#include "JRendition.hpp" #include "Source.hpp" #include "JSource.hpp" #include "VideoSrc.hpp" #include "JVideoSrc.hpp" +#include #include #include "Subtitle.hpp" +#include #include "JSubtitle.hpp" #include "Metadata.hpp" #include "JMetadata.hpp" @@ -53,6 +49,10 @@ namespace margelo::nitro::omni { enum class CastStatus; } #include "JPlayerStatus.hpp" #include "CastStatus.hpp" #include "JCastStatus.hpp" +#include "Track.hpp" +#include "JTrack.hpp" +#include "Rendition.hpp" +#include "JRendition.hpp" namespace margelo::nitro::omni { @@ -108,62 +108,6 @@ namespace margelo::nitro::omni { auto __result = method(_javaPart); return static_cast(__result); } - std::vector JHybridOmniPlayerSpec::getVideos() { - static const auto method = _javaPart->javaClassStatic()->getMethod>()>("getVideos"); - auto __result = method(_javaPart); - return [&](auto&& __input) { - size_t __size = __input->size(); - std::vector __vector; - __vector.reserve(__size); - for (size_t __i = 0; __i < __size; __i++) { - auto __element = __input->getElement(__i); - __vector.push_back(__element->toCpp()); - } - return __vector; - }(__result); - } - std::vector JHybridOmniPlayerSpec::getAudios() { - static const auto method = _javaPart->javaClassStatic()->getMethod>()>("getAudios"); - auto __result = method(_javaPart); - return [&](auto&& __input) { - size_t __size = __input->size(); - std::vector __vector; - __vector.reserve(__size); - for (size_t __i = 0; __i < __size; __i++) { - auto __element = __input->getElement(__i); - __vector.push_back(__element->toCpp()); - } - return __vector; - }(__result); - } - std::vector JHybridOmniPlayerSpec::getSubtitles() { - static const auto method = _javaPart->javaClassStatic()->getMethod>()>("getSubtitles"); - auto __result = method(_javaPart); - return [&](auto&& __input) { - size_t __size = __input->size(); - std::vector __vector; - __vector.reserve(__size); - for (size_t __i = 0; __i < __size; __i++) { - auto __element = __input->getElement(__i); - __vector.push_back(__element->toCpp()); - } - return __vector; - }(__result); - } - std::vector JHybridOmniPlayerSpec::getRendition() { - static const auto method = _javaPart->javaClassStatic()->getMethod>()>("getRendition"); - auto __result = method(_javaPart); - return [&](auto&& __input) { - size_t __size = __input->size(); - std::vector __vector; - __vector.reserve(__size); - for (size_t __i = 0; __i < __size; __i++) { - auto __element = __input->getElement(__i); - __vector.push_back(__element->toCpp()); - } - return __vector; - }(__result); - } std::optional JHybridOmniPlayerSpec::getSource() { static const auto method = _javaPart->javaClassStatic()->getMethod()>("getSource"); auto __result = method(_javaPart); @@ -239,6 +183,62 @@ namespace margelo::nitro::omni { auto __result = method(_javaPart); return __result->toCpp(); } + std::vector JHybridOmniPlayerSpec::getVideos() { + static const auto method = _javaPart->javaClassStatic()->getMethod>()>("getVideos"); + auto __result = method(_javaPart); + return [&](auto&& __input) { + size_t __size = __input->size(); + std::vector __vector; + __vector.reserve(__size); + for (size_t __i = 0; __i < __size; __i++) { + auto __element = __input->getElement(__i); + __vector.push_back(__element->toCpp()); + } + return __vector; + }(__result); + } + std::vector JHybridOmniPlayerSpec::getAudios() { + static const auto method = _javaPart->javaClassStatic()->getMethod>()>("getAudios"); + auto __result = method(_javaPart); + return [&](auto&& __input) { + size_t __size = __input->size(); + std::vector __vector; + __vector.reserve(__size); + for (size_t __i = 0; __i < __size; __i++) { + auto __element = __input->getElement(__i); + __vector.push_back(__element->toCpp()); + } + return __vector; + }(__result); + } + std::vector JHybridOmniPlayerSpec::getSubtitles() { + static const auto method = _javaPart->javaClassStatic()->getMethod>()>("getSubtitles"); + auto __result = method(_javaPart); + return [&](auto&& __input) { + size_t __size = __input->size(); + std::vector __vector; + __vector.reserve(__size); + for (size_t __i = 0; __i < __size; __i++) { + auto __element = __input->getElement(__i); + __vector.push_back(__element->toCpp()); + } + return __vector; + }(__result); + } + std::vector JHybridOmniPlayerSpec::getRenditions() { + static const auto method = _javaPart->javaClassStatic()->getMethod>()>("getRenditions"); + auto __result = method(_javaPart); + return [&](auto&& __input) { + size_t __size = __input->size(); + std::vector __vector; + __vector.reserve(__size); + for (size_t __i = 0; __i < __size; __i++) { + auto __element = __input->getElement(__i); + __vector.push_back(__element->toCpp()); + } + return __vector; + }(__result); + } // Methods void JHybridOmniPlayerSpec::play() { diff --git a/nitrogen/generated/android/c++/JHybridOmniPlayerSpec.hpp b/nitrogen/generated/android/c++/JHybridOmniPlayerSpec.hpp index fe76fcd..654793c 100644 --- a/nitrogen/generated/android/c++/JHybridOmniPlayerSpec.hpp +++ b/nitrogen/generated/android/c++/JHybridOmniPlayerSpec.hpp @@ -55,10 +55,6 @@ namespace margelo::nitro::omni { void setShowNotification(std::optional showNotification) override; bool getHasPrev() override; bool getHasNext() override; - std::vector getVideos() override; - std::vector getAudios() override; - std::vector getSubtitles() override; - std::vector getRendition() override; std::optional getSource() override; void setSource(const std::optional& source) override; PlayerStatus getStatus() override; @@ -75,6 +71,10 @@ namespace margelo::nitro::omni { void setMuted(bool muted) override; bool getIsAutoQuality() override; CastStatus getCastStatus() override; + std::vector getVideos() override; + std::vector getAudios() override; + std::vector getSubtitles() override; + std::vector getRenditions() override; public: // Methods diff --git a/nitrogen/generated/android/c++/JTrackProperty.hpp b/nitrogen/generated/android/c++/JTrackProperty.hpp new file mode 100644 index 0000000..b4d9e75 --- /dev/null +++ b/nitrogen/generated/android/c++/JTrackProperty.hpp @@ -0,0 +1,61 @@ +/// +/// JTrackProperty.hpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © Marc Rousavy @ Margelo +/// + +#pragma once + +#include +#include "TrackProperty.hpp" + +namespace margelo::nitro::omni { + + using namespace facebook; + + /** + * The C++ JNI bridge between the C++ enum "TrackProperty" and the Kotlin enum "TrackProperty". + */ + struct JTrackProperty final: public jni::JavaClass { + public: + static constexpr auto kJavaDescriptor = "Lcom/margelo/nitro/omni/TrackProperty;"; + + public: + /** + * Convert this Java/Kotlin-based enum to the C++ enum TrackProperty. + */ + [[maybe_unused]] + [[nodiscard]] + TrackProperty toCpp() const { + static const auto clazz = javaClassStatic(); + static const auto fieldOrdinal = clazz->getField("value"); + int ordinal = this->getFieldValue(fieldOrdinal); + return static_cast(ordinal); + } + + public: + /** + * Create a Java/Kotlin-based enum with the given C++ enum's value. + */ + [[maybe_unused]] + static jni::alias_ref fromCpp(TrackProperty value) { + static const auto clazz = javaClassStatic(); + switch (value) { + case TrackProperty::VIDEOS: + static const auto fieldVIDEOS = clazz->getStaticField("VIDEOS"); + return clazz->getStaticFieldValue(fieldVIDEOS); + case TrackProperty::AUDIOS: + static const auto fieldAUDIOS = clazz->getStaticField("AUDIOS"); + return clazz->getStaticFieldValue(fieldAUDIOS); + case TrackProperty::SUBTITLES: + static const auto fieldSUBTITLES = clazz->getStaticField("SUBTITLES"); + return clazz->getStaticFieldValue(fieldSUBTITLES); + default: + std::string stringValue = std::to_string(static_cast(value)); + throw std::invalid_argument("Invalid enum value (" + stringValue + "!"); + } + } + }; + +} // namespace margelo::nitro::omni diff --git a/nitrogen/generated/android/kotlin/com/margelo/nitro/omni/Func_void_std__vector_Rendition_.kt b/nitrogen/generated/android/kotlin/com/margelo/nitro/omni/Func_void_std__vector_Rendition_.kt new file mode 100644 index 0000000..e9186fb --- /dev/null +++ b/nitrogen/generated/android/kotlin/com/margelo/nitro/omni/Func_void_std__vector_Rendition_.kt @@ -0,0 +1,80 @@ +/// +/// Func_void_std__vector_Rendition_.kt +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © Marc Rousavy @ Margelo +/// + +package com.margelo.nitro.omni + +import androidx.annotation.Keep +import com.facebook.jni.HybridData +import com.facebook.proguard.annotations.DoNotStrip +import dalvik.annotation.optimization.FastNative + + +/** + * Represents the JavaScript callback `(value: array) => void`. + * This can be either implemented in C++ (in which case it might be a callback coming from JS), + * or in Kotlin/Java (in which case it is a native callback). + */ +@DoNotStrip +@Keep +@Suppress("ClassName", "RedundantUnitReturnType") +fun interface Func_void_std__vector_Rendition_: (Array) -> Unit { + /** + * Call the given JS callback. + * @throws Throwable if the JS function itself throws an error, or if the JS function/runtime has already been deleted. + */ + @DoNotStrip + @Keep + override fun invoke(value: Array): Unit +} + +/** + * Represents the JavaScript callback `(value: array) => void`. + * This is implemented in C++, via a `std::function<...>`. + * The callback might be coming from JS. + */ +@DoNotStrip +@Keep +@Suppress( + "KotlinJniMissingFunction", "unused", + "RedundantSuppression", "RedundantUnitReturnType", "FunctionName", + "ConvertSecondaryConstructorToPrimary", "ClassName", "LocalVariableName", +) +class Func_void_std__vector_Rendition__cxx: Func_void_std__vector_Rendition_ { + @DoNotStrip + @Keep + private val mHybridData: HybridData + + @DoNotStrip + @Keep + private constructor(hybridData: HybridData) { + mHybridData = hybridData + } + + @DoNotStrip + @Keep + override fun invoke(value: Array): Unit + = invoke_cxx(value) + + @FastNative + private external fun invoke_cxx(value: Array): Unit +} + +/** + * Represents the JavaScript callback `(value: array) => void`. + * This is implemented in Java/Kotlin, via a `(Array) -> Unit`. + * The callback is always coming from native. + */ +@DoNotStrip +@Keep +@Suppress("ClassName", "RedundantUnitReturnType", "unused") +class Func_void_std__vector_Rendition__java(private val function: (Array) -> Unit): Func_void_std__vector_Rendition_ { + @DoNotStrip + @Keep + override fun invoke(value: Array): Unit { + return this.function(value) + } +} diff --git a/nitrogen/generated/android/kotlin/com/margelo/nitro/omni/Func_void_std__vector_Track_.kt b/nitrogen/generated/android/kotlin/com/margelo/nitro/omni/Func_void_std__vector_Track_.kt new file mode 100644 index 0000000..0283738 --- /dev/null +++ b/nitrogen/generated/android/kotlin/com/margelo/nitro/omni/Func_void_std__vector_Track_.kt @@ -0,0 +1,80 @@ +/// +/// Func_void_std__vector_Track_.kt +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © Marc Rousavy @ Margelo +/// + +package com.margelo.nitro.omni + +import androidx.annotation.Keep +import com.facebook.jni.HybridData +import com.facebook.proguard.annotations.DoNotStrip +import dalvik.annotation.optimization.FastNative + + +/** + * Represents the JavaScript callback `(value: array) => void`. + * This can be either implemented in C++ (in which case it might be a callback coming from JS), + * or in Kotlin/Java (in which case it is a native callback). + */ +@DoNotStrip +@Keep +@Suppress("ClassName", "RedundantUnitReturnType") +fun interface Func_void_std__vector_Track_: (Array) -> Unit { + /** + * Call the given JS callback. + * @throws Throwable if the JS function itself throws an error, or if the JS function/runtime has already been deleted. + */ + @DoNotStrip + @Keep + override fun invoke(value: Array): Unit +} + +/** + * Represents the JavaScript callback `(value: array) => void`. + * This is implemented in C++, via a `std::function<...>`. + * The callback might be coming from JS. + */ +@DoNotStrip +@Keep +@Suppress( + "KotlinJniMissingFunction", "unused", + "RedundantSuppression", "RedundantUnitReturnType", "FunctionName", + "ConvertSecondaryConstructorToPrimary", "ClassName", "LocalVariableName", +) +class Func_void_std__vector_Track__cxx: Func_void_std__vector_Track_ { + @DoNotStrip + @Keep + private val mHybridData: HybridData + + @DoNotStrip + @Keep + private constructor(hybridData: HybridData) { + mHybridData = hybridData + } + + @DoNotStrip + @Keep + override fun invoke(value: Array): Unit + = invoke_cxx(value) + + @FastNative + private external fun invoke_cxx(value: Array): Unit +} + +/** + * Represents the JavaScript callback `(value: array) => void`. + * This is implemented in Java/Kotlin, via a `(Array) -> Unit`. + * The callback is always coming from native. + */ +@DoNotStrip +@Keep +@Suppress("ClassName", "RedundantUnitReturnType", "unused") +class Func_void_std__vector_Track__java(private val function: (Array) -> Unit): Func_void_std__vector_Track_ { + @DoNotStrip + @Keep + override fun invoke(value: Array): Unit { + return this.function(value) + } +} diff --git a/nitrogen/generated/android/kotlin/com/margelo/nitro/omni/HybridOmniEventMapSpec.kt b/nitrogen/generated/android/kotlin/com/margelo/nitro/omni/HybridOmniEventMapSpec.kt index 829a593..11328c2 100644 --- a/nitrogen/generated/android/kotlin/com/margelo/nitro/omni/HybridOmniEventMapSpec.kt +++ b/nitrogen/generated/android/kotlin/com/margelo/nitro/omni/HybridOmniEventMapSpec.kt @@ -118,6 +118,42 @@ abstract class HybridOmniEventMapSpec: HybridObject() { return __result } + abstract fun addTracksListener(key: TrackProperty, cb: (value: Array) -> Unit): Unit + + @DoNotStrip + @Keep + private fun addTracksListener_cxx(key: TrackProperty, cb: Func_void_std__vector_Track_): Unit { + val __result = addTracksListener(key, cb) + return __result + } + + abstract fun removeTracksListener(key: TrackProperty, cb: (value: Array) -> Unit): Unit + + @DoNotStrip + @Keep + private fun removeTracksListener_cxx(key: TrackProperty, cb: Func_void_std__vector_Track_): Unit { + val __result = removeTracksListener(key, cb) + return __result + } + + abstract fun addRenditionsListener(cb: (value: Array) -> Unit): Unit + + @DoNotStrip + @Keep + private fun addRenditionsListener_cxx(cb: Func_void_std__vector_Rendition_): Unit { + val __result = addRenditionsListener(cb) + return __result + } + + abstract fun removeRenditionsListener(cb: (value: Array) -> Unit): Unit + + @DoNotStrip + @Keep + private fun removeRenditionsListener_cxx(cb: Func_void_std__vector_Rendition_): Unit { + val __result = removeRenditionsListener(cb) + return __result + } + abstract fun addOnEndListener(cb: () -> Unit): Unit @DoNotStrip 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 55c99a7..b2a11de 100644 --- a/nitrogen/generated/android/kotlin/com/margelo/nitro/omni/HybridOmniPlayerSpec.kt +++ b/nitrogen/generated/android/kotlin/com/margelo/nitro/omni/HybridOmniPlayerSpec.kt @@ -43,22 +43,6 @@ abstract class HybridOmniPlayerSpec: HybridObject() { @get:Keep abstract val hasNext: Boolean - @get:DoNotStrip - @get:Keep - abstract val videos: Array - - @get:DoNotStrip - @get:Keep - abstract val audios: Array - - @get:DoNotStrip - @get:Keep - abstract val subtitles: Array - - @get:DoNotStrip - @get:Keep - abstract val rendition: Array - @get:DoNotStrip @get:Keep @set:DoNotStrip @@ -112,6 +96,22 @@ abstract class HybridOmniPlayerSpec: HybridObject() { @get:DoNotStrip @get:Keep abstract val castStatus: CastStatus + + @get:DoNotStrip + @get:Keep + abstract val videos: Array + + @get:DoNotStrip + @get:Keep + abstract val audios: Array + + @get:DoNotStrip + @get:Keep + abstract val subtitles: Array + + @get:DoNotStrip + @get:Keep + abstract val renditions: Array // Methods @DoNotStrip diff --git a/nitrogen/generated/android/kotlin/com/margelo/nitro/omni/TrackProperty.kt b/nitrogen/generated/android/kotlin/com/margelo/nitro/omni/TrackProperty.kt new file mode 100644 index 0000000..76ecf66 --- /dev/null +++ b/nitrogen/generated/android/kotlin/com/margelo/nitro/omni/TrackProperty.kt @@ -0,0 +1,24 @@ +/// +/// TrackProperty.kt +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © Marc Rousavy @ Margelo +/// + +package com.margelo.nitro.omni + +import androidx.annotation.Keep +import com.facebook.proguard.annotations.DoNotStrip + +/** + * Represents the JavaScript enum/union "TrackProperty". + */ +@DoNotStrip +@Keep +enum class TrackProperty(@DoNotStrip @Keep val value: Int) { + VIDEOS(0), + AUDIOS(1), + SUBTITLES(2); + + companion object +} diff --git a/nitrogen/generated/shared/c++/HybridOmniEventMapSpec.cpp b/nitrogen/generated/shared/c++/HybridOmniEventMapSpec.cpp index ba66ff3..721015e 100644 --- a/nitrogen/generated/shared/c++/HybridOmniEventMapSpec.cpp +++ b/nitrogen/generated/shared/c++/HybridOmniEventMapSpec.cpp @@ -24,6 +24,10 @@ namespace margelo::nitro::omni { prototype.registerHybridMethod("removeCastStatusListener", &HybridOmniEventMapSpec::removeCastStatusListener); prototype.registerHybridMethod("addSourceListener", &HybridOmniEventMapSpec::addSourceListener); prototype.registerHybridMethod("removeSourceListener", &HybridOmniEventMapSpec::removeSourceListener); + prototype.registerHybridMethod("addTracksListener", &HybridOmniEventMapSpec::addTracksListener); + prototype.registerHybridMethod("removeTracksListener", &HybridOmniEventMapSpec::removeTracksListener); + prototype.registerHybridMethod("addRenditionsListener", &HybridOmniEventMapSpec::addRenditionsListener); + prototype.registerHybridMethod("removeRenditionsListener", &HybridOmniEventMapSpec::removeRenditionsListener); prototype.registerHybridMethod("addOnEndListener", &HybridOmniEventMapSpec::addOnEndListener); prototype.registerHybridMethod("removeOnEndListener", &HybridOmniEventMapSpec::removeOnEndListener); prototype.registerHybridMethod("addOnPrevListener", &HybridOmniEventMapSpec::addOnPrevListener); diff --git a/nitrogen/generated/shared/c++/HybridOmniEventMapSpec.hpp b/nitrogen/generated/shared/c++/HybridOmniEventMapSpec.hpp index 68df47e..efe35bd 100644 --- a/nitrogen/generated/shared/c++/HybridOmniEventMapSpec.hpp +++ b/nitrogen/generated/shared/c++/HybridOmniEventMapSpec.hpp @@ -23,6 +23,8 @@ namespace margelo::nitro::omni { enum class PlayerStatus; } namespace margelo::nitro::omni { enum class CastStatus; } // Forward declaration of `Source` to properly resolve imports. namespace margelo::nitro::omni { struct Source; } +// Forward declaration of `TrackProperty` to properly resolve imports. +namespace margelo::nitro::omni { enum class TrackProperty; } // Forward declaration of `Track` to properly resolve imports. namespace margelo::nitro::omni { struct Track; } // Forward declaration of `Rendition` to properly resolve imports. @@ -35,9 +37,11 @@ namespace margelo::nitro::omni { struct Rendition; } #include "CastStatus.hpp" #include "Source.hpp" #include -#include +#include "TrackProperty.hpp" #include "Track.hpp" +#include #include "Rendition.hpp" +#include namespace margelo::nitro::omni { @@ -80,6 +84,10 @@ namespace margelo::nitro::omni { virtual void removeCastStatusListener(const std::function& cb) = 0; virtual void addSourceListener(const std::function& /* value */)>& cb) = 0; virtual void removeSourceListener(const std::function& /* value */)>& cb) = 0; + virtual void addTracksListener(TrackProperty key, const std::function& /* value */)>& cb) = 0; + virtual void removeTracksListener(TrackProperty key, const std::function& /* value */)>& cb) = 0; + virtual void addRenditionsListener(const std::function& /* value */)>& cb) = 0; + virtual void removeRenditionsListener(const std::function& /* value */)>& cb) = 0; virtual void addOnEndListener(const std::function& cb) = 0; virtual void removeOnEndListener(const std::function& cb) = 0; virtual void addOnPrevListener(const std::function& cb) = 0; diff --git a/nitrogen/generated/shared/c++/HybridOmniPlayerSpec.cpp b/nitrogen/generated/shared/c++/HybridOmniPlayerSpec.cpp index aa53a94..6c84a79 100644 --- a/nitrogen/generated/shared/c++/HybridOmniPlayerSpec.cpp +++ b/nitrogen/generated/shared/c++/HybridOmniPlayerSpec.cpp @@ -19,10 +19,6 @@ namespace margelo::nitro::omni { prototype.registerHybridSetter("showNotification", &HybridOmniPlayerSpec::setShowNotification); prototype.registerHybridGetter("hasPrev", &HybridOmniPlayerSpec::getHasPrev); prototype.registerHybridGetter("hasNext", &HybridOmniPlayerSpec::getHasNext); - prototype.registerHybridGetter("videos", &HybridOmniPlayerSpec::getVideos); - prototype.registerHybridGetter("audios", &HybridOmniPlayerSpec::getAudios); - prototype.registerHybridGetter("subtitles", &HybridOmniPlayerSpec::getSubtitles); - prototype.registerHybridGetter("rendition", &HybridOmniPlayerSpec::getRendition); prototype.registerHybridGetter("source", &HybridOmniPlayerSpec::getSource); prototype.registerHybridSetter("source", &HybridOmniPlayerSpec::setSource); prototype.registerHybridGetter("status", &HybridOmniPlayerSpec::getStatus); @@ -39,6 +35,10 @@ namespace margelo::nitro::omni { prototype.registerHybridSetter("muted", &HybridOmniPlayerSpec::setMuted); prototype.registerHybridGetter("isAutoQuality", &HybridOmniPlayerSpec::getIsAutoQuality); prototype.registerHybridGetter("castStatus", &HybridOmniPlayerSpec::getCastStatus); + prototype.registerHybridGetter("videos", &HybridOmniPlayerSpec::getVideos); + prototype.registerHybridGetter("audios", &HybridOmniPlayerSpec::getAudios); + prototype.registerHybridGetter("subtitles", &HybridOmniPlayerSpec::getSubtitles); + prototype.registerHybridGetter("renditions", &HybridOmniPlayerSpec::getRenditions); 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 b851272..ff35abe 100644 --- a/nitrogen/generated/shared/c++/HybridOmniPlayerSpec.hpp +++ b/nitrogen/generated/shared/c++/HybridOmniPlayerSpec.hpp @@ -15,26 +15,26 @@ // Forward declaration of `HybridOmniEventMapSpec` to properly resolve imports. namespace margelo::nitro::omni { class HybridOmniEventMapSpec; } -// Forward declaration of `Track` to properly resolve imports. -namespace margelo::nitro::omni { struct Track; } -// Forward declaration of `Rendition` to properly resolve imports. -namespace margelo::nitro::omni { struct Rendition; } // Forward declaration of `Source` to properly resolve imports. namespace margelo::nitro::omni { struct Source; } // Forward declaration of `PlayerStatus` to properly resolve imports. namespace margelo::nitro::omni { enum class PlayerStatus; } // Forward declaration of `CastStatus` to properly resolve imports. namespace margelo::nitro::omni { enum class CastStatus; } +// Forward declaration of `Track` to properly resolve imports. +namespace margelo::nitro::omni { struct Track; } +// Forward declaration of `Rendition` to properly resolve imports. +namespace margelo::nitro::omni { struct Rendition; } #include #include "HybridOmniEventMapSpec.hpp" #include -#include "Track.hpp" -#include -#include "Rendition.hpp" #include "Source.hpp" #include "PlayerStatus.hpp" #include "CastStatus.hpp" +#include "Track.hpp" +#include +#include "Rendition.hpp" namespace margelo::nitro::omni { @@ -68,10 +68,6 @@ namespace margelo::nitro::omni { virtual void setShowNotification(std::optional showNotification) = 0; virtual bool getHasPrev() = 0; virtual bool getHasNext() = 0; - virtual std::vector getVideos() = 0; - virtual std::vector getAudios() = 0; - virtual std::vector getSubtitles() = 0; - virtual std::vector getRendition() = 0; virtual std::optional getSource() = 0; virtual void setSource(const std::optional& source) = 0; virtual PlayerStatus getStatus() = 0; @@ -88,6 +84,10 @@ namespace margelo::nitro::omni { virtual void setMuted(bool muted) = 0; virtual bool getIsAutoQuality() = 0; virtual CastStatus getCastStatus() = 0; + virtual std::vector getVideos() = 0; + virtual std::vector getAudios() = 0; + virtual std::vector getSubtitles() = 0; + virtual std::vector getRenditions() = 0; public: // Methods diff --git a/nitrogen/generated/shared/c++/TrackProperty.hpp b/nitrogen/generated/shared/c++/TrackProperty.hpp new file mode 100644 index 0000000..85cfcad --- /dev/null +++ b/nitrogen/generated/shared/c++/TrackProperty.hpp @@ -0,0 +1,80 @@ +/// +/// TrackProperty.hpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © Marc Rousavy @ Margelo +/// + +#pragma once + +#if __has_include() +#include +#else +#error NitroModules cannot be found! Are you sure you installed NitroModules properly? +#endif +#if __has_include() +#include +#else +#error NitroModules cannot be found! Are you sure you installed NitroModules properly? +#endif +#if __has_include() +#include +#else +#error NitroModules cannot be found! Are you sure you installed NitroModules properly? +#endif + +namespace margelo::nitro::omni { + + /** + * An enum which can be represented as a JavaScript union (TrackProperty). + */ + enum class TrackProperty { + VIDEOS SWIFT_NAME(videos) = 0, + AUDIOS SWIFT_NAME(audios) = 1, + SUBTITLES SWIFT_NAME(subtitles) = 2, + } CLOSED_ENUM; + +} // namespace margelo::nitro::omni + +namespace margelo::nitro { + + // C++ TrackProperty <> JS TrackProperty (union) + template <> + struct JSIConverter final { + static inline margelo::nitro::omni::TrackProperty fromJSI(jsi::Runtime& runtime, const jsi::Value& arg) { + std::string unionValue = JSIConverter::fromJSI(runtime, arg); + switch (hashString(unionValue.c_str(), unionValue.size())) { + case hashString("videos"): return margelo::nitro::omni::TrackProperty::VIDEOS; + case hashString("audios"): return margelo::nitro::omni::TrackProperty::AUDIOS; + case hashString("subtitles"): return margelo::nitro::omni::TrackProperty::SUBTITLES; + default: [[unlikely]] + throw std::invalid_argument("Cannot convert \"" + unionValue + "\" to enum TrackProperty - invalid value!"); + } + } + static inline jsi::Value toJSI(jsi::Runtime& runtime, margelo::nitro::omni::TrackProperty arg) { + switch (arg) { + case margelo::nitro::omni::TrackProperty::VIDEOS: return JSIConverter::toJSI(runtime, "videos"); + case margelo::nitro::omni::TrackProperty::AUDIOS: return JSIConverter::toJSI(runtime, "audios"); + case margelo::nitro::omni::TrackProperty::SUBTITLES: return JSIConverter::toJSI(runtime, "subtitles"); + default: [[unlikely]] + throw std::invalid_argument("Cannot convert TrackProperty to JS - invalid value: " + + std::to_string(static_cast(arg)) + "!"); + } + } + static inline bool canConvert(jsi::Runtime& runtime, const jsi::Value& value) { + if (!value.isString()) { + return false; + } + std::string unionValue = JSIConverter::fromJSI(runtime, value); + switch (hashString(unionValue.c_str(), unionValue.size())) { + case hashString("videos"): + case hashString("audios"): + case hashString("subtitles"): + return true; + default: + return false; + } + } + }; + +} // namespace margelo::nitro diff --git a/src/events.tsx b/src/events.tsx index ae9f777..b4645b5 100644 --- a/src/events.tsx +++ b/src/events.tsx @@ -60,6 +60,14 @@ export function usePlayerState( case "source": em.addSourceListener(setState); return () => em.removeSourceListener(setState); + case "videos": + case "audios": + case "subtitles": + em.addTracksListener(key, setState); + return () => em.removeTracksListener(key, setState); + case "renditions": + em.addRenditionsListener(setState); + return () => em.removeRenditionsListener(setState); } }, [player, key]); diff --git a/src/events.web.tsx b/src/events.web.tsx index dfe5118..397440d 100644 --- a/src/events.web.tsx +++ b/src/events.web.tsx @@ -202,6 +202,32 @@ export const stateMapper = { if (r.remotePlaybackState === "connected") return "connected"; return r.remotePlaybackAvailability; }), + ...createMapper("audios", selectAudioTrack, (audio) => { + if (!audio) return []; + return audio.audioTrackList.map((track, i) => ({ + id: track.id ?? i.toString(), + label: track.label, + language: track.language, + selected: track.enabled, + })); + }), + ...createMapper("renditions", selectQuality, (quality) => { + if (!quality) return []; + const active = quality.activeVideoRendition; + return quality.videoRenditionList.map((rendition, i) => ({ + id: rendition.id ?? i.toString(), + width: rendition.width ?? 0, + height: rendition.height ?? 0, + bitrate: rendition.bitrate ?? 0, + selected: + rendition.selected || + (active != null && + rendition.id === active.id && + rendition.width === active.width && + rendition.height === active.height && + rendition.bitrate === active.bitrate), + })); + }), }; // biome-ignore-start lint/correctness/useHookAtTopLevel: key must be const @@ -216,18 +242,36 @@ export function usePlayerState( key: Key, _refresh?: number, ): OmniPlayerState[Key] { - if (key === "source") { - const player = usePlayer() as WebOmniPlayer; - const source = useSyncExternalStore( - player.subscribeSource, - () => player.source, - () => player.source, - ); - return source as OmniPlayerState[Key]; + switch (key as keyof OmniPlayerState) { + case "source": { + const player = usePlayer() as WebOmniPlayer; + const source = useSyncExternalStore( + player.subscribeSource, + () => player.source, + () => player.source, + ); + return source as OmniPlayerState[Key]; + } + case "videos": { + const player = usePlayer(); + return player.videos as OmniPlayerState[Key]; + } + case "subtitles": { + const player = usePlayer() as WebOmniPlayer; + useStoreSelector(selectTextTrack); + useSyncExternalStore( + player.subscribeOverlaySubtitle, + player.getOverlaySubtitle, + () => null, + ); + return player.subtitles as OmniPlayerState[Key]; + } + default: { + const config = stateMapper[key]; + const ret = useStoreSelector(config?.selector as Selector); + if (!config) throw new Error(`No mapper for ${key}`); + return config.mapper(ret) as OmniPlayerState[Key]; + } } - const config = stateMapper[key as keyof Omit]; - const ret = useStoreSelector(config?.selector as Selector); - if (!config) throw new Error(`No mapper for ${key}`); - return config.mapper(ret) as OmniPlayerState[Key]; } // biome-ignore-end lint/correctness/useHookAtTopLevel: key must be const diff --git a/src/player.web.tsx b/src/player.web.tsx index 1cdb34d..3e40374 100644 --- a/src/player.web.tsx +++ b/src/player.web.tsx @@ -1,4 +1,3 @@ -import type { MediaVideoRendition } from "@videojs/core"; import type { VideoPlayerStore } from "@videojs/core/dom"; import { selectAudioTrack, @@ -76,7 +75,6 @@ export class WebOmniPlayer implements OmniPlayer { return () => this.sourceListeners.delete(callback); }; - get source(): Source | undefined { return this._source; } @@ -203,14 +201,7 @@ export class WebOmniPlayer implements OmniPlayer { } get audios(): Track[] { - const audio = selectAudioTrack(this._store.state); - if (!audio) return []; - return audio.audioTrackList.map((track, i) => ({ - id: track.id ?? i.toString(), - label: track.label, - language: track.language, - selected: track.enabled, - })); + return stateMapper.audios.mapper(selectAudioTrack(this._store.state)); } selectAudio(audio: Track): void { @@ -266,30 +257,8 @@ export class WebOmniPlayer implements OmniPlayer { getOverlaySubtitle = (): Subtitle | null => this.overlaySubtitle; - get rendition(): Rendition[] { - function isSameRendition( - a: MediaVideoRendition, - b: MediaVideoRendition | null, - ): boolean { - return ( - b != null && - a.id === b.id && - a.width === b.width && - a.height === b.height && - a.bitrate === b.bitrate - ); - } - - const quality = selectQuality(this._store.state); - if (!quality) return []; - const active = quality.activeVideoRendition; - return quality.videoRenditionList.map((rendition, i) => ({ - id: rendition.id ?? i.toString(), - width: rendition.width ?? 0, - height: rendition.height ?? 0, - bitrate: rendition.bitrate ?? 0, - selected: rendition.selected || isSameRendition(rendition, active), - })); + get renditions(): Rendition[] { + return stateMapper.renditions.mapper(selectQuality(this._store.state)); } selectRendition(rendition?: Rendition): void { diff --git a/src/specs/omni-player.nitro.ts b/src/specs/omni-player.nitro.ts index 45634b4..d422403 100644 --- a/src/specs/omni-player.nitro.ts +++ b/src/specs/omni-player.nitro.ts @@ -6,14 +6,24 @@ import type { OmniPlayer as OmniPlayerT, PlayerBackend, PlayerStatus, + Rendition, + Track, } from "../types/player"; import type { CastOptions, Source } from "../types/source"; export type NumberProperty = Exclude< keyof OmniPlayerState, - "status" | "isPlaying" | "muted" | "source" + | "status" + | "isPlaying" + | "muted" + | "source" + | "videos" + | "audios" + | "subtitles" + | "renditions" >; export type BoolProperty = "isPlaying" | "muted" | "isAutoQuality"; +export type TrackProperty = "videos" | "audios" | "subtitles"; export interface OmniEventMap extends HybridObject<{ android: "kotlin" }> { addStateListener(key: NumberProperty, cb: (value: number) => void): void; @@ -32,6 +42,12 @@ export interface OmniEventMap extends HybridObject<{ android: "kotlin" }> { addSourceListener(cb: (value?: Source) => void): void; removeSourceListener(cb: (value?: Source) => void): void; + addTracksListener(key: TrackProperty, cb: (value: Track[]) => void): void; + removeTracksListener(key: TrackProperty, cb: (value: Track[]) => void): void; + + addRenditionsListener(cb: (value: Rendition[]) => void): void; + removeRenditionsListener(cb: (value: Rendition[]) => void): void; + addOnEndListener(cb: OmniEvents["end"]): void; removeOnEndListener(cb: OmniEvents["end"]): void; diff --git a/src/types/player.ts b/src/types/player.ts index 65890fd..259691e 100644 --- a/src/types/player.ts +++ b/src/types/player.ts @@ -15,13 +15,9 @@ export interface OmniPlayer extends OmniPlayerState { readonly hasPrev: boolean; readonly hasNext: boolean; - readonly videos: Track[]; selectVideo(video: Track): void; - readonly audios: Track[]; selectAudio(audio: Track): void; - readonly subtitles: Track[]; selectSubtitle(subtitle?: Track): void; - readonly rendition: Rendition[]; selectRendition(rendition?: Rendition): void; } @@ -38,6 +34,11 @@ export interface OmniPlayerState { muted: boolean; readonly isAutoQuality: boolean; readonly castStatus: CastStatus; + + readonly videos: Track[]; + readonly audios: Track[]; + readonly subtitles: Track[]; + readonly renditions: Rendition[]; } export type PlayerStatus = "idle" | "loading" | "readyToPlay" | "error"; diff --git a/src/view.web.tsx b/src/view.web.tsx index d2e9f75..669a305 100644 --- a/src/view.web.tsx +++ b/src/view.web.tsx @@ -52,7 +52,7 @@ const SubtitleOverlay = ({ // jassub uses this as libass' storage size so subtitles stay sharp // even while a low-bitrate rendition is playing — otherwise the // raster tracks the currently-decoded (possibly tiny) frame size. - const renditions = player.rendition; + const renditions = player.renditions; const videoWidth = Math.max(0, ...renditions.map((r) => r.width)); const videoHeight = Math.max(0, ...renditions.map((r) => r.height)); import("jassub")