Spaces:
Running on Zero
Running on Zero
Commit History
-
fix(01-11): activation-aware resume grace; iframe stage context created at mount
Found while proving the unlock under a strict autoplay policy: - playBuffer's bounded resume() used one fixed grace. Under load the audio sink can take seconds to start after a legitimate in-gesture unlock (0.7-5 s measured on the headless fleet), and a fixed 2 s produced a false "audio is blocked" error. Nothing separates "blocked" from "slow" except whether the document has ever been activated, so the grace now follows navigator.userActivation.hasBeenActive: 2 s when it never was (nothing can unblock it - say so quickly), 8 s once it has (the resume is legitimate and only waiting for the sink). Unknown (older browsers) is treated as activated. - stage.html creates its AudioContext at 'avatar:mount', as the inline transport does at boot, so audioState reads 'suspended' rather than 'none' before the first gesture under the iframe transport and the strict-policy precondition holds under both transports.90f70d6 -
fix(01-11): resume the AudioContext inside the gesture, refuse to speak on a stopped clock
The owner heard nothing on a phone. The only resume() on the turn path was in playBuffer, after the ~7 s server round trip and therefore outside the tap's user activation; a browser that gates audio behind a gesture (iOS Safari, Chromium in a cross-origin embed) leaves the context suspended, and Chromium keeps a blocked resume() promise pending forever, so the page sat on "speaking..." in silence. - stagePort gains a seventh method, unlockAudio(): synchronous, issues resume() in the caller's own call stack (inline), or relays it to the frame (iframe; stage.html handles 'avatar:unlockAudio') - turn-loop.js calls it as the FIRST statement of dispatchTurn, replay, requestSlower and startListening, before any await; host.js calls avatar.unlockAudio() first thing in all six gesture handlers, and the facade exposes it so a host can pre-unlock on any tap - playBuffer bounds its own resume() (RESUME_GRACE_MS) and, if the context is still not running, emits 'error' where: 'audio' a…6b68c4d
-
feat(01-06): drive the standalone harness from the generated timeline
- demo-konnichiwa.wav is now the real zundamon synthesis of konnichiwa - DEMO_TIMELINE replaced by the generated golden timeline, placeholder retired - VOICEVOX credit rendered on the harness page, as the character terms require - the browser test now asserts the timeline ends within one frame of the AudioBuffer5cfcfd5 -
test(01-03): enforce the transport seam statically and in a real browser
- tests/test_transport_seam.py: 36 static cases; no stage-core or shared module may name the host framework, no transport may assign window.Avatar or implement turn behaviour, and AVATAR_SURFACE is parsed from facade.js rather than duplicated - host-token checks are case-insensitive, so a capitalised mention cannot slip past - tests/e2e/test_stage_standalone.py: 4 tests against avatar/stage.html on a static server - one three.mjs instance, idle life, demo lip-sync, no duplicate-instance console warning. Sampling runs in-page at frame rate: a 250 ms poll would miss a 120 ms blink on most runs and be flaky by construction - tests/e2e/test_facade_parity.py: boots the real app twice, once per transport, and compares the live method sets, debug keys and deferred-method errors - stage.html: mirror the event stream onto window.__stageEvents so the standalone harness can assert on speech-end with no driver on the other side - avatar_component.py: js_on_load is compiled into a NON…d6ea280 -
feat(01-03): add both transports, the standalone stage page and the Gradio component
- avatar/avatar.js: inline transport, 145 lines of stagePort plumbing only - avatar/avatar-iframe.js: postMessage transport, same facade and same turn loop - both use a deferred event bus so events emitted during mountStage (notably the no-expressionManager error) survive until installFacade builds the real one - avatar/stage.html: iframe target and standalone debug harness; ?demo=1 plays a canned WAV against a hand-authored placeholder timeline - avatar/assets/demo-konnichiwa.wav: 24000 Hz mono 16-bit, 1.129 s - avatar_component.py + app.py: AVATAR_TRANSPORT selects the module in one place - vrm-stage.js: reword the import-map comment so the stage core names no host - ignore and un-lint Gradio's auto-generated .pyi component stubf822421