fix(sound): unlock WebKit audio so sound works on iOS (fixes #2530) - #3214
Open
woahwhattheheck wants to merge 1 commit into
Open
fix(sound): unlock WebKit audio so sound works on iOS (fixes #2530)#3214woahwhattheheck wants to merge 1 commit into
woahwhattheheck wants to merge 1 commit into
Conversation
Safari and every browser on iOS create an AudioContext in the suspended state unless it is constructed inside a user gesture, and only leave that state when something calls resume() from inside one. SoundSys builds its context while the page is loading and nothing ever resumed it, so effects stayed silent for the whole session there. SoundSys also read AudioContext as a bare identifier before falling back to webkitAudioContext. On Safari builds that only ship the prefixed constructor there is no such global binding, so that line threw a ReferenceError and took the whole sound system down instead of using the fallback. MusicPlayer logged the autoplay rejection and gave up, so a track blocked for lack of a user gesture never started. Resolve the constructor by property lookup, resume the context on the first gesture, and retry blocked playback on the next interaction. fixes FreezingMoon#2530
|
@woahwhattheheck is attempting to deploy a commit to the FreezingMoon Team on Vercel. A member of the Team first needs to authorize it. |
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.
Fixes #2530
I don't own an iPhone either, so this is from reading the audio path rather than from a device. Three separate things in it are broken on WebKit, and every browser on iOS is WebKit, so they all apply to Chrome on iPhone as well as Safari.
1. The AudioContext is never resumed
SoundSysconstructs itsAudioContextin the constructor, while the page is still loading. WebKit hands back a context in thesuspendedstate unless it was created inside a user gesture, and only leaves that state when something callsresume()from inside one. Nothing in the codebase ever called it, so every SFX, heartbeat and shout is silent for the whole session — the buffers decode andsource.start(0)runs, but a suspended context produces no output.2.
AudioContextis read as a bare identifierenvHasSoundcorrectly checks'AudioContext' in window || 'webkitAudioContext' in window, but the constructor then referencesAudioContextunqualified. On a Safari build that only ships the prefixed constructor there is no global binding by that name, so this throwsReferenceErrorinstead of evaluating toundefinedand falling through to the fallback — taking the whole sound system down on exactly the browsers the fallback exists for.3. A blocked autoplay was never retried
MusicPlayer.run()caught the rejection, logged it and gave up. WhensetAudioMode('full')triggersplayMusic()outside a gesture, WebKit rejects withNotAllowedErrorand the track then never starts.What this changes
src/sound/audio-context.tswith two small helpers:resolveAudioContextCtor()resolves the constructor by property lookup, andunlockAudioContextOnGesture()resumes a suspended context on the firstpointerdown/touchend/keydown, then unhooks. If the resume is still refused the listeners stay armed for the next gesture.soundsys.tsuses both, and derivesenvHasSoundfrom the same resolution so the check and the construction can't disagree.musicplayer.jsretries once on the next gesture when playback is rejected withNotAllowedError. The pending retry is cancelled bystopMusic()and when a new track starts, so nothing can start playing after the user has muted or switched.Behaviour on browsers that already worked is unchanged: a context that is already
runninggets no listeners attached, andresolveAudioContextCtor()returns the same unprefixed constructor it used before.Scope — this does not by itself restore music on iOS
Every audio asset in the repo is
.ogg(81 files; no mp3/m4a/aac). WebKit does not decode Ogg Vorbis, so even with the code fixed the music tracks and the buffered SFX still need a non-Ogg encoding to play on iOS. That's an asset/pipeline change rather than a code change and I've deliberately left it out of this PR. Please don't read this as "music works on iPhone now" — it removes the code-side blockers, and I'd be glad to do the asset side as a follow-up if you want to decide on a format and where the files should live.Tests
src/__tests__/sound/audio-context.test.tscovers constructor resolution when only the prefixed global exists (the ReferenceError case), the null case, no listeners when already running, resume on first gesture then unhook, staying armed when the resume is refused, and the returned canceller.Beyond that, whatever CI runs on this PR — I can't run the suite locally. Note the workflow will likely sit at action_required until a maintainer approves it, same as my other PR.
Not posting a wallet address here; happy to sort that out separately if this lands.