diff --git a/packages/studio/src/player/hooks/timelinePlayerSync.test.ts b/packages/studio/src/player/hooks/timelinePlayerSync.test.ts new file mode 100644 index 0000000000..2ce84579d0 --- /dev/null +++ b/packages/studio/src/player/hooks/timelinePlayerSync.test.ts @@ -0,0 +1,20 @@ +import { describe, expect, it } from "vitest"; +import { timelineElementsChanged } from "./timelinePlayerSync"; + +describe("timelineElementsChanged", () => { + const clip = { id: "v", tag: "video", start: 0, duration: 4, track: 1 }; + it.each([ + ["muted", { muted: true }], + ["sound", { hasAudio: true }], + ["volume", { volume: 0 }], + ["speed", { playbackRate: 2 }], + ["hidden", { hidden: true }], + ["audio group volume", { audioGroupVolume: 0 }], + ["audio group hidden", { audioGroupHidden: true }], + ["fade in", { fadeIn: 1 }], + ["fade out", { fadeOut: 1 }], + ])("sees a clip whose %s changed with no timing change", (_name, change) => { + expect(timelineElementsChanged([clip], [{ ...clip, ...change }])).toBe(true); + expect(timelineElementsChanged([{ ...clip, ...change }], [{ ...clip, ...change }])).toBe(false); + }); +}); diff --git a/packages/studio/src/player/hooks/timelinePlayerSync.ts b/packages/studio/src/player/hooks/timelinePlayerSync.ts index cf937f8882..3aff3b9239 100644 --- a/packages/studio/src/player/hooks/timelinePlayerSync.ts +++ b/packages/studio/src/player/hooks/timelinePlayerSync.ts @@ -1,5 +1,22 @@ import type { TimelineElement } from "../store/playerStore"; +const RENDERED_FIELDS: readonly (keyof TimelineElement)[] = [ + "id", + "start", + "duration", + "track", + "sourceDuration", + "muted", + "hasAudio", + "volume", + "playbackRate", + "hidden", + "audioGroupVolume", + "audioGroupHidden", + "fadeIn", + "fadeOut", +]; + /** Whether a derived timeline changes any field that affects rendering. */ export function timelineElementsChanged( previous: TimelineElement[], @@ -8,13 +25,6 @@ export function timelineElementsChanged( if (next.length !== previous.length) return true; return next.some((element, index) => { const prior = previous[index]; - return ( - !prior || - element.id !== prior.id || - element.start !== prior.start || - element.duration !== prior.duration || - element.track !== prior.track || - element.sourceDuration !== prior.sourceDuration - ); + return !prior || RENDERED_FIELDS.some((key) => element[key] !== prior[key]); }); } diff --git a/packages/studio/src/player/lib/timelineDOM.test.ts b/packages/studio/src/player/lib/timelineDOM.test.ts index 6b3e20867f..76f35ddb67 100644 --- a/packages/studio/src/player/lib/timelineDOM.test.ts +++ b/packages/studio/src/player/lib/timelineDOM.test.ts @@ -547,3 +547,74 @@ describe("a composition clip's source length", () => { expect(card?.sourceDuration).toBeUndefined(); }); }); + +describe("what the live clip list says a clip plays", () => { + function manifestVideo(attrs: string): TimelineElement { + const doc = makeDoc( + `
`, + ); + return createTimelineElementFromManifestClip({ + clip: { + id: "v", + label: "v", + kind: "video", + tagName: "video", + start: 0, + duration: 4, + track: 0, + assetUrl: null, + }, + fallbackIndex: 0, + doc, + hostEl: doc.getElementById("v"), + }); + } + + it("carries data-volume and muted from the element", () => { + expect(manifestVideo('data-volume="0" muted')).toMatchObject({ volume: 0, muted: true }); + const plain = manifestVideo(""); + expect(plain.volume).toBeUndefined(); + expect(plain.muted).toBeUndefined(); + }); + + it("carries the clip-edge fades, and drops a zero one", () => { + const faded = manifestVideo('data-fade-in="1.5" data-fade-out="0"'); + expect(faded.fadeIn).toBe(1.5); + expect(faded.fadeOut).toBeUndefined(); + }); + + it("gives a video with neither muted nor data-has-audio sound, as the compiler does", () => { + expect(manifestVideo("").hasAudio).toBe(true); + expect(manifestVideo("muted").hasAudio).toBeUndefined(); + expect(manifestVideo('data-has-audio="false"').hasAudio).toBeUndefined(); + expect(manifestVideo('muted data-has-audio="true"')).toMatchObject({ + hasAudio: true, + muted: true, + }); + }); + + it.each(["", ' data-has-audio="true"'])( + "does not give a timed wrapper the sound of an untimed video%s inside it", + (videoAttrs) => { + const doc = makeDoc( + `