The interface and actual behaviour
A button’s appearance cannot determine whether audio is really playing. The browser can load metadata, pause playback or reach the end of a track. Events from the audio engine therefore need to update the state used by the interface. NoirWave’s source excerpt shows that connection directly.
Move events into shared state
timeupdate updates the current position, loadedmetadata sets duration, and play and pause update playback state in Zustand. The ended event calls the next-track action. The interface is consequently informed by audio events as well as the user’s most recent button click.
More than one view
PlayerBar and FullscreenPlayer use the same store. This provides a foundation for avoiding separate versions of playback truth in each view. Scrubbing uses setProgress() to update audio.currentTime. Controls affect the audio element, while native events feed information back into shared state.
Boundaries to check
Track changes, seeking before metadata is ready, pauses and track endings are important boundaries. Tests for these boundaries can follow the event order: initial state, audio event and expected state in both views. This makes it easier to locate a mismatch in the audio layer or the interface.