mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-01 19:42:03 +00:00
fix(player): preserve iframe media attributes for runtime sync (#291)
## Summary - `_setupParentMedia()` (added in #266) was stripping `data-start`, `data-duration`, and `src` from audio/video elements inside the composition iframe - The runtime's `syncRuntimeMedia` queries `audio[data-start]` to find media clips — removing these attributes made the runtime unable to find, sync, or play audio - Result: silent audio in studio preview and any context where `__player.play()` is called directly (not through the web component) ## Fix - Keep all iframe media attributes intact so the runtime can track time position and manage playback - When parent-frame media `play()` succeeds (mobile use case), mute the iframe copies via `volume = 0` to prevent double audio - On desktop and in the studio (which calls `__player.play()` directly), the runtime's own media sync handles playback normally ## Test plan - [x] 21 player unit tests pass - [x] Verified with John Wu's slideshow project: audio element preserves `data-start`, `data-duration`, `src` after runtime init - [x] Verified runtime `syncRuntimeMedia` finds and plays audio (currentTime advances in sync with timeline) - [x] Build passes (lint, format, typecheck)
This commit is contained in:
@@ -489,7 +489,30 @@ class HyperframesPlayer extends HTMLElement {
|
||||
|
||||
private _playParentMedia() {
|
||||
for (const m of this._parentMedia) {
|
||||
if (m.el.src) m.el.play().catch(() => {});
|
||||
if (m.el.src) {
|
||||
m.el
|
||||
.play()
|
||||
.then(() => {
|
||||
// Parent play succeeded — mute the iframe copy to prevent double audio.
|
||||
// This runs asynchronously, so the runtime may briefly play both copies,
|
||||
// but the overlap is inaudible (same audio at the same position).
|
||||
this._muteIframeMedia();
|
||||
})
|
||||
.catch(() => {});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _muteIframeMedia() {
|
||||
try {
|
||||
const doc = this.iframe.contentDocument;
|
||||
if (!doc) return;
|
||||
const mediaEls = doc.querySelectorAll<HTMLMediaElement>(
|
||||
"audio[data-start], video[data-start]",
|
||||
);
|
||||
for (const el of mediaEls) el.volume = 0;
|
||||
} catch {
|
||||
// cross-origin
|
||||
}
|
||||
}
|
||||
|
||||
@@ -549,11 +572,13 @@ class HyperframesPlayer extends HTMLElement {
|
||||
|
||||
this._createParentMedia(src, tag, start, duration);
|
||||
|
||||
// Disable the iframe element so the runtime ignores it
|
||||
iframeEl.removeAttribute("src");
|
||||
iframeEl.removeAttribute("data-start");
|
||||
iframeEl.removeAttribute("data-duration");
|
||||
iframeEl.querySelectorAll("source").forEach((s) => s.remove());
|
||||
// DO NOT strip data-start, data-duration, or src from the iframe elements.
|
||||
// The runtime's syncRuntimeMedia queries audio[data-start] — removing these
|
||||
// attributes makes the runtime unable to find, sync, or play media clips.
|
||||
// The iframe copies remain fully functional for the runtime. On mobile,
|
||||
// parent copies provide the audible output via the component's play() method.
|
||||
// On desktop and in the studio (which calls __player.play() directly),
|
||||
// the runtime's own media sync handles playback.
|
||||
}
|
||||
} catch {
|
||||
// Cross-origin iframe — can't access DOM, fall back to iframe media
|
||||
|
||||
Reference in New Issue
Block a user