fix(cli): align video output boundaries (#2490)

* fix(cli): align video output boundaries

* test(cli): honor CI ffmpeg fixture path

* test(cli): decouple duration precedence from ffmpeg

* test(cli): pin half-open video boundaries

* test(producer): refresh style 7 boundary golden
This commit is contained in:
Miguel Ángel
2026-07-15 16:07:44 -04:00
committed by GitHub
parent f45f762473
commit 35e623b4f3
7 changed files with 107 additions and 9 deletions
+27
View File
@@ -325,6 +325,33 @@ describe("initSandboxRuntimeModular", () => {
expect(child.style.visibility).toBe("hidden");
});
it("uses a half-open interval around a timed element's end boundary", () => {
const root = document.createElement("div");
root.setAttribute("data-composition-id", "main");
root.setAttribute("data-root", "true");
root.setAttribute("data-start", "0");
root.setAttribute("data-width", "1920");
root.setAttribute("data-height", "1080");
document.body.appendChild(root);
const clip = document.createElement("div");
clip.setAttribute("data-start", "0");
clip.setAttribute("data-duration", "2.5");
root.appendChild(clip);
window.__timelines = { main: createMockTimeline(5) };
initSandboxRuntimeModular();
window.__player?.renderSeek(2.5 - 1e-9);
expect(clip.style.visibility).toBe("visible");
window.__player?.renderSeek(2.5);
expect(clip.style.visibility).toBe("hidden");
window.__player?.renderSeek(2.5 + 1e-9);
expect(clip.style.visibility).toBe("hidden");
});
it("keeps external composition hosts visible through their authored duration", async () => {
const root = document.createElement("div");
root.setAttribute("data-composition-id", "main");
+2 -2
View File
@@ -583,7 +583,7 @@ export function initSandboxRuntimeModular(): void {
const computedEnd =
duration != null && duration > 0 ? start + duration : Number.POSITIVE_INFINITY;
return (
currentTime >= start && (Number.isFinite(computedEnd) ? currentTime <= computedEnd : true)
currentTime >= start && (Number.isFinite(computedEnd) ? currentTime < computedEnd : true)
);
};
@@ -2827,7 +2827,7 @@ export function initSandboxRuntimeModular(): void {
const mediaStart =
Number.parseFloat(rawEl.dataset.playbackStart ?? rawEl.dataset.mediaStart ?? "0") ||
0;
if (Number.isFinite(start) && state.currentTime >= start && state.currentTime <= end) {
if (Number.isFinite(start) && state.currentTime >= start && state.currentTime < end) {
if (!rawEl.paused) {
clock.attachAudioSource({ el: rawEl, compositionStart: start, mediaStart });
foundActive = true;
+22
View File
@@ -250,6 +250,28 @@ describe("syncRuntimeMedia", () => {
expect(clip.el.play).toHaveBeenCalled();
});
it("uses a half-open interval around a clip's end boundary", () => {
const clip = createMockClip({ start: 0, end: 2.5 });
Object.defineProperty(clip.el, "readyState", { value: 4, writable: true });
syncRuntimeMedia({
clips: [clip],
timeSeconds: 2.5 - 1e-9,
playing: true,
playbackRate: 1,
});
expect(clip.el.play).toHaveBeenCalledTimes(1);
syncRuntimeMedia({ clips: [clip], timeSeconds: 2.5, playing: true, playbackRate: 1 });
syncRuntimeMedia({
clips: [clip],
timeSeconds: 2.5 + 1e-9,
playing: true,
playbackRate: 1,
});
expect(clip.el.play).toHaveBeenCalledTimes(1);
});
it("plays synchronously even when media is unbuffered (preserves user gesture)", () => {
// Calling play() synchronously inside the user-gesture call chain lets the
// browser queue playback until data buffers, while consuming the transient
+1 -1
View File
@@ -173,7 +173,7 @@ export function syncRuntimeMedia(params: {
// (el.ended resets to false when the user scrubs back, so seeks work.)
const isActive =
params.timeSeconds >= clip.start &&
params.timeSeconds <= clip.end &&
params.timeSeconds < clip.end &&
relTime >= 0 &&
(!el.ended || clip.loop);
if (isActive) {