Updated MediaSession.tsx

A new and more cleaner MediaSession.tsx also fixed TypeError: Failed to execute 'setPositionState' on 'MediaSession': The provided duration cannot be NaN.
This commit is contained in:
Chris 2025-10-15 22:42:48 +03:00 committed by Pas
parent 31b3b0d369
commit bf530902cc

View file

@ -10,176 +10,209 @@ export function MediaSession() {
(s) => s.setShouldStartFromBeginning, (s) => s.setShouldStartFromBeginning,
); );
const mediaPlaying = usePlayerStore((s) => s.mediaPlaying);
const progress = usePlayerStore((s) => s.progress);
const meta = usePlayerStore((s) => s.meta);
const display = usePlayerStore((s) => s.display);
const shouldUpdatePositionState = useRef(false); const shouldUpdatePositionState = useRef(false);
const lastPlaybackPosition = useRef(0); const lastPlaybackPosition = useRef(0);
const data = usePlayerStore.getState();
const changeEpisode = useCallback( const changeEpisode = useCallback(
(change: number) => { (change: number) => {
const nextEp = data.meta?.episodes?.find( const nextEp = meta?.episodes?.find(
(v) => v.number === (data.meta?.episode?.number ?? 0) + change, (v) => v.number === (meta?.episode?.number ?? 0) + change,
); );
if (!data.meta || !nextEp) return; if (!meta || !nextEp) return;
const metaCopy = { ...data.meta }; const metaCopy = { ...meta };
metaCopy.episode = nextEp; metaCopy.episode = nextEp;
setShouldStartFromBeginning(true); setShouldStartFromBeginning(true);
setDirectMeta(metaCopy); setDirectMeta(metaCopy);
}, },
[data.meta, setDirectMeta, setShouldStartFromBeginning], [meta, setDirectMeta, setShouldStartFromBeginning],
); );
const updatePositionState = useCallback( const updatePositionState = useCallback(
(position: number) => { (position: number) => {
// If the browser doesn't support setPositionState, return
if (typeof navigator.mediaSession.setPositionState !== "function") return; if (typeof navigator.mediaSession.setPositionState !== "function") return;
// If the updated position needs to be buffered, queue an update const { duration, buffered } = progress;
if (position > data.progress.buffered) { const { playbackRate } = mediaPlaying;
if (
typeof duration !== "number" ||
Number.isNaN(duration) ||
!Number.isFinite(duration) ||
duration <= 0
) {
return;
}
if (
typeof position !== "number" ||
Number.isNaN(position) ||
position < 0
) {
position = 0;
}
if (position > buffered) {
shouldUpdatePositionState.current = true; shouldUpdatePositionState.current = true;
} }
if (position > data.progress.duration) return;
lastPlaybackPosition.current = data.progress.time; if (position > duration) {
position = duration;
}
lastPlaybackPosition.current = progress.time;
navigator.mediaSession.setPositionState({ navigator.mediaSession.setPositionState({
duration: data.progress.duration, duration,
playbackRate: data.mediaPlaying.playbackRate, playbackRate,
position, position,
}); });
}, },
[ [mediaPlaying, progress],
data.mediaPlaying.playbackRate,
data.progress.buffered,
data.progress.duration,
data.progress.time,
],
); );
useEffect(() => { useEffect(() => {
if (!("mediaSession" in navigator)) return; if (!("mediaSession" in navigator)) return;
navigator.mediaSession.playbackState = mediaPlaying.isPaused
? "paused"
: "playing";
}, [mediaPlaying.isPaused]);
// If the media is paused, update the navigator useEffect(() => {
if (data.mediaPlaying.isPaused) { if (!("mediaSession" in navigator)) return;
navigator.mediaSession.playbackState = "paused"; if (
} else { typeof progress.duration !== "number" ||
navigator.mediaSession.playbackState = "playing"; Number.isNaN(progress.duration) ||
progress.duration <= 0
) {
return;
} }
}, [data.mediaPlaying.isPaused]); updatePositionState(progress.time);
}, [
progress.time,
mediaPlaying.playbackRate,
progress.duration,
updatePositionState,
]);
useEffect(() => { useEffect(() => {
if (!("mediaSession" in navigator)) return; if (!("mediaSession" in navigator)) return;
updatePositionState(data.progress.time); const { time, duration } = progress;
}, [data.progress.time, data.mediaPlaying.playbackRate, updatePositionState]); const { isLoading } = mediaPlaying;
useEffect(() => { if (
if (!("mediaSession" in navigator)) return; typeof duration !== "number" ||
// If not already updating the position state, and the media is loading, queue an update Number.isNaN(duration) ||
if (!shouldUpdatePositionState.current && data.mediaPlaying.isLoading) { duration <= 0
) {
return;
}
if (!shouldUpdatePositionState.current && isLoading) {
shouldUpdatePositionState.current = true; shouldUpdatePositionState.current = true;
} }
// If the user has skipped (or MediaSession desynced) by more than 5 seconds, queue an update
if ( if (
Math.abs(data.progress.time - lastPlaybackPosition.current) >= 5 && !isLoading &&
!data.mediaPlaying.isLoading && !shouldUpdatePositionState.current &&
!shouldUpdatePositionState.current Math.abs(time - lastPlaybackPosition.current) >= 5
) { ) {
shouldUpdatePositionState.current = true; shouldUpdatePositionState.current = true;
} }
// If not loading and the position state is queued, update it if (shouldUpdatePositionState.current && !isLoading) {
if (shouldUpdatePositionState.current && !data.mediaPlaying.isLoading) {
shouldUpdatePositionState.current = false; shouldUpdatePositionState.current = false;
updatePositionState(data.progress.time); updatePositionState(time);
} }
lastPlaybackPosition.current = data.progress.time; lastPlaybackPosition.current = time;
}, [updatePositionState, data.progress.time, data.mediaPlaying.isLoading]); }, [mediaPlaying, progress, updatePositionState]);
useEffect(() => { useEffect(() => {
if ( if (
!("mediaSession" in navigator) || !("mediaSession" in navigator) ||
(!data.mediaPlaying.isLoading && (!mediaPlaying.isLoading && mediaPlaying.isPlaying && !display)
data.mediaPlaying.isPlaying && ) {
!data.display)
)
return; return;
}
let title: string | undefined; let title: string | undefined;
let artist: string | undefined; let artist: string | undefined;
if (data.meta?.type === "movie") { if (meta?.type === "movie") {
title = data.meta?.title; title = meta.title;
} else if (data.meta?.type === "show") { } else if (meta?.type === "show") {
artist = data.meta?.title; artist = meta.title;
title = `S${data.meta?.season?.number} E${data.meta?.episode?.number}: ${data.meta?.episode?.title}`; title = `S${meta.season?.number} E${meta.episode?.number}: ${meta.episode?.title}`;
} }
navigator.mediaSession.metadata = new MediaMetadata({ navigator.mediaSession.metadata = new MediaMetadata({
title, title,
artist, artist,
artwork: [ artwork: [
{ { src: meta?.poster ?? "", sizes: "342x513", type: "image/png" },
src: data.meta?.poster ?? "",
sizes: "342x513",
type: "image/png",
},
], ],
}); });
navigator.mediaSession.setActionHandler("play", () => { navigator.mediaSession.setActionHandler("play", () => {
if (data.mediaPlaying.isLoading) return; if (mediaPlaying.isLoading) return;
data.display?.play(); display?.play();
updatePositionState(progress.time);
updatePositionState(data.progress.time);
}); });
navigator.mediaSession.setActionHandler("pause", () => { navigator.mediaSession.setActionHandler("pause", () => {
if (data.mediaPlaying.isLoading) return; if (mediaPlaying.isLoading) return;
data.display?.pause(); display?.pause();
updatePositionState(progress.time);
updatePositionState(data.progress.time);
}); });
navigator.mediaSession.setActionHandler("seekto", (e) => { navigator.mediaSession.setActionHandler("seekto", (e) => {
if (!e.seekTime) return; if (e.seekTime == null) return;
data.display?.setTime(e.seekTime); display?.setTime(e.seekTime);
updatePositionState(e.seekTime); updatePositionState(e.seekTime);
}); });
if ((data.meta?.episode?.number ?? 1) !== 1) { if ((meta?.episode?.number ?? 1) > 1) {
navigator.mediaSession.setActionHandler("previoustrack", () => { navigator.mediaSession.setActionHandler("previoustrack", () =>
changeEpisode(-1); changeEpisode(-1),
}); );
} else { } else {
navigator.mediaSession.setActionHandler("previoustrack", null); navigator.mediaSession.setActionHandler("previoustrack", null);
} }
if (data.meta?.episode?.number !== data.meta?.episodes?.length) { const totalEpisodes = meta?.episodes?.length ?? 0;
navigator.mediaSession.setActionHandler("nexttrack", () => { const currentEpisodeNumber = meta?.episode?.number ?? 0;
changeEpisode(1); if (currentEpisodeNumber > 0 && currentEpisodeNumber < totalEpisodes) {
}); navigator.mediaSession.setActionHandler("nexttrack", () =>
changeEpisode(1),
);
} else { } else {
navigator.mediaSession.setActionHandler("nexttrack", null); navigator.mediaSession.setActionHandler("nexttrack", null);
} }
}, [ }, [
changeEpisode, changeEpisode,
updatePositionState, updatePositionState,
data.mediaPlaying.hasPlayedOnce, mediaPlaying.isLoading,
data.mediaPlaying.isLoading, mediaPlaying.isPlaying,
data.progress.duration, display,
data.progress.time, progress.duration,
data.meta?.episode?.number, progress.time,
data.meta?.episodes?.length, meta?.episode?.number,
data.display, meta?.episodes?.length,
data.mediaPlaying, meta?.episode?.title,
data.meta?.episode?.title, meta?.title,
data.meta?.title, meta?.type,
data.meta?.type, meta?.poster,
data.meta?.poster, meta?.season?.number,
data.meta?.season?.number,
]); ]);
return null; return null;
} }
// what did we learn today? never use isNaN instead of Number.isNaN !!!