diff --git a/android/src/main/java/dev/zoriya/omni/MpvPlayer.kt b/android/src/main/java/dev/zoriya/omni/MpvPlayer.kt index de5d660..237ffa2 100644 --- a/android/src/main/java/dev/zoriya/omni/MpvPlayer.kt +++ b/android/src/main/java/dev/zoriya/omni/MpvPlayer.kt @@ -25,7 +25,6 @@ import androidx.media3.common.PlaybackException import androidx.media3.common.PlaybackParameters import androidx.media3.common.Player import androidx.media3.common.Timeline -import androidx.media3.common.Timeline.Window.SINGLE_WINDOW_UID import androidx.media3.common.TrackGroup import androidx.media3.common.TrackSelectionParameters import androidx.media3.common.Tracks @@ -98,7 +97,8 @@ class MpvPlayer(ctx: Context) : BasePlayer(), MPVLib.EventObserver { observeProperty("sid", MPVLib.MpvFormat.MPV_FORMAT_INT64) } - private var currentMediaItem: MediaItem? = null + private var mediaItems: List = emptyList() + private var currentMediaItemIndex: Int = INDEX_UNSET private var currentTrackSelectionParameters = TrackSelectionParameters.DEFAULT_WITHOUT_CONTEXT private var playerError: PlaybackException? = null private var playlistMetadata: MediaMetadata = MediaMetadata.EMPTY @@ -112,6 +112,8 @@ class MpvPlayer(ctx: Context) : BasePlayer(), MPVLib.EventObserver { .add(COMMAND_SEEK_TO_DEFAULT_POSITION) .add(COMMAND_SEEK_BACK) .add(COMMAND_SEEK_FORWARD) + .add(COMMAND_SEEK_TO_NEXT_MEDIA_ITEM) + .add(COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM) .add(COMMAND_SET_SPEED_AND_PITCH) .add(COMMAND_GET_VOLUME) .add(COMMAND_SET_VOLUME) @@ -147,23 +149,25 @@ class MpvPlayer(ctx: Context) : BasePlayer(), MPVLib.EventObserver { startIndex: Int, startPositionMs: Long ) { - val prev = currentMediaItem - val target = when { - mediaItems.isEmpty() -> null - startIndex in mediaItems.indices -> mediaItems[startIndex] - else -> mediaItems[0] + val prev = currentMediaItemIndex + val targetIndex = when { + mediaItems.isEmpty() -> INDEX_UNSET + startIndex in mediaItems.indices -> startIndex + else -> 0 } - currentMediaItem = target + this.mediaItems = mediaItems + currentMediaItemIndex = targetIndex + val target = if (targetIndex != INDEX_UNSET) mediaItems[targetIndex] else null playlistMetadata = target?.mediaMetadata ?: MediaMetadata.EMPTY playerError = null mpv.command(arrayOf("stop")) - val targetMs = if (startPositionMs == TIME_UNSET) 0L else startPositionMs.coerceAtLeast(0L) - mpv.setPropertyDouble("start", targetMs / 1000.0) - if (target != null) { + val targetMs = if (startPositionMs == TIME_UNSET) 0L else startPositionMs.coerceAtLeast(0L) + mpv.setPropertyDouble("start", targetMs / 1000.0) + val uri = target.localConfiguration?.uri?.toString() if (!uri.isNullOrEmpty()) { mpv.command(arrayOf("loadfile", uri, "replace")) @@ -178,14 +182,14 @@ class MpvPlayer(ctx: Context) : BasePlayer(), MPVLib.EventObserver { EVENT_MEDIA_METADATA_CHANGED, EVENT_PLAYLIST_METADATA_CHANGED ) - if (prev != currentMediaItem) { + if (prev != currentMediaItemIndex) { events.add(EVENT_MEDIA_ITEM_TRANSITION) } notifyListeners(events.toTypedArray()) { it.onTimelineChanged(currentTimeline, TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED) it.onMediaMetadataChanged(mediaMetadata) it.onPlaylistMetadataChanged(playlistMetadata) - if (prev != currentMediaItem) { + if (prev != currentMediaItemIndex) { it.onMediaItemTransition( currentMediaItem, MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED @@ -206,8 +210,9 @@ class MpvPlayer(ctx: Context) : BasePlayer(), MPVLib.EventObserver { } override fun removeMediaItems(fromIndex: Int, toIndex: Int) { - val prev = currentMediaItem - currentMediaItem = null + val prev = currentMediaItemIndex + mediaItems = emptyList() + currentMediaItemIndex = INDEX_UNSET playlistMetadata = MediaMetadata.EMPTY mpv.command(arrayOf("stop")) @@ -216,14 +221,14 @@ class MpvPlayer(ctx: Context) : BasePlayer(), MPVLib.EventObserver { EVENT_MEDIA_METADATA_CHANGED, EVENT_PLAYLIST_METADATA_CHANGED ) - if (prev != null) { + if (prev != INDEX_UNSET) { events.add(EVENT_MEDIA_ITEM_TRANSITION) } notifyListeners(events.toTypedArray()) { it.onTimelineChanged(currentTimeline, TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED) it.onMediaMetadataChanged(mediaMetadata) it.onPlaylistMetadataChanged(playlistMetadata) - if (prev != null) { + if (prev != INDEX_UNSET) { it.onMediaItemTransition(null, MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED) } } @@ -235,7 +240,7 @@ class MpvPlayer(ctx: Context) : BasePlayer(), MPVLib.EventObserver { override fun getPlaybackState(): Int = when (true) { - (currentMediaItem == null) -> STATE_IDLE + (currentMediaItemIndex == INDEX_UNSET) -> STATE_IDLE mpv.getPropertyBoolean("paused-for-cache") -> STATE_BUFFERING mpv.getPropertyBoolean("eof-reached") -> STATE_ENDED else -> STATE_READY @@ -267,7 +272,14 @@ class MpvPlayer(ctx: Context) : BasePlayer(), MPVLib.EventObserver { seekCommand: Int, isRepeatingCurrentItem: Boolean ) { - if (mediaItemIndex != INDEX_UNSET && mediaItemIndex != 0) return + val targetIndex = if (mediaItemIndex == INDEX_UNSET) currentMediaItemIndex else mediaItemIndex + if (targetIndex == INDEX_UNSET || targetIndex !in mediaItems.indices) return + + if (targetIndex != currentMediaItemIndex) { + setMediaItems(mediaItems, targetIndex, positionMs) + return + } + val targetMs = if (positionMs == TIME_UNSET) 0L else positionMs.coerceAtLeast(0L) mpv.command(arrayOf("seek", (targetMs / 1000.0).toString(), "absolute")) } @@ -379,7 +391,7 @@ class MpvPlayer(ctx: Context) : BasePlayer(), MPVLib.EventObserver { } override fun getMediaMetadata(): MediaMetadata = - currentMediaItem?.mediaMetadata ?: MediaMetadata.EMPTY + getCurrentMediaItem()?.mediaMetadata ?: MediaMetadata.EMPTY override fun getPlaylistMetadata(): MediaMetadata = playlistMetadata @@ -388,19 +400,23 @@ class MpvPlayer(ctx: Context) : BasePlayer(), MPVLib.EventObserver { } override fun getCurrentTimeline(): Timeline { - val item = currentMediaItem ?: return Timeline.EMPTY - val durationUs = if (duration == TIME_UNSET) TIME_UNSET else duration * 1000L + if (mediaItems.isEmpty()) return Timeline.EMPTY return object : Timeline() { - override fun getWindowCount(): Int = 1 + override fun getWindowCount(): Int = mediaItems.size override fun getWindow( windowIndex: Int, window: Window, defaultPositionProjectionUs: Long ): Window { - check(windowIndex == 0) + check(windowIndex in mediaItems.indices) + val item = mediaItems[windowIndex] + val durationUs = if (windowIndex == currentMediaItemIndex) { + val dur = duration + if (dur == TIME_UNSET) TIME_UNSET else dur * 1000L + } else TIME_UNSET return window.set( - SINGLE_WINDOW_UID, + windowIndex, item, null, TIME_UNSET, @@ -417,30 +433,31 @@ class MpvPlayer(ctx: Context) : BasePlayer(), MPVLib.EventObserver { ) } - override fun getPeriodCount(): Int = 1 + override fun getPeriodCount(): Int = mediaItems.size override fun getPeriod(periodIndex: Int, period: Period, setIds: Boolean): Period { - check(periodIndex == 0) - val uid = SINGLE_WINDOW_UID - val id = if (setIds) uid else null - return period.set(id, uid, 0, durationUs, 0L) + check(periodIndex in mediaItems.indices) + val durationUs = if (periodIndex == currentMediaItemIndex) { + val dur = duration + if (dur == TIME_UNSET) TIME_UNSET else dur * 1000L + } else TIME_UNSET + return period.set(periodIndex, periodIndex, 0, durationUs, 0L) } override fun getIndexOfPeriod(uid: Any): Int { - return if (uid == SINGLE_WINDOW_UID) 0 else INDEX_UNSET + return if (uid is Int && uid in mediaItems.indices) uid else INDEX_UNSET } override fun getUidOfPeriod(periodIndex: Int): Any { - check(periodIndex == 0) - return SINGLE_WINDOW_UID + check(periodIndex in mediaItems.indices) + return periodIndex } } } - override fun getCurrentPeriodIndex() = if (currentMediaItem == null) INDEX_UNSET else 0 + override fun getCurrentPeriodIndex() = if (currentMediaItemIndex == INDEX_UNSET) INDEX_UNSET else currentMediaItemIndex - override fun getCurrentMediaItemIndex() = - if (currentMediaItem == null) INDEX_UNSET else 0 + override fun getCurrentMediaItemIndex() = currentMediaItemIndex override fun getDuration(): Long { val duration = mpv.getPropertyDouble("duration") ?: return TIME_UNSET @@ -600,10 +617,10 @@ class MpvPlayer(ctx: Context) : BasePlayer(), MPVLib.EventObserver { MPVLib.MpvEvent.MPV_EVENT_PLAYBACK_RESTART -> { val positionMs = getCurrentPosition() val position = Player.PositionInfo( - SINGLE_WINDOW_UID, + currentMediaItemIndex, getCurrentMediaItemIndex(), getCurrentMediaItem(), - SINGLE_WINDOW_UID, + currentMediaItemIndex, getCurrentPeriodIndex(), positionMs, positionMs, diff --git a/android/src/main/java/dev/zoriya/omni/OmniPlayer.kt b/android/src/main/java/dev/zoriya/omni/OmniPlayer.kt index 612f8f9..8ebbca6 100644 --- a/android/src/main/java/dev/zoriya/omni/OmniPlayer.kt +++ b/android/src/main/java/dev/zoriya/omni/OmniPlayer.kt @@ -20,13 +20,14 @@ import androidx.core.net.toUri import androidx.media3.common.MediaItem.RequestMetadata import androidx.media3.common.MediaItem.SubtitleConfiguration import androidx.media3.common.TrackSelectionOverride -import androidx.media3.exoplayer.ExoPlayer import androidx.media3.session.DefaultMediaNotificationProvider import androidx.media3.session.MediaSession import androidx.media3.session.MediaSessionService import dev.zoriya.omni.utils.ThreadHelper.mainThreadProperty import dev.zoriya.omni.utils.ThreadHelper.runOnMainThread import dev.zoriya.omni.utils.ThreadHelper.runOnMainThreadSync +import kotlinx.coroutines.InternalCoroutinesApi +import kotlinx.coroutines.internal.synchronized @SuppressLint("UnsafeOptInUsageError") class OmniPlayer : HybridOmniPlayerSpec() { @@ -39,9 +40,8 @@ class OmniPlayer : HybridOmniPlayerSpec() { override var showNotification: Boolean? = false set(value) { - Log.e("omni", "Toggle show notif, old: ${field}, new: $value") if (value == true) { - if (notificationPlayer != null) { + if (notificationPlayer != null && notificationPlayer?.isPlaying == true) { throw Error("Two players can't display notifications at the same time.") } notificationPlayer = player @@ -68,55 +68,84 @@ class OmniPlayer : HybridOmniPlayerSpec() { set(value) { Log.e("omni", "update source") currentSource = value - val src = source.src.firstOrNull() ?: return player.setMediaItem(MediaItem.EMPTY) - // val headers = Bundle().apply { - // putStringArrayList( - // MpvPlayer.REQUEST_HEADER_NAMES_KEY, - // ArrayList(src.headers.keys) - // ) - // putStringArrayList( - // MpvPlayer.REQUEST_HEADER_VALUES_KEY, - // ArrayList(src.headers.values) - // ) - // source.startTime?.let { - // putLong(MpvPlayer.REQUEST_START_MS_KEY, (it.coerceAtLeast(0.0) * 1000.0).toLong()) - // } - // } + val mediaItems = mutableListOf() + + value.prev?.let { prevSource -> + prevSource.src.firstOrNull()?.let { src -> + mediaItems.add(buildMediaItem(src, prevSource.metadata, prevSource.subtitles)) + } + } + + value.src.firstOrNull()?.let { src -> + mediaItems.add(buildMediaItem(src, value.metadata, value.subtitles)) + } + + value.next?.let { nextSource -> + nextSource.src.firstOrNull()?.let { src -> + mediaItems.add(buildMediaItem(src, nextSource.metadata, nextSource.subtitles)) + } + } - val item = MediaItem.Builder() - .setUri(src.uri) - .setMimeType(src.mimeType) - .setMediaId(src.uri) - .setMediaMetadata( - MediaMetadata.Builder() - .setTitle(value.metadata?.title) - .setAlbumTitle(value.metadata?.album) - .setArtist(value.metadata?.artist) - .apply { - value.metadata?.imageLink?.let { setArtworkUri(it.toUri()) } - } - .build()) - .setSubtitleConfigurations(value.subtitles.map { subtitle -> - SubtitleConfiguration.Builder(subtitle.link.toUri()) - .setId(subtitle.id) - .setLanguage(subtitle.language) - .setLabel(subtitle.label) - .setMimeType(subtitle.mimeType) - .build() - }) - .setRequestMetadata( - RequestMetadata.Builder() - .setMediaUri(src.uri.toUri()) - // .setExtras(headers) - .build() - ) - .build() runOnMainThreadSync { - player.setMediaItem(item) + if (mediaItems.isEmpty()) { + player.setMediaItem(MediaItem.EMPTY) + } else { + val startIndex = if (value.prev != null) 1 else 0 + val startPositionMs = (value.startTime?.coerceAtLeast(0.0) ?: 0.0) * 1000.0 + player.setMediaItems(mediaItems, startIndex, startPositionMs.toLong()) + } player.prepare() } } + private fun buildMediaItem( + src: com.margelo.nitro.omni.VideoSrc, + metadata: com.margelo.nitro.omni.Metadata?, + subtitles: Array + ): MediaItem { + // val headers = Bundle().apply { + // putStringArrayList( + // MpvPlayer.REQUEST_HEADER_NAMES_KEY, + // ArrayList(src.headers.keys) + // ) + // putStringArrayList( + // MpvPlayer.REQUEST_HEADER_VALUES_KEY, + // ArrayList(src.headers.values) + // ) + // source.startTime?.let { + // putLong(MpvPlayer.REQUEST_START_MS_KEY, (it.coerceAtLeast(0.0) * 1000.0).toLong()) + // } + // } + return MediaItem.Builder() + .setUri(src.uri) + .setMimeType(src.mimeType) + .setMediaId(src.uri) + .setMediaMetadata( + MediaMetadata.Builder() + .setTitle(metadata?.title) + .setAlbumTitle(metadata?.album) + .setArtist(metadata?.artist) + .apply { + metadata?.imageLink?.let { setArtworkUri(it.toUri()) } + } + .build()) + .setSubtitleConfigurations(subtitles.map { subtitle -> + SubtitleConfiguration.Builder(subtitle.link.toUri()) + .setId(subtitle.id) + .setLanguage(subtitle.language) + .setLabel(subtitle.label) + .setMimeType(subtitle.mimeType) + .build() + }) + .setRequestMetadata( + RequestMetadata.Builder() + .setMediaUri(src.uri.toUri()) + // .setExtras(headers) + .build() + ) + .build() + } + fun setSurface(holder: SurfaceHolder?) { runOnMainThread { if (holder == null) { diff --git a/nitrogen/generated/android/c++/JBaseSource.hpp b/nitrogen/generated/android/c++/JBaseSource.hpp new file mode 100644 index 0000000..78aa649 --- /dev/null +++ b/nitrogen/generated/android/c++/JBaseSource.hpp @@ -0,0 +1,120 @@ +/// +/// JBaseSource.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 "BaseSource.hpp" + +#include "JMetadata.hpp" +#include "JMixAudioMode.hpp" +#include "JSubtitle.hpp" +#include "JVideoSrc.hpp" +#include "Metadata.hpp" +#include "MixAudioMode.hpp" +#include "Subtitle.hpp" +#include "VideoSrc.hpp" +#include +#include +#include +#include + +namespace margelo::nitro::omni { + + using namespace facebook; + + /** + * The C++ JNI bridge between the C++ struct "BaseSource" and the the Kotlin data class "BaseSource". + */ + struct JBaseSource final: public jni::JavaClass { + public: + static constexpr auto kJavaDescriptor = "Lcom/margelo/nitro/omni/BaseSource;"; + + public: + /** + * Convert this Java/Kotlin-based struct to the C++ struct BaseSource by copying all values to C++. + */ + [[maybe_unused]] + [[nodiscard]] + BaseSource toCpp() const { + static const auto clazz = javaClassStatic(); + static const auto fieldSrc = clazz->getField>("src"); + jni::local_ref> src = this->getFieldValue(fieldSrc); + static const auto fieldStartTime = clazz->getField("startTime"); + jni::local_ref startTime = this->getFieldValue(fieldStartTime); + static const auto fieldSubtitles = clazz->getField>("subtitles"); + jni::local_ref> subtitles = this->getFieldValue(fieldSubtitles); + static const auto fieldMetadata = clazz->getField("metadata"); + jni::local_ref metadata = this->getFieldValue(fieldMetadata); + static const auto fieldMixAudio = clazz->getField("mixAudio"); + jni::local_ref mixAudio = this->getFieldValue(fieldMixAudio); + return BaseSource( + [&]() { + size_t __size = src->size(); + std::vector __vector; + __vector.reserve(__size); + for (size_t __i = 0; __i < __size; __i++) { + auto __element = src->getElement(__i); + __vector.push_back(__element->toCpp()); + } + return __vector; + }(), + startTime != nullptr ? std::make_optional(startTime->value()) : std::nullopt, + [&]() { + size_t __size = subtitles->size(); + std::vector __vector; + __vector.reserve(__size); + for (size_t __i = 0; __i < __size; __i++) { + auto __element = subtitles->getElement(__i); + __vector.push_back(__element->toCpp()); + } + return __vector; + }(), + metadata != nullptr ? std::make_optional(metadata->toCpp()) : std::nullopt, + mixAudio != nullptr ? std::make_optional(mixAudio->toCpp()) : std::nullopt + ); + } + + public: + /** + * Create a Java/Kotlin-based struct by copying all values from the given C++ struct to Java. + */ + [[maybe_unused]] + static jni::local_ref fromCpp(const BaseSource& value) { + using JSignature = JBaseSource(jni::alias_ref>, jni::alias_ref, jni::alias_ref>, jni::alias_ref, jni::alias_ref); + static const auto clazz = javaClassStatic(); + static const auto create = clazz->getStaticMethod("fromCpp"); + return create( + clazz, + [&]() { + size_t __size = value.src.size(); + jni::local_ref> __array = jni::JArrayClass::newArray(__size); + for (size_t __i = 0; __i < __size; __i++) { + const auto& __element = value.src[__i]; + auto __elementJni = JVideoSrc::fromCpp(__element); + __array->setElement(__i, *__elementJni); + } + return __array; + }(), + value.startTime.has_value() ? jni::JDouble::valueOf(value.startTime.value()) : nullptr, + [&]() { + size_t __size = value.subtitles.size(); + jni::local_ref> __array = jni::JArrayClass::newArray(__size); + for (size_t __i = 0; __i < __size; __i++) { + const auto& __element = value.subtitles[__i]; + auto __elementJni = JSubtitle::fromCpp(__element); + __array->setElement(__i, *__elementJni); + } + return __array; + }(), + value.metadata.has_value() ? JMetadata::fromCpp(value.metadata.value()) : nullptr, + value.mixAudio.has_value() ? JMixAudioMode::fromCpp(value.mixAudio.value()) : nullptr + ); + } + }; + +} // namespace margelo::nitro::omni diff --git a/nitrogen/generated/android/c++/JHybridOmniPlayerFactorySpec.cpp b/nitrogen/generated/android/c++/JHybridOmniPlayerFactorySpec.cpp index 8e95bf1..fcfaba5 100644 --- a/nitrogen/generated/android/c++/JHybridOmniPlayerFactorySpec.cpp +++ b/nitrogen/generated/android/c++/JHybridOmniPlayerFactorySpec.cpp @@ -11,6 +11,8 @@ namespace margelo::nitro::omni { class HybridOmniPlayerSpec; } // Forward declaration of `Source` to properly resolve imports. namespace margelo::nitro::omni { struct Source; } +// Forward declaration of `BaseSource` to properly resolve imports. +namespace margelo::nitro::omni { struct BaseSource; } // Forward declaration of `VideoSrc` to properly resolve imports. namespace margelo::nitro::omni { struct VideoSrc; } // Forward declaration of `Subtitle` to properly resolve imports. @@ -25,11 +27,13 @@ namespace margelo::nitro::omni { enum class MixAudioMode; } #include "JHybridOmniPlayerSpec.hpp" #include "Source.hpp" #include "JSource.hpp" +#include "BaseSource.hpp" +#include +#include "JBaseSource.hpp" #include "VideoSrc.hpp" #include #include "JVideoSrc.hpp" #include -#include #include #include "Subtitle.hpp" #include "JSubtitle.hpp" diff --git a/nitrogen/generated/android/c++/JHybridOmniPlayerSpec.cpp b/nitrogen/generated/android/c++/JHybridOmniPlayerSpec.cpp index 41cc4f2..20734d2 100644 --- a/nitrogen/generated/android/c++/JHybridOmniPlayerSpec.cpp +++ b/nitrogen/generated/android/c++/JHybridOmniPlayerSpec.cpp @@ -11,6 +11,8 @@ namespace margelo::nitro::omni { class HybridOmniEventMapSpec; } // Forward declaration of `Source` to properly resolve imports. namespace margelo::nitro::omni { struct Source; } +// Forward declaration of `BaseSource` to properly resolve imports. +namespace margelo::nitro::omni { struct BaseSource; } // Forward declaration of `VideoSrc` to properly resolve imports. namespace margelo::nitro::omni { struct VideoSrc; } // Forward declaration of `Subtitle` to properly resolve imports. @@ -31,11 +33,13 @@ namespace margelo::nitro::omni { enum class PlayerStatus; } #include "JHybridOmniEventMapSpec.hpp" #include "Source.hpp" #include "JSource.hpp" +#include "BaseSource.hpp" +#include +#include "JBaseSource.hpp" #include "VideoSrc.hpp" #include #include "JVideoSrc.hpp" #include -#include #include #include "Subtitle.hpp" #include "JSubtitle.hpp" diff --git a/nitrogen/generated/android/c++/JMetadata.hpp b/nitrogen/generated/android/c++/JMetadata.hpp index 1d00f8f..a3a0f42 100644 --- a/nitrogen/generated/android/c++/JMetadata.hpp +++ b/nitrogen/generated/android/c++/JMetadata.hpp @@ -40,17 +40,11 @@ namespace margelo::nitro::omni { jni::local_ref artist = this->getFieldValue(fieldArtist); static const auto fieldImageLink = clazz->getField("imageLink"); jni::local_ref imageLink = this->getFieldValue(fieldImageLink); - static const auto fieldHasPrev = clazz->getField("hasPrev"); - jni::local_ref hasPrev = this->getFieldValue(fieldHasPrev); - static const auto fieldHasNext = clazz->getField("hasNext"); - jni::local_ref hasNext = this->getFieldValue(fieldHasNext); return Metadata( title->toStdString(), album != nullptr ? std::make_optional(album->toStdString()) : std::nullopt, artist != nullptr ? std::make_optional(artist->toStdString()) : std::nullopt, - imageLink != nullptr ? std::make_optional(imageLink->toStdString()) : std::nullopt, - hasPrev != nullptr ? std::make_optional(static_cast(hasPrev->value())) : std::nullopt, - hasNext != nullptr ? std::make_optional(static_cast(hasNext->value())) : std::nullopt + imageLink != nullptr ? std::make_optional(imageLink->toStdString()) : std::nullopt ); } @@ -60,7 +54,7 @@ namespace margelo::nitro::omni { */ [[maybe_unused]] static jni::local_ref fromCpp(const Metadata& value) { - using JSignature = JMetadata(jni::alias_ref, jni::alias_ref, jni::alias_ref, jni::alias_ref, jni::alias_ref, jni::alias_ref); + using JSignature = JMetadata(jni::alias_ref, jni::alias_ref, jni::alias_ref, jni::alias_ref); static const auto clazz = javaClassStatic(); static const auto create = clazz->getStaticMethod("fromCpp"); return create( @@ -68,9 +62,7 @@ namespace margelo::nitro::omni { jni::make_jstring(value.title), value.album.has_value() ? jni::make_jstring(value.album.value()) : nullptr, value.artist.has_value() ? jni::make_jstring(value.artist.value()) : nullptr, - value.imageLink.has_value() ? jni::make_jstring(value.imageLink.value()) : nullptr, - value.hasPrev.has_value() ? jni::JBoolean::valueOf(value.hasPrev.value()) : nullptr, - value.hasNext.has_value() ? jni::JBoolean::valueOf(value.hasNext.value()) : nullptr + value.imageLink.has_value() ? jni::make_jstring(value.imageLink.value()) : nullptr ); } }; diff --git a/nitrogen/generated/android/c++/JSource.hpp b/nitrogen/generated/android/c++/JSource.hpp index 4412f30..d7b9cc8 100644 --- a/nitrogen/generated/android/c++/JSource.hpp +++ b/nitrogen/generated/android/c++/JSource.hpp @@ -10,6 +10,8 @@ #include #include "Source.hpp" +#include "BaseSource.hpp" +#include "JBaseSource.hpp" #include "JMetadata.hpp" #include "JMixAudioMode.hpp" #include "JSubtitle.hpp" @@ -42,6 +44,10 @@ namespace margelo::nitro::omni { [[nodiscard]] Source toCpp() const { static const auto clazz = javaClassStatic(); + static const auto fieldPrev = clazz->getField("prev"); + jni::local_ref prev = this->getFieldValue(fieldPrev); + static const auto fieldNext = clazz->getField("next"); + jni::local_ref next = this->getFieldValue(fieldNext); static const auto fieldSrc = clazz->getField>("src"); jni::local_ref> src = this->getFieldValue(fieldSrc); static const auto fieldStartTime = clazz->getField("startTime"); @@ -53,6 +59,8 @@ namespace margelo::nitro::omni { static const auto fieldMixAudio = clazz->getField("mixAudio"); jni::local_ref mixAudio = this->getFieldValue(fieldMixAudio); return Source( + prev != nullptr ? std::make_optional(prev->toCpp()) : std::nullopt, + next != nullptr ? std::make_optional(next->toCpp()) : std::nullopt, [&]() { size_t __size = src->size(); std::vector __vector; @@ -85,11 +93,13 @@ namespace margelo::nitro::omni { */ [[maybe_unused]] static jni::local_ref fromCpp(const Source& value) { - using JSignature = JSource(jni::alias_ref>, jni::alias_ref, jni::alias_ref>, jni::alias_ref, jni::alias_ref); + using JSignature = JSource(jni::alias_ref, jni::alias_ref, jni::alias_ref>, jni::alias_ref, jni::alias_ref>, jni::alias_ref, jni::alias_ref); static const auto clazz = javaClassStatic(); static const auto create = clazz->getStaticMethod("fromCpp"); return create( clazz, + value.prev.has_value() ? JBaseSource::fromCpp(value.prev.value()) : nullptr, + value.next.has_value() ? JBaseSource::fromCpp(value.next.value()) : nullptr, [&]() { size_t __size = value.src.size(); jni::local_ref> __array = jni::JArrayClass::newArray(__size); diff --git a/nitrogen/generated/android/kotlin/com/margelo/nitro/omni/BaseSource.kt b/nitrogen/generated/android/kotlin/com/margelo/nitro/omni/BaseSource.kt new file mode 100644 index 0000000..a6b8fd1 --- /dev/null +++ b/nitrogen/generated/android/kotlin/com/margelo/nitro/omni/BaseSource.kt @@ -0,0 +1,50 @@ +/// +/// BaseSource.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 object/struct "BaseSource". + */ +@DoNotStrip +@Keep +data class BaseSource( + @DoNotStrip + @Keep + val src: Array, + @DoNotStrip + @Keep + val startTime: Double?, + @DoNotStrip + @Keep + val subtitles: Array, + @DoNotStrip + @Keep + val metadata: Metadata?, + @DoNotStrip + @Keep + val mixAudio: MixAudioMode? +) { + /* primary constructor */ + + companion object { + /** + * Constructor called from C++ + */ + @DoNotStrip + @Keep + @Suppress("unused") + @JvmStatic + private fun fromCpp(src: Array, startTime: Double?, subtitles: Array, metadata: Metadata?, mixAudio: MixAudioMode?): BaseSource { + return BaseSource(src, startTime, subtitles, metadata, mixAudio) + } + } +} diff --git a/nitrogen/generated/android/kotlin/com/margelo/nitro/omni/Metadata.kt b/nitrogen/generated/android/kotlin/com/margelo/nitro/omni/Metadata.kt index fa6fd15..987775b 100644 --- a/nitrogen/generated/android/kotlin/com/margelo/nitro/omni/Metadata.kt +++ b/nitrogen/generated/android/kotlin/com/margelo/nitro/omni/Metadata.kt @@ -28,13 +28,7 @@ data class Metadata( val artist: String?, @DoNotStrip @Keep - val imageLink: String?, - @DoNotStrip - @Keep - val hasPrev: Boolean?, - @DoNotStrip - @Keep - val hasNext: Boolean? + val imageLink: String? ) { /* primary constructor */ @@ -46,8 +40,8 @@ data class Metadata( @Keep @Suppress("unused") @JvmStatic - private fun fromCpp(title: String, album: String?, artist: String?, imageLink: String?, hasPrev: Boolean?, hasNext: Boolean?): Metadata { - return Metadata(title, album, artist, imageLink, hasPrev, hasNext) + private fun fromCpp(title: String, album: String?, artist: String?, imageLink: String?): Metadata { + return Metadata(title, album, artist, imageLink) } } } diff --git a/nitrogen/generated/android/kotlin/com/margelo/nitro/omni/Source.kt b/nitrogen/generated/android/kotlin/com/margelo/nitro/omni/Source.kt index c8525c7..605f7b1 100644 --- a/nitrogen/generated/android/kotlin/com/margelo/nitro/omni/Source.kt +++ b/nitrogen/generated/android/kotlin/com/margelo/nitro/omni/Source.kt @@ -17,6 +17,12 @@ import com.facebook.proguard.annotations.DoNotStrip @DoNotStrip @Keep data class Source( + @DoNotStrip + @Keep + val prev: BaseSource?, + @DoNotStrip + @Keep + val next: BaseSource?, @DoNotStrip @Keep val src: Array, @@ -43,8 +49,8 @@ data class Source( @Keep @Suppress("unused") @JvmStatic - private fun fromCpp(src: Array, startTime: Double?, subtitles: Array, metadata: Metadata?, mixAudio: MixAudioMode?): Source { - return Source(src, startTime, subtitles, metadata, mixAudio) + private fun fromCpp(prev: BaseSource?, next: BaseSource?, src: Array, startTime: Double?, subtitles: Array, metadata: Metadata?, mixAudio: MixAudioMode?): Source { + return Source(prev, next, src, startTime, subtitles, metadata, mixAudio) } } } diff --git a/nitrogen/generated/shared/c++/BaseSource.hpp b/nitrogen/generated/shared/c++/BaseSource.hpp new file mode 100644 index 0000000..d48193a --- /dev/null +++ b/nitrogen/generated/shared/c++/BaseSource.hpp @@ -0,0 +1,111 @@ +/// +/// BaseSource.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 +#if __has_include() +#include +#else +#error NitroModules cannot be found! Are you sure you installed NitroModules properly? +#endif + +// Forward declaration of `VideoSrc` to properly resolve imports. +namespace margelo::nitro::omni { struct VideoSrc; } +// Forward declaration of `Subtitle` to properly resolve imports. +namespace margelo::nitro::omni { struct Subtitle; } +// Forward declaration of `Metadata` to properly resolve imports. +namespace margelo::nitro::omni { struct Metadata; } +// Forward declaration of `MixAudioMode` to properly resolve imports. +namespace margelo::nitro::omni { enum class MixAudioMode; } + +#include "VideoSrc.hpp" +#include +#include +#include "Subtitle.hpp" +#include "Metadata.hpp" +#include "MixAudioMode.hpp" + +namespace margelo::nitro::omni { + + /** + * A struct which can be represented as a JavaScript object (BaseSource). + */ + struct BaseSource final { + public: + std::vector src SWIFT_PRIVATE; + std::optional startTime SWIFT_PRIVATE; + std::vector subtitles SWIFT_PRIVATE; + std::optional metadata SWIFT_PRIVATE; + std::optional mixAudio SWIFT_PRIVATE; + + public: + BaseSource() = default; + explicit BaseSource(std::vector src, std::optional startTime, std::vector subtitles, std::optional metadata, std::optional mixAudio): src(src), startTime(startTime), subtitles(subtitles), metadata(metadata), mixAudio(mixAudio) {} + + public: + friend bool operator==(const BaseSource& lhs, const BaseSource& rhs) = default; + }; + +} // namespace margelo::nitro::omni + +namespace margelo::nitro { + + // C++ BaseSource <> JS BaseSource (object) + template <> + struct JSIConverter final { + static inline margelo::nitro::omni::BaseSource fromJSI(jsi::Runtime& runtime, const jsi::Value& arg) { + jsi::Object obj = arg.asObject(runtime); + return margelo::nitro::omni::BaseSource( + JSIConverter>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "src"))), + JSIConverter>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "startTime"))), + JSIConverter>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "subtitles"))), + JSIConverter>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "metadata"))), + JSIConverter>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "mixAudio"))) + ); + } + static inline jsi::Value toJSI(jsi::Runtime& runtime, const margelo::nitro::omni::BaseSource& arg) { + jsi::Object obj(runtime); + obj.setProperty(runtime, PropNameIDCache::get(runtime, "src"), JSIConverter>::toJSI(runtime, arg.src)); + obj.setProperty(runtime, PropNameIDCache::get(runtime, "startTime"), JSIConverter>::toJSI(runtime, arg.startTime)); + obj.setProperty(runtime, PropNameIDCache::get(runtime, "subtitles"), JSIConverter>::toJSI(runtime, arg.subtitles)); + obj.setProperty(runtime, PropNameIDCache::get(runtime, "metadata"), JSIConverter>::toJSI(runtime, arg.metadata)); + obj.setProperty(runtime, PropNameIDCache::get(runtime, "mixAudio"), JSIConverter>::toJSI(runtime, arg.mixAudio)); + return obj; + } + static inline bool canConvert(jsi::Runtime& runtime, const jsi::Value& value) { + if (!value.isObject()) { + return false; + } + jsi::Object obj = value.getObject(runtime); + if (!nitro::isPlainObject(runtime, obj)) { + return false; + } + if (!JSIConverter>::canConvert(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "src")))) return false; + if (!JSIConverter>::canConvert(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "startTime")))) return false; + if (!JSIConverter>::canConvert(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "subtitles")))) return false; + if (!JSIConverter>::canConvert(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "metadata")))) return false; + if (!JSIConverter>::canConvert(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "mixAudio")))) return false; + return true; + } + }; + +} // namespace margelo::nitro diff --git a/nitrogen/generated/shared/c++/Metadata.hpp b/nitrogen/generated/shared/c++/Metadata.hpp index 1d3f163..60de8a7 100644 --- a/nitrogen/generated/shared/c++/Metadata.hpp +++ b/nitrogen/generated/shared/c++/Metadata.hpp @@ -44,12 +44,10 @@ namespace margelo::nitro::omni { std::optional album SWIFT_PRIVATE; std::optional artist SWIFT_PRIVATE; std::optional imageLink SWIFT_PRIVATE; - std::optional hasPrev SWIFT_PRIVATE; - std::optional hasNext SWIFT_PRIVATE; public: Metadata() = default; - explicit Metadata(std::string title, std::optional album, std::optional artist, std::optional imageLink, std::optional hasPrev, std::optional hasNext): title(title), album(album), artist(artist), imageLink(imageLink), hasPrev(hasPrev), hasNext(hasNext) {} + explicit Metadata(std::string title, std::optional album, std::optional artist, std::optional imageLink): title(title), album(album), artist(artist), imageLink(imageLink) {} public: friend bool operator==(const Metadata& lhs, const Metadata& rhs) = default; @@ -68,9 +66,7 @@ namespace margelo::nitro { JSIConverter::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "title"))), JSIConverter>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "album"))), JSIConverter>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "artist"))), - JSIConverter>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "imageLink"))), - JSIConverter>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "hasPrev"))), - JSIConverter>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "hasNext"))) + JSIConverter>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "imageLink"))) ); } static inline jsi::Value toJSI(jsi::Runtime& runtime, const margelo::nitro::omni::Metadata& arg) { @@ -79,8 +75,6 @@ namespace margelo::nitro { obj.setProperty(runtime, PropNameIDCache::get(runtime, "album"), JSIConverter>::toJSI(runtime, arg.album)); obj.setProperty(runtime, PropNameIDCache::get(runtime, "artist"), JSIConverter>::toJSI(runtime, arg.artist)); obj.setProperty(runtime, PropNameIDCache::get(runtime, "imageLink"), JSIConverter>::toJSI(runtime, arg.imageLink)); - obj.setProperty(runtime, PropNameIDCache::get(runtime, "hasPrev"), JSIConverter>::toJSI(runtime, arg.hasPrev)); - obj.setProperty(runtime, PropNameIDCache::get(runtime, "hasNext"), JSIConverter>::toJSI(runtime, arg.hasNext)); return obj; } static inline bool canConvert(jsi::Runtime& runtime, const jsi::Value& value) { @@ -95,8 +89,6 @@ namespace margelo::nitro { if (!JSIConverter>::canConvert(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "album")))) return false; if (!JSIConverter>::canConvert(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "artist")))) return false; if (!JSIConverter>::canConvert(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "imageLink")))) return false; - if (!JSIConverter>::canConvert(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "hasPrev")))) return false; - if (!JSIConverter>::canConvert(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "hasNext")))) return false; return true; } }; diff --git a/nitrogen/generated/shared/c++/Source.hpp b/nitrogen/generated/shared/c++/Source.hpp index d84af8b..021a412 100644 --- a/nitrogen/generated/shared/c++/Source.hpp +++ b/nitrogen/generated/shared/c++/Source.hpp @@ -28,6 +28,8 @@ #error NitroModules cannot be found! Are you sure you installed NitroModules properly? #endif +// Forward declaration of `BaseSource` to properly resolve imports. +namespace margelo::nitro::omni { struct BaseSource; } // Forward declaration of `VideoSrc` to properly resolve imports. namespace margelo::nitro::omni { struct VideoSrc; } // Forward declaration of `Subtitle` to properly resolve imports. @@ -37,9 +39,10 @@ namespace margelo::nitro::omni { struct Metadata; } // Forward declaration of `MixAudioMode` to properly resolve imports. namespace margelo::nitro::omni { enum class MixAudioMode; } +#include "BaseSource.hpp" +#include #include "VideoSrc.hpp" #include -#include #include "Subtitle.hpp" #include "Metadata.hpp" #include "MixAudioMode.hpp" @@ -51,6 +54,8 @@ namespace margelo::nitro::omni { */ struct Source final { public: + std::optional prev SWIFT_PRIVATE; + std::optional next SWIFT_PRIVATE; std::vector src SWIFT_PRIVATE; std::optional startTime SWIFT_PRIVATE; std::vector subtitles SWIFT_PRIVATE; @@ -59,7 +64,7 @@ namespace margelo::nitro::omni { public: Source() = default; - explicit Source(std::vector src, std::optional startTime, std::vector subtitles, std::optional metadata, std::optional mixAudio): src(src), startTime(startTime), subtitles(subtitles), metadata(metadata), mixAudio(mixAudio) {} + explicit Source(std::optional prev, std::optional next, std::vector src, std::optional startTime, std::vector subtitles, std::optional metadata, std::optional mixAudio): prev(prev), next(next), src(src), startTime(startTime), subtitles(subtitles), metadata(metadata), mixAudio(mixAudio) {} public: friend bool operator==(const Source& lhs, const Source& rhs) = default; @@ -75,6 +80,8 @@ namespace margelo::nitro { static inline margelo::nitro::omni::Source fromJSI(jsi::Runtime& runtime, const jsi::Value& arg) { jsi::Object obj = arg.asObject(runtime); return margelo::nitro::omni::Source( + JSIConverter>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "prev"))), + JSIConverter>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "next"))), JSIConverter>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "src"))), JSIConverter>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "startTime"))), JSIConverter>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "subtitles"))), @@ -84,6 +91,8 @@ namespace margelo::nitro { } static inline jsi::Value toJSI(jsi::Runtime& runtime, const margelo::nitro::omni::Source& arg) { jsi::Object obj(runtime); + obj.setProperty(runtime, PropNameIDCache::get(runtime, "prev"), JSIConverter>::toJSI(runtime, arg.prev)); + obj.setProperty(runtime, PropNameIDCache::get(runtime, "next"), JSIConverter>::toJSI(runtime, arg.next)); obj.setProperty(runtime, PropNameIDCache::get(runtime, "src"), JSIConverter>::toJSI(runtime, arg.src)); obj.setProperty(runtime, PropNameIDCache::get(runtime, "startTime"), JSIConverter>::toJSI(runtime, arg.startTime)); obj.setProperty(runtime, PropNameIDCache::get(runtime, "subtitles"), JSIConverter>::toJSI(runtime, arg.subtitles)); @@ -99,6 +108,8 @@ namespace margelo::nitro { if (!nitro::isPlainObject(runtime, obj)) { return false; } + if (!JSIConverter>::canConvert(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "prev")))) return false; + if (!JSIConverter>::canConvert(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "next")))) return false; if (!JSIConverter>::canConvert(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "src")))) return false; if (!JSIConverter>::canConvert(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "startTime")))) return false; if (!JSIConverter>::canConvert(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "subtitles")))) return false; diff --git a/src/types/source.ts b/src/types/source.ts index 426e9ee..35d7d98 100644 --- a/src/types/source.ts +++ b/src/types/source.ts @@ -1,4 +1,4 @@ -export interface Source { +export interface BaseSource { src: VideoSrc[]; startTime?: number; subtitles: Subtitle[]; @@ -6,6 +6,11 @@ export interface Source { mixAudio?: MixAudioMode; } +export interface Source extends BaseSource { + prev?: BaseSource + next?: BaseSource +} + export interface VideoSrc { uri: string; mimeType?: string; @@ -27,9 +32,6 @@ export interface Metadata { album?: string; artist?: string; imageLink?: string; - // if one of those is true, the prev/next button will be there and a `onPrevSelected` even will be fired. - hasPrev?: boolean; - hasNext?: boolean; } export type MixAudioMode = "mixWithOthers" | "doNotMix" | "duckOthers" | "auto";