wip: Add prev/next

This commit is contained in:
2026-05-10 16:57:28 +02:00
parent 1e3734aeb5
commit 1e4a6e3787
14 changed files with 467 additions and 125 deletions
@@ -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<MediaItem> = 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,
@@ -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<MediaItem>()
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<com.margelo.nitro.omni.Subtitle>
): 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) {
+120
View File
@@ -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 <fbjni/fbjni.h>
#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 <optional>
#include <string>
#include <unordered_map>
#include <vector>
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<JBaseSource> {
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<jni::JArrayClass<JVideoSrc>>("src");
jni::local_ref<jni::JArrayClass<JVideoSrc>> src = this->getFieldValue(fieldSrc);
static const auto fieldStartTime = clazz->getField<jni::JDouble>("startTime");
jni::local_ref<jni::JDouble> startTime = this->getFieldValue(fieldStartTime);
static const auto fieldSubtitles = clazz->getField<jni::JArrayClass<JSubtitle>>("subtitles");
jni::local_ref<jni::JArrayClass<JSubtitle>> subtitles = this->getFieldValue(fieldSubtitles);
static const auto fieldMetadata = clazz->getField<JMetadata>("metadata");
jni::local_ref<JMetadata> metadata = this->getFieldValue(fieldMetadata);
static const auto fieldMixAudio = clazz->getField<JMixAudioMode>("mixAudio");
jni::local_ref<JMixAudioMode> mixAudio = this->getFieldValue(fieldMixAudio);
return BaseSource(
[&]() {
size_t __size = src->size();
std::vector<VideoSrc> __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<Subtitle> __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<JBaseSource::javaobject> fromCpp(const BaseSource& value) {
using JSignature = JBaseSource(jni::alias_ref<jni::JArrayClass<JVideoSrc>>, jni::alias_ref<jni::JDouble>, jni::alias_ref<jni::JArrayClass<JSubtitle>>, jni::alias_ref<JMetadata>, jni::alias_ref<JMixAudioMode>);
static const auto clazz = javaClassStatic();
static const auto create = clazz->getStaticMethod<JSignature>("fromCpp");
return create(
clazz,
[&]() {
size_t __size = value.src.size();
jni::local_ref<jni::JArrayClass<JVideoSrc>> __array = jni::JArrayClass<JVideoSrc>::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<jni::JArrayClass<JSubtitle>> __array = jni::JArrayClass<JSubtitle>::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
@@ -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 <optional>
#include "JBaseSource.hpp"
#include "VideoSrc.hpp"
#include <vector>
#include "JVideoSrc.hpp"
#include <string>
#include <optional>
#include <unordered_map>
#include "Subtitle.hpp"
#include "JSubtitle.hpp"
+5 -1
View File
@@ -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 <optional>
#include "JBaseSource.hpp"
#include "VideoSrc.hpp"
#include <vector>
#include "JVideoSrc.hpp"
#include <string>
#include <optional>
#include <unordered_map>
#include "Subtitle.hpp"
#include "JSubtitle.hpp"
+3 -11
View File
@@ -40,17 +40,11 @@ namespace margelo::nitro::omni {
jni::local_ref<jni::JString> artist = this->getFieldValue(fieldArtist);
static const auto fieldImageLink = clazz->getField<jni::JString>("imageLink");
jni::local_ref<jni::JString> imageLink = this->getFieldValue(fieldImageLink);
static const auto fieldHasPrev = clazz->getField<jni::JBoolean>("hasPrev");
jni::local_ref<jni::JBoolean> hasPrev = this->getFieldValue(fieldHasPrev);
static const auto fieldHasNext = clazz->getField<jni::JBoolean>("hasNext");
jni::local_ref<jni::JBoolean> 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<bool>(hasPrev->value())) : std::nullopt,
hasNext != nullptr ? std::make_optional(static_cast<bool>(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<JMetadata::javaobject> fromCpp(const Metadata& value) {
using JSignature = JMetadata(jni::alias_ref<jni::JString>, jni::alias_ref<jni::JString>, jni::alias_ref<jni::JString>, jni::alias_ref<jni::JString>, jni::alias_ref<jni::JBoolean>, jni::alias_ref<jni::JBoolean>);
using JSignature = JMetadata(jni::alias_ref<jni::JString>, jni::alias_ref<jni::JString>, jni::alias_ref<jni::JString>, jni::alias_ref<jni::JString>);
static const auto clazz = javaClassStatic();
static const auto create = clazz->getStaticMethod<JSignature>("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
);
}
};
+11 -1
View File
@@ -10,6 +10,8 @@
#include <fbjni/fbjni.h>
#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<JBaseSource>("prev");
jni::local_ref<JBaseSource> prev = this->getFieldValue(fieldPrev);
static const auto fieldNext = clazz->getField<JBaseSource>("next");
jni::local_ref<JBaseSource> next = this->getFieldValue(fieldNext);
static const auto fieldSrc = clazz->getField<jni::JArrayClass<JVideoSrc>>("src");
jni::local_ref<jni::JArrayClass<JVideoSrc>> src = this->getFieldValue(fieldSrc);
static const auto fieldStartTime = clazz->getField<jni::JDouble>("startTime");
@@ -53,6 +59,8 @@ namespace margelo::nitro::omni {
static const auto fieldMixAudio = clazz->getField<JMixAudioMode>("mixAudio");
jni::local_ref<JMixAudioMode> 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<VideoSrc> __vector;
@@ -85,11 +93,13 @@ namespace margelo::nitro::omni {
*/
[[maybe_unused]]
static jni::local_ref<JSource::javaobject> fromCpp(const Source& value) {
using JSignature = JSource(jni::alias_ref<jni::JArrayClass<JVideoSrc>>, jni::alias_ref<jni::JDouble>, jni::alias_ref<jni::JArrayClass<JSubtitle>>, jni::alias_ref<JMetadata>, jni::alias_ref<JMixAudioMode>);
using JSignature = JSource(jni::alias_ref<JBaseSource>, jni::alias_ref<JBaseSource>, jni::alias_ref<jni::JArrayClass<JVideoSrc>>, jni::alias_ref<jni::JDouble>, jni::alias_ref<jni::JArrayClass<JSubtitle>>, jni::alias_ref<JMetadata>, jni::alias_ref<JMixAudioMode>);
static const auto clazz = javaClassStatic();
static const auto create = clazz->getStaticMethod<JSignature>("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<jni::JArrayClass<JVideoSrc>> __array = jni::JArrayClass<JVideoSrc>::newArray(__size);
@@ -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<VideoSrc>,
@DoNotStrip
@Keep
val startTime: Double?,
@DoNotStrip
@Keep
val subtitles: Array<Subtitle>,
@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<VideoSrc>, startTime: Double?, subtitles: Array<Subtitle>, metadata: Metadata?, mixAudio: MixAudioMode?): BaseSource {
return BaseSource(src, startTime, subtitles, metadata, mixAudio)
}
}
}
@@ -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)
}
}
}
@@ -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<VideoSrc>,
@@ -43,8 +49,8 @@ data class Source(
@Keep
@Suppress("unused")
@JvmStatic
private fun fromCpp(src: Array<VideoSrc>, startTime: Double?, subtitles: Array<Subtitle>, metadata: Metadata?, mixAudio: MixAudioMode?): Source {
return Source(src, startTime, subtitles, metadata, mixAudio)
private fun fromCpp(prev: BaseSource?, next: BaseSource?, src: Array<VideoSrc>, startTime: Double?, subtitles: Array<Subtitle>, metadata: Metadata?, mixAudio: MixAudioMode?): Source {
return Source(prev, next, src, startTime, subtitles, metadata, mixAudio)
}
}
}
+111
View File
@@ -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(<NitroModules/JSIConverter.hpp>)
#include <NitroModules/JSIConverter.hpp>
#else
#error NitroModules cannot be found! Are you sure you installed NitroModules properly?
#endif
#if __has_include(<NitroModules/NitroDefines.hpp>)
#include <NitroModules/NitroDefines.hpp>
#else
#error NitroModules cannot be found! Are you sure you installed NitroModules properly?
#endif
#if __has_include(<NitroModules/JSIHelpers.hpp>)
#include <NitroModules/JSIHelpers.hpp>
#else
#error NitroModules cannot be found! Are you sure you installed NitroModules properly?
#endif
#if __has_include(<NitroModules/PropNameIDCache.hpp>)
#include <NitroModules/PropNameIDCache.hpp>
#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 <vector>
#include <optional>
#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<VideoSrc> src SWIFT_PRIVATE;
std::optional<double> startTime SWIFT_PRIVATE;
std::vector<Subtitle> subtitles SWIFT_PRIVATE;
std::optional<Metadata> metadata SWIFT_PRIVATE;
std::optional<MixAudioMode> mixAudio SWIFT_PRIVATE;
public:
BaseSource() = default;
explicit BaseSource(std::vector<VideoSrc> src, std::optional<double> startTime, std::vector<Subtitle> subtitles, std::optional<Metadata> metadata, std::optional<MixAudioMode> 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<margelo::nitro::omni::BaseSource> 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<std::vector<margelo::nitro::omni::VideoSrc>>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "src"))),
JSIConverter<std::optional<double>>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "startTime"))),
JSIConverter<std::vector<margelo::nitro::omni::Subtitle>>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "subtitles"))),
JSIConverter<std::optional<margelo::nitro::omni::Metadata>>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "metadata"))),
JSIConverter<std::optional<margelo::nitro::omni::MixAudioMode>>::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<std::vector<margelo::nitro::omni::VideoSrc>>::toJSI(runtime, arg.src));
obj.setProperty(runtime, PropNameIDCache::get(runtime, "startTime"), JSIConverter<std::optional<double>>::toJSI(runtime, arg.startTime));
obj.setProperty(runtime, PropNameIDCache::get(runtime, "subtitles"), JSIConverter<std::vector<margelo::nitro::omni::Subtitle>>::toJSI(runtime, arg.subtitles));
obj.setProperty(runtime, PropNameIDCache::get(runtime, "metadata"), JSIConverter<std::optional<margelo::nitro::omni::Metadata>>::toJSI(runtime, arg.metadata));
obj.setProperty(runtime, PropNameIDCache::get(runtime, "mixAudio"), JSIConverter<std::optional<margelo::nitro::omni::MixAudioMode>>::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<std::vector<margelo::nitro::omni::VideoSrc>>::canConvert(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "src")))) return false;
if (!JSIConverter<std::optional<double>>::canConvert(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "startTime")))) return false;
if (!JSIConverter<std::vector<margelo::nitro::omni::Subtitle>>::canConvert(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "subtitles")))) return false;
if (!JSIConverter<std::optional<margelo::nitro::omni::Metadata>>::canConvert(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "metadata")))) return false;
if (!JSIConverter<std::optional<margelo::nitro::omni::MixAudioMode>>::canConvert(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "mixAudio")))) return false;
return true;
}
};
} // namespace margelo::nitro
+2 -10
View File
@@ -44,12 +44,10 @@ namespace margelo::nitro::omni {
std::optional<std::string> album SWIFT_PRIVATE;
std::optional<std::string> artist SWIFT_PRIVATE;
std::optional<std::string> imageLink SWIFT_PRIVATE;
std::optional<bool> hasPrev SWIFT_PRIVATE;
std::optional<bool> hasNext SWIFT_PRIVATE;
public:
Metadata() = default;
explicit Metadata(std::string title, std::optional<std::string> album, std::optional<std::string> artist, std::optional<std::string> imageLink, std::optional<bool> hasPrev, std::optional<bool> hasNext): title(title), album(album), artist(artist), imageLink(imageLink), hasPrev(hasPrev), hasNext(hasNext) {}
explicit Metadata(std::string title, std::optional<std::string> album, std::optional<std::string> artist, std::optional<std::string> 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<std::string>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "title"))),
JSIConverter<std::optional<std::string>>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "album"))),
JSIConverter<std::optional<std::string>>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "artist"))),
JSIConverter<std::optional<std::string>>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "imageLink"))),
JSIConverter<std::optional<bool>>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "hasPrev"))),
JSIConverter<std::optional<bool>>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "hasNext")))
JSIConverter<std::optional<std::string>>::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<std::optional<std::string>>::toJSI(runtime, arg.album));
obj.setProperty(runtime, PropNameIDCache::get(runtime, "artist"), JSIConverter<std::optional<std::string>>::toJSI(runtime, arg.artist));
obj.setProperty(runtime, PropNameIDCache::get(runtime, "imageLink"), JSIConverter<std::optional<std::string>>::toJSI(runtime, arg.imageLink));
obj.setProperty(runtime, PropNameIDCache::get(runtime, "hasPrev"), JSIConverter<std::optional<bool>>::toJSI(runtime, arg.hasPrev));
obj.setProperty(runtime, PropNameIDCache::get(runtime, "hasNext"), JSIConverter<std::optional<bool>>::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<std::optional<std::string>>::canConvert(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "album")))) return false;
if (!JSIConverter<std::optional<std::string>>::canConvert(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "artist")))) return false;
if (!JSIConverter<std::optional<std::string>>::canConvert(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "imageLink")))) return false;
if (!JSIConverter<std::optional<bool>>::canConvert(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "hasPrev")))) return false;
if (!JSIConverter<std::optional<bool>>::canConvert(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "hasNext")))) return false;
return true;
}
};
+13 -2
View File
@@ -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 <optional>
#include "VideoSrc.hpp"
#include <vector>
#include <optional>
#include "Subtitle.hpp"
#include "Metadata.hpp"
#include "MixAudioMode.hpp"
@@ -51,6 +54,8 @@ namespace margelo::nitro::omni {
*/
struct Source final {
public:
std::optional<BaseSource> prev SWIFT_PRIVATE;
std::optional<BaseSource> next SWIFT_PRIVATE;
std::vector<VideoSrc> src SWIFT_PRIVATE;
std::optional<double> startTime SWIFT_PRIVATE;
std::vector<Subtitle> subtitles SWIFT_PRIVATE;
@@ -59,7 +64,7 @@ namespace margelo::nitro::omni {
public:
Source() = default;
explicit Source(std::vector<VideoSrc> src, std::optional<double> startTime, std::vector<Subtitle> subtitles, std::optional<Metadata> metadata, std::optional<MixAudioMode> mixAudio): src(src), startTime(startTime), subtitles(subtitles), metadata(metadata), mixAudio(mixAudio) {}
explicit Source(std::optional<BaseSource> prev, std::optional<BaseSource> next, std::vector<VideoSrc> src, std::optional<double> startTime, std::vector<Subtitle> subtitles, std::optional<Metadata> metadata, std::optional<MixAudioMode> 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<std::optional<margelo::nitro::omni::BaseSource>>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "prev"))),
JSIConverter<std::optional<margelo::nitro::omni::BaseSource>>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "next"))),
JSIConverter<std::vector<margelo::nitro::omni::VideoSrc>>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "src"))),
JSIConverter<std::optional<double>>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "startTime"))),
JSIConverter<std::vector<margelo::nitro::omni::Subtitle>>::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<std::optional<margelo::nitro::omni::BaseSource>>::toJSI(runtime, arg.prev));
obj.setProperty(runtime, PropNameIDCache::get(runtime, "next"), JSIConverter<std::optional<margelo::nitro::omni::BaseSource>>::toJSI(runtime, arg.next));
obj.setProperty(runtime, PropNameIDCache::get(runtime, "src"), JSIConverter<std::vector<margelo::nitro::omni::VideoSrc>>::toJSI(runtime, arg.src));
obj.setProperty(runtime, PropNameIDCache::get(runtime, "startTime"), JSIConverter<std::optional<double>>::toJSI(runtime, arg.startTime));
obj.setProperty(runtime, PropNameIDCache::get(runtime, "subtitles"), JSIConverter<std::vector<margelo::nitro::omni::Subtitle>>::toJSI(runtime, arg.subtitles));
@@ -99,6 +108,8 @@ namespace margelo::nitro {
if (!nitro::isPlainObject(runtime, obj)) {
return false;
}
if (!JSIConverter<std::optional<margelo::nitro::omni::BaseSource>>::canConvert(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "prev")))) return false;
if (!JSIConverter<std::optional<margelo::nitro::omni::BaseSource>>::canConvert(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "next")))) return false;
if (!JSIConverter<std::vector<margelo::nitro::omni::VideoSrc>>::canConvert(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "src")))) return false;
if (!JSIConverter<std::optional<double>>::canConvert(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "startTime")))) return false;
if (!JSIConverter<std::vector<margelo::nitro::omni::Subtitle>>::canConvert(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "subtitles")))) return false;
+6 -4
View File
@@ -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";