Compare commits
2 Commits
backround-
...
background
| Author | SHA1 | Date | |
|---|---|---|---|
| 53bc70ab05 | |||
| 303ac83fe2 |
@@ -199,6 +199,89 @@ export function MusicPlayerProvider({ children }: { children: ReactNode }) {
|
||||
else audio.pause();
|
||||
}, [isPlaying, currentTrack]);
|
||||
|
||||
// OS-level media controls (lock screen, notification shade, media keys, etc.)
|
||||
// via the Media Session API. Wire the transport actions to our state.
|
||||
useEffect(() => {
|
||||
if (typeof navigator === "undefined" || !("mediaSession" in navigator)) return;
|
||||
const ms = navigator.mediaSession;
|
||||
const handlers: [MediaSessionAction, MediaSessionActionHandler][] = [
|
||||
["play", () => setIsPlaying(true)],
|
||||
["pause", () => setIsPlaying(false)],
|
||||
["previoustrack", () => stepRef.current(-1)],
|
||||
["nexttrack", () => stepRef.current(1)],
|
||||
[
|
||||
"seekto",
|
||||
(details) => {
|
||||
if (typeof details.seekTime === "number") seek(details.seekTime);
|
||||
},
|
||||
],
|
||||
[
|
||||
"seekbackward",
|
||||
(details) => seek((audioRef.current?.currentTime ?? 0) - (details.seekOffset ?? 10)),
|
||||
],
|
||||
[
|
||||
"seekforward",
|
||||
(details) => seek((audioRef.current?.currentTime ?? 0) + (details.seekOffset ?? 10)),
|
||||
],
|
||||
];
|
||||
for (const [action, handler] of handlers) {
|
||||
try {
|
||||
ms.setActionHandler(action, handler);
|
||||
} catch {
|
||||
// Action unsupported by this browser — ignore.
|
||||
}
|
||||
}
|
||||
return () => {
|
||||
for (const [action] of handlers) {
|
||||
try {
|
||||
ms.setActionHandler(action, null);
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
};
|
||||
}, [seek]);
|
||||
|
||||
// Keep the OS-visible metadata in sync with the current track.
|
||||
useEffect(() => {
|
||||
if (typeof navigator === "undefined" || !("mediaSession" in navigator)) return;
|
||||
navigator.mediaSession.metadata = currentTrack
|
||||
? new MediaMetadata({
|
||||
title: currentTrack.title,
|
||||
artist: "Gregor Lohaus",
|
||||
})
|
||||
: null;
|
||||
}, [currentTrack]);
|
||||
|
||||
// Reflect play/pause state to the OS so the right button is shown.
|
||||
useEffect(() => {
|
||||
if (typeof navigator === "undefined" || !("mediaSession" in navigator)) return;
|
||||
navigator.mediaSession.playbackState = currentTrack
|
||||
? isPlaying
|
||||
? "playing"
|
||||
: "paused"
|
||||
: "none";
|
||||
}, [isPlaying, currentTrack]);
|
||||
|
||||
// Keep the scrubber position on the OS controls in sync.
|
||||
useEffect(() => {
|
||||
if (typeof navigator === "undefined" || !("mediaSession" in navigator)) return;
|
||||
if (!("setPositionState" in navigator.mediaSession)) return;
|
||||
try {
|
||||
if (duration > 0 && Number.isFinite(duration)) {
|
||||
navigator.mediaSession.setPositionState({
|
||||
duration,
|
||||
position: Math.min(currentTime, duration),
|
||||
playbackRate: audioRef.current?.playbackRate ?? 1,
|
||||
});
|
||||
} else {
|
||||
navigator.mediaSession.setPositionState();
|
||||
}
|
||||
} catch {
|
||||
// Some browsers throw on invalid state — ignore.
|
||||
}
|
||||
}, [currentTime, duration]);
|
||||
|
||||
const value = useMemo<MusicPlayerValue>(
|
||||
() => ({
|
||||
tracks,
|
||||
|
||||
Reference in New Issue
Block a user