Basic vlc implementation

This commit is contained in:
2026-04-10 18:52:41 +02:00
parent 014c75d575
commit 7a5f495b39
13 changed files with 123 additions and 212 deletions
+2
View File
@@ -137,6 +137,8 @@ dependencies {
// Add a dependency on NitroModules
implementation project(":react-native-nitro-modules")
implementation("org.videolan.android:libvlc-all:4.0.0-eap24")
}
if (isNewArchitectureEnabled()) {
@@ -1,76 +1,121 @@
package dev.zoriya.omni
import android.net.Uri
import com.margelo.nitro.NitroModules
import com.margelo.nitro.omni.HybridOmniPlayerPropsSpec
import com.margelo.nitro.omni.HybridOmniPlayerSpec
import com.margelo.nitro.omni.PlayerStatus
import com.margelo.nitro.omni.Rendition
import com.margelo.nitro.omni.Track
import com.margelo.nitro.omni.Variant_NullType_Rendition
import dev.zoriya.omni.utils.deferredObservable
import org.videolan.libvlc.LibVLC
import org.videolan.libvlc.Media
import org.videolan.libvlc.MediaPlayer
import org.videolan.libvlc.interfaces.IMedia
class OmniPlayer(source: HybridOmniPlayerPropsSpec) : HybridOmniPlayerSpec() {
override val hasPrev: Boolean
get() = TODO("Not yet implemented")
override val hasNext: Boolean
get() = TODO("Not yet implemented")
override val status: PlayerStatus
get() = TODO("Not yet implemented")
override val isPlaying: Boolean
get() = TODO("Not yet implemented")
class OmniPlayer() : HybridOmniPlayerSpec() {
val vlc = LibVLC(NitroModules.applicationContext ?: throw Error("No Context available!"))
val player = MediaPlayer(vlc)
var source: HybridOmniPlayerPropsSpec by deferredObservable { _, _, new ->
val src = new.src.firstOrNull()?.uri ?: return@deferredObservable
player.media = Media(vlc, Uri.parse(src))
new.startTime?.let { start ->
player.time = (start * 1000.0).toLong().coerceAtLeast(0L)
}
}
override val hasPrev get() = source.metadata?.hasPrev ?: false
override val hasNext get() = source.metadata?.hasNext ?: false
override val status
get() = when (player.playerState) {
IMedia.State.NothingSpecial -> PlayerStatus.IDLE
IMedia.State.Opening -> PlayerStatus.LOADING
IMedia.State.Playing -> PlayerStatus.READYTOPLAY
IMedia.State.Paused -> PlayerStatus.READYTOPLAY
IMedia.State.Stopped -> PlayerStatus.IDLE
IMedia.State.Ended -> PlayerStatus.IDLE
IMedia.State.Error -> PlayerStatus.ERROR
else -> PlayerStatus.ERROR
}
override val isPlaying: Boolean get() = player.isPlaying
override var currentTime: Double
get() = TODO("Not yet implemented")
set(value) {}
get() = player.time.coerceAtLeast(0L) / 1000.0
set(value) {
player.time = (value * 1000.0).toLong().coerceAtLeast(0L)
}
override val buffered: Double
get() = TODO("Not yet implemented")
override val duration: Double
get() = TODO("Not yet implemented")
get() {
val total = duration
if (total <= 0.0) return 0.0
return (total * player.position.toDouble()).coerceIn(0.0, total)
}
override val duration: Double get() = player.length.coerceAtLeast(0L) / 1000.0
override var playbackRate: Double
get() = TODO("Not yet implemented")
set(value) {}
get() = player.rate.toDouble()
set(value) {
if (value > 0.0) player.rate = value.toFloat()
}
override var volume: Double
get() = TODO("Not yet implemented")
set(value) {}
override val videos: Array<Track>
get() = TODO("Not yet implemented")
override val audios: Array<Track>
get() = TODO("Not yet implemented")
override val subtitles: Array<Track>
get() = TODO("Not yet implemented")
override val rendition: Array<Rendition>
get() = TODO("Not yet implemented")
get() = (player.volume.coerceIn(0, 100) / 100.0)
set(value) {
player.volume = (value.coerceIn(0.0, 1.0) * 100.0).toInt()
}
override val videos get() = getTracks(IMedia.Track.Type.Video)
override val audios get() = getTracks(IMedia.Track.Type.Audio)
override val subtitles get() = getTracks(IMedia.Track.Type.Text)
fun getTracks(type: Int): Array<Track> {
val tracks = player.getTracks(type)
val selected = player.getSelectedTrack(type)
return tracks.map {
Track(
id = it.id,
label = it.name,
language = it.language,
selected = it.id == selected.id
)
}.toTypedArray()
}
override val rendition: Array<Rendition> get() = emptyArray()
override fun play() {
TODO("Not yet implemented")
player.play()
}
override fun pause() {
TODO("Not yet implemented")
player.pause()
}
override fun seekBy(offset: Double) {
TODO("Not yet implemented")
currentTime += offset
}
override fun playPrev() {
TODO("Not yet implemented")
// App-level action; no default implementation in VLC player.
}
override fun playNext() {
TODO("Not yet implemented")
// App-level action; no default implementation in VLC player.
}
override fun selectVideo(video: Track) {
TODO("Not yet implemented")
player.selectTrack(video.id)
}
override fun selectAudio(audio: Track) {
TODO("Not yet implemented")
player.selectTrack(audio.id)
}
override fun selectSubtitle(subtitle: Track) {
TODO("Not yet implemented")
override fun selectSubtitle(subtitle: Track?) {
when (subtitle) {
null -> player.unselectTrackType(IMedia.Track.Type.Text)
else -> player.selectTrack(subtitle.id)
}
}
override fun selectRendition(rendition: Variant_NullType_Rendition?) {
TODO("Not yet implemented")
override fun selectRendition(rendition: Rendition?) {
// Not supported by libVLC MediaPlayer track APIs.
}
}
}
@@ -7,6 +7,8 @@ import com.margelo.nitro.omni.HybridOmniPlayerSpec
class OmniPlayerFactory(val context: ThemedReactContext): HybridOmniPlayerFactorySpec() {
override fun createPlayer(props: HybridOmniPlayerPropsSpec): HybridOmniPlayerSpec {
return OmniPlayer(props)
return OmniPlayer().apply {
source = props
}
}
}
@@ -0,0 +1,21 @@
package dev.zoriya.omni.utils
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
inline fun <T : Any> deferredObservable(
crossinline onChange: (thisRef: Any?, oldValue: T?, newValue: T) -> Unit
): ReadWriteProperty<Any?, T> =
object : ReadWriteProperty<Any?, T> {
private var value: T? = null
override fun getValue(thisRef: Any?, property: KProperty<*>): T {
return value ?: throw IllegalStateException("Property ${property.name} should be initialized before get.")
}
override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
val old = this.value
this.value = value
onChange(old, value)
}
}
-1
View File
@@ -41,7 +41,6 @@ target_sources(
# Android-specific Nitrogen C++ sources
../nitrogen/generated/android/c++/JHybridOmniPlayerPropsSpec.cpp
../nitrogen/generated/android/c++/JHybridOmniPlayerSpec.cpp
../nitrogen/generated/android/c++/JVariant_NullType_Rendition.cpp
../nitrogen/generated/android/c++/JHybridOmniPlayerFactorySpec.cpp
../nitrogen/generated/android/c++/JHybridOmniViewSpec.cpp
../nitrogen/generated/android/c++/views/JHybridOmniViewStateUpdater.cpp
+5 -9
View File
@@ -23,10 +23,6 @@ namespace margelo::nitro::omni { struct Rendition; }
#include <optional>
#include "Rendition.hpp"
#include "JRendition.hpp"
#include <NitroModules/Null.hpp>
#include <variant>
#include "JVariant_NullType_Rendition.hpp"
#include <NitroModules/JNull.hpp>
namespace margelo::nitro::omni {
@@ -201,13 +197,13 @@ namespace margelo::nitro::omni {
static const auto method = _javaPart->javaClassStatic()->getMethod<void(jni::alias_ref<JTrack> /* audio */)>("selectAudio");
method(_javaPart, JTrack::fromCpp(audio));
}
void JHybridOmniPlayerSpec::selectSubtitle(const Track& subtitle) {
void JHybridOmniPlayerSpec::selectSubtitle(const std::optional<Track>& subtitle) {
static const auto method = _javaPart->javaClassStatic()->getMethod<void(jni::alias_ref<JTrack> /* subtitle */)>("selectSubtitle");
method(_javaPart, JTrack::fromCpp(subtitle));
method(_javaPart, subtitle.has_value() ? JTrack::fromCpp(subtitle.value()) : nullptr);
}
void JHybridOmniPlayerSpec::selectRendition(const std::optional<std::variant<nitro::NullType, Rendition>>& rendition) {
static const auto method = _javaPart->javaClassStatic()->getMethod<void(jni::alias_ref<JVariant_NullType_Rendition> /* rendition */)>("selectRendition");
method(_javaPart, rendition.has_value() ? JVariant_NullType_Rendition::fromCpp(rendition.value()) : nullptr);
void JHybridOmniPlayerSpec::selectRendition(const std::optional<Rendition>& rendition) {
static const auto method = _javaPart->javaClassStatic()->getMethod<void(jni::alias_ref<JRendition> /* rendition */)>("selectRendition");
method(_javaPart, rendition.has_value() ? JRendition::fromCpp(rendition.value()) : nullptr);
}
} // namespace margelo::nitro::omni
+2 -2
View File
@@ -76,8 +76,8 @@ namespace margelo::nitro::omni {
void playNext() override;
void selectVideo(const Track& video) override;
void selectAudio(const Track& audio) override;
void selectSubtitle(const Track& subtitle) override;
void selectRendition(const std::optional<std::variant<nitro::NullType, Rendition>>& rendition) override;
void selectSubtitle(const std::optional<Track>& subtitle) override;
void selectRendition(const std::optional<Rendition>& rendition) override;
private:
jni::global_ref<JHybridOmniPlayerSpec::JavaPart> _javaPart;
@@ -1,26 +0,0 @@
///
/// JVariant_NullType_Rendition.cpp
/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE.
/// https://github.com/mrousavy/nitro
/// Copyright © Marc Rousavy @ Margelo
///
#include "JVariant_NullType_Rendition.hpp"
namespace margelo::nitro::omni {
/**
* Converts JVariant_NullType_Rendition to std::variant<nitro::NullType, Rendition>
*/
std::variant<nitro::NullType, Rendition> JVariant_NullType_Rendition::toCpp() const {
if (isInstanceOf(JVariant_NullType_Rendition_impl::First::javaClassStatic())) {
// It's a `nitro::NullType`
auto jniValue = static_cast<const JVariant_NullType_Rendition_impl::First*>(this)->getValue();
return nitro::null;
} else if (isInstanceOf(JVariant_NullType_Rendition_impl::Second::javaClassStatic())) {
// It's a `Rendition`
auto jniValue = static_cast<const JVariant_NullType_Rendition_impl::Second*>(this)->getValue();
return jniValue->toCpp();
}
throw std::invalid_argument("Variant is unknown Kotlin instance!");
}
} // namespace margelo::nitro::omni
@@ -1,72 +0,0 @@
///
/// JVariant_NullType_Rendition.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 <variant>
#include <NitroModules/Null.hpp>
#include "Rendition.hpp"
#include <variant>
#include <NitroModules/JNull.hpp>
#include "JRendition.hpp"
#include <string>
namespace margelo::nitro::omni {
using namespace facebook;
/**
* The C++ JNI bridge between the C++ std::variant and the Java class "Variant_NullType_Rendition".
*/
class JVariant_NullType_Rendition: public jni::JavaClass<JVariant_NullType_Rendition> {
public:
static constexpr auto kJavaDescriptor = "Lcom/margelo/nitro/omni/Variant_NullType_Rendition;";
static jni::local_ref<JVariant_NullType_Rendition> create_0(jni::alias_ref<JNull> value) {
static const auto method = javaClassStatic()->getStaticMethod<JVariant_NullType_Rendition(jni::alias_ref<JNull>)>("create");
return method(javaClassStatic(), value);
}
static jni::local_ref<JVariant_NullType_Rendition> create_1(jni::alias_ref<JRendition> value) {
static const auto method = javaClassStatic()->getStaticMethod<JVariant_NullType_Rendition(jni::alias_ref<JRendition>)>("create");
return method(javaClassStatic(), value);
}
static jni::local_ref<JVariant_NullType_Rendition> fromCpp(const std::variant<nitro::NullType, Rendition>& variant) {
switch (variant.index()) {
case 0: return create_0(JNull::null());
case 1: return create_1(JRendition::fromCpp(std::get<1>(variant)));
default: throw std::invalid_argument("Variant holds unknown index! (" + std::to_string(variant.index()) + ")");
}
}
[[nodiscard]] std::variant<nitro::NullType, Rendition> toCpp() const;
};
namespace JVariant_NullType_Rendition_impl {
class First final: public jni::JavaClass<First, JVariant_NullType_Rendition> {
public:
static constexpr auto kJavaDescriptor = "Lcom/margelo/nitro/omni/Variant_NullType_Rendition$First;";
[[nodiscard]] jni::local_ref<JNull> getValue() const {
static const auto field = javaClassStatic()->getField<JNull>("value");
return getFieldValue(field);
}
};
class Second final: public jni::JavaClass<Second, JVariant_NullType_Rendition> {
public:
static constexpr auto kJavaDescriptor = "Lcom/margelo/nitro/omni/Variant_NullType_Rendition$Second;";
[[nodiscard]] jni::local_ref<JRendition> getValue() const {
static const auto field = javaClassStatic()->getField<JRendition>("value");
return getFieldValue(field);
}
};
} // namespace JVariant_NullType_Rendition_impl
} // namespace margelo::nitro::omni
@@ -10,7 +10,6 @@ package com.margelo.nitro.omni
import androidx.annotation.Keep
import com.facebook.jni.HybridData
import com.facebook.proguard.annotations.DoNotStrip
import com.margelo.nitro.core.NullType
import com.margelo.nitro.core.HybridObject
/**
@@ -115,11 +114,11 @@ abstract class HybridOmniPlayerSpec: HybridObject() {
@DoNotStrip
@Keep
abstract fun selectSubtitle(subtitle: Track): Unit
abstract fun selectSubtitle(subtitle: Track?): Unit
@DoNotStrip
@Keep
abstract fun selectRendition(rendition: Variant_NullType_Rendition?): Unit
abstract fun selectRendition(rendition: Rendition?): Unit
// Default implementation of `HybridObject.toString()`
override fun toString(): String {
@@ -1,53 +0,0 @@
///
/// Variant_NullType_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 com.facebook.proguard.annotations.DoNotStrip
import com.margelo.nitro.core.NullType
/**
* Represents the TypeScript variant "NullType | Rendition".
*/
@Suppress("ClassName")
@DoNotStrip
sealed class Variant_NullType_Rendition {
@DoNotStrip
data class First(@DoNotStrip val value: NullType): Variant_NullType_Rendition()
@DoNotStrip
data class Second(@DoNotStrip val value: Rendition): Variant_NullType_Rendition()
val isFirst: Boolean
get() = this is First
val isSecond: Boolean
get() = this is Second
fun asFirstOrNull(): NullType? {
val value = (this as? First)?.value ?: return null
return value
}
fun asSecondOrNull(): Rendition? {
val value = (this as? Second)?.value ?: return null
return value
}
inline fun <R> match(first: (NullType) -> R, second: (Rendition) -> R): R {
return when (this) {
is First -> first(value)
is Second -> second(value)
}
}
companion object {
@JvmStatic
@DoNotStrip
fun create(value: NullType): Variant_NullType_Rendition = First(value)
@JvmStatic
@DoNotStrip
fun create(value: Rendition): Variant_NullType_Rendition = Second(value)
}
}
+2 -4
View File
@@ -24,8 +24,6 @@ namespace margelo::nitro::omni { struct Rendition; }
#include "Track.hpp"
#include <vector>
#include "Rendition.hpp"
#include <NitroModules/Null.hpp>
#include <variant>
#include <optional>
namespace margelo::nitro::omni {
@@ -81,8 +79,8 @@ namespace margelo::nitro::omni {
virtual void playNext() = 0;
virtual void selectVideo(const Track& video) = 0;
virtual void selectAudio(const Track& audio) = 0;
virtual void selectSubtitle(const Track& subtitle) = 0;
virtual void selectRendition(const std::optional<std::variant<nitro::NullType, Rendition>>& rendition) = 0;
virtual void selectSubtitle(const std::optional<Track>& subtitle) = 0;
virtual void selectRendition(const std::optional<Rendition>& rendition) = 0;
protected:
// Hybrid Setup
+2 -2
View File
@@ -23,9 +23,9 @@ export interface OmniPlayer {
readonly audios: Track[];
selectAudio(audio: Track): void;
readonly subtitles: Track[];
selectSubtitle(subtitle: Track): void;
selectSubtitle(subtitle?: Track): void;
readonly rendition: Rendition[];
selectRendition(rendition: Rendition | null): void;
selectRendition(rendition?: Rendition): void;
}
export type PlayerStatus = "idle" | "loading" | "readyToPlay" | "error";