import { Slider } from '@olly/modern-sk'; import { useTranslation } from 'react-i18next'; import { Icon } from '../common/Icon'; import { ArtTile } from '../common/ArtTile'; import { useAppDispatch, useAppSelector } from '../../hooks/useAppDispatch'; import { pause, resume, toggleMute, setVolume, toggleShuffle, setRepeat, toggleQueue, } from '../../store/slices/player'; import { openTrackInfo } from '../../store/slices/ui'; import { useAudioPlayer } from '../../hooks/useAudioPlayer'; import { useStreamCached } from '../../hooks/useStreamCached'; import { useResolvedQueueEntry } from '../../hooks/useResolvedQueueEntry'; import { formatDuration } from '../../lib/format'; import { getCoverUrl, getTrackCoverUrl } from '../../api/endpoints/streaming'; export function PersistentPlayer() { const { t } = useTranslation(); const dispatch = useAppDispatch(); const { seek, playNext, playPrev } = useAudioPlayer(); const player = useAppSelector((s) => s.player); const queue = useAppSelector((s) => s.queue); const token = useAppSelector((s) => s.auth.accessToken); const currentEntry = queue.entries[queue.currentIndex]; // Read through to the live Track cache so enrichment updates reach the player, // not just the play-time snapshot frozen in the queue slice. const current = useResolvedQueueEntry(currentEntry); // Source indicator: cached → playing locally, otherwise streaming. const cached = useStreamCached(currentEntry?.trackId); if (!currentEntry && !player.currentTrackId) { return
{t('player.nothingPlaying')}
; } const artUrl = getCoverUrl(currentEntry?.albumArtUrl) ?? (token && current?.hasCover ? getTrackCoverUrl(current.trackId, token, true) : undefined); const seedLabel = current?.albumTitle ?? current?.title ?? ''; const onStream = !cached; return (
currentEntry && dispatch(openTrackInfo(currentEntry.trackId)) } style={{ cursor: currentEntry ? 'pointer' : 'default' }} title={currentEntry ? t('trackInfo.open') : undefined} >
{current?.title ?? '—'}
{current?.artistName ?? ''}
{onStream ? t('player.streaming') : t('player.local')}
{formatDuration(player.position * 1000)} seek(v)} aria-label={t('player.play')} /> {formatDuration(player.duration * 1000)}
dispatch(setVolume(v))} aria-label="Volume" />
); }