mirror of
https://github.com/zoriya/react-native-omni.git
synced 2026-08-15 18:33:50 +00:00
Basic vlc implementation
This commit is contained in:
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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
|
||||
+2
-3
@@ -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 {
|
||||
|
||||
-53
@@ -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
@@ -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
@@ -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";
|
||||
|
||||
Reference in New Issue
Block a user