mirror of
https://github.com/zoriya/react-native-omni.git
synced 2026-08-05 13:46:59 +00:00
Implement media sessions notification
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
|
||||
<uses-feature
|
||||
@@ -8,7 +9,7 @@
|
||||
|
||||
<application>
|
||||
<service
|
||||
android:name=".OmniPlaybackService"
|
||||
android:name="dev.zoriya.omni.OmniPlayerService"
|
||||
android:enabled="true"
|
||||
android:exported="false"
|
||||
android:foregroundServiceType="mediaPlayback">
|
||||
|
||||
@@ -1,86 +0,0 @@
|
||||
package dev.zoriya.omni
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Intent
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.media3.common.Player
|
||||
import androidx.media3.session.DefaultMediaNotificationProvider
|
||||
import androidx.media3.session.MediaSession
|
||||
import androidx.media3.session.MediaSessionService
|
||||
import com.margelo.nitro.NitroModules
|
||||
import java.util.concurrent.atomic.AtomicReference
|
||||
|
||||
@SuppressLint("UnsafeOptInUsageError")
|
||||
class OmniPlaybackService : MediaSessionService() {
|
||||
private var mediaSession: MediaSession? = null
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
serviceRef.set(this)
|
||||
setMediaNotificationProvider(
|
||||
DefaultMediaNotificationProvider.Builder(this)
|
||||
.setChannelId(NOTIFICATION_CHANNEL_ID)
|
||||
.build()
|
||||
)
|
||||
attachedPlayer.get()?.let { ensureSession(it) }
|
||||
}
|
||||
|
||||
override fun onGetSession(controllerInfo: MediaSession.ControllerInfo): MediaSession? {
|
||||
attachedPlayer.get()?.let { ensureSession(it) }
|
||||
return mediaSession
|
||||
}
|
||||
|
||||
override fun onTaskRemoved(rootIntent: Intent?) {
|
||||
if (!isPlaybackOngoing) {
|
||||
pauseAllPlayersAndStopSelf()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
mediaSession?.release()
|
||||
mediaSession = null
|
||||
serviceRef.compareAndSet(this, null)
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
private fun ensureSession(player: Player) {
|
||||
if (mediaSession?.player === player) return
|
||||
mediaSession?.release()
|
||||
mediaSession = MediaSession.Builder(this, player)
|
||||
.setId(SESSION_ID)
|
||||
.build()
|
||||
}
|
||||
|
||||
private fun clearSessionIf(player: Player) {
|
||||
if (mediaSession?.player !== player) return
|
||||
mediaSession?.release()
|
||||
mediaSession = null
|
||||
stopSelf()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val SESSION_ID = "omni-session"
|
||||
private const val NOTIFICATION_CHANNEL_ID = "omni_playback"
|
||||
private val attachedPlayer = AtomicReference<Player?>(null)
|
||||
private val serviceRef = AtomicReference<OmniPlaybackService?>(null)
|
||||
|
||||
fun attachPlayer(player: Player) {
|
||||
attachedPlayer.set(player)
|
||||
serviceRef.get()?.ensureSession(player)
|
||||
}
|
||||
|
||||
fun detachPlayer(player: Player) {
|
||||
if (!attachedPlayer.compareAndSet(player, null)) return
|
||||
serviceRef.get()?.clearSessionIf(player)
|
||||
}
|
||||
|
||||
fun ensureStarted(player: Player) {
|
||||
val context = NitroModules.Companion.applicationContext ?: return
|
||||
attachPlayer(player)
|
||||
ContextCompat.startForegroundService(
|
||||
context,
|
||||
Intent(context, OmniPlaybackService::class.java)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,14 @@
|
||||
package dev.zoriya.omni
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.util.Log
|
||||
import android.view.SurfaceHolder
|
||||
import androidx.media3.common.C
|
||||
import androidx.media3.common.MediaItem
|
||||
import androidx.media3.common.MediaMetadata
|
||||
import androidx.media3.common.Player
|
||||
import com.margelo.nitro.NitroModules
|
||||
import com.margelo.nitro.omni.HybridOmniPlayerSpec
|
||||
import com.margelo.nitro.omni.PlayerStatus
|
||||
@@ -12,7 +16,12 @@ import com.margelo.nitro.omni.Rendition
|
||||
import com.margelo.nitro.omni.Source
|
||||
import com.margelo.nitro.omni.Track
|
||||
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.session.DefaultMediaNotificationProvider
|
||||
import androidx.media3.session.MediaSession
|
||||
import androidx.media3.session.MediaSessionService
|
||||
|
||||
@SuppressLint("UnsafeOptInUsageError")
|
||||
class OmniPlayer : HybridOmniPlayerSpec() {
|
||||
@@ -20,15 +29,26 @@ class OmniPlayer : HybridOmniPlayerSpec() {
|
||||
val player = MpvPlayer(ctx)
|
||||
override val eventMap = EventMap(player)
|
||||
|
||||
init {
|
||||
OmniPlaybackService.attachPlayer(player)
|
||||
}
|
||||
override var showNotification: Boolean? = false
|
||||
set(value) {
|
||||
Log.e("omni", "Toggle show notif, old: ${field}, new: ${value}")
|
||||
if (value == true) {
|
||||
if (notificationPlayer != null) {
|
||||
throw Error("Two players can't display notifications at the same time.")
|
||||
}
|
||||
notificationPlayer = player
|
||||
ctx.startForegroundService(Intent(ctx, OmniPlayerService::class.java))
|
||||
} else if (field == true) {
|
||||
ctx.stopService(Intent(ctx, OmniPlayerService::class.java))
|
||||
}
|
||||
field = value
|
||||
}
|
||||
|
||||
override fun dispose() {
|
||||
showNotification = false
|
||||
super.dispose()
|
||||
|
||||
eventMap.dispose()
|
||||
OmniPlaybackService.detachPlayer(player)
|
||||
player.release()
|
||||
}
|
||||
|
||||
@@ -38,7 +58,50 @@ class OmniPlayer : HybridOmniPlayerSpec() {
|
||||
?: throw IllegalStateException("source should be initialized before get")
|
||||
set(value) {
|
||||
currentSource = value
|
||||
player.setMediaItem(buildMediaItem(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())
|
||||
// }
|
||||
// }
|
||||
player.setMediaItem(
|
||||
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()
|
||||
)
|
||||
}
|
||||
|
||||
fun setSurface(holder: SurfaceHolder?) {
|
||||
@@ -53,10 +116,9 @@ class OmniPlayer : HybridOmniPlayerSpec() {
|
||||
override val hasNext get() = player.hasNextMediaItem()
|
||||
override val status: PlayerStatus
|
||||
get() = when (player.playbackState) {
|
||||
androidx.media3.common.Player.STATE_IDLE,
|
||||
androidx.media3.common.Player.STATE_ENDED -> PlayerStatus.IDLE
|
||||
|
||||
androidx.media3.common.Player.STATE_BUFFERING -> PlayerStatus.LOADING
|
||||
Player.STATE_IDLE,
|
||||
Player.STATE_ENDED -> PlayerStatus.IDLE
|
||||
Player.STATE_BUFFERING -> PlayerStatus.LOADING
|
||||
else -> PlayerStatus.READYTOPLAY
|
||||
}
|
||||
|
||||
@@ -104,7 +166,6 @@ class OmniPlayer : HybridOmniPlayerSpec() {
|
||||
override val rendition: Array<Rendition> get() = emptyArray()
|
||||
|
||||
override fun play() {
|
||||
OmniPlaybackService.ensureStarted(player)
|
||||
player.play()
|
||||
}
|
||||
|
||||
@@ -147,57 +208,6 @@ class OmniPlayer : HybridOmniPlayerSpec() {
|
||||
override fun selectRendition(rendition: Rendition?) {
|
||||
}
|
||||
|
||||
private fun buildMediaItem(source: Source): MediaItem {
|
||||
val src = source.src.firstOrNull() ?: return MediaItem.EMPTY
|
||||
|
||||
val mediaMetadata = MediaMetadata.Builder()
|
||||
.setTitle(source.metadata?.title)
|
||||
.setAlbumTitle(source.metadata?.album)
|
||||
.setArtist(source.metadata?.artist)
|
||||
.apply {
|
||||
source.metadata?.imageLink?.let { setArtworkUri(it.toUri()) }
|
||||
}
|
||||
.build()
|
||||
|
||||
val subtitleConfigurations = source.subtitles.map { subtitle ->
|
||||
MediaItem.SubtitleConfiguration.Builder(subtitle.link.toUri())
|
||||
.setId(subtitle.id)
|
||||
.setLanguage(subtitle.language)
|
||||
.setLabel(subtitle.label)
|
||||
.setMimeType(subtitle.mimeType)
|
||||
.build()
|
||||
}
|
||||
|
||||
// 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 requestMetadata = MediaItem.RequestMetadata.Builder()
|
||||
.setMediaUri(src.uri.toUri())
|
||||
// .setExtras(headers)
|
||||
.build()
|
||||
|
||||
val itemBuilder = MediaItem.Builder()
|
||||
.setUri(src.uri)
|
||||
.setMimeType(src.mimeType)
|
||||
.setMediaId(src.uri)
|
||||
.setMediaMetadata(mediaMetadata)
|
||||
.setSubtitleConfigurations(subtitleConfigurations)
|
||||
.setRequestMetadata(requestMetadata)
|
||||
|
||||
return itemBuilder.build()
|
||||
}
|
||||
|
||||
private fun tracksByType(trackType: Int): Array<Track> {
|
||||
val groups = player.currentTracks.groups.filter { it.type == trackType }
|
||||
if (groups.isEmpty()) return emptyArray()
|
||||
@@ -237,4 +247,43 @@ class OmniPlayer : HybridOmniPlayerSpec() {
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
var notificationPlayer: Player? = null
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("UnsafeOptInUsageError")
|
||||
class OmniPlayerService : MediaSessionService() {
|
||||
private val ctx = NitroModules.applicationContext ?: throw Error("No Context available!")
|
||||
private val player = OmniPlayer.notificationPlayer ?: throw Error("No player available")
|
||||
var mediaSession: MediaSession = MediaSession.Builder(ctx, player).build()
|
||||
|
||||
init {
|
||||
Log.e("omni", "service inited")
|
||||
}
|
||||
|
||||
override fun onCreate() {
|
||||
Log.e("omni", "service created")
|
||||
super.onCreate()
|
||||
setMediaNotificationProvider(
|
||||
DefaultMediaNotificationProvider.Builder(ctx).build()
|
||||
)
|
||||
}
|
||||
|
||||
override fun onGetSession(controllerInfo: MediaSession.ControllerInfo) = mediaSession
|
||||
|
||||
override fun onTaskRemoved(rootIntent: Intent?) {
|
||||
if (!isPlaybackOngoing) {
|
||||
pauseAllPlayersAndStopSelf()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
mediaSession.release()
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
override fun getApplicationContext(): Context? {
|
||||
return NitroModules.applicationContext
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package dev.zoriya.omni
|
||||
|
||||
import android.util.Log
|
||||
import com.facebook.react.uimanager.ThemedReactContext
|
||||
import com.margelo.nitro.omni.HybridOmniPlayerFactorySpec
|
||||
import com.margelo.nitro.omni.HybridOmniPlayerSpec
|
||||
|
||||
@@ -3,7 +3,6 @@ package dev.zoriya.omni
|
||||
import android.util.Log
|
||||
import android.view.SurfaceHolder
|
||||
import android.view.SurfaceView
|
||||
import android.view.View
|
||||
import android.widget.FrameLayout
|
||||
import com.facebook.react.uimanager.ThemedReactContext
|
||||
import com.margelo.nitro.omni.HybridOmniPlayerSpec
|
||||
@@ -25,7 +24,6 @@ class OmniView(val context: ThemedReactContext) : HybridOmniViewSpec(), SurfaceH
|
||||
|
||||
override lateinit var player: HybridOmniPlayerSpec
|
||||
override var autoplay: Boolean? = true
|
||||
override var showNotification: Boolean? = true
|
||||
override var autoPip: Boolean? = true
|
||||
|
||||
override fun afterUpdate() {
|
||||
@@ -62,7 +60,6 @@ class OmniView(val context: ThemedReactContext) : HybridOmniViewSpec(), SurfaceH
|
||||
}
|
||||
|
||||
override fun onDropView() {
|
||||
Log.e("omniView", "omni-view dropped")
|
||||
if (!::player.isInitialized) return
|
||||
|
||||
val omniPlayer = player as? OmniPlayer ?: return
|
||||
@@ -72,7 +69,6 @@ class OmniView(val context: ThemedReactContext) : HybridOmniViewSpec(), SurfaceH
|
||||
|
||||
override fun surfaceCreated(holder: SurfaceHolder) {
|
||||
surfaceReady = true
|
||||
Log.e("omniView", "surface created")
|
||||
boundPlayer?.setSurface(holder)
|
||||
}
|
||||
|
||||
@@ -81,12 +77,9 @@ class OmniView(val context: ThemedReactContext) : HybridOmniViewSpec(), SurfaceH
|
||||
format: Int,
|
||||
width: Int,
|
||||
height: Int
|
||||
) {
|
||||
// Surface size is resolved from SurfaceHolder in MpvPlayer.
|
||||
}
|
||||
) { }
|
||||
|
||||
override fun surfaceDestroyed(holder: SurfaceHolder) {
|
||||
Log.e("omniView", "surface destroyed")
|
||||
surfaceReady = false
|
||||
boundPlayer?.setSurface(null)
|
||||
}
|
||||
|
||||
+2
-1
@@ -48,6 +48,7 @@ function PlayerExample({
|
||||
const currentTime = usePlayerState("currentTime");
|
||||
const duration = usePlayerState("duration");
|
||||
const playbackRate = usePlayerState("playbackRate");
|
||||
|
||||
const muted = usePlayerState("muted");
|
||||
const volume = usePlayerState("volume");
|
||||
const [logs, setLogs] = useState<string[]>([]);
|
||||
@@ -450,7 +451,7 @@ function App(): React.JSX.Element {
|
||||
);
|
||||
|
||||
return (
|
||||
<OmniProvider source={source}>
|
||||
<OmniProvider source={source} showNotification>
|
||||
<PlayerExample
|
||||
onPrev={handlePrev}
|
||||
onNext={handleNext}
|
||||
|
||||
@@ -94,6 +94,15 @@ namespace margelo::nitro::omni {
|
||||
static const auto method = _javaPart->javaClassStatic()->getMethod<void(jni::alias_ref<JSource> /* source */)>("setSource");
|
||||
method(_javaPart, JSource::fromCpp(source));
|
||||
}
|
||||
std::optional<bool> JHybridOmniPlayerSpec::getShowNotification() {
|
||||
static const auto method = _javaPart->javaClassStatic()->getMethod<jni::local_ref<jni::JBoolean>()>("getShowNotification");
|
||||
auto __result = method(_javaPart);
|
||||
return __result != nullptr ? std::make_optional(static_cast<bool>(__result->value())) : std::nullopt;
|
||||
}
|
||||
void JHybridOmniPlayerSpec::setShowNotification(std::optional<bool> showNotification) {
|
||||
static const auto method = _javaPart->javaClassStatic()->getMethod<void(jni::alias_ref<jni::JBoolean> /* showNotification */)>("setShowNotification");
|
||||
method(_javaPart, showNotification.has_value() ? jni::JBoolean::valueOf(showNotification.value()) : nullptr);
|
||||
}
|
||||
bool JHybridOmniPlayerSpec::getHasPrev() {
|
||||
static const auto method = _javaPart->javaClassStatic()->getMethod<jboolean()>("getHasPrev");
|
||||
auto __result = method(_javaPart);
|
||||
|
||||
@@ -53,6 +53,8 @@ namespace margelo::nitro::omni {
|
||||
std::shared_ptr<HybridOmniEventMapSpec> getEventMap() override;
|
||||
Source getSource() override;
|
||||
void setSource(const Source& source) override;
|
||||
std::optional<bool> getShowNotification() override;
|
||||
void setShowNotification(std::optional<bool> showNotification) override;
|
||||
bool getHasPrev() override;
|
||||
bool getHasNext() override;
|
||||
std::vector<Track> getVideos() override;
|
||||
|
||||
@@ -63,15 +63,6 @@ namespace margelo::nitro::omni {
|
||||
static const auto method = _javaPart->javaClassStatic()->getMethod<void(jni::alias_ref<jni::JBoolean> /* autoplay */)>("setAutoplay");
|
||||
method(_javaPart, autoplay.has_value() ? jni::JBoolean::valueOf(autoplay.value()) : nullptr);
|
||||
}
|
||||
std::optional<bool> JHybridOmniViewSpec::getShowNotification() {
|
||||
static const auto method = _javaPart->javaClassStatic()->getMethod<jni::local_ref<jni::JBoolean>()>("getShowNotification");
|
||||
auto __result = method(_javaPart);
|
||||
return __result != nullptr ? std::make_optional(static_cast<bool>(__result->value())) : std::nullopt;
|
||||
}
|
||||
void JHybridOmniViewSpec::setShowNotification(std::optional<bool> showNotification) {
|
||||
static const auto method = _javaPart->javaClassStatic()->getMethod<void(jni::alias_ref<jni::JBoolean> /* showNotification */)>("setShowNotification");
|
||||
method(_javaPart, showNotification.has_value() ? jni::JBoolean::valueOf(showNotification.value()) : nullptr);
|
||||
}
|
||||
std::optional<bool> JHybridOmniViewSpec::getAutoPip() {
|
||||
static const auto method = _javaPart->javaClassStatic()->getMethod<jni::local_ref<jni::JBoolean>()>("getAutoPip");
|
||||
auto __result = method(_javaPart);
|
||||
|
||||
@@ -54,8 +54,6 @@ namespace margelo::nitro::omni {
|
||||
void setPlayer(const std::shared_ptr<HybridOmniPlayerSpec>& player) override;
|
||||
std::optional<bool> getAutoplay() override;
|
||||
void setAutoplay(std::optional<bool> autoplay) override;
|
||||
std::optional<bool> getShowNotification() override;
|
||||
void setShowNotification(std::optional<bool> showNotification) override;
|
||||
std::optional<bool> getAutoPip() override;
|
||||
void setAutoPip(std::optional<bool> autoPip) override;
|
||||
|
||||
|
||||
@@ -45,10 +45,6 @@ void JHybridOmniViewStateUpdater::updateViewProps(jni::alias_ref<jni::JClass> /*
|
||||
hybridView->setAutoplay(props->autoplay.value);
|
||||
props->autoplay.isDirty = false;
|
||||
}
|
||||
if (props->showNotification.isDirty) {
|
||||
hybridView->setShowNotification(props->showNotification.value);
|
||||
props->showNotification.isDirty = false;
|
||||
}
|
||||
if (props->autoPip.isDirty) {
|
||||
hybridView->setAutoPip(props->autoPip.value);
|
||||
props->autoPip.isDirty = false;
|
||||
|
||||
+6
@@ -35,6 +35,12 @@ abstract class HybridOmniPlayerSpec: HybridObject() {
|
||||
@set:Keep
|
||||
abstract var source: Source
|
||||
|
||||
@get:DoNotStrip
|
||||
@get:Keep
|
||||
@set:DoNotStrip
|
||||
@set:Keep
|
||||
abstract var showNotification: Boolean?
|
||||
|
||||
@get:DoNotStrip
|
||||
@get:Keep
|
||||
abstract val hasPrev: Boolean
|
||||
|
||||
-6
@@ -38,12 +38,6 @@ abstract class HybridOmniViewSpec: HybridView() {
|
||||
@set:Keep
|
||||
abstract var autoplay: Boolean?
|
||||
|
||||
@get:DoNotStrip
|
||||
@get:Keep
|
||||
@set:DoNotStrip
|
||||
@set:Keep
|
||||
abstract var showNotification: Boolean?
|
||||
|
||||
@get:DoNotStrip
|
||||
@get:Keep
|
||||
@set:DoNotStrip
|
||||
|
||||
@@ -17,6 +17,8 @@ namespace margelo::nitro::omni {
|
||||
prototype.registerHybridGetter("eventMap", &HybridOmniPlayerSpec::getEventMap);
|
||||
prototype.registerHybridGetter("source", &HybridOmniPlayerSpec::getSource);
|
||||
prototype.registerHybridSetter("source", &HybridOmniPlayerSpec::setSource);
|
||||
prototype.registerHybridGetter("showNotification", &HybridOmniPlayerSpec::getShowNotification);
|
||||
prototype.registerHybridSetter("showNotification", &HybridOmniPlayerSpec::setShowNotification);
|
||||
prototype.registerHybridGetter("hasPrev", &HybridOmniPlayerSpec::getHasPrev);
|
||||
prototype.registerHybridGetter("hasNext", &HybridOmniPlayerSpec::getHasNext);
|
||||
prototype.registerHybridGetter("videos", &HybridOmniPlayerSpec::getVideos);
|
||||
|
||||
+3
-1
@@ -27,11 +27,11 @@ namespace margelo::nitro::omni { enum class PlayerStatus; }
|
||||
#include <memory>
|
||||
#include "HybridOmniEventMapSpec.hpp"
|
||||
#include "Source.hpp"
|
||||
#include <optional>
|
||||
#include "Track.hpp"
|
||||
#include <vector>
|
||||
#include "Rendition.hpp"
|
||||
#include "PlayerStatus.hpp"
|
||||
#include <optional>
|
||||
|
||||
namespace margelo::nitro::omni {
|
||||
|
||||
@@ -63,6 +63,8 @@ namespace margelo::nitro::omni {
|
||||
virtual std::shared_ptr<HybridOmniEventMapSpec> getEventMap() = 0;
|
||||
virtual Source getSource() = 0;
|
||||
virtual void setSource(const Source& source) = 0;
|
||||
virtual std::optional<bool> getShowNotification() = 0;
|
||||
virtual void setShowNotification(std::optional<bool> showNotification) = 0;
|
||||
virtual bool getHasPrev() = 0;
|
||||
virtual bool getHasNext() = 0;
|
||||
virtual std::vector<Track> getVideos() = 0;
|
||||
|
||||
@@ -18,8 +18,6 @@ namespace margelo::nitro::omni {
|
||||
prototype.registerHybridSetter("player", &HybridOmniViewSpec::setPlayer);
|
||||
prototype.registerHybridGetter("autoplay", &HybridOmniViewSpec::getAutoplay);
|
||||
prototype.registerHybridSetter("autoplay", &HybridOmniViewSpec::setAutoplay);
|
||||
prototype.registerHybridGetter("showNotification", &HybridOmniViewSpec::getShowNotification);
|
||||
prototype.registerHybridSetter("showNotification", &HybridOmniViewSpec::setShowNotification);
|
||||
prototype.registerHybridGetter("autoPip", &HybridOmniViewSpec::getAutoPip);
|
||||
prototype.registerHybridSetter("autoPip", &HybridOmniViewSpec::setAutoPip);
|
||||
});
|
||||
|
||||
@@ -51,8 +51,6 @@ namespace margelo::nitro::omni {
|
||||
virtual void setPlayer(const std::shared_ptr<HybridOmniPlayerSpec>& player) = 0;
|
||||
virtual std::optional<bool> getAutoplay() = 0;
|
||||
virtual void setAutoplay(std::optional<bool> autoplay) = 0;
|
||||
virtual std::optional<bool> getShowNotification() = 0;
|
||||
virtual void setShowNotification(std::optional<bool> showNotification) = 0;
|
||||
virtual std::optional<bool> getAutoPip() = 0;
|
||||
virtual void setAutoPip(std::optional<bool> autoPip) = 0;
|
||||
|
||||
|
||||
@@ -46,16 +46,6 @@ namespace margelo::nitro::omni::views {
|
||||
throw std::runtime_error(std::string("OmniView.autoplay: ") + exc.what());
|
||||
}
|
||||
}()),
|
||||
showNotification([&]() -> CachedProp<std::optional<bool>> {
|
||||
try {
|
||||
const react::RawValue* rawValue = rawProps.at("showNotification", nullptr, nullptr);
|
||||
if (rawValue == nullptr) return sourceProps.showNotification;
|
||||
const auto& [runtime, value] = (std::pair<jsi::Runtime*, jsi::Value>)*rawValue;
|
||||
return CachedProp<std::optional<bool>>::fromRawValue(*runtime, value, sourceProps.showNotification);
|
||||
} catch (const std::exception& exc) {
|
||||
throw std::runtime_error(std::string("OmniView.showNotification: ") + exc.what());
|
||||
}
|
||||
}()),
|
||||
autoPip([&]() -> CachedProp<std::optional<bool>> {
|
||||
try {
|
||||
const react::RawValue* rawValue = rawProps.at("autoPip", nullptr, nullptr);
|
||||
@@ -81,7 +71,6 @@ namespace margelo::nitro::omni::views {
|
||||
switch (hashString(propName)) {
|
||||
case hashString("player"): return true;
|
||||
case hashString("autoplay"): return true;
|
||||
case hashString("showNotification"): return true;
|
||||
case hashString("autoPip"): return true;
|
||||
case hashString("hybridRef"): return true;
|
||||
default: return false;
|
||||
|
||||
@@ -44,7 +44,6 @@ namespace margelo::nitro::omni::views {
|
||||
public:
|
||||
CachedProp<std::shared_ptr<HybridOmniPlayerSpec>> player;
|
||||
CachedProp<std::optional<bool>> autoplay;
|
||||
CachedProp<std::optional<bool>> showNotification;
|
||||
CachedProp<std::optional<bool>> autoPip;
|
||||
CachedProp<std::optional<std::function<void(const std::shared_ptr<HybridOmniViewSpec>& /* ref */)>>> hybridRef;
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
"validAttributes": {
|
||||
"player": true,
|
||||
"autoplay": true,
|
||||
"showNotification": true,
|
||||
"autoPip": true,
|
||||
"hybridRef": true
|
||||
}
|
||||
|
||||
+20
-4
@@ -20,10 +20,17 @@ export const useEvent = <Event extends keyof OmniEvents>(
|
||||
}, [player, event, callback]);
|
||||
};
|
||||
|
||||
// export const usePlayerState = (key: "progress", refresh?: number): OmniPlayerState["progress"];
|
||||
export const usePlayerState = <Key extends keyof OmniPlayerState>(
|
||||
export function usePlayerState<Key extends keyof OmniPlayerState>(
|
||||
key: Key,
|
||||
): OmniPlayerState[Key] => {
|
||||
): OmniPlayerState[Key];
|
||||
export function usePlayerState(
|
||||
key: "currentTime",
|
||||
refresh?: number,
|
||||
): OmniPlayerState["currentTime"];
|
||||
export function usePlayerState<Key extends keyof OmniPlayerState>(
|
||||
key: Key,
|
||||
refresh?: number,
|
||||
): OmniPlayerState[Key] {
|
||||
const player = usePlayer() as OmniPlayer;
|
||||
const [ret, setState] = useState<any>(player[key]);
|
||||
|
||||
@@ -47,5 +54,14 @@ export const usePlayerState = <Key extends keyof OmniPlayerState>(
|
||||
}
|
||||
}, [player, key]);
|
||||
|
||||
if (key === "currentTime") refresh ??= 1;
|
||||
useEffect(() => {
|
||||
if (!refresh || refresh <= 0) return;
|
||||
const int = setInterval(() => {
|
||||
setState(player[key])
|
||||
}, refresh);
|
||||
return () => clearInterval(int);
|
||||
}, [refresh, key, player]);
|
||||
|
||||
return ret;
|
||||
};
|
||||
}
|
||||
|
||||
+10
-7
@@ -5,28 +5,31 @@ import type { OmniPlayer } from "./types/player";
|
||||
import type { Source } from "./types/source";
|
||||
import { useLazyRef } from "./utils/lazy-ref";
|
||||
|
||||
const ProviderFactory = NitroModules.createHybridObject<OmniPlayerFactory>(
|
||||
"OmniPlayerFactory",
|
||||
);
|
||||
const ProviderFactory =
|
||||
NitroModules.createHybridObject<OmniPlayerFactory>("OmniPlayerFactory");
|
||||
|
||||
const PlayerCtx = createContext<OmniPlayer>(null!);
|
||||
|
||||
export const OmniProvider = ({
|
||||
children,
|
||||
source,
|
||||
showNotification = false,
|
||||
}: {
|
||||
source: Source;
|
||||
children: ReactNode;
|
||||
showNotification?: boolean;
|
||||
}) => {
|
||||
const player = useLazyRef(() => ProviderFactory.createPlayer(source));
|
||||
|
||||
useEffect(() => {
|
||||
player.current.source = source;
|
||||
player.source = source;
|
||||
}, [source]);
|
||||
|
||||
return (
|
||||
<PlayerCtx.Provider value={player.current}>{children}</PlayerCtx.Provider>
|
||||
);
|
||||
useEffect(() => {
|
||||
player.showNotification = showNotification;
|
||||
}, [showNotification]);
|
||||
|
||||
return <PlayerCtx.Provider value={player}>{children}</PlayerCtx.Provider>;
|
||||
};
|
||||
|
||||
export const usePlayer = () => {
|
||||
|
||||
@@ -2,6 +2,7 @@ import type { Source } from "./source";
|
||||
|
||||
export interface OmniPlayer extends OmniPlayerState {
|
||||
source: Source;
|
||||
showNotification?: boolean;
|
||||
|
||||
play(): void;
|
||||
pause(): void;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
export interface OmniViewProps {
|
||||
autoplay?: boolean;
|
||||
showNotification?: boolean;
|
||||
autoPip?: boolean;
|
||||
}
|
||||
|
||||
+4
-11
@@ -1,13 +1,6 @@
|
||||
import { type RefObject, useRef } from "react";
|
||||
import { useState } from "react";
|
||||
|
||||
const empty = Symbol("useLazyRef empty value");
|
||||
|
||||
export const useLazyRef = <T>(init: () => T): RefObject<T> => {
|
||||
const resultRef = useRef<T | typeof empty>(empty);
|
||||
|
||||
if (resultRef.current === empty) {
|
||||
resultRef.current = init();
|
||||
}
|
||||
|
||||
return resultRef as RefObject<T>;
|
||||
export const useLazyRef = <T>(init: () => T): T => {
|
||||
const [ret] = useState<T>(init);
|
||||
return ret
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user