2026-06-08frontend
`loadAndPlay` added to AudioPlayerProvider to fix single-click playback
- Change:
loadAndPlay(track)added toAudioPlayerContext. It sets anautoPlayRefflag before callingsetTrack, and the provider'suseEffect(which creates theAudioelement) now callsaudio.play()immediately if that flag is set.AlbumCardnow callsloadAndPlayinstead ofloadTrack+ the manualpendingPlayref +useEffect. - Why: React runs child effects before parent effects.
AlbumCard's effect (child) was firingtogglePlay()beforeAudioPlayerProvider's effect (ancestor) had created the newAudioelement —audioRef.currentwas stillnullat that point, sotogglePlayreturned early and the track never started. The user had to click a second time, by which point the element existed. Moving the auto-play trigger inside the provider's own effect eliminates the race. - Affected Modules:
app/_component/audio-player-context.tsx,app/catalogue/album-card.tsx - Trade-offs:
- Pro: Playback starts on the first click; the timing issue is structurally impossible because the flag is consumed in the same effect that creates the element;
AlbumCardis simpler (nouseEffect, no ref). - Con:
loadAndPlayis a second entry point alongsideloadTrack; callers that want auto-play must use the right one. Any future consumer ofloadTrackthat also wants auto-play will need to be updated to useloadAndPlayinstead.
- Pro: Playback starts on the first click; the timing issue is structurally impossible because the flag is consumed in the same effect that creates the element;
Provider — audio-player-context.tsx
const autoPlayRef = useRef(false)
// Inside the useEffect that creates the Audio element:
const audio = new Audio(track.musicUrl)
audioRef.current = audio
if (autoPlayRef.current) {
autoPlayRef.current = false
audio.play() // element is guaranteed to exist here
}
const loadAndPlay = useCallback((next: AudioTrack) => {
autoPlayRef.current = true // set before state update, consumed in next effect run
setTrack(current => current?.postId === next.postId ? current : next)
}, [])
Consumer — album-card.tsx
const { track, isPlaying, loadAndPlay, togglePlay } = useAudioPlayerContext()
function handlePlayClick(e: React.MouseEvent) {
e.preventDefault()
e.stopPropagation()
if (!post.music_url) return
if (isCurrentTrack) {
togglePlay()
} else {
loadAndPlay({ postId: post.id, musicUrl: post.music_url, ... })
// no pendingPlay ref, no useEffect — provider handles playback start
}
}