diff --git a/src/js/main.ts b/src/js/main.ts index 3545859..bd6ca47 100644 --- a/src/js/main.ts +++ b/src/js/main.ts @@ -3,7 +3,7 @@ * through the feature modules. No business logic lives here, only wiring. */ -import { createRadioCore } from './radioCore'; +import { createRadioCore, isLoadingLike } from './radioCore'; import { radioSelect, player, loadingNoise, errorNoise, loadingMsg, errorMsg, prevButton, playButton, pauseButton, stopButton, nextButton, logoButton, @@ -179,8 +179,11 @@ player.addEventListener('pause', () => { // During loading/retrying/recovering the main player pauses while the loading // sound takes over. Actively re-assert 'playing' so macOS doesn't flash // "Not Playing" in the gap. Only signal 'paused' in normal playback states. + // NOTE: deliberately narrower than isFeedbackAudible — 'error' is not + // re-asserted here (pre-existing drift from the other three state lists; + // whether that's intent or a bug gets decided in faza R3/R5, not silently). if ('mediaSession' in navigator) { - if (s === 'loading' || s === 'retrying' || s === 'recovering') { + if (isLoadingLike(s) || s === 'recovering') { navigator.mediaSession.playbackState = 'playing'; } else if (s === 'playing') { navigator.mediaSession.playbackState = 'paused'; diff --git a/src/js/mediaSession.ts b/src/js/mediaSession.ts index 55945a4..b8b3c8f 100644 --- a/src/js/mediaSession.ts +++ b/src/js/mediaSession.ts @@ -10,6 +10,7 @@ */ import type { RadioCore, RadioState } from './radioCore'; +import { isLoadingLike, isErrorLike, isFeedbackAudible, playbackStateFor } from './radioCore'; import { LABELS } from './labels'; import { cloudinaryImageUrl } from './cloudinary'; import { radioSelect, posterImage, loadingMsg, loadingNoise, errorNoise } from './dom'; @@ -38,10 +39,9 @@ function registerMediaSessionHandlers() { navigator.mediaSession.setActionHandler('nexttrack', () => core?.nextRadio()); navigator.mediaSession.setActionHandler('pause', () => { if (!core) return; - const s = core.getState(); - // During loading/error the sound effects are playing, not the stream. - // "Pause" should cancel everything (same as the on-screen stop button). - if (s === 'loading' || s === 'retrying' || s === 'error' || s === 'recovering') { + // While a feedback sound is what's audible (not the stream), "pause" + // should cancel everything (same as the on-screen stop button). + if (isFeedbackAudible(core.getState())) { core.stopRadio(); } else { core.pauseRadio(); @@ -90,8 +90,7 @@ errorNoise.addEventListener('timeupdate', clearPositionState); // it picks up audio from the main player. function reassertPlaybackState() { if (!('mediaSession' in navigator) || !core) return; - const s = core.getState(); - if (s === 'playing' || s === 'loading' || s === 'retrying' || s === 'error' || s === 'recovering') { + if (playbackStateFor(core.getState()) === 'playing') { navigator.mediaSession.playbackState = 'playing'; clearPositionState(); } @@ -102,8 +101,8 @@ errorNoise.addEventListener('pause', reassertPlaybackState); export const updateMediaSession = (newState: RadioState) => { const title = radioSelect.options[radioSelect.selectedIndex].text; const isIdle = newState === 'idle'; - const isLoading = newState === 'loading' || newState === 'retrying'; - const hasError = newState === 'error' || newState === 'recovering'; + const isLoading = isLoadingLike(newState); + const hasError = isErrorLike(newState); const isLive = newState === 'playing'; const idleText = hasRestoredStation ? title : LABELS.appName; @@ -124,11 +123,11 @@ export const updateMediaSession = (newState: RadioState) => { } // Keep session alive during loading/error (sounds are playing via