From af4a3bb50e64b710cc642d3bd2921ea6802a6230 Mon Sep 17 00:00:00 2001 From: Junwei Zhao Date: Thu, 30 Oct 2025 16:23:02 +1100 Subject: [PATCH] Showing music detail on iphone --- frontend/src/components/player/Player.tsx | 78 +++++++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/frontend/src/components/player/Player.tsx b/frontend/src/components/player/Player.tsx index bcb1cb7..1ea245e 100644 --- a/frontend/src/components/player/Player.tsx +++ b/frontend/src/components/player/Player.tsx @@ -107,6 +107,84 @@ export default function Player({ } }, [audioRef]) + // Media Session API for lock screen controls (iOS, Android) + useEffect(() => { + if (!currentMusic || !('mediaSession' in navigator)) return + + const getAbsoluteImageUrl = (thumbnail: string | null) => { + if (!thumbnail) return undefined + if (thumbnail.startsWith('http')) return thumbnail + // Convert relative path to absolute URL + const baseUrl = window.location.origin + return `${baseUrl}/music/${thumbnail}` + } + + // Set metadata for lock screen + navigator.mediaSession.metadata = new MediaMetadata({ + title: currentMusic.title, + artist: currentMusic.artist || 'Unknown Artist', + album: currentMusic.album || '', + artwork: currentMusic.thumbnail ? [ + { src: getAbsoluteImageUrl(currentMusic.thumbnail), sizes: '512x512', type: 'image/jpeg' }, + ] : undefined, + }) + + // Set up action handlers + navigator.mediaSession.setActionHandler('play', () => { + onTogglePlay() + }) + + navigator.mediaSession.setActionHandler('pause', () => { + onTogglePlay() + }) + + navigator.mediaSession.setActionHandler('previoustrack', () => { + onPrevious() + }) + + navigator.mediaSession.setActionHandler('nexttrack', () => { + onNext() + }) + + navigator.mediaSession.setActionHandler('seekto', (details) => { + if (details.seekTime && audioRef.current) { + audioRef.current.currentTime = details.seekTime + setCurrentTime(details.seekTime) + } + }) + + // Update position state + const updatePositionState = () => { + if (audioRef.current && !isNaN(audioRef.current.duration)) { + try { + navigator.mediaSession.setPositionState({ + duration: audioRef.current.duration, + playbackRate: audioRef.current.playbackRate, + position: audioRef.current.currentTime, + }) + } catch (e) { + // Ignore errors on browsers that don't fully support position state + } + } + } + + // Update position state on time update + const audio = audioRef.current + if (audio) { + audio.addEventListener('timeupdate', updatePositionState) + audio.addEventListener('loadedmetadata', updatePositionState) + audio.addEventListener('play', updatePositionState) + audio.addEventListener('pause', updatePositionState) + + return () => { + audio.removeEventListener('timeupdate', updatePositionState) + audio.removeEventListener('loadedmetadata', updatePositionState) + audio.removeEventListener('play', updatePositionState) + audio.removeEventListener('pause', updatePositionState) + } + } + }, [currentMusic, isPlaying, onTogglePlay, onNext, onPrevious, audioRef]) + const handleSeek = (value: number[]) => { if (audioRef.current) { audioRef.current.currentTime = value[0]