fix(player): replace or clear the audio-src proxy instead of stacking#1409
Conversation
setupFromUrl (the audio-src attribute path) blindly created a new proxy on every call, and the attribute had no removal handling. Changing audio-src left the previous proxy in the pool, where it kept preloading and played in parallel with the new one; removing audio-src left it playing entirely (srcdoc already handled removal, audio-src did not). Track the attribute's proxy in _urlAudioEntry/_urlAudioSrc: re-setting a different URL tears the previous one down first, re-setting the same URL is a no-op, and removing the attribute routes to a new teardownUrlAudio(). If the URL is already adopted by the composition, dedup wins and no separate proxy is tracked, so teardown targets the tracked proxy by reference and never removes the composition's own clip. A mid-playback swap under parent ownership brings the fresh proxy online so it is not silent until the next play tick. The proxy still persists across iframe reloads, since audio-src is a player-level attribute. Also extracts a shared _resolveIframeMediaSrc helper (the adopt/detach paths duplicated the URL resolution) and drops an unused getter. Adds parent-media tests: replace-not-stack, same-URL no-op, teardown clears, teardown removes only the tracked proxy, safe teardown with nothing set, and no duplication/hijack when the composition already owns the same clip.
miguel-heygen
left a comment
There was a problem hiding this comment.
Strengths
packages/player/src/parent-media.ts:220–254—setupFromUrl/teardownUrlAudioare clean and the dedup logic (don't own the tracked entry when the composition already adopted the same URL) is subtle but correct: tracking clears to null so teardown can't accidentally remove a clip the composition owns.parent-media.test.ts— six test cases that directly exercise the exact failure modes described in the PR body; the composition-owns-same-clip case (teardownUrlAudio removes only the tracked proxy) is especially good._resolveIframeMediaSrcrefactor is a clean DRY improvement with no behavior change.
Reproducing the original bug (confirmed)
Before this PR, attributeChangedCallback for audio-src:
case "audio-src":
if (val) this._media.setupFromUrl(val);
// no else → removing the attribute is a no-op
break;And old setupFromUrl was:
setupFromUrl(audioSrc: string): void {
this._createEntry(audioSrc, "audio", 0, Infinity);
}Calling with a different URL just pushed a second <audio> element into _entries. Both preloaded and played in parallel. Removing the attribute did nothing. Bug is real and well-described.
Findings
nit — packages/player/src/hyperframes-player.test.ts has no integration-level test for player.removeAttribute("audio-src"). The fix path in attributeChangedCallback (the else this._media.teardownUrlAudio() branch, line 226) is correct and the unit-level coverage in parent-media.test.ts is thorough, but an integration test would round-trip the fix through the actual attribute callback. Low risk given how thin that call chain is, but a follow-up test would close the gap.
nit — The removed get playbackErrorPosted() getter is confirmed unused externally (only referenced in parent-media.ts itself). Clean removal.
CI / state
All required checks pass. regression-shards is skipping (expected on PRs per this repo's configuration). mergeStateStatus: BLOCKED is a reviewer-gate only — no CI-gate failure.
Verdict: APPROVE
Reasoning: The bug is real, the fix is mechanically correct (dedup tracking with reference-based teardown prevents composition-owned clips from being removed), tests cover the key failure modes directly, and all CI is green.
— magi-helper
Problem
setupFromUrl(theaudio-srcattribute path inParentMediaManager) blindly created a new proxy on every call, andattributeChangedCallbackhad no removal handling foraudio-src. So changingaudio-srcfrom one URL to another left the previous proxy in the pool, where it kept preloading and played in parallel with the new track; removingaudio-srcleft it playing entirely.srcdocalready handled its own removal;audio-srcdid not.Fix
Track the attribute's proxy in
_urlAudioEntry/_urlAudioSrc:teardownUrlAudio(),The proxy still persists across iframe reloads, since
audio-srcis a player-level attribute (only set up fromconnectedCallback/attributeChangedCallback, never re-run on a composition reload).Also pulls the duplicated iframe-src resolution in the adopt/detach paths into a shared
_resolveIframeMediaSrchelper, and drops an unused getter.Tests
parent-media.test.ts: replace-not-stack, same-URL no-op (same element reference), teardown clears, teardown removes only the tracked proxy and leaves composition-adopted clips, safe teardown with nothing set, and no duplication/hijack when the composition already owns the same clip. Full player suite green (143 tests).