Merge fix/boot-silent-no-autostart: boot silent, no un-gestured auto-start
This commit is contained in:
@@ -83,13 +83,14 @@ test("audio is a 0-10 level dial", async ({ page }) => {
|
||||
expect(ctl.max).toBe("10");
|
||||
});
|
||||
|
||||
test("the experience auto-starts once loaded: video on + audio at 3/10", async ({ page }) => {
|
||||
test("boots silent (video off, audio 0); turning video on couples audio to 3/10", async ({ page }) => {
|
||||
await boot(page);
|
||||
// No gesture needed — autoStart() flips Video on and lifts audio to a gentle level
|
||||
// the moment the preload finishes.
|
||||
await expect.poll(() => page.evaluate(() => (document.querySelector("#visual") as HTMLInputElement).checked)).toBe(true);
|
||||
// Boots SILENT — no un-gestured auto-start, so the browser autoplay block never bites.
|
||||
expect(await page.evaluate(() => (document.querySelector("#visual") as HTMLInputElement).checked)).toBe(false);
|
||||
expect(await page.evaluate(() => (document.querySelector("#audio") as HTMLInputElement).value)).toBe("0");
|
||||
// Turning Video on (a click gesture) lifts the audio dial to a gentle 3/10.
|
||||
await enableVideo(page);
|
||||
await expect.poll(() => page.evaluate(() => (document.querySelector("#audio") as HTMLInputElement).value)).toBe("3");
|
||||
// The level label reflects it.
|
||||
expect(await page.evaluate(() => document.querySelector("#audio-level-val")?.textContent)).toBe("3");
|
||||
});
|
||||
|
||||
|
||||
+6
-14
@@ -1260,8 +1260,12 @@ async function main() {
|
||||
$("stage").addEventListener("wheel", onWheel, { passive: false });
|
||||
update(); // render the initial state (both toggles off → black, silent)
|
||||
await preloadAllMedia(); // download all media, updating the loading bar
|
||||
hideLoading(); // experience is ready to run smoothly
|
||||
autoStart(); // once loaded: video on + gentle audio (3/10), no gesture needed
|
||||
hideLoading(); // experience is ready — boots SILENT (video off, audio 0)
|
||||
// No un-gestured auto-start: a browser autoplay policy blocks an audio play() made
|
||||
// outside a user gesture (muted video survives, sound does not), so an auto-start
|
||||
// would show video but stay silent. Instead the experience waits for the operator to
|
||||
// turn Video on — that click IS the gesture, and its handler (above) lifts audio to
|
||||
// 3/10 and starts the soundtrack in-gesture, so sound reliably comes up with video.
|
||||
}
|
||||
|
||||
// Populate the language dropdown from the registry and wire live switching.
|
||||
@@ -1297,16 +1301,4 @@ function setLanguage(lang) {
|
||||
if (lastAffect) renderAffect(lastAffect.strength, lastAffect.intensity, lastAffect.right);
|
||||
}
|
||||
|
||||
// Auto-start the experience the moment media is ready: bring Video on and the audio
|
||||
// level to 3/10 (the same gentle default the first manual Video-on couples in). The
|
||||
// audio <audio> elements ideally unlock inside a user gesture (Safari); this fires
|
||||
// outside one, so a browser with a strict autoplay policy may BLOCK the first audio
|
||||
// play() until any interaction — the on-screen audio status readout will show it.
|
||||
function autoStart() {
|
||||
$("visual").checked = true;
|
||||
videoEverOn = true;
|
||||
if (audioLevel() === 0) { $("audio").value = "3"; updateAudioLevelLabel(); }
|
||||
applyAudio();
|
||||
update();
|
||||
}
|
||||
main();
|
||||
|
||||
+31
-12
@@ -2,12 +2,15 @@
|
||||
cleanly when Playwright or its browser binary is absent.
|
||||
|
||||
NOTE: headless engines relax BOTH autoplay and GPU compositing, so these assert
|
||||
the WIRING (auto-start on load, dial→play/volume, video-off blanking) — real
|
||||
Safari/iOS autoplay and real-GPU compositing still need a device by-ear/eye check.
|
||||
the WIRING (boot-silent, video-on couples audio, dial→play/volume, video-off
|
||||
blanking) — real Safari/iOS autoplay and real-GPU compositing still need a device
|
||||
by-ear/eye check.
|
||||
|
||||
The experience AUTO-STARTS once the preload finishes: Video turns on and the audio
|
||||
level dial rises to 3/10 with no user gesture (app.js autoStart()). Audio is a 0–10
|
||||
range dial (not a checkbox); set it by writing `value` + firing an `input` event.
|
||||
The experience BOOTS SILENT — video off, audio dial at 0 — so nothing plays until a
|
||||
real user gesture. Turning Video ON (a click) couples the audio dial up to 3/10 and
|
||||
starts the soundtrack IN that gesture, sidestepping the browser autoplay block that
|
||||
swallowed an un-gestured auto-start. Audio is a 0–10 range dial (not a checkbox);
|
||||
set it by writing `value` + firing an `input` event.
|
||||
|
||||
Install: pip install -e '.[e2e]' && python -m playwright install chromium
|
||||
"""
|
||||
@@ -63,7 +66,7 @@ def page(app_url):
|
||||
pg = browser.new_page()
|
||||
pg.goto(app_url)
|
||||
# wait out the "Loading Universe…" splash (it overlays + blocks clicks); the
|
||||
# auto-start fires as it dismisses.
|
||||
# experience boots silent (video off, audio 0) as it dismisses.
|
||||
pg.wait_for_selector("#loading", state="detached", timeout=60000)
|
||||
yield pg
|
||||
browser.close()
|
||||
@@ -84,10 +87,23 @@ def _set_audio(page, level):
|
||||
)
|
||||
|
||||
|
||||
def test_app_auto_starts_video_and_audio(page):
|
||||
# Once the preload finishes the experience auto-starts: Video on + audio at 3/10,
|
||||
# video showing (black hidden), a scale soundtrack playing — no tap-to-start wall.
|
||||
assert page.is_checked("#visual") is True
|
||||
def test_app_boots_silent_video_off(page):
|
||||
# The experience boots SILENT: Video off, audio dial at 0, screen black, both
|
||||
# crossfade elements paused — nothing plays until a real user gesture.
|
||||
assert page.is_checked("#visual") is False
|
||||
assert page.evaluate("document.getElementById('audio').value") == "0"
|
||||
assert page.evaluate("document.getElementById('audio-level-val').textContent") == "0"
|
||||
page.wait_for_selector("#black:not(.hidden)") # screen blanked
|
||||
assert page.evaluate(
|
||||
"(() => { const a = document.getElementById('aud'), b = document.getElementById('aud-b');"
|
||||
" return a.paused && b.paused; })()"
|
||||
) is True
|
||||
|
||||
|
||||
def test_video_on_couples_audio_to_three_and_plays(page):
|
||||
# Turning Video on (a click gesture) lifts the audio dial to 3/10 and starts the
|
||||
# soundtrack IN that gesture: video shows (black hidden), a scale soundtrack plays.
|
||||
_toggle_visual(page) # off -> on
|
||||
page.wait_for_function("document.getElementById('audio').value === '3'")
|
||||
page.wait_for_function("document.getElementById('audio-level-val').textContent === '3'")
|
||||
page.wait_for_selector("#black.hidden", state="attached") # video showing
|
||||
@@ -113,8 +129,11 @@ def test_audio_dial_zero_silences(page):
|
||||
assert page.evaluate("document.getElementById('audio').value") == "0"
|
||||
|
||||
|
||||
def test_video_off_blanks_after_autostart(page):
|
||||
# Video is auto-on; turning it off blanks the screen and hides the GPU layers.
|
||||
def test_video_off_blanks(page):
|
||||
# Turn Video on (boot is off), then off again: that blanks the screen and hides
|
||||
# the GPU layers.
|
||||
_toggle_visual(page) # off -> on
|
||||
page.wait_for_selector("#black.hidden", state="attached")
|
||||
_toggle_visual(page) # on -> off
|
||||
page.wait_for_selector("#black:not(.hidden)")
|
||||
page.wait_for_function(
|
||||
|
||||
Reference in New Issue
Block a user