2026-06-08frontend

`loadAndPlay` added to AudioPlayerProvider to fix single-click playback

  • Change: loadAndPlay(track) added to AudioPlayerContext. It sets an autoPlayRef flag before calling setTrack, and the provider's useEffect (which creates the Audio element) now calls audio.play() immediately if that flag is set. AlbumCard now calls loadAndPlay instead of loadTrack + the manual pendingPlay ref + useEffect.
  • Why: React runs child effects before parent effects. AlbumCard's effect (child) was firing togglePlay() before AudioPlayerProvider's effect (ancestor) had created the new Audio element — audioRef.current was still null at that point, so togglePlay returned 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; AlbumCard is simpler (no useEffect, no ref).
    • Con: loadAndPlay is a second entry point alongside loadTrack; callers that want auto-play must use the right one. Any future consumer of loadTrack that also wants auto-play will need to be updated to use loadAndPlay instead.

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
  }
}