|
2 | 2 | import { afterEach, describe, expect, it, vi } from "vitest"; |
3 | 3 | import { mediaElementTime } from "./mediaClock"; |
4 | 4 | import { HF_COLOR_GRADING_ATTR, serializeHfColorGrading } from "../colorGrading"; |
5 | | -import { STUDIO_PREVIEW_LAZY_ATTR, STUDIO_PREVIEW_MARK_META } from "../studioPreviewMark"; |
| 5 | +import { |
| 6 | + PLAYER_FRAME_ATTR, |
| 7 | + STUDIO_PREVIEW_LAZY_ATTR, |
| 8 | + STUDIO_PREVIEW_MARK_META, |
| 9 | +} from "../studioPreviewMark"; |
6 | 10 | import { revealClip } from "./clipReveal"; |
7 | 11 | import type { RuntimeTimelineLike } from "./types"; |
8 | 12 |
|
@@ -91,6 +95,12 @@ const servePreview = () => |
91 | 95 | document.head.appendChild( |
92 | 96 | Object.assign(document.createElement("meta"), { name: STUDIO_PREVIEW_MARK_META }), |
93 | 97 | ); |
| 98 | +const asSafari = () => |
| 99 | + vi |
| 100 | + .spyOn(navigator, "userAgent", "get") |
| 101 | + .mockReturnValue( |
| 102 | + "Mozilla/5.0 AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4 Safari/605.1.15", |
| 103 | + ); |
94 | 104 | const neverDecodes = (clip: HTMLElement) => { |
95 | 105 | withImage(clip).querySelector("img")!.decode = () => new Promise<void>(() => {}); |
96 | 106 | return clip; |
@@ -429,6 +439,64 @@ describe("runtime entry", () => { |
429 | 439 | expect(now.getAttribute("src")).toBe("now.mp4"); |
430 | 440 | }); |
431 | 441 |
|
| 442 | + // Three takes of one file, each a picture and a sound, as an edit cut from a single source is. |
| 443 | + const takesOfOneFile = () => { |
| 444 | + const root = mountRoot(); |
| 445 | + const take = (start: string) => { |
| 446 | + const pair = [timed(root, "video", start), timed(root, "audio", start)]; |
| 447 | + for (const el of pair) el.setAttribute("src", "/source.mp4"); |
| 448 | + return pair; |
| 449 | + }; |
| 450 | + return [take("0"), take("3"), take("9")]; |
| 451 | + }; |
| 452 | + const inPlayerFrame = (userAgent: "safari" | "chrome") => { |
| 453 | + const frame = document.createElement("iframe"); |
| 454 | + frame.setAttribute(PLAYER_FRAME_ATTR, ""); |
| 455 | + vi.spyOn(window, "frameElement", "get").mockReturnValue(frame); |
| 456 | + if (userAgent === "safari") asSafari(); |
| 457 | + }; |
| 458 | + |
| 459 | + it("in the player on Safari a file keeps the take on screen and the next one loading, and frees the rest", async () => { |
| 460 | + spyLoad(); |
| 461 | + inPlayerFrame("safari"); |
| 462 | + const [first, second, third] = takesOfOneFile(); |
| 463 | + |
| 464 | + await evaluateRuntime(); |
| 465 | + expect([first, second, third].map((take) => armed(...take))).toEqual([ |
| 466 | + [true, true], |
| 467 | + [true, true], |
| 468 | + [false, false], |
| 469 | + ]); |
| 470 | + |
| 471 | + window.__player?.seek(3.5); |
| 472 | + expect([first, second, third].map((take) => armed(...take))).toEqual([ |
| 473 | + [false, false], |
| 474 | + [true, true], |
| 475 | + [true, true], |
| 476 | + ]); |
| 477 | + }); |
| 478 | + |
| 479 | + it("in the player on Chromium every take of a file loads at parse", async () => { |
| 480 | + spyLoad(); |
| 481 | + inPlayerFrame("chrome"); |
| 482 | + const takes = takesOfOneFile(); |
| 483 | + |
| 484 | + await evaluateRuntime(); |
| 485 | + expect(takes.map((take) => armed(...take))).toEqual([ |
| 486 | + [true, true], |
| 487 | + [true, true], |
| 488 | + [true, true], |
| 489 | + ]); |
| 490 | + }); |
| 491 | + |
| 492 | + it("in Studio's preview a clip 5 s ahead stays unloaded, as the look-ahead is 2 s", async () => { |
| 493 | + servePreview(); |
| 494 | + const [now, soon] = videos(mountRoot(), "0", "5"); |
| 495 | + |
| 496 | + await evaluateRuntime(); |
| 497 | + expect(armed(now, soon)).toEqual([true, false]); |
| 498 | + }); |
| 499 | + |
432 | 500 | it("loads a clip with no authored length as a render does, so no reload drops its duration", async () => { |
433 | 501 | spyLoad(); |
434 | 502 | servePreview(); |
@@ -940,6 +1008,42 @@ describe("runtime entry", () => { |
940 | 1008 | expect([first.preload, later.preload, untrimmed.preload]).toEqual(["", "none", ""]); |
941 | 1009 | }); |
942 | 1010 |
|
| 1011 | + it("gives each parsed clip preload none in the player's frame, where no clip fetches ahead of the first take", async () => { |
| 1012 | + const frame = document.createElement("iframe"); |
| 1013 | + frame.setAttribute(PLAYER_FRAME_ATTR, ""); |
| 1014 | + vi.spyOn(window, "frameElement", "get").mockReturnValue(frame); |
| 1015 | + asSafari(); |
| 1016 | + const root = mountRoot(); |
| 1017 | + timed(root, "div", "0"); |
| 1018 | + await evaluateRuntime(); |
| 1019 | + const [first, later] = videos(root, "0", "5"); |
| 1020 | + await Promise.resolve(); |
| 1021 | + expect([first.preload, later.preload]).toEqual(["", "none"]); |
| 1022 | + }); |
| 1023 | + |
| 1024 | + it("leaves Chromium loading every clip at parse in the player's frame", async () => { |
| 1025 | + const frame = document.createElement("iframe"); |
| 1026 | + frame.setAttribute(PLAYER_FRAME_ATTR, ""); |
| 1027 | + vi.spyOn(window, "frameElement", "get").mockReturnValue(frame); |
| 1028 | + const root = mountRoot(); |
| 1029 | + timed(root, "div", "0"); |
| 1030 | + await evaluateRuntime(); |
| 1031 | + const [first, later] = videos(root, "0", "5"); |
| 1032 | + await Promise.resolve(); |
| 1033 | + expect([first.preload, later.preload]).toEqual(["", ""]); |
| 1034 | + }); |
| 1035 | + |
| 1036 | + it("leaves a frame the player did not mark loading every clip at parse", async () => { |
| 1037 | + vi.spyOn(window, "frameElement", "get").mockReturnValue(document.createElement("iframe")); |
| 1038 | + asSafari(); |
| 1039 | + const root = mountRoot(); |
| 1040 | + timed(root, "div", "0"); |
| 1041 | + await evaluateRuntime(); |
| 1042 | + const [first, later] = videos(root, "0", "5"); |
| 1043 | + await Promise.resolve(); |
| 1044 | + expect([first.preload, later.preload]).toEqual(["", ""]); |
| 1045 | + }); |
| 1046 | + |
943 | 1047 | it("defers a clip at 0 inside a later scene, by its start in the film", async () => { |
944 | 1048 | servePreview(); |
945 | 1049 | const root = mountRoot(); |
|
0 commit comments