mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-03 23:03:09 +00:00
fix(player): parent-frame media playback for mobile (#266)
* fix(player): parent-frame media playback for mobile Mobile browsers block media.play() inside iframes when the user gesture happened in the parent frame — postMessage doesn't transfer user activation (per the User Activation v2 spec). ## Problem The player renders compositions in a sandboxed iframe. When a user taps play in the parent frame, the player sends a postMessage to the iframe's runtime, which calls audio.play(). On mobile, this fails silently because the iframe has no user activation context. ## Solution The player now extracts ALL timed media elements (audio/video with data-start) from the iframe's DOM (same-origin access), creates parent-frame copies, and disables the iframe originals. On play(), parentMedia.play() runs synchronously in the gesture call stack, satisfying mobile autoplay policy. ### Generic media handling - Finds all `audio[data-start], video[data-start]` in the iframe - Creates a parent-frame copy for each (Audio or Video element) - Preserves data-start offsets for correct seek positioning - Strips data-start from iframe elements so the runtime ignores them - Falls back to iframe media for cross-origin iframes ### `audio-src` attribute Convenience for the common single-narration case. When set, the player starts preloading audio immediately — before the iframe loads. This eliminates the loading delay that caused jittery playback. ### No active sync Both parent media and the GSAP timeline are real-time systems. When started simultaneously, they naturally stay within ~10ms — no drift correction needed. Active sync with coarse granularity (50ms polling) caused MORE jitter than it prevented via repeated audio seeks. ## CI - Added unified `test` job replacing separate per-package test jobs - Added root `test` script: `bun run --filter '*' test` - New packages with test scripts are automatically included - Added happy-dom for player DOM tests ## Tests - 10 new tests for parent-frame media: preloading, play, pause, seek, muted/rate sync, cleanup, attribute changes - All 21 player tests pass Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(shader-transitions): pass CI when no test files exist Add --passWithNoTests to vitest run so the unified test job doesn't fail on packages that have a test script but no test files yet. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(cli): update tests for new id field and GSAP lint rule - normalize.test.ts: loadTranscript now assigns id fields (w0, w1, etc.) to SRT/VTT results and empty string for words-json passthrough - lintProject.test.ts: add GSAP CDN script to validHtml() fixture to satisfy the missing_gsap_script lint rule added in core Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(cli): add missing data-start/data-duration to validHtml fixture The validHtml() test fixture was missing data-start and data-duration attributes, triggering the root_composition_missing_data_start and root_composition_missing_data_duration lint warnings. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(ci): fetch LFS objects for producer test job Producer regression tests compare rendered output against reference MP4 files stored in git LFS. Without lfs: true, checkout fetches pointer files instead of actual videos, causing "moov atom not found" errors. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * ci: remove redundant test-producer job The regression workflow already runs the same 28 producer fixtures in a Docker container with prod-matching Chrome/fonts/ffmpeg, sharded across 8 parallel matrix jobs with 40-min timeouts. The CI test-producer job was a duplicate that ran on bare runners with worse determinism and a 15-min timeout too short for all fixtures. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
9bf4956fae
commit
b23b0751da
@@ -153,8 +153,8 @@ How are you
|
||||
const { words, format } = loadTranscript(path);
|
||||
expect(format).toBe("srt");
|
||||
expect(words).toEqual([
|
||||
{ text: "Hello world", start: 1.0, end: 3.5 },
|
||||
{ text: "How are you", start: 4.0, end: 6.0 },
|
||||
{ text: "Hello world", start: 1.0, end: 3.5, id: "w0" },
|
||||
{ text: "How are you", start: 4.0, end: 6.0, id: "w1" },
|
||||
]);
|
||||
});
|
||||
|
||||
@@ -171,8 +171,8 @@ How are you
|
||||
const { words, format } = loadTranscript(path);
|
||||
expect(format).toBe("vtt");
|
||||
expect(words).toEqual([
|
||||
{ text: "Hello world", start: 1.0, end: 3.5 },
|
||||
{ text: "How are you", start: 4.0, end: 6.0 },
|
||||
{ text: "Hello world", start: 1.0, end: 3.5, id: "w0" },
|
||||
{ text: "How are you", start: 4.0, end: 6.0, id: "w1" },
|
||||
]);
|
||||
});
|
||||
|
||||
@@ -206,7 +206,10 @@ Short format
|
||||
const path = tmpFile("normalized.json", JSON.stringify(input));
|
||||
const { words, format } = loadTranscript(path);
|
||||
expect(format).toBe("words-json");
|
||||
expect(words).toEqual(input);
|
||||
expect(words).toEqual([
|
||||
{ text: "Hello", start: 0, end: 0.5, id: "" },
|
||||
{ text: "world", start: 0.6, end: 1.2, id: "" },
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user