@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.
- package/onmark-release.json +94 -64
- package/package.json +4 -4
- package/packages/authoring/dist/src/frame-motion.d.ts +10 -2
- package/packages/authoring/dist/src/frame-motion.js +29 -6
- package/packages/authoring/dist/src/index.d.ts +2 -2
- package/packages/authoring/dist/src/motion.d.ts +16 -4
- package/packages/authoring/dist/src/presentation.js +160 -6
- package/packages/authoring/dist/src/types.d.ts +1 -1
- package/packages/bundler/dist/src/authored_html.d.ts +19 -7
- package/packages/bundler/dist/src/authored_html.js +109 -21
- package/packages/bundler/dist/src/bundle_projection.d.ts +9 -0
- package/packages/bundler/dist/src/bundle_projection.js +66 -0
- package/packages/bundler/dist/src/command.js +38 -14
- package/packages/bundler/dist/src/generated/bundle-manifest.d.ts +4 -5
- package/packages/bundler/dist/src/generated/bundle-manifest.js +1 -0
- package/packages/bundler/dist/src/generated/bundle-projection-validator.d.ts +7 -0
- package/packages/bundler/dist/src/generated/bundle-projection-validator.js +432 -0
- package/packages/bundler/dist/src/generated/bundle-projection.d.ts +25 -0
- package/packages/bundler/dist/src/generated/bundle-projection.js +2 -0
- package/packages/bundler/dist/src/index.d.ts +1 -1
- package/packages/bundler/dist/src/presentation.d.ts +14 -7
- package/packages/bundler/dist/src/presentation.js +71 -30
- package/packages/motion-gsap/dist/src/index.d.ts +10 -2
- package/packages/motion-gsap/dist/src/index.js +27 -5
- package/packages/runtime/dist/src/generated/browser-request.d.ts +19 -2
- package/packages/runtime/dist/src/generated/browser-response.d.ts +46 -1
- package/packages/runtime/dist/src/generated/bundle-layout.d.ts +1 -1
- package/packages/runtime/dist/src/generated/bundle-layout.js +1 -0
- package/packages/runtime/dist/src/generated/runtime-contract.d.ts +4 -0
- package/packages/runtime/dist/src/generated/runtime-contract.js +4 -0
- package/packages/runtime/dist/src/generated/validators.d.ts +2 -2
- package/packages/runtime/dist/src/generated/validators.js +1482 -572
- package/packages/runtime/dist/src/index.d.ts +4 -4
- package/packages/runtime/dist/src/index.js +1 -1
- package/packages/runtime/dist/src/presentation.d.ts +18 -3
- package/packages/runtime/dist/src/presentation.js +112 -7
- package/packages/runtime/dist/src/session.d.ts +7 -3
- package/packages/runtime/dist/src/session.js +77 -20
- 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<
|
|
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.#
|
|
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<
|
|
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({
|
|
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
|
|
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
|
-
|
|
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
|
|
265
|
-
if (
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
|
324
|
-
if (
|
|
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,
|
|
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";
|