diff --git a/simulator/static/app.js b/simulator/static/app.js index 92bee4f..a0a5096 100644 --- a/simulator/static/app.js +++ b/simulator/static/app.js @@ -960,10 +960,25 @@ function fadeVolume(el, to, ms, done) { }, 30); } -// The current altitude's soundtrack url (the ring carries each scale's `audio`). +// Fallback per-scale soundtrack paths, used when the ring from /api/ring lacks the +// `audio` field (e.g. a server process started before the audio field existed). +// Mirrors simulator/build_pool_manifest.py SCALE_AUDIO — the 5 scales + files are +// fixed, committed facts, so this keeps audio working without a server restart. +const SCALE_AUDIO_FALLBACK = { + cosmos: "cosmos/pillars.loop.mp3", + orbit: "orbit/spaceamb.loop.mp3", + coast: "coast/waves.loop.mp3", + reef: "reef/soundscape.loop.mp3", + abyss: "abyss/whale.loop.mp3", +}; + +// The current altitude's soundtrack url: the ring's `audio` field, else the +// scale-id fallback above. function soundtrackUrl() { const s = ring && ring.scales[ringIndex]; - return s && s.audio ? "/media/audio/" + s.audio : null; + if (!s) return null; + const a = s.audio || SCALE_AUDIO_FALLBACK[s.id]; + return a ? "/media/audio/" + a : null; } // Load `url` into the single element and fade it in. Call this SYNCHRONOUSLY from diff --git a/tests/test_e2e_audio.py b/tests/test_e2e_audio.py index 1fd86de..2111e89 100644 --- a/tests/test_e2e_audio.py +++ b/tests/test_e2e_audio.py @@ -101,6 +101,26 @@ def test_audio_before_video_stays_audio_only(page): page.wait_for_selector("#black:not(.hidden)") # still blanked +def test_audio_then_video_leaves_both_on(page): + _toggle(page, "audio") # audio first + page.wait_for_function("!document.getElementById('aud').paused") + _toggle(page, "visual") # then video + page.wait_for_selector("#black.hidden", state="attached") # video showing + assert page.is_checked("#audio") is True + assert page.evaluate("!document.getElementById('aud').paused") is True # audio still plays + + +def test_soundtrack_fallback_when_ring_lacks_audio(page): + # simulate a stale server whose /api/ring omits the per-scale `audio` field; + # the client falls back to the scale-id soundtrack map so audio still plays + page.evaluate("ring.scales.forEach(s => { delete s.audio; })") + _toggle(page, "audio") + page.wait_for_function( + "(() => { const a = document.getElementById('aud');" + " return a.src.includes('/media/audio/cosmos/') && !a.paused; })()" + ) + + def test_video_off_blanks_after_being_on(page): _toggle(page, "visual") # on page.wait_for_selector("#black.hidden", state="attached")