mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-03 04:38:33 +00:00
fix(core): route the media-element transport through the group bus
main added `scheduleMediaElementPlayback` -- a pitch-preserving HTMLMediaElement transport -- while this branch was open, and the runtime tries it FIRST for audio, falling back to the decoded-buffer path only when it returns null. The rebase therefore left every grouped track bypassing the very bus this branch exists to add: the new path connected its gain straight to master, while only the fallback went through `resolveDestination`. It now uses `resolveDestination` too, which is a no-op for an ungrouped element (it returns master) and the group's input gain for a member. Three `init.test.ts` cases spied on `decodeAudioElement` to assert WHICH audio elements get scheduled. That path is now the fallback, so the spies read zero through no fault of the behaviour under test — they move to `scheduleMediaElementPlayback`, with a comment saying why, and keep their original claims: a `data-hidden` clip is excluded under the `audio-track-mute` canary, still scheduled without it, and a two-clip un-hide is one reschedule. core's runtime suites pass (init 78, webAudioTransport 61).
This commit is contained in:
@@ -1323,16 +1323,20 @@ describe("initSandboxRuntimeModular", () => {
|
|||||||
// playing in preview for anyone not enrolled.
|
// playing in preview for anyone not enrolled.
|
||||||
window.__hf?.setCanaries?.({ "audio-track-mute": true });
|
window.__hf?.setCanaries?.({ "audio-track-mute": true });
|
||||||
|
|
||||||
const decodeSpy = vi
|
// `scheduleMediaElementPlayback`, not `decodeAudioElement`: the media-element
|
||||||
.spyOn(WebAudioTransport.prototype, "decodeAudioElement")
|
// transport is the path the runtime tries FIRST for audio, and the decoded
|
||||||
|
// buffer is only its fallback. What is under test either way is which
|
||||||
|
// ELEMENTS get scheduled at all.
|
||||||
|
const scheduleSpy = vi
|
||||||
|
.spyOn(WebAudioTransport.prototype, "scheduleMediaElementPlayback")
|
||||||
.mockResolvedValue(null);
|
.mockResolvedValue(null);
|
||||||
|
|
||||||
const player = window.__player;
|
const player = window.__player;
|
||||||
player?.play();
|
player?.play();
|
||||||
player?.seek(0);
|
player?.seek(0);
|
||||||
|
|
||||||
expect(decodeSpy).toHaveBeenCalledTimes(1);
|
expect(scheduleSpy).toHaveBeenCalledTimes(1);
|
||||||
expect(decodeSpy.mock.calls[0]?.[0]).toBe(audibleAudio);
|
expect(scheduleSpy.mock.calls[0]?.[0]).toBe(audibleAudio);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("still schedules a data-hidden audio clip when the host has not opted in", () => {
|
it("still schedules a data-hidden audio clip when the host has not opted in", () => {
|
||||||
@@ -1356,16 +1360,16 @@ describe("initSandboxRuntimeModular", () => {
|
|||||||
window.__timelines = { main: createMockTimeline(10) };
|
window.__timelines = { main: createMockTimeline(10) };
|
||||||
initSandboxRuntimeModular();
|
initSandboxRuntimeModular();
|
||||||
|
|
||||||
const decodeSpy = vi
|
const scheduleSpy = vi
|
||||||
.spyOn(WebAudioTransport.prototype, "decodeAudioElement")
|
.spyOn(WebAudioTransport.prototype, "scheduleMediaElementPlayback")
|
||||||
.mockResolvedValue(null);
|
.mockResolvedValue(null);
|
||||||
|
|
||||||
const player = window.__player;
|
const player = window.__player;
|
||||||
player?.play();
|
player?.play();
|
||||||
player?.seek(0);
|
player?.seek(0);
|
||||||
|
|
||||||
expect(decodeSpy).toHaveBeenCalledTimes(1);
|
expect(scheduleSpy).toHaveBeenCalledTimes(1);
|
||||||
expect(decodeSpy.mock.calls[0]?.[0]).toBe(hiddenAudio);
|
expect(scheduleSpy.mock.calls[0]?.[0]).toBe(hiddenAudio);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("batches a mid-playback data-hidden toggle into exactly one Web Audio reschedule", () => {
|
it("batches a mid-playback data-hidden toggle into exactly one Web Audio reschedule", () => {
|
||||||
@@ -1406,8 +1410,8 @@ describe("initSandboxRuntimeModular", () => {
|
|||||||
// toggles away from.
|
// toggles away from.
|
||||||
player?.play();
|
player?.play();
|
||||||
|
|
||||||
const decodeSpy = vi
|
const scheduleSpy = vi
|
||||||
.spyOn(WebAudioTransport.prototype, "decodeAudioElement")
|
.spyOn(WebAudioTransport.prototype, "scheduleMediaElementPlayback")
|
||||||
.mockResolvedValue(null);
|
.mockResolvedValue(null);
|
||||||
const generationSpy = vi.spyOn(WebAudioTransport.prototype, "startGeneration");
|
const generationSpy = vi.spyOn(WebAudioTransport.prototype, "startGeneration");
|
||||||
|
|
||||||
@@ -1419,7 +1423,7 @@ describe("initSandboxRuntimeModular", () => {
|
|||||||
player?.seek(1, { keepPlaying: true });
|
player?.seek(1, { keepPlaying: true });
|
||||||
|
|
||||||
expect(generationSpy).toHaveBeenCalledTimes(1);
|
expect(generationSpy).toHaveBeenCalledTimes(1);
|
||||||
expect(decodeSpy).toHaveBeenCalledTimes(2);
|
expect(scheduleSpy).toHaveBeenCalledTimes(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Scheduling does NOT replace the active set: it bumps a generation, which
|
// Scheduling does NOT replace the active set: it bumps a generation, which
|
||||||
|
|||||||
@@ -272,7 +272,14 @@ export class WebAudioTransport {
|
|||||||
const elapsed = compositionTime - compositionStart;
|
const elapsed = compositionTime - compositionStart;
|
||||||
const timing: AutomationTiming = { scheduledAt, elapsed, rate: safeRate };
|
const timing: AutomationTiming = { scheduledAt, elapsed, rate: safeRate };
|
||||||
const fx = attachElementFxChain(this._ctx, el, sourceNode, gainNode, timing);
|
const fx = attachElementFxChain(this._ctx, el, sourceNode, gainNode, timing);
|
||||||
gainNode.connect(this._masterGain);
|
// The group bus, not master, for a member — same as the decoded-buffer
|
||||||
|
// path. This transport is the PRIMARY one for audio (the decode path is
|
||||||
|
// its fallback), so routing it at master would have left every grouped
|
||||||
|
// track bypassing the bus whose whole premise is that a group is one
|
||||||
|
// signal.
|
||||||
|
gainNode.connect(
|
||||||
|
this.resolveDestination(el, scheduledAt, compositionTime, safeRate) ?? this._masterGain,
|
||||||
|
);
|
||||||
scheduleVolumeLane(el, gainNode, timing);
|
scheduleVolumeLane(el, gainNode, timing);
|
||||||
|
|
||||||
this._rate = safeRate;
|
this._rate = safeRate;
|
||||||
|
|||||||
Reference in New Issue
Block a user