mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-03 04:38:33 +00:00
Merge pull request #1069 from func25/fix/preview-reuse-and-waapi-seek-baselines
This commit is contained in:
@@ -1,7 +1,24 @@
|
||||
import { describe, it, expect, vi } from "vitest";
|
||||
import { beforeEach, afterEach, describe, it, expect, vi } from "vitest";
|
||||
import { createWaapiAdapter } from "./waapi";
|
||||
|
||||
describe("waapi adapter", () => {
|
||||
const originalDocument = (globalThis as { document?: unknown }).document;
|
||||
|
||||
beforeEach(() => {
|
||||
(globalThis as { document?: unknown }).document = {
|
||||
getAnimations: vi.fn(() => []),
|
||||
};
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
if (originalDocument === undefined) {
|
||||
delete (globalThis as { document?: unknown }).document;
|
||||
return;
|
||||
}
|
||||
|
||||
(globalThis as { document?: unknown }).document = originalDocument;
|
||||
});
|
||||
|
||||
it("has correct name", () => {
|
||||
expect(createWaapiAdapter().name).toBe("waapi");
|
||||
});
|
||||
@@ -87,4 +104,76 @@ describe("waapi adapter", () => {
|
||||
const adapter = createWaapiAdapter();
|
||||
expect(() => adapter.discover()).not.toThrow();
|
||||
});
|
||||
|
||||
it("anchors newly discovered WAAPI animations to the seek where they first appear", () => {
|
||||
const existing = { pause: vi.fn(), currentTime: 0 };
|
||||
const dynamic = { pause: vi.fn(), currentTime: 0 };
|
||||
let includeDynamic = false;
|
||||
(document as any).getAnimations = vi.fn(() =>
|
||||
includeDynamic ? [existing, dynamic] : [existing],
|
||||
);
|
||||
|
||||
const adapter = createWaapiAdapter();
|
||||
adapter.discover();
|
||||
|
||||
adapter.seek({ time: 0.6 });
|
||||
expect(existing.currentTime).toBe(600);
|
||||
|
||||
includeDynamic = true;
|
||||
adapter.seek({ time: 0.7 });
|
||||
expect(existing.currentTime).toBe(700);
|
||||
expect(dynamic.currentTime).toBe(0);
|
||||
|
||||
adapter.seek({ time: 0.8 });
|
||||
expect(dynamic.currentTime).toBe(100);
|
||||
|
||||
delete (document as any).getAnimations;
|
||||
});
|
||||
|
||||
it("rebases newly discovered WAAPI animations that inherit absolute composition time", () => {
|
||||
const existing = { pause: vi.fn(), currentTime: 0 };
|
||||
const dynamic = { pause: vi.fn(), currentTime: 700 };
|
||||
let includeDynamic = false;
|
||||
(document as any).getAnimations = vi.fn(() =>
|
||||
includeDynamic ? [existing, dynamic] : [existing],
|
||||
);
|
||||
|
||||
const adapter = createWaapiAdapter();
|
||||
adapter.discover();
|
||||
|
||||
adapter.seek({ time: 0.6 });
|
||||
expect(existing.currentTime).toBe(600);
|
||||
|
||||
includeDynamic = true;
|
||||
adapter.seek({ time: 0.7 });
|
||||
expect(dynamic.currentTime).toBe(0);
|
||||
|
||||
adapter.seek({ time: 0.8 });
|
||||
expect(dynamic.currentTime).toBe(100);
|
||||
|
||||
delete (document as any).getAnimations;
|
||||
});
|
||||
|
||||
it("does not double-count inherited absolute time when discover runs again after time has advanced", () => {
|
||||
const existing = { pause: vi.fn(), currentTime: 0 };
|
||||
const dynamic = { pause: vi.fn(), currentTime: 700 };
|
||||
let includeDynamic = false;
|
||||
(document as any).getAnimations = vi.fn(() =>
|
||||
includeDynamic ? [existing, dynamic] : [existing],
|
||||
);
|
||||
|
||||
const adapter = createWaapiAdapter();
|
||||
adapter.discover();
|
||||
|
||||
adapter.seek({ time: 0.6 });
|
||||
expect(existing.currentTime).toBe(600);
|
||||
|
||||
includeDynamic = true;
|
||||
adapter.discover();
|
||||
adapter.seek({ time: 0.7 });
|
||||
|
||||
expect(dynamic.currentTime).toBe(200);
|
||||
|
||||
delete (document as any).getAnimations;
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,15 +2,77 @@ import type { RuntimeDeterministicAdapter } from "../types";
|
||||
import { swallow } from "../diagnostics";
|
||||
|
||||
export function createWaapiAdapter(): RuntimeDeterministicAdapter {
|
||||
let didDiscover = false;
|
||||
let lastSeekTimeMs = 0;
|
||||
const baselines = new WeakMap<
|
||||
Animation,
|
||||
{
|
||||
compositionTimeMs: number;
|
||||
animationTimeMs: number;
|
||||
}
|
||||
>();
|
||||
|
||||
const snapshotAnimations = () => {
|
||||
if (!document.getAnimations) return [];
|
||||
try {
|
||||
return document.getAnimations();
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
const readAnimationTimeMs = (animation: Animation) => {
|
||||
const raw = Number(animation.currentTime);
|
||||
return Number.isFinite(raw) && raw > 0 ? raw : 0;
|
||||
};
|
||||
|
||||
const normalizeInitialAnimationTimeMs = (animationTimeMs: number, compositionTimeMs: number) => {
|
||||
if (compositionTimeMs <= 0) {
|
||||
return animationTimeMs;
|
||||
}
|
||||
|
||||
if (animationTimeMs >= compositionTimeMs) {
|
||||
return Math.max(0, animationTimeMs - compositionTimeMs);
|
||||
}
|
||||
|
||||
return animationTimeMs;
|
||||
};
|
||||
|
||||
const ensureBaseline = (animation: Animation, compositionTimeMs: number) => {
|
||||
const existing = baselines.get(animation);
|
||||
if (existing) {
|
||||
return existing;
|
||||
}
|
||||
|
||||
const baseline = {
|
||||
compositionTimeMs,
|
||||
animationTimeMs: didDiscover
|
||||
? normalizeInitialAnimationTimeMs(readAnimationTimeMs(animation), compositionTimeMs)
|
||||
: readAnimationTimeMs(animation),
|
||||
};
|
||||
baselines.set(animation, baseline);
|
||||
return baseline;
|
||||
};
|
||||
|
||||
return {
|
||||
name: "waapi",
|
||||
discover: () => {},
|
||||
discover: () => {
|
||||
didDiscover = true;
|
||||
for (const animation of snapshotAnimations()) {
|
||||
ensureBaseline(animation, lastSeekTimeMs);
|
||||
}
|
||||
},
|
||||
seek: (ctx) => {
|
||||
if (!document.getAnimations) return;
|
||||
const timeMs = Math.max(0, (Number(ctx.time) || 0) * 1000);
|
||||
for (const animation of document.getAnimations()) {
|
||||
lastSeekTimeMs = timeMs;
|
||||
for (const animation of snapshotAnimations()) {
|
||||
const baseline = didDiscover
|
||||
? ensureBaseline(animation, timeMs)
|
||||
: ensureBaseline(animation, 0);
|
||||
const localTimeMs =
|
||||
baseline.animationTimeMs + Math.max(0, timeMs - baseline.compositionTimeMs);
|
||||
try {
|
||||
animation.currentTime = timeMs;
|
||||
animation.currentTime = localTimeMs;
|
||||
} catch (err) {
|
||||
// ignore animations that reject currentTime writes
|
||||
swallow("runtime.adapters.waapi.site1", err);
|
||||
|
||||
Reference in New Issue
Block a user