Pure transition function for the media playback state machine.
Given the native media event, the current high-level state, and a small context read
off the element, returns the next MediaPlaybackState. This is the single source
of truth for the component's state — the component binds native events to this reducer
and reflects the result; it never invents transitions of its own.
Design rules baked in here:
error always wins (an errored element can still emit stray suspend/stalled).
seeking → buffering so a ±30s skip / scrub into un-buffered territory shows the
indicator during the seek; seeked clears it only when the element actually has data
(else we stay buffering until canplay).
stalled / suspend are CONSERVATIVE: they only buffer when the user is actively
trying to play (not paused, not ended) — suspend fires benignly after a normal load,
and we must not flash a spinner then.
playing always clears loading/buffering (the element resumed on its own after a seek).
Pure transition function for the media playback state machine.
Given the native media event, the current high-level state, and a small context read off the element, returns the next MediaPlaybackState. This is the single source of truth for the component's state — the component binds native events to this reducer and reflects the result; it never invents transitions of its own.
Design rules baked in here:
erroralways wins (an errored element can still emit straysuspend/stalled).seeking→bufferingso a ±30s skip / scrub into un-buffered territory shows the indicator during the seek;seekedclears it only when the element actually has data (else we staybufferinguntilcanplay).stalled/suspendare CONSERVATIVE: they only buffer when the user is actively trying to play (not paused, not ended) —suspendfires benignly after a normal load, and we must not flash a spinner then.playingalways clears loading/buffering (the element resumed on its own after a seek).