@onmark/cli 0.3.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/onmark-release.json +94 -64
  2. package/package.json +4 -4
  3. package/packages/authoring/dist/src/frame-motion.d.ts +10 -2
  4. package/packages/authoring/dist/src/frame-motion.js +29 -6
  5. package/packages/authoring/dist/src/index.d.ts +2 -2
  6. package/packages/authoring/dist/src/motion.d.ts +16 -4
  7. package/packages/authoring/dist/src/presentation.js +160 -6
  8. package/packages/authoring/dist/src/types.d.ts +1 -1
  9. package/packages/bundler/dist/src/authored_html.d.ts +19 -7
  10. package/packages/bundler/dist/src/authored_html.js +109 -21
  11. package/packages/bundler/dist/src/bundle_projection.d.ts +9 -0
  12. package/packages/bundler/dist/src/bundle_projection.js +66 -0
  13. package/packages/bundler/dist/src/command.js +38 -14
  14. package/packages/bundler/dist/src/generated/bundle-manifest.d.ts +4 -5
  15. package/packages/bundler/dist/src/generated/bundle-manifest.js +1 -0
  16. package/packages/bundler/dist/src/generated/bundle-projection-validator.d.ts +7 -0
  17. package/packages/bundler/dist/src/generated/bundle-projection-validator.js +432 -0
  18. package/packages/bundler/dist/src/generated/bundle-projection.d.ts +25 -0
  19. package/packages/bundler/dist/src/generated/bundle-projection.js +2 -0
  20. package/packages/bundler/dist/src/index.d.ts +1 -1
  21. package/packages/bundler/dist/src/presentation.d.ts +14 -7
  22. package/packages/bundler/dist/src/presentation.js +71 -30
  23. package/packages/motion-gsap/dist/src/index.d.ts +10 -2
  24. package/packages/motion-gsap/dist/src/index.js +27 -5
  25. package/packages/runtime/dist/src/generated/browser-request.d.ts +19 -2
  26. package/packages/runtime/dist/src/generated/browser-response.d.ts +46 -1
  27. package/packages/runtime/dist/src/generated/bundle-layout.d.ts +1 -1
  28. package/packages/runtime/dist/src/generated/bundle-layout.js +1 -0
  29. package/packages/runtime/dist/src/generated/runtime-contract.d.ts +4 -0
  30. package/packages/runtime/dist/src/generated/runtime-contract.js +4 -0
  31. package/packages/runtime/dist/src/generated/validators.d.ts +2 -2
  32. package/packages/runtime/dist/src/generated/validators.js +1482 -572
  33. package/packages/runtime/dist/src/index.d.ts +4 -4
  34. package/packages/runtime/dist/src/index.js +1 -1
  35. package/packages/runtime/dist/src/presentation.d.ts +18 -3
  36. package/packages/runtime/dist/src/presentation.js +112 -7
  37. package/packages/runtime/dist/src/session.d.ts +7 -3
  38. package/packages/runtime/dist/src/session.js +77 -20
  39. package/packages/runtime/dist/src/types.d.ts +3 -2
@@ -1,13 +1,13 @@
1
1
  export { ProtocolDecodeError, decodeBrowserRequest, decodeBrowserResponse, } from "./generated/codec.js";
2
2
  export type { BrowserRequest } from "./generated/browser-request.js";
3
- export type { BrowserResponse } from "./generated/browser-response.js";
3
+ export type { BrowserMediaLayout, BrowserMediaPlacement, BrowserResponse, } from "./generated/browser-response.js";
4
4
  export type { BrowserPlan, BrowserVideo } from "./generated/browser-request.js";
5
5
  export { runtimeFrameAt, type RuntimeFrame } from "./clock.js";
6
6
  export { videoFrameSelection, type RuntimeVideo, type VideoFrameSelection, } from "./media.js";
7
7
  export { DecodedVideo, materializedVideoSource, type BrowserVideoElement, type DecodedVideoOptions, } from "./video.js";
8
- export { MAX_PRESENTATION_EFFECTS, PresentationRuntimeAdapter, type ContainerPresentation, type FrameEffect, type OverlayPresentation, type PresentationBindings, type PresentationExtensions, type RuntimeOverlay, type RuntimeNode, type RuntimeScene, type RuntimeShot, type VideoPresentation, } from "./presentation.js";
8
+ export { MAX_PRESENTATION_EFFECTS, PresentationRuntimeAdapter, type ContainerPresentation, type FrameEffect, type OverlayPresentation, type PresentationBindings, type PresentationExtensions, type RuntimeOverlay, type RuntimeNode, type RuntimeScene, type RuntimeShot, type RuntimeTransition, type TransitionPresentation, type VideoPresentation, } from "./presentation.js";
9
9
  export { MAX_PRESENTATION_RESOURCES, PRESENTATION_RESOURCE_KINDS, type PresentationResource, type PresentationResourceKind, } from "./resource.js";
10
10
  export { BUNDLE_ASSET_DIRECTORY, PRESENTATION_TEMPORAL_CAPABILITIES, type PresentationTemporalCapability, } from "./generated/bundle-layout.js";
11
- export { MAX_BROWSER_OVERLAYS, MAX_BROWSER_OVERLAY_TEXT_CHARACTERS, MAX_BROWSER_VIDEOS, MAX_FAILURE_MESSAGE_CHARACTERS, MAX_PENDING_RESOURCE_CHARACTERS, MAX_PENDING_RESOURCES, } from "./generated/runtime-contract.js";
11
+ export { BROWSER_PROTOCOL_VERSION, BROWSER_OBJECT_POSITION_SCALE, MAX_BROWSER_MEDIA_LAYOUTS, MAX_BROWSER_OVERLAYS, MAX_BROWSER_OVERLAY_TEXT_CHARACTERS, MAX_BROWSER_VIDEOS, MAX_BROWSER_TRANSITIONS, MAX_FAILURE_MESSAGE_CHARACTERS, MAX_PENDING_RESOURCE_CHARACTERS, MAX_PENDING_RESOURCES, } from "./generated/runtime-contract.js";
12
12
  export { RUNTIME_HOST_NAME, installRuntimeHost, type RuntimeHost, } from "./host.js";
13
- export { RuntimeAdapterError, RuntimeSession, type RuntimeAdapter, type RuntimeAdapterFailureKind, type RuntimePlan, } from "./session.js";
13
+ export { RuntimeAdapterError, RuntimeSession, type RuntimeAdapter, type RuntimeAdapterFailureKind, type RuntimeMediaMode, type RuntimePlan, } from "./session.js";
@@ -7,6 +7,6 @@ export { DecodedVideo, materializedVideoSource, } from "./video.js";
7
7
  export { MAX_PRESENTATION_EFFECTS, PresentationRuntimeAdapter, } from "./presentation.js";
8
8
  export { MAX_PRESENTATION_RESOURCES, PRESENTATION_RESOURCE_KINDS, } from "./resource.js";
9
9
  export { BUNDLE_ASSET_DIRECTORY, PRESENTATION_TEMPORAL_CAPABILITIES, } from "./generated/bundle-layout.js";
10
- export { MAX_BROWSER_OVERLAYS, MAX_BROWSER_OVERLAY_TEXT_CHARACTERS, MAX_BROWSER_VIDEOS, MAX_FAILURE_MESSAGE_CHARACTERS, MAX_PENDING_RESOURCE_CHARACTERS, MAX_PENDING_RESOURCES, } from "./generated/runtime-contract.js";
10
+ export { BROWSER_PROTOCOL_VERSION, BROWSER_OBJECT_POSITION_SCALE, MAX_BROWSER_MEDIA_LAYOUTS, MAX_BROWSER_OVERLAYS, MAX_BROWSER_OVERLAY_TEXT_CHARACTERS, MAX_BROWSER_VIDEOS, MAX_BROWSER_TRANSITIONS, MAX_FAILURE_MESSAGE_CHARACTERS, MAX_PENDING_RESOURCE_CHARACTERS, MAX_PENDING_RESOURCES, } from "./generated/runtime-contract.js";
11
11
  export { RUNTIME_HOST_NAME, installRuntimeHost, } from "./host.js";
12
12
  export { RuntimeAdapterError, RuntimeSession, } from "./session.js";
@@ -1,6 +1,7 @@
1
1
  import type { RuntimeFrame } from "./clock.js";
2
+ import type { BrowserMediaLayout, BrowserMediaPlacement } from "./generated/browser-response.js";
2
3
  import { type RuntimeVideo } from "./media.js";
3
- import { type RuntimeAdapter, type RuntimePlan } from "./session.js";
4
+ import { type RuntimeAdapter, type RuntimeMediaMode, type RuntimePlan } from "./session.js";
4
5
  import { type BrowserVideoElement } from "./video.js";
5
6
  import { type PresentationResource } from "./resource.js";
6
7
  /** Maximum exact-frame effects retained by one presentation adapter. */
@@ -11,6 +12,8 @@ export type RuntimeOverlay = RuntimePlan["overlays"][number];
11
12
  export type RuntimeScene = RuntimePlan["scenes"][number];
12
13
  /** Immutable shot container projected from Timeline IR. */
13
14
  export type RuntimeShot = RuntimePlan["shots"][number];
15
+ /** Immutable transition relationship projected from Timeline IR. */
16
+ export type RuntimeTransition = RuntimePlan["transitions"][number];
14
17
  /** Immutable film identity projected from Timeline IR. */
15
18
  export type RuntimeNode = RuntimePlan["film"];
16
19
  /** Presentation-owned effects for one semantic container. */
@@ -23,6 +26,10 @@ export interface ContainerPresentation {
23
26
  export interface VideoPresentation {
24
27
  readonly element: BrowserVideoElement;
25
28
  readonly source: string;
29
+ /** Includes the media box in layout without exposing its pixels. */
30
+ setLayoutVisible(visible: boolean): void;
31
+ /** Measures one visible layout-only media box. */
32
+ measureLayout(): BrowserMediaPlacement;
26
33
  setVisible(visible: boolean): void;
27
34
  dispose(): void;
28
35
  }
@@ -32,6 +39,13 @@ export interface OverlayPresentation {
32
39
  setVisible(visible: boolean): void;
33
40
  dispose(): void;
34
41
  }
42
+ /** Presentation-owned binding between two adjacent shot elements. */
43
+ export interface TransitionPresentation {
44
+ readonly element: HTMLElement;
45
+ readonly incomingElement: HTMLElement;
46
+ readonly outgoingElement: HTMLElement;
47
+ dispose(): void;
48
+ }
35
49
  /** One paused browser effect driven exclusively by the authored frame. */
36
50
  export interface FrameEffect {
37
51
  /** Applies the exact frame before the runtime reports it as staged. */
@@ -50,6 +64,7 @@ export interface PresentationBindings {
50
64
  bindFilm(plan: RuntimePlan): ContainerPresentation;
51
65
  bindScene(scene: RuntimeScene): ContainerPresentation;
52
66
  bindShot(shot: RuntimeShot): ContainerPresentation;
67
+ bindTransition(transition: RuntimeTransition): TransitionPresentation;
53
68
  bindVideo(placement: RuntimeVideo): VideoPresentation;
54
69
  bindOverlay(placement: RuntimeOverlay): OverlayPresentation;
55
70
  bindExtensions(plan: RuntimePlan): Promise<PresentationExtensions>;
@@ -58,8 +73,8 @@ export interface PresentationBindings {
58
73
  export declare class PresentationRuntimeAdapter implements RuntimeAdapter {
59
74
  #private;
60
75
  constructor(bindings: PresentationBindings, readinessTimeoutMilliseconds: number);
61
- load(plan: RuntimePlan): Promise<void>;
62
- prepare(_frame: RuntimeFrame): Promise<void>;
76
+ load(plan: RuntimePlan, mediaMode?: RuntimeMediaMode): Promise<void>;
77
+ prepare(_frame: RuntimeFrame): Promise<BrowserMediaLayout>;
63
78
  seek(frame: RuntimeFrame): Promise<void>;
64
79
  confirm(frame: RuntimeFrame): Promise<void>;
65
80
  dispose(): Promise<void>;
@@ -21,7 +21,7 @@ export class PresentationRuntimeAdapter {
21
21
  this.#bindings = bindings;
22
22
  this.#readinessTimeoutMilliseconds = readinessTimeoutMilliseconds;
23
23
  }
24
- async load(plan) {
24
+ async load(plan, mediaMode = "decoded") {
25
25
  if (this.#state.kind !== "empty") {
26
26
  throw new RuntimeAdapterError("operation", "presentation load requires the empty state");
27
27
  }
@@ -34,10 +34,13 @@ export class PresentationRuntimeAdapter {
34
34
  };
35
35
  let boundStructure;
36
36
  const videos = [];
37
+ const layoutVideos = [];
37
38
  const overlays = [];
39
+ const transitions = [];
38
40
  try {
39
41
  boundStructure = this.#bindStructure(plan, structure);
40
- this.#bindVideos(plan, videos);
42
+ this.#bindTransitions(plan, transitions);
43
+ this.#bindVideos(plan, mediaMode, videos, layoutVideos);
41
44
  this.#bindOverlays(plan, overlays);
42
45
  const extensions = await this.#bindings.bindExtensions(plan);
43
46
  // Take both returned collections before either ownership projection or
@@ -51,7 +54,7 @@ export class PresentationRuntimeAdapter {
51
54
  await loadVideos(videos);
52
55
  }
53
56
  catch (error) {
54
- const cleanupFailure = await releasePresentation(effects, resources, structure, videos, overlays);
57
+ const cleanupFailure = await releasePresentation(effects, resources, structure, transitions, videos, layoutVideos, overlays);
55
58
  // A binding attempt may mutate author-owned state that generic cleanup
56
59
  // cannot prove reusable. A fresh page owns the only valid retry.
57
60
  this.#state = { kind: "disposed" };
@@ -63,7 +66,9 @@ export class PresentationRuntimeAdapter {
63
66
  frameRate: plan.frameRate,
64
67
  resources,
65
68
  structure: boundStructure,
69
+ transitions,
66
70
  videos,
71
+ layoutVideos,
67
72
  overlays,
68
73
  };
69
74
  }
@@ -71,10 +76,11 @@ export class PresentationRuntimeAdapter {
71
76
  const state = this.#loadedState("prepare");
72
77
  try {
73
78
  await preparePresentationResources(state.resources, this.#readinessTimeoutMilliseconds);
79
+ return measureMediaLayout(state.layoutVideos, state.structure);
74
80
  }
75
81
  catch (error) {
76
82
  this.#state = { ...state, kind: "failed" };
77
- throw error;
83
+ throw RuntimeAdapterError.fromUnknown(error, "presentation preparation failed");
78
84
  }
79
85
  }
80
86
  async seek(frame) {
@@ -122,7 +128,7 @@ export class PresentationRuntimeAdapter {
122
128
  : undefined;
123
129
  this.#state = { kind: "disposed" };
124
130
  this.#staged = undefined;
125
- const failure = await releasePresentation(loaded?.effects ?? [], loaded?.resources ?? [], loaded?.structure, loaded?.videos ?? [], loaded?.overlays ?? []);
131
+ const failure = await releasePresentation(loaded?.effects ?? [], loaded?.resources ?? [], loaded?.structure, loaded?.transitions ?? [], loaded?.videos ?? [], loaded?.layoutVideos ?? [], loaded?.overlays ?? []);
126
132
  if (failure !== undefined) {
127
133
  throw RuntimeAdapterError.fromUnknown(failure, "presentation cleanup failed");
128
134
  }
@@ -147,9 +153,17 @@ export class PresentationRuntimeAdapter {
147
153
  shots: structure.shots,
148
154
  };
149
155
  }
150
- #bindVideos(plan, videos) {
156
+ #bindVideos(plan, mediaMode, videos, layoutVideos) {
157
+ if (mediaMode === "omitted") {
158
+ return;
159
+ }
151
160
  for (const placement of plan.videos) {
152
161
  const presentation = this.#bindings.bindVideo(placement);
162
+ if (mediaMode === "layoutOnly") {
163
+ layoutVideos.push({ placement, presentation });
164
+ presentation.setVisible(false);
165
+ continue;
166
+ }
153
167
  const resource = new DecodedVideo({
154
168
  element: presentation.element,
155
169
  nodeId: placement.node.nodeId,
@@ -160,6 +174,11 @@ export class PresentationRuntimeAdapter {
160
174
  presentation.setVisible(false);
161
175
  }
162
176
  }
177
+ #bindTransitions(plan, transitions) {
178
+ for (const placement of plan.transitions) {
179
+ transitions.push(this.#bindings.bindTransition(placement));
180
+ }
181
+ }
163
182
  #bindOverlays(plan, overlays) {
164
183
  for (const placement of plan.overlays) {
165
184
  const presentation = this.#bindings.bindOverlay(placement);
@@ -293,7 +312,7 @@ function presentationLoadFailure(error, cleanupFailure) {
293
312
  }
294
313
  return RuntimeAdapterError.fromUnknown(error, "presentation failed to load");
295
314
  }
296
- async function releasePresentation(effects, resources, structure, videos, overlays) {
315
+ async function releasePresentation(effects, resources, structure, transitions, videos, layoutVideos, overlays) {
297
316
  let failure = await releaseFrameEffects(effects);
298
317
  const resourceFailure = await releasePresentationResources(resources);
299
318
  failure ??= resourceFailure;
@@ -301,14 +320,94 @@ async function releasePresentation(effects, resources, structure, videos, overla
301
320
  const releaseFailure = releaseVideo(video);
302
321
  failure ??= releaseFailure;
303
322
  }
323
+ for (const video of layoutVideos) {
324
+ const releaseFailure = releaseLayoutVideo(video);
325
+ failure ??= releaseFailure;
326
+ }
304
327
  for (const overlay of overlays) {
305
328
  const releaseFailure = releaseOverlay(overlay);
306
329
  failure ??= releaseFailure;
307
330
  }
331
+ for (const transition of transitions.toReversed()) {
332
+ const releaseFailure = releaseTransition(transition);
333
+ failure ??= releaseFailure;
334
+ }
308
335
  const structureFailure = releaseStructure(structure);
309
336
  failure ??= structureFailure;
310
337
  return failure;
311
338
  }
339
+ function measureMediaLayout(videos, structure) {
340
+ try {
341
+ return collectMediaLayout(videos, structure);
342
+ }
343
+ catch (error) {
344
+ throw mediaLayoutFailure(error);
345
+ }
346
+ }
347
+ function collectMediaLayout(videos, structure) {
348
+ const byShot = groupLayoutVideos(videos);
349
+ const scenes = new Map(structure.scenes.map((scene) => [scene.placement.node.nodeId, scene]));
350
+ const placements = [];
351
+ for (const shot of structure.shots) {
352
+ const group = byShot.get(shot.placement.node.nodeId);
353
+ if (group === undefined) {
354
+ continue;
355
+ }
356
+ const scene = scenes.get(shot.placement.sceneId);
357
+ if (scene === undefined) {
358
+ throw new RuntimeAdapterError("operation", "layout-only video names an unbound scene");
359
+ }
360
+ scene.presentation.setVisible(true);
361
+ shot.presentation.setVisible(true);
362
+ for (const video of group) {
363
+ video.presentation.setLayoutVisible(true);
364
+ }
365
+ try {
366
+ for (const video of group) {
367
+ const placement = video.presentation.measureLayout();
368
+ if (placement.nodeId !== video.placement.node.nodeId) {
369
+ throw new RuntimeAdapterError("operation", "measured video layout names the wrong browser node");
370
+ }
371
+ placements.push(placement);
372
+ }
373
+ }
374
+ finally {
375
+ for (const video of group) {
376
+ video.presentation.setLayoutVisible(false);
377
+ }
378
+ shot.presentation.setVisible(false);
379
+ scene.presentation.setVisible(false);
380
+ }
381
+ }
382
+ // Freeze the owned array in place; generated JSON types remain mutable-shaped.
383
+ Object.freeze(placements);
384
+ return placements;
385
+ }
386
+ function mediaLayoutFailure(error) {
387
+ if (error instanceof RuntimeAdapterError) {
388
+ return error;
389
+ }
390
+ const message = error instanceof Error
391
+ ? error.message
392
+ : "browser media layout measurement failed";
393
+ return new RuntimeAdapterError("operation", message);
394
+ }
395
+ function groupLayoutVideos(videos) {
396
+ const groups = new Map();
397
+ for (const video of videos) {
398
+ const group = groups.get(video.placement.shotId);
399
+ if (group === undefined) {
400
+ groups.set(video.placement.shotId, [video]);
401
+ }
402
+ else {
403
+ group.push(video);
404
+ }
405
+ }
406
+ return groups;
407
+ }
408
+ function releaseTransition(transition) {
409
+ return releaseAll([() => transition.dispose()]);
410
+ }
312
411
  function releaseStructure(structure) {
313
412
  if (structure === undefined) {
314
413
  return undefined;
@@ -359,6 +458,12 @@ function releaseVideo(video) {
359
458
  () => video.presentation.dispose(),
360
459
  ]);
361
460
  }
461
+ function releaseLayoutVideo(video) {
462
+ return releaseAll([
463
+ () => video.presentation.setLayoutVisible(false),
464
+ () => video.presentation.dispose(),
465
+ ]);
466
+ }
362
467
  function releaseOverlay(overlay) {
363
468
  return releaseAll([
364
469
  () => overlay.presentation.setVisible(false),
@@ -1,5 +1,5 @@
1
1
  import type { BrowserPlan, BrowserRequest } from "./generated/browser-request.js";
2
- import type { BrowserResponse } from "./generated/browser-response.js";
2
+ import type { BrowserMediaLayout, BrowserResponse } from "./generated/browser-response.js";
3
3
  import { type RuntimeFrame } from "./clock.js";
4
4
  type Immutable<T> = T extends object ? {
5
5
  readonly [Key in keyof T]: Immutable<T[Key]>;
@@ -14,9 +14,9 @@ export type RuntimePlan = Immutable<BrowserPlan>;
14
14
  */
15
15
  export interface RuntimeAdapter {
16
16
  /** Installs one owned snapshot of the immutable browser plan. */
17
- load(plan: RuntimePlan): Promise<void>;
17
+ load(plan: RuntimePlan, mediaMode: RuntimeMediaMode): Promise<void>;
18
18
  /** Resolves only after resources at the evaluation start are stable. */
19
- prepare(frame: RuntimeFrame): Promise<void>;
19
+ prepare(frame: RuntimeFrame): Promise<BrowserMediaLayout>;
20
20
  /** Stages one exact frame and registers media presentation observers. */
21
21
  seek(frame: RuntimeFrame): Promise<void>;
22
22
  /** Verifies media presentation after native compositor capture. */
@@ -24,6 +24,10 @@ export interface RuntimeAdapter {
24
24
  /** Releases all resources owned by this adapter. */
25
25
  dispose(): Promise<void>;
26
26
  }
27
+ /** Browser responsibility selected before presentation loading begins. */
28
+ export type RuntimeMediaMode = Extract<BrowserRequest["command"], {
29
+ type: "load";
30
+ }>["mediaMode"];
27
31
  export type RuntimeAdapterFailureKind = "operation" | "readinessTimeout";
28
32
  /** Expected failure reported by a browser adapter. */
29
33
  export declare class RuntimeAdapterError extends Error {
@@ -2,7 +2,7 @@
2
2
  // Owns command ordering and state while a narrow adapter owns browser effects;
3
3
  // this split keeps protocol behavior deterministic and directly testable.
4
4
  import { decodeBrowserResponse } from "./generated/codec.js";
5
- import { MAX_FAILURE_MESSAGE_CHARACTERS, MAX_PENDING_RESOURCE_CHARACTERS, MAX_PENDING_RESOURCES, } from "./generated/runtime-contract.js";
5
+ import { BROWSER_PROTOCOL_VERSION, MAX_BROWSER_MEDIA_LAYOUTS, MAX_FAILURE_MESSAGE_CHARACTERS, MAX_PENDING_RESOURCE_CHARACTERS, MAX_PENDING_RESOURCES, } from "./generated/runtime-contract.js";
6
6
  import { runtimeFrameAt } from "./clock.js";
7
7
  import { exactRatioIsCanonical, videoSourceMappingIsValid } from "./media.js";
8
8
  /** Expected failure reported by a browser adapter. */
@@ -48,7 +48,7 @@ export class RuntimeSession {
48
48
  #execute(request) {
49
49
  switch (request.command.type) {
50
50
  case "load":
51
- return this.#load(request.requestId, request.command.plan);
51
+ return this.#load(request.requestId, request.command.plan, request.command.mediaMode);
52
52
  case "prepare":
53
53
  return this.#prepare(request.requestId, request.command.evaluationStart);
54
54
  case "seek":
@@ -59,18 +59,18 @@ export class RuntimeSession {
59
59
  return this.#dispose(request.requestId);
60
60
  }
61
61
  }
62
- async #load(requestId, plan) {
62
+ async #load(requestId, plan, mediaMode) {
63
63
  if (this.#state.kind !== "empty") {
64
64
  return invalidRequest(requestId, "load requires an empty browser session");
65
65
  }
66
- const violation = planViolation(plan);
66
+ const violation = planViolation(plan, mediaMode);
67
67
  if (violation !== undefined) {
68
68
  return invalidRequest(requestId, violation);
69
69
  }
70
70
  const ownedPlan = snapshotPlan(plan);
71
71
  const nextState = loadedState(ownedPlan);
72
72
  try {
73
- await this.#adapter.load(ownedPlan);
73
+ await this.#adapter.load(ownedPlan, mediaMode);
74
74
  }
75
75
  catch (error) {
76
76
  // Loading may have entered author code and partially mutated its owned
@@ -89,7 +89,13 @@ export class RuntimeSession {
89
89
  return invalidRequest(requestId, "prepare must use the plan evaluation start");
90
90
  }
91
91
  try {
92
- await this.#adapter.prepare(runtimeFrameAt(evaluationStart, this.#state.frameRate));
92
+ const mediaLayout = await this.#adapter.prepare(runtimeFrameAt(evaluationStart, this.#state.frameRate));
93
+ this.#state = { ...this.#state, kind: "ready" };
94
+ return response(requestId, {
95
+ type: "prepared",
96
+ evaluationStart,
97
+ mediaLayout,
98
+ });
93
99
  }
94
100
  catch (error) {
95
101
  // Preparation may have started author-owned asynchronous work that the
@@ -98,8 +104,6 @@ export class RuntimeSession {
98
104
  this.#state = { kind: "failed" };
99
105
  return readinessFailure(requestId, "prepareFailed", error);
100
106
  }
101
- this.#state = { ...this.#state, kind: "ready" };
102
- return response(requestId, { type: "prepared", evaluationStart });
103
107
  }
104
108
  async #seek(requestId, frame) {
105
109
  if (this.#state.kind !== "ready") {
@@ -157,7 +161,11 @@ export class RuntimeSession {
157
161
  }
158
162
  }
159
163
  function response(requestId, event) {
160
- return decodeBrowserResponse({ version: 3, requestId, event });
164
+ return decodeBrowserResponse({
165
+ version: BROWSER_PROTOCOL_VERSION,
166
+ requestId,
167
+ event,
168
+ });
161
169
  }
162
170
  function invalidRequest(requestId, message) {
163
171
  return response(requestId, {
@@ -195,7 +203,11 @@ function readinessFailure(requestId, operationCode, error) {
195
203
  }
196
204
  return operationFailure(requestId, operationCode, error);
197
205
  }
198
- function planViolation(plan) {
206
+ function planViolation(plan, mediaMode) {
207
+ if (mediaMode === "layoutOnly" &&
208
+ plan.videos.length > MAX_BROWSER_MEDIA_LAYOUTS) {
209
+ return "layout-only plan exceeds the browser media-layout limit";
210
+ }
199
211
  if (!exactRatioIsCanonical(plan.frameRate)) {
200
212
  return "plan frame rate is not canonical";
201
213
  }
@@ -217,6 +229,7 @@ function planViolation(plan) {
217
229
  }
218
230
  if (!hasCanonicalNodeOrder(plan.scenes) ||
219
231
  !hasCanonicalNodeOrder(plan.shots) ||
232
+ !hasCanonicalNodeOrder(plan.transitions) ||
220
233
  !hasCanonicalNodeOrder(plan.videos) ||
221
234
  !hasCanonicalNodeOrder(plan.overlays)) {
222
235
  return "plan node collection is not in canonical order";
@@ -238,7 +251,7 @@ function planViolation(plan) {
238
251
  }
239
252
  sceneIntervals.set(scene.node.nodeId, scene.interval);
240
253
  }
241
- const shotIntervals = new Map();
254
+ const shots = new Map();
242
255
  for (const shot of plan.shots) {
243
256
  const nodeViolation = claimNode(shot.node, nodeIds, authoredIds);
244
257
  if (nodeViolation !== undefined) {
@@ -254,21 +267,35 @@ function planViolation(plan) {
254
267
  if (!insideInterval(shot.interval, sceneInterval)) {
255
268
  return "plan shot interval falls outside its scene";
256
269
  }
257
- shotIntervals.set(shot.node.nodeId, shot.interval);
270
+ shots.set(shot.node.nodeId, {
271
+ interval: shot.interval,
272
+ order: shots.size,
273
+ sceneId: shot.sceneId,
274
+ });
275
+ }
276
+ for (const transition of plan.transitions) {
277
+ const nodeViolation = claimNode(transition.node, nodeIds, authoredIds);
278
+ if (nodeViolation !== undefined) {
279
+ return nodeViolation;
280
+ }
281
+ const relationViolation = transitionRelationViolation(transition, shots, plan);
282
+ if (relationViolation !== undefined) {
283
+ return relationViolation;
284
+ }
258
285
  }
259
286
  for (const video of plan.videos) {
260
287
  const nodeViolation = claimNode(video.node, nodeIds, authoredIds);
261
288
  if (nodeViolation !== undefined) {
262
289
  return nodeViolation;
263
290
  }
264
- const shotInterval = shotIntervals.get(video.shotId);
265
- if (shotInterval === undefined) {
291
+ const shot = shots.get(video.shotId);
292
+ if (shot === undefined) {
266
293
  return "plan video names an unknown shot";
267
294
  }
268
295
  if (!insideEvaluation(video.interval, plan.evaluation)) {
269
296
  return "plan video interval falls outside evaluation";
270
297
  }
271
- if (!insideInterval(video.interval, shotInterval)) {
298
+ if (!insideInterval(video.interval, shot.interval)) {
272
299
  return "plan video interval falls outside its shot";
273
300
  }
274
301
  if (!videoSourceMappingIsValid(video, plan.frameRate)) {
@@ -280,7 +307,7 @@ function planViolation(plan) {
280
307
  if (nodeViolation !== undefined) {
281
308
  return nodeViolation;
282
309
  }
283
- const parentViolation = overlayParentViolation(overlay, shotIntervals);
310
+ const parentViolation = overlayParentViolation(overlay, shots);
284
311
  if (parentViolation !== undefined) {
285
312
  return parentViolation;
286
313
  }
@@ -293,6 +320,26 @@ function planViolation(plan) {
293
320
  }
294
321
  return undefined;
295
322
  }
323
+ function transitionRelationViolation(transition, shots, plan) {
324
+ const outgoing = shots.get(transition.outgoingShotId);
325
+ const incoming = shots.get(transition.incomingShotId);
326
+ if (outgoing === undefined ||
327
+ incoming === undefined ||
328
+ incoming.order !== outgoing.order + 1 ||
329
+ outgoing.sceneId !== incoming.sceneId) {
330
+ return "plan transition does not connect adjacent projected shots";
331
+ }
332
+ if (!validPlacement(transition.interval, plan.timeline, plan.evaluation)) {
333
+ return "plan transition interval is invalid for this evaluation";
334
+ }
335
+ if (!insideInterval(transition.interval, outgoing.interval) ||
336
+ !insideInterval(transition.interval, incoming.interval) ||
337
+ transition.interval.start !== incoming.interval.start ||
338
+ transition.interval.end !== outgoing.interval.end) {
339
+ return "plan transition interval does not match its adjacent shot boundary";
340
+ }
341
+ return undefined;
342
+ }
296
343
  function hasDenseNodeIdentity(identities) {
297
344
  for (let expected = 0; expected < identities.size; expected += 1) {
298
345
  if (!identities.has(expected)) {
@@ -311,7 +358,7 @@ function hasCanonicalNodeOrder(entries) {
311
358
  }
312
359
  return true;
313
360
  }
314
- function overlayParentViolation(overlay, shotIntervals) {
361
+ function overlayParentViolation(overlay, shots) {
315
362
  if (overlay.kind === "caption") {
316
363
  return overlay.shotId === undefined || overlay.shotId === null
317
364
  ? undefined
@@ -320,11 +367,11 @@ function overlayParentViolation(overlay, shotIntervals) {
320
367
  if (overlay.shotId === undefined || overlay.shotId === null) {
321
368
  return "plan overlay names an unknown shot";
322
369
  }
323
- const shotInterval = shotIntervals.get(overlay.shotId);
324
- if (shotInterval === undefined) {
370
+ const shot = shots.get(overlay.shotId);
371
+ if (shot === undefined) {
325
372
  return "plan overlay names an unknown shot";
326
373
  }
327
- if (!insideInterval(overlay.interval, shotInterval)) {
374
+ if (!insideInterval(overlay.interval, shot.interval)) {
328
375
  return "plan overlay interval falls outside its shot";
329
376
  }
330
377
  return undefined;
@@ -372,6 +419,7 @@ function snapshotPlan(plan) {
372
419
  const film = snapshotNode(plan.film);
373
420
  const scenes = Object.freeze(plan.scenes.map(snapshotScene));
374
421
  const shots = Object.freeze(plan.shots.map(snapshotShot));
422
+ const transitions = Object.freeze(plan.transitions.map(snapshotTransition));
375
423
  const videos = Object.freeze(plan.videos.map(snapshotVideo));
376
424
  const overlays = Object.freeze(plan.overlays.map(snapshotOverlay));
377
425
  return Object.freeze({
@@ -383,10 +431,19 @@ function snapshotPlan(plan) {
383
431
  film,
384
432
  scenes,
385
433
  shots,
434
+ transitions,
386
435
  videos,
387
436
  overlays,
388
437
  });
389
438
  }
439
+ function snapshotTransition(transition) {
440
+ return Object.freeze({
441
+ incomingShotId: transition.incomingShotId,
442
+ interval: Object.freeze({ ...transition.interval }),
443
+ node: snapshotNode(transition.node),
444
+ outgoingShotId: transition.outgoingShotId,
445
+ });
446
+ }
390
447
  function snapshotScene(scene) {
391
448
  return Object.freeze({
392
449
  node: snapshotNode(scene.node),
@@ -1,6 +1,7 @@
1
1
  export type { RuntimeFrame } from "./clock.js";
2
+ export type { BrowserMediaLayout, BrowserMediaPlacement, } from "./generated/browser-response.js";
2
3
  export type { RuntimeVideo } from "./media.js";
3
4
  export type { PresentationFrameBehavior, PresentationTemporalCapability, PresentationVisualCapability, } from "./generated/bundle-layout.js";
4
- export type { RuntimePlan } from "./session.js";
5
- export type { ContainerPresentation, FrameEffect, OverlayPresentation, PresentationBindings, PresentationExtensions, RuntimeOverlay, RuntimeNode, RuntimeScene, RuntimeShot, VideoPresentation, } from "./presentation.js";
5
+ export type { RuntimeMediaMode, RuntimePlan } from "./session.js";
6
+ export type { ContainerPresentation, FrameEffect, OverlayPresentation, PresentationBindings, PresentationExtensions, RuntimeOverlay, RuntimeNode, RuntimeScene, RuntimeShot, RuntimeTransition, TransitionPresentation, VideoPresentation, } from "./presentation.js";
6
7
  export type { PresentationResource, PresentationResourceKind, } from "./resource.js";