Repository navigation
fix(playback): radio after a cold iOS launch plays instead of dropping a healthy song (#311) - #327
Open
lolimmlost wants to merge 4 commits into
Open
lolimmlost wants to merge 4 commits into
lolimmlost wants to merge 4 commits into
Conversation
…g a healthy song (#311) - Unlock audio inside the tap: startRadio calls unlockAudioForGesture() before its ~4s fetch; PlayerBar registers the unlock (init/resume the AudioContext, play+pause each idle deck) so the later play() is allowed. - A play() refused by autoplay policy (NotAllowedError) is not a stall: the song loader and stall recovery pause and show "Tap play to start" instead of burning recovery attempts that can't succeed. - Recovery attempt 3 reloads with load() instead of src = '' first. The cleared src fired an error event that the song loader read as "unavailable" and removed the song from the queue. The loader also ignores errors from a deck with no src. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LAQj7kfXYU9jy1L9ByWw8r
…istant is down The only way to open the device picker on a locally playing device was the speaker button, shown only when /api/speakers reported MA configured. With MA unreachable the button disappeared, and with it any way to hand playback to another device (e.g. the iPhone). Always show it; the picker already hides its speaker section when MA isn't available. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LAQj7kfXYU9jy1L9ByWw8r
…back starts (#311) The 4s info toast vanished before it could be acted on. Keep it until something plays, and make its Play button the tap itself (unlock the decks, then play locally), so it works even when the UI is stuck in remote-control mode (#256). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LAQj7kfXYU9jy1L9ByWw8r
A store-only pause (remote_command from another device, takeover) changes PlayerBar's remote-aware callbacks, re-running useMediaSession's setup effect while the element is still playing. Its initial-setup path then synced the store back to isPlaying=true. Skip that sync within 3s of a user pause, the same window the other recovery paths already honour. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LAQj7kfXYU9jy1L9ByWw8r
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #311. Closes #256.
What happened
On a fresh iOS PWA launch, Liked Songs → Radio never played. Recovery then removed a healthy song (the Kreutzer sonata) from the queue as "unavailable", and the next one was silent too.
Testing the "transfer to a cold phone" path turned up three more problems, fixed here too.
Fixes
startRadiocallsunlockAudioForGesture()synchronously, before its ~4s/api/radio/seededfetch. PlayerBar registers the unlock (src/lib/utils/audio-unlock.ts): it initialises and resumes the AudioContext, then play()+pause() each idle deck in the same tick (or a silent clip for an empty deck). The later play() is then allowed.NotAllowedErroris not a stall (iOS: radio started after a cold launch is blocked by autoplay policy, and stall recovery then deletes a healthy song as "unavailable" #311). When the song loader'scanplayplay(), or a stall-recovery attempt, is refused by autoplay policy, playback pauses and a prompt appears. Recovery doesn't spend its 3 attempts on a play() that can't succeed without a tap.load()instead ofsrc = ''first. The cleared src fired anerrorevent that the loader's handler read as "unavailable". The loader also ignores errors from a deck with no src./api/speakersfailed (MA down), so playback couldn't be handed to another device. It always shows now; the picker already hides its speaker section without MA.useMediaSession's setup effect while the element is still playing, and its initial-setup path synced the store back toisPlaying=true. It now skips that sync within 3s of a user pause, the same window the other recovery paths use.Tests
useStallRecovery.test.ts(4) andaudio-unlock.test.ts(5). 3 of the recovery tests fail on the old code.Device test (iOS PWA) — passed 2026-10-11 on
1f9441bisPlaying=falsewith no flip back), and transferring back resumes at the paused position.🤖 Generated with Claude Code
https://claude.ai/code/session_01LAQj7kfXYU9jy1L9ByWw8r