Implement media sessions notification

This commit is contained in:
2026-04-22 22:51:10 +02:00
parent 6635266fc8
commit a0b43ae009
25 changed files with 173 additions and 219 deletions
+2 -1
View File
@@ -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
View File
@@ -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;
-9
View File
@@ -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);
-2
View File
@@ -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;
@@ -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
@@ -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
+2
View File
@@ -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
View File
@@ -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;
-2
View File
@@ -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);
});
-2
View File
@@ -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;
-1
View File
@@ -6,7 +6,6 @@
"validAttributes": {
"player": true,
"autoplay": true,
"showNotification": true,
"autoPip": true,
"hybridRef": true
}
+20 -4
View File
@@ -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
View File
@@ -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 = () => {
+1
View File
@@ -2,6 +2,7 @@ import type { Source } from "./source";
export interface OmniPlayer extends OmniPlayerState {
source: Source;
showNotification?: boolean;
play(): void;
pause(): void;
-1
View File
@@ -1,5 +1,4 @@
export interface OmniViewProps {
autoplay?: boolean;
showNotification?: boolean;
autoPip?: boolean;
}
+4 -11
View File
@@ -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
};