@scenar/remotion 0.11.1 → 0.12.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/PresentationStack.d.ts +69 -0
- package/PresentationStack.d.ts.map +1 -0
- package/PresentationStack.js +104 -0
- package/PresentationStack.js.map +1 -0
- package/ScenarioComposition.d.ts +25 -1
- package/ScenarioComposition.d.ts.map +1 -1
- package/ScenarioComposition.js +8 -2
- package/ScenarioComposition.js.map +1 -1
- package/index.d.ts +2 -0
- package/index.d.ts.map +1 -1
- package/index.js +1 -0
- package/index.js.map +1 -1
- package/package.json +3 -3
- package/src/PresentationStack.test.ts +33 -0
- package/src/PresentationStack.tsx +191 -0
- package/src/ScenarioComposition.tsx +46 -3
- package/src/index.ts +6 -0
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
import { type NarrationManifest, type ScenarioBundle } from "@scenar/core";
|
|
3
|
+
/**
|
|
4
|
+
* The canonical viewport a scenario lays out at inside the video frame.
|
|
5
|
+
* Mirrors the embed's `ScenarEmbedViewport` vocabulary (`widthPx` /
|
|
6
|
+
* `heightPx`) — the same numbers a bundle would announce over the embed
|
|
7
|
+
* handshake.
|
|
8
|
+
*/
|
|
9
|
+
export interface RenderViewport {
|
|
10
|
+
readonly widthPx: number;
|
|
11
|
+
readonly heightPx: number;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* The contain-fit placement of a canonical box inside a video frame: one
|
|
15
|
+
* uniform scale factor (the DD-008 rule — the frame boundary owns the only
|
|
16
|
+
* scale), centered with letterbox/pillarbox margins on the shorter axis.
|
|
17
|
+
* Exported for tests.
|
|
18
|
+
*/
|
|
19
|
+
export declare function computeContainFit(frameWidth: number, frameHeight: number, boxWidth: number, boxHeight: number): {
|
|
20
|
+
scale: number;
|
|
21
|
+
offsetX: number;
|
|
22
|
+
offsetY: number;
|
|
23
|
+
};
|
|
24
|
+
interface PresentationStackProps<T> {
|
|
25
|
+
/** The applied bundle (cards synthesized) the player renders. */
|
|
26
|
+
bundle: ScenarioBundle<T> & {
|
|
27
|
+
narrationManifest?: NarrationManifest;
|
|
28
|
+
};
|
|
29
|
+
captions: boolean;
|
|
30
|
+
viewport: RenderViewport;
|
|
31
|
+
/** Wrap each step's content in `<ScenarioStage>` (staged bundles). */
|
|
32
|
+
stage: boolean;
|
|
33
|
+
/** Render the cursor overlay for interactions. */
|
|
34
|
+
cursor: boolean;
|
|
35
|
+
children: (data: T, stepIndex: number) => ReactNode;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* The full presentation stack for the video-export path — the same stack
|
|
39
|
+
* the packed embed entry mounts around the player (scenar#35), assembled
|
|
40
|
+
* from the pieces that are already frame-driven under a TimeSource:
|
|
41
|
+
*
|
|
42
|
+
* fixed canonical box (geometry the embed's DemoViewport provides)
|
|
43
|
+
* └ ViewportTransformLayer(camera — its export path interpolates
|
|
44
|
+
* per frame with `interpolateViewportTransform`)
|
|
45
|
+
* └ ScenarioPlayer
|
|
46
|
+
* └ Cursor ← child of the camera, so it scales and
|
|
47
|
+
* pans with the content under a zoom
|
|
48
|
+
* + useStepInteractions ← switches to the frame-driven scheduler
|
|
49
|
+
* whenever a TimeSource is present
|
|
50
|
+
*
|
|
51
|
+
* Geometry: `DemoViewport` is deliberately a passthrough under
|
|
52
|
+
* `isVideoExport` (see ScenarioCaptureMount), so this stack owns it — a
|
|
53
|
+
* definite canonical box (`viewport.widthPx` × `heightPx`) exposing the
|
|
54
|
+
* `--scenar-shell-height` variable the shells consume, contain-fit into
|
|
55
|
+
* the composition frame with exactly one scale factor (DD-008). Without a
|
|
56
|
+
* definite box, surfaces that size through the shell-geometry contract
|
|
57
|
+
* collapse to zero height — the scenar#35 black frames.
|
|
58
|
+
*
|
|
59
|
+
* Interaction state assumes frames render in timeline order — the same
|
|
60
|
+
* sequential model `ScenarioCaptureMount.walkTo` documents. The render
|
|
61
|
+
* pipeline enforces it by rendering with concurrency 1.
|
|
62
|
+
*
|
|
63
|
+
* Must render below the composition's `TimeSourceProvider`, or
|
|
64
|
+
* `useStepInteractions` silently selects the wall-clock browser path
|
|
65
|
+
* (the capture mount's load-bearing lesson).
|
|
66
|
+
*/
|
|
67
|
+
export declare function PresentationStack<T>({ bundle, captions, viewport, stage, cursor, children, }: PresentationStackProps<T>): import("react/jsx-runtime").JSX.Element;
|
|
68
|
+
export {};
|
|
69
|
+
//# sourceMappingURL=PresentationStack.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PresentationStack.d.ts","sourceRoot":"","sources":["../src/PresentationStack.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,SAAS,EAAiC,MAAM,OAAO,CAAC;AAEtE,OAAO,EACL,KAAK,iBAAiB,EACtB,KAAK,cAAc,EAEpB,MAAM,cAAc,CAAC;AAStB;;;;;GAKG;AACH,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAC/B,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,GAChB;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAOrD;AAED,UAAU,sBAAsB,CAAC,CAAC;IAChC,iEAAiE;IACjE,MAAM,EAAE,cAAc,CAAC,CAAC,CAAC,GAAG;QAAE,iBAAiB,CAAC,EAAE,iBAAiB,CAAA;KAAE,CAAC;IACtE,QAAQ,EAAE,OAAO,CAAC;IAClB,QAAQ,EAAE,cAAc,CAAC;IACzB,sEAAsE;IACtE,KAAK,EAAE,OAAO,CAAC;IACf,kDAAkD;IAClD,MAAM,EAAE,OAAO,CAAC;IAChB,QAAQ,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,SAAS,EAAE,MAAM,KAAK,SAAS,CAAC;CACrD;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,EACnC,MAAM,EACN,QAAQ,EACR,QAAQ,EACR,KAAK,EACL,MAAM,EACN,QAAQ,GACT,EAAE,sBAAsB,CAAC,CAAC,CAAC,2CA+F3B"}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useCallback, useRef, useState } from "react";
|
|
3
|
+
import { useVideoConfig } from "remotion";
|
|
4
|
+
import { VIEWPORT_TRANSFORM_IDENTITY, } from "@scenar/core";
|
|
5
|
+
import { Cursor, ScenarioPlayer, ScenarioStage, ViewportTransformLayer, useStepInteractions, } from "@scenar/react";
|
|
6
|
+
/**
|
|
7
|
+
* The contain-fit placement of a canonical box inside a video frame: one
|
|
8
|
+
* uniform scale factor (the DD-008 rule — the frame boundary owns the only
|
|
9
|
+
* scale), centered with letterbox/pillarbox margins on the shorter axis.
|
|
10
|
+
* Exported for tests.
|
|
11
|
+
*/
|
|
12
|
+
export function computeContainFit(frameWidth, frameHeight, boxWidth, boxHeight) {
|
|
13
|
+
const scale = Math.min(frameWidth / boxWidth, frameHeight / boxHeight);
|
|
14
|
+
return {
|
|
15
|
+
scale,
|
|
16
|
+
offsetX: (frameWidth - boxWidth * scale) / 2,
|
|
17
|
+
offsetY: (frameHeight - boxHeight * scale) / 2,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* The full presentation stack for the video-export path — the same stack
|
|
22
|
+
* the packed embed entry mounts around the player (scenar#35), assembled
|
|
23
|
+
* from the pieces that are already frame-driven under a TimeSource:
|
|
24
|
+
*
|
|
25
|
+
* fixed canonical box (geometry the embed's DemoViewport provides)
|
|
26
|
+
* └ ViewportTransformLayer(camera — its export path interpolates
|
|
27
|
+
* per frame with `interpolateViewportTransform`)
|
|
28
|
+
* └ ScenarioPlayer
|
|
29
|
+
* └ Cursor ← child of the camera, so it scales and
|
|
30
|
+
* pans with the content under a zoom
|
|
31
|
+
* + useStepInteractions ← switches to the frame-driven scheduler
|
|
32
|
+
* whenever a TimeSource is present
|
|
33
|
+
*
|
|
34
|
+
* Geometry: `DemoViewport` is deliberately a passthrough under
|
|
35
|
+
* `isVideoExport` (see ScenarioCaptureMount), so this stack owns it — a
|
|
36
|
+
* definite canonical box (`viewport.widthPx` × `heightPx`) exposing the
|
|
37
|
+
* `--scenar-shell-height` variable the shells consume, contain-fit into
|
|
38
|
+
* the composition frame with exactly one scale factor (DD-008). Without a
|
|
39
|
+
* definite box, surfaces that size through the shell-geometry contract
|
|
40
|
+
* collapse to zero height — the scenar#35 black frames.
|
|
41
|
+
*
|
|
42
|
+
* Interaction state assumes frames render in timeline order — the same
|
|
43
|
+
* sequential model `ScenarioCaptureMount.walkTo` documents. The render
|
|
44
|
+
* pipeline enforces it by rendering with concurrency 1.
|
|
45
|
+
*
|
|
46
|
+
* Must render below the composition's `TimeSourceProvider`, or
|
|
47
|
+
* `useStepInteractions` silently selects the wall-clock browser path
|
|
48
|
+
* (the capture mount's load-bearing lesson).
|
|
49
|
+
*/
|
|
50
|
+
export function PresentationStack({ bundle, captions, viewport, stage, cursor, children, }) {
|
|
51
|
+
const { width: frameWidth, height: frameHeight } = useVideoConfig();
|
|
52
|
+
const containerRef = useRef(null);
|
|
53
|
+
const cameraRef = useRef(null);
|
|
54
|
+
const [stepIndex, setStepIndex] = useState(0);
|
|
55
|
+
const [cursorTarget, setCursorTarget] = useState(undefined);
|
|
56
|
+
const [showRipple, setShowRipple] = useState(true);
|
|
57
|
+
const [dragging, setDragging] = useState(false);
|
|
58
|
+
const [cameraTransform, setCameraTransform] = useState(VIEWPORT_TRANSFORM_IDENTITY);
|
|
59
|
+
// Reset the cursor when the step changes so a prior step's target never
|
|
60
|
+
// lingers; the new step's interactions re-set it at their scheduled time.
|
|
61
|
+
// One handler feeds both step-change callbacks: authored steps deliver
|
|
62
|
+
// data, card steps deliver the card — the wiring needs only the index,
|
|
63
|
+
// and it must track BOTH so card-step interactions (the outro's
|
|
64
|
+
// cursor-clear and viewport-reset housekeeping) reach the scheduler.
|
|
65
|
+
// Mirrors the packed embed entry's handler exactly, so the two outputs
|
|
66
|
+
// cannot diverge on cursor semantics.
|
|
67
|
+
const handleStepChange = useCallback((_dataOrCard, index) => {
|
|
68
|
+
setStepIndex(index);
|
|
69
|
+
setCursorTarget(undefined);
|
|
70
|
+
}, []);
|
|
71
|
+
useStepInteractions({
|
|
72
|
+
stepIndex,
|
|
73
|
+
narrationManifest: bundle.narrationManifest,
|
|
74
|
+
containerRef,
|
|
75
|
+
cameraRef,
|
|
76
|
+
setCursorTarget,
|
|
77
|
+
setShowRipple,
|
|
78
|
+
setDragging,
|
|
79
|
+
setViewportTransform: setCameraTransform,
|
|
80
|
+
steps: bundle.steps,
|
|
81
|
+
});
|
|
82
|
+
const fit = computeContainFit(frameWidth, frameHeight, viewport.widthPx, viewport.heightPx);
|
|
83
|
+
return (
|
|
84
|
+
// The letterbox/pillarbox canvas: painted with the theme surface token
|
|
85
|
+
// so uncovered margins read as an intentional page, not dead pixels.
|
|
86
|
+
_jsx("div", { style: {
|
|
87
|
+
position: "absolute",
|
|
88
|
+
inset: 0,
|
|
89
|
+
background: "var(--scenar-surface)",
|
|
90
|
+
}, children: _jsx("div", { ref: containerRef, style: {
|
|
91
|
+
position: "absolute",
|
|
92
|
+
left: fit.offsetX,
|
|
93
|
+
top: fit.offsetY,
|
|
94
|
+
width: viewport.widthPx,
|
|
95
|
+
height: viewport.heightPx,
|
|
96
|
+
transform: `scale(${fit.scale})`,
|
|
97
|
+
transformOrigin: "top left",
|
|
98
|
+
overflow: "hidden",
|
|
99
|
+
"--scenar-shell-height": `${viewport.heightPx}px`,
|
|
100
|
+
}, children: _jsxs(ViewportTransformLayer, { transform: cameraTransform, contentRef: cameraRef, children: [_jsx(ScenarioPlayer, { bundle: bundle, captions: captions, onStepChange: handleStepChange, onCardStepChange: handleStepChange, children: stage
|
|
101
|
+
? (data, i) => _jsx(ScenarioStage, { children: children(data, i) })
|
|
102
|
+
: children }), cursor && (_jsx(Cursor, { target: cursorTarget, containerRef: cameraRef, showRipple: showRipple, isDragging: dragging }))] }) }) }));
|
|
103
|
+
}
|
|
104
|
+
//# sourceMappingURL=PresentationStack.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PresentationStack.js","sourceRoot":"","sources":["../src/PresentationStack.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAkB,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AACtE,OAAO,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAC1C,OAAO,EAGL,2BAA2B,GAC5B,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,MAAM,EACN,cAAc,EACd,aAAa,EACb,sBAAsB,EACtB,mBAAmB,GACpB,MAAM,eAAe,CAAC;AAavB;;;;;GAKG;AACH,MAAM,UAAU,iBAAiB,CAC/B,UAAkB,EAClB,WAAmB,EACnB,QAAgB,EAChB,SAAiB;IAEjB,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,UAAU,GAAG,QAAQ,EAAE,WAAW,GAAG,SAAS,CAAC,CAAC;IACvE,OAAO;QACL,KAAK;QACL,OAAO,EAAE,CAAC,UAAU,GAAG,QAAQ,GAAG,KAAK,CAAC,GAAG,CAAC;QAC5C,OAAO,EAAE,CAAC,WAAW,GAAG,SAAS,GAAG,KAAK,CAAC,GAAG,CAAC;KAC/C,CAAC;AACJ,CAAC;AAcD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,MAAM,UAAU,iBAAiB,CAAI,EACnC,MAAM,EACN,QAAQ,EACR,QAAQ,EACR,KAAK,EACL,MAAM,EACN,QAAQ,GACkB;IAC1B,MAAM,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,WAAW,EAAE,GAAG,cAAc,EAAE,CAAC;IACpE,MAAM,YAAY,GAAG,MAAM,CAAiB,IAAI,CAAC,CAAC;IAClD,MAAM,SAAS,GAAG,MAAM,CAAiB,IAAI,CAAC,CAAC;IAC/C,MAAM,CAAC,SAAS,EAAE,YAAY,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC;IAC9C,MAAM,CAAC,YAAY,EAAE,eAAe,CAAC,GAAG,QAAQ,CAAqB,SAAS,CAAC,CAAC;IAChF,MAAM,CAAC,UAAU,EAAE,aAAa,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC;IACnD,MAAM,CAAC,QAAQ,EAAE,WAAW,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;IAChD,MAAM,CAAC,eAAe,EAAE,kBAAkB,CAAC,GAAG,QAAQ,CAAC,2BAA2B,CAAC,CAAC;IAEpF,wEAAwE;IACxE,0EAA0E;IAC1E,uEAAuE;IACvE,uEAAuE;IACvE,gEAAgE;IAChE,qEAAqE;IACrE,uEAAuE;IACvE,sCAAsC;IACtC,MAAM,gBAAgB,GAAG,WAAW,CAAC,CAAC,WAAoB,EAAE,KAAa,EAAE,EAAE;QAC3E,YAAY,CAAC,KAAK,CAAC,CAAC;QACpB,eAAe,CAAC,SAAS,CAAC,CAAC;IAC7B,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,mBAAmB,CAAC;QAClB,SAAS;QACT,iBAAiB,EAAE,MAAM,CAAC,iBAAiB;QAC3C,YAAY;QACZ,SAAS;QACT,eAAe;QACf,aAAa;QACb,WAAW;QACX,oBAAoB,EAAE,kBAAkB;QACxC,KAAK,EAAE,MAAM,CAAC,KAAK;KACpB,CAAC,CAAC;IAEH,MAAM,GAAG,GAAG,iBAAiB,CAC3B,UAAU,EACV,WAAW,EACX,QAAQ,CAAC,OAAO,EAChB,QAAQ,CAAC,QAAQ,CAClB,CAAC;IAEF,OAAO;IACL,uEAAuE;IACvE,qEAAqE;IACrE,cACE,KAAK,EAAE;YACL,QAAQ,EAAE,UAAU;YACpB,KAAK,EAAE,CAAC;YACR,UAAU,EAAE,uBAAuB;SACpC,YAMD,cACE,GAAG,EAAE,YAAY,EACjB,KAAK,EACH;gBACE,QAAQ,EAAE,UAAU;gBACpB,IAAI,EAAE,GAAG,CAAC,OAAO;gBACjB,GAAG,EAAE,GAAG,CAAC,OAAO;gBAChB,KAAK,EAAE,QAAQ,CAAC,OAAO;gBACvB,MAAM,EAAE,QAAQ,CAAC,QAAQ;gBACzB,SAAS,EAAE,SAAS,GAAG,CAAC,KAAK,GAAG;gBAChC,eAAe,EAAE,UAAU;gBAC3B,QAAQ,EAAE,QAAQ;gBAClB,uBAAuB,EAAE,GAAG,QAAQ,CAAC,QAAQ,IAAI;aAC3B,YAG1B,MAAC,sBAAsB,IAAC,SAAS,EAAE,eAAe,EAAE,UAAU,EAAE,SAAS,aACvE,KAAC,cAAc,IACb,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,QAAQ,EAClB,YAAY,EAAE,gBAAgB,EAC9B,gBAAgB,EAAE,gBAAgB,YAEjC,KAAK;4BACJ,CAAC,CAAC,CAAC,IAAO,EAAE,CAAS,EAAE,EAAE,CAAC,KAAC,aAAa,cAAE,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC,GAAiB;4BAC5E,CAAC,CAAC,QAAQ,GACG,EAChB,MAAM,IAAI,CACT,KAAC,MAAM,IACL,MAAM,EAAE,YAAY,EACpB,YAAY,EAAE,SAAS,EACvB,UAAU,EAAE,UAAU,EACtB,UAAU,EAAE,QAAQ,GACpB,CACH,IACsB,GACrB,GACF,CACP,CAAC;AACJ,CAAC"}
|
package/ScenarioComposition.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type ReactNode } from "react";
|
|
2
2
|
import { type ScenarioBundle, type SfxSound } from "@scenar/core";
|
|
3
|
+
import { type RenderViewport } from "./PresentationStack.js";
|
|
3
4
|
interface ScenarioCompositionProps<T> {
|
|
4
5
|
/** Self-contained scenario bundle with steps and optional narration. */
|
|
5
6
|
bundle: ScenarioBundle<T>;
|
|
@@ -28,6 +29,29 @@ interface ScenarioCompositionProps<T> {
|
|
|
28
29
|
* render` stages them at (`soundtrack/sfx/<name>.mp3`).
|
|
29
30
|
*/
|
|
30
31
|
sfxSrcs?: Record<SfxSound, string>;
|
|
32
|
+
/**
|
|
33
|
+
* Mount the full presentation stack around the player — the same stack
|
|
34
|
+
* packed embeds mount: a definite canonical box contain-fit into the
|
|
35
|
+
* video frame (the geometry `DemoViewport` provides in browsers but
|
|
36
|
+
* deliberately passes through under `isVideoExport`), the
|
|
37
|
+
* `ViewportTransformLayer` camera, the `Cursor`, and TimeSource-driven
|
|
38
|
+
* step interactions. Without it, only step content renders:
|
|
39
|
+
* interactions' visible effects are absent from the MP4 and surfaces
|
|
40
|
+
* that size through the shell-geometry contract
|
|
41
|
+
* (`--scenar-shell-height`) collapse (scenar#35). Omitted, the
|
|
42
|
+
* composition keeps its prior bare-player output.
|
|
43
|
+
*/
|
|
44
|
+
viewport?: RenderViewport;
|
|
45
|
+
/**
|
|
46
|
+
* Wrap each step's content in `<ScenarioStage>` (staged bundles — the
|
|
47
|
+
* `scenar pack --stage` presentation). Only meaningful with `viewport`.
|
|
48
|
+
*/
|
|
49
|
+
stage?: boolean;
|
|
50
|
+
/**
|
|
51
|
+
* Render the cursor overlay for interactions. Defaults to true; only
|
|
52
|
+
* meaningful with `viewport`.
|
|
53
|
+
*/
|
|
54
|
+
cursor?: boolean;
|
|
31
55
|
}
|
|
32
56
|
/**
|
|
33
57
|
* Root component for rendering a Scenar scenario as a Remotion video.
|
|
@@ -71,6 +95,6 @@ interface ScenarioCompositionProps<T> {
|
|
|
71
95
|
* );
|
|
72
96
|
* ```
|
|
73
97
|
*/
|
|
74
|
-
export declare function ScenarioComposition<T>({ bundle, children, fps: fpsProp, useStaticFile: useStaticFileProp, captions, sfxSrcs, }: ScenarioCompositionProps<T>): import("react/jsx-runtime").JSX.Element;
|
|
98
|
+
export declare function ScenarioComposition<T>({ bundle, children, fps: fpsProp, useStaticFile: useStaticFileProp, captions, sfxSrcs, viewport, stage, cursor, }: ScenarioCompositionProps<T>): import("react/jsx-runtime").JSX.Element;
|
|
75
99
|
export {};
|
|
76
100
|
//# sourceMappingURL=ScenarioComposition.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ScenarioComposition.d.ts","sourceRoot":"","sources":["../src/ScenarioComposition.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,SAAS,EAAW,MAAM,OAAO,CAAC;AAShD,OAAO,EACL,KAAK,cAAc,EACnB,KAAK,QAAQ,EAId,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"ScenarioComposition.d.ts","sourceRoot":"","sources":["../src/ScenarioComposition.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,SAAS,EAAW,MAAM,OAAO,CAAC;AAShD,OAAO,EACL,KAAK,cAAc,EACnB,KAAK,QAAQ,EAId,MAAM,cAAc,CAAC;AAOtB,OAAO,EAAqB,KAAK,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAsBhF,UAAU,wBAAwB,CAAC,CAAC;IAClC,wEAAwE;IACxE,MAAM,EAAE,cAAc,CAAC,CAAC,CAAC,CAAC;IAC1B,mEAAmE;IACnE,QAAQ,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,SAAS,EAAE,MAAM,KAAK,SAAS,CAAC;IACpD,0CAA0C;IAC1C,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;;;;;OAKG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;;;OAKG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IACnC;;;;;;;;;;;OAWG;IACH,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B;;;OAGG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB;;;OAGG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AACH,wBAAgB,mBAAmB,CAAC,CAAC,EAAE,EACrC,MAAM,EACN,QAAQ,EACR,GAAG,EAAE,OAAO,EACZ,aAAa,EAAE,iBAAwB,EACvC,QAAgB,EAChB,OAA0B,EAC1B,QAAQ,EACR,KAAa,EACb,MAAa,GACd,EAAE,wBAAwB,CAAC,CAAC,CAAC,2CA6J7B"}
|
package/ScenarioComposition.js
CHANGED
|
@@ -3,6 +3,7 @@ import { useMemo } from "react";
|
|
|
3
3
|
import { Audio, OffthreadVideo, Sequence, staticFile, useCurrentFrame, useVideoConfig, } from "remotion";
|
|
4
4
|
import { computeMusicEnvelope, deriveSfxTimeline, musicGainAt, } from "@scenar/core";
|
|
5
5
|
import { TimeSourceProvider, VideoExportProvider, ScenarioPlayer, } from "@scenar/react";
|
|
6
|
+
import { PresentationStack } from "./PresentationStack.js";
|
|
6
7
|
import { msToFrames, useScenarioTimeline } from "./useScenarioTimeline.js";
|
|
7
8
|
const DEFAULT_FPS = 30;
|
|
8
9
|
/**
|
|
@@ -62,7 +63,7 @@ const DEFAULT_SFX_SRCS = {
|
|
|
62
63
|
* );
|
|
63
64
|
* ```
|
|
64
65
|
*/
|
|
65
|
-
export function ScenarioComposition({ bundle, children, fps: fpsProp, useStaticFile: useStaticFileProp = true, captions = false, sfxSrcs = DEFAULT_SFX_SRCS, }) {
|
|
66
|
+
export function ScenarioComposition({ bundle, children, fps: fpsProp, useStaticFile: useStaticFileProp = true, captions = false, sfxSrcs = DEFAULT_SFX_SRCS, viewport, stage = false, cursor = true, }) {
|
|
66
67
|
const videoConfig = useVideoConfig();
|
|
67
68
|
const fps = fpsProp ?? videoConfig.fps ?? DEFAULT_FPS;
|
|
68
69
|
const frame = useCurrentFrame();
|
|
@@ -122,7 +123,12 @@ export function ScenarioComposition({ bundle, children, fps: fpsProp, useStaticF
|
|
|
122
123
|
src: sfxSrcs[event.sound],
|
|
123
124
|
}));
|
|
124
125
|
}, [soundtrack, bundle.steps, bundle.narrationManifest, timeline, fps, sfxSrcs]);
|
|
125
|
-
return (_jsxs(_Fragment, { children: [_jsx(TimeSourceProvider, { currentTimeMs: currentTimeMs, stepStartTimesMs: timeline.stepStartTimesMs, children: _jsx(VideoExportProvider, { presenterMedia: presenterMedia, children:
|
|
126
|
+
return (_jsxs(_Fragment, { children: [_jsx(TimeSourceProvider, { currentTimeMs: currentTimeMs, stepStartTimesMs: timeline.stepStartTimesMs, children: _jsx(VideoExportProvider, { presenterMedia: presenterMedia, children: viewport ? (
|
|
127
|
+
// The full presentation stack (scenar#35). Rendered below the
|
|
128
|
+
// TimeSourceProvider on purpose: its useStepInteractions call
|
|
129
|
+
// must see the time source or it silently selects the
|
|
130
|
+
// wall-clock browser scheduler (the capture mount's lesson).
|
|
131
|
+
_jsx(PresentationStack, { bundle: playerBundle, captions: captions, viewport: viewport, stage: stage, cursor: cursor, children: children })) : (_jsx(ScenarioPlayer, { bundle: playerBundle, captions: captions, children: children })) }) }), timeline.audioClips.map((clip) => (_jsx(Sequence, { from: clip.startFrame, durationInFrames: clip.durationFrames, children: _jsx(Audio, { src: resolveAssetSrc(clip.src, useStaticFileProp) }) }, clip.stepIndex))), soundtrack?.musicSrc && (
|
|
126
132
|
// The music spans the whole composition: `loop` folds a shorter
|
|
127
133
|
// asset seamlessly; the volume function carries fade-in, ducking,
|
|
128
134
|
// and the closing fade-out, so no trim math is needed here.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ScenarioComposition.js","sourceRoot":"","sources":["../src/ScenarioComposition.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAkB,OAAO,EAAE,MAAM,OAAO,CAAC;AAChD,OAAO,EACL,KAAK,EACL,cAAc,EACd,QAAQ,EACR,UAAU,EACV,eAAe,EACf,cAAc,GACf,MAAM,UAAU,CAAC;AAClB,OAAO,EAGL,oBAAoB,EACpB,iBAAiB,EACjB,WAAW,GACZ,MAAM,cAAc,CAAC;AACtB,OAAO,EAEL,kBAAkB,EAClB,mBAAmB,EACnB,cAAc,GACf,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,UAAU,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAE3E,MAAM,WAAW,GAAG,EAAE,CAAC;AAEvB;;;;GAIG;AACH,MAAM,aAAa,GAAG,GAAG,CAAC;AAE1B;;;;GAIG;AACH,MAAM,gBAAgB,GAA6B;IACjD,KAAK,EAAE,0BAA0B;IACjC,SAAS,EAAE,8BAA8B;CAC1C,CAAC;
|
|
1
|
+
{"version":3,"file":"ScenarioComposition.js","sourceRoot":"","sources":["../src/ScenarioComposition.tsx"],"names":[],"mappings":"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"}
|
package/index.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
export type { ScenarioBundle, NarrationManifest, ScenarioStep, StepTimeline, TitleCard, TitleCards, StepCard, AppliedTitleCards, } from "@scenar/core";
|
|
2
2
|
export { applyTitleCards } from "@scenar/core";
|
|
3
3
|
export { ScenarioComposition } from "./ScenarioComposition.js";
|
|
4
|
+
export type { RenderViewport } from "./PresentationStack.js";
|
|
5
|
+
export { PresentationStack, computeContainFit } from "./PresentationStack.js";
|
|
4
6
|
export type { ScenarioTimeline, AudioClip } from "./useScenarioTimeline.js";
|
|
5
7
|
export { useScenarioTimeline, calculateScenarioTimeline, msToFrames, } from "./useScenarioTimeline.js";
|
|
6
8
|
//# sourceMappingURL=index.d.ts.map
|
package/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAIA,YAAY,EACV,cAAc,EACd,iBAAiB,EACjB,YAAY,EACZ,YAAY,EACZ,SAAS,EACT,UAAU,EACV,QAAQ,EACR,iBAAiB,GAClB,MAAM,cAAc,CAAC;AAItB,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAG/C,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAIA,YAAY,EACV,cAAc,EACd,iBAAiB,EACjB,YAAY,EACZ,YAAY,EACZ,SAAS,EACT,UAAU,EACV,QAAQ,EACR,iBAAiB,GAClB,MAAM,cAAc,CAAC;AAItB,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAG/C,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAK/D,YAAY,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAC7D,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAG9E,YAAY,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAC5E,OAAO,EACL,mBAAmB,EACnB,yBAAyB,EACzB,UAAU,GACX,MAAM,0BAA0B,CAAC"}
|
package/index.js
CHANGED
|
@@ -6,5 +6,6 @@
|
|
|
6
6
|
export { applyTitleCards } from "@scenar/core";
|
|
7
7
|
// Composition
|
|
8
8
|
export { ScenarioComposition } from "./ScenarioComposition.js";
|
|
9
|
+
export { PresentationStack, computeContainFit } from "./PresentationStack.js";
|
|
9
10
|
export { useScenarioTimeline, calculateScenarioTimeline, msToFrames, } from "./useScenarioTimeline.js";
|
|
10
11
|
//# sourceMappingURL=index.js.map
|
package/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,gCAAgC;AAChC,iEAAiE;AAajE,sEAAsE;AACtE,sEAAsE;AACtE,sCAAsC;AACtC,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAE/C,cAAc;AACd,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,gCAAgC;AAChC,iEAAiE;AAajE,sEAAsE;AACtE,sEAAsE;AACtE,sCAAsC;AACtC,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAE/C,cAAc;AACd,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAM/D,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAI9E,OAAO,EACL,mBAAmB,EACnB,yBAAyB,EACzB,UAAU,GACX,MAAM,0BAA0B,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@scenar/remotion",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"description": "Remotion integration for Scenar — render scenario demos as pixel-perfect MP4 videos.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -29,8 +29,8 @@
|
|
|
29
29
|
}
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@scenar/core": "0.
|
|
33
|
-
"@scenar/react": "0.
|
|
32
|
+
"@scenar/core": "0.12.0",
|
|
33
|
+
"@scenar/react": "0.12.0"
|
|
34
34
|
},
|
|
35
35
|
"peerDependencies": {
|
|
36
36
|
"@remotion/renderer": "4.0.448",
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { computeContainFit } from "./PresentationStack.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The contain-fit math is the presentation stack's one piece of geometry:
|
|
6
|
+
* a single uniform scale factor (DD-008 — the frame boundary owns the only
|
|
7
|
+
* scale), centered margins on the shorter axis.
|
|
8
|
+
*/
|
|
9
|
+
describe("computeContainFit", () => {
|
|
10
|
+
it("pillarboxes a 16:10 canonical box in a 16:9 frame (the render default)", () => {
|
|
11
|
+
const fit = computeContainFit(1920, 1080, 1440, 900);
|
|
12
|
+
expect(fit.scale).toBeCloseTo(1080 / 900); // height-limited: 1.2
|
|
13
|
+
expect(fit.offsetY).toBe(0);
|
|
14
|
+
expect(fit.offsetX).toBeCloseTo((1920 - 1440 * 1.2) / 2); // 96px pillars
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it("letterboxes a wide box in a tall frame", () => {
|
|
18
|
+
const fit = computeContainFit(1080, 1920, 1440, 900);
|
|
19
|
+
expect(fit.scale).toBeCloseTo(1080 / 1440); // width-limited: 0.75
|
|
20
|
+
expect(fit.offsetX).toBe(0);
|
|
21
|
+
expect(fit.offsetY).toBeCloseTo((1920 - 900 * 0.75) / 2);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("is the identity when the box matches the frame", () => {
|
|
25
|
+
const fit = computeContainFit(1440, 900, 1440, 900);
|
|
26
|
+
expect(fit).toEqual({ scale: 1, offsetX: 0, offsetY: 0 });
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("scales up as well as down (a small box fills a large frame)", () => {
|
|
30
|
+
const fit = computeContainFit(2880, 1800, 1440, 900);
|
|
31
|
+
expect(fit).toEqual({ scale: 2, offsetX: 0, offsetY: 0 });
|
|
32
|
+
});
|
|
33
|
+
});
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { type ReactNode, useCallback, useRef, useState } from "react";
|
|
2
|
+
import { useVideoConfig } from "remotion";
|
|
3
|
+
import {
|
|
4
|
+
type NarrationManifest,
|
|
5
|
+
type ScenarioBundle,
|
|
6
|
+
VIEWPORT_TRANSFORM_IDENTITY,
|
|
7
|
+
} from "@scenar/core";
|
|
8
|
+
import {
|
|
9
|
+
Cursor,
|
|
10
|
+
ScenarioPlayer,
|
|
11
|
+
ScenarioStage,
|
|
12
|
+
ViewportTransformLayer,
|
|
13
|
+
useStepInteractions,
|
|
14
|
+
} from "@scenar/react";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* The canonical viewport a scenario lays out at inside the video frame.
|
|
18
|
+
* Mirrors the embed's `ScenarEmbedViewport` vocabulary (`widthPx` /
|
|
19
|
+
* `heightPx`) — the same numbers a bundle would announce over the embed
|
|
20
|
+
* handshake.
|
|
21
|
+
*/
|
|
22
|
+
export interface RenderViewport {
|
|
23
|
+
readonly widthPx: number;
|
|
24
|
+
readonly heightPx: number;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* The contain-fit placement of a canonical box inside a video frame: one
|
|
29
|
+
* uniform scale factor (the DD-008 rule — the frame boundary owns the only
|
|
30
|
+
* scale), centered with letterbox/pillarbox margins on the shorter axis.
|
|
31
|
+
* Exported for tests.
|
|
32
|
+
*/
|
|
33
|
+
export function computeContainFit(
|
|
34
|
+
frameWidth: number,
|
|
35
|
+
frameHeight: number,
|
|
36
|
+
boxWidth: number,
|
|
37
|
+
boxHeight: number,
|
|
38
|
+
): { scale: number; offsetX: number; offsetY: number } {
|
|
39
|
+
const scale = Math.min(frameWidth / boxWidth, frameHeight / boxHeight);
|
|
40
|
+
return {
|
|
41
|
+
scale,
|
|
42
|
+
offsetX: (frameWidth - boxWidth * scale) / 2,
|
|
43
|
+
offsetY: (frameHeight - boxHeight * scale) / 2,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
interface PresentationStackProps<T> {
|
|
48
|
+
/** The applied bundle (cards synthesized) the player renders. */
|
|
49
|
+
bundle: ScenarioBundle<T> & { narrationManifest?: NarrationManifest };
|
|
50
|
+
captions: boolean;
|
|
51
|
+
viewport: RenderViewport;
|
|
52
|
+
/** Wrap each step's content in `<ScenarioStage>` (staged bundles). */
|
|
53
|
+
stage: boolean;
|
|
54
|
+
/** Render the cursor overlay for interactions. */
|
|
55
|
+
cursor: boolean;
|
|
56
|
+
children: (data: T, stepIndex: number) => ReactNode;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* The full presentation stack for the video-export path — the same stack
|
|
61
|
+
* the packed embed entry mounts around the player (scenar#35), assembled
|
|
62
|
+
* from the pieces that are already frame-driven under a TimeSource:
|
|
63
|
+
*
|
|
64
|
+
* fixed canonical box (geometry the embed's DemoViewport provides)
|
|
65
|
+
* └ ViewportTransformLayer(camera — its export path interpolates
|
|
66
|
+
* per frame with `interpolateViewportTransform`)
|
|
67
|
+
* └ ScenarioPlayer
|
|
68
|
+
* └ Cursor ← child of the camera, so it scales and
|
|
69
|
+
* pans with the content under a zoom
|
|
70
|
+
* + useStepInteractions ← switches to the frame-driven scheduler
|
|
71
|
+
* whenever a TimeSource is present
|
|
72
|
+
*
|
|
73
|
+
* Geometry: `DemoViewport` is deliberately a passthrough under
|
|
74
|
+
* `isVideoExport` (see ScenarioCaptureMount), so this stack owns it — a
|
|
75
|
+
* definite canonical box (`viewport.widthPx` × `heightPx`) exposing the
|
|
76
|
+
* `--scenar-shell-height` variable the shells consume, contain-fit into
|
|
77
|
+
* the composition frame with exactly one scale factor (DD-008). Without a
|
|
78
|
+
* definite box, surfaces that size through the shell-geometry contract
|
|
79
|
+
* collapse to zero height — the scenar#35 black frames.
|
|
80
|
+
*
|
|
81
|
+
* Interaction state assumes frames render in timeline order — the same
|
|
82
|
+
* sequential model `ScenarioCaptureMount.walkTo` documents. The render
|
|
83
|
+
* pipeline enforces it by rendering with concurrency 1.
|
|
84
|
+
*
|
|
85
|
+
* Must render below the composition's `TimeSourceProvider`, or
|
|
86
|
+
* `useStepInteractions` silently selects the wall-clock browser path
|
|
87
|
+
* (the capture mount's load-bearing lesson).
|
|
88
|
+
*/
|
|
89
|
+
export function PresentationStack<T>({
|
|
90
|
+
bundle,
|
|
91
|
+
captions,
|
|
92
|
+
viewport,
|
|
93
|
+
stage,
|
|
94
|
+
cursor,
|
|
95
|
+
children,
|
|
96
|
+
}: PresentationStackProps<T>) {
|
|
97
|
+
const { width: frameWidth, height: frameHeight } = useVideoConfig();
|
|
98
|
+
const containerRef = useRef<HTMLDivElement>(null);
|
|
99
|
+
const cameraRef = useRef<HTMLDivElement>(null);
|
|
100
|
+
const [stepIndex, setStepIndex] = useState(0);
|
|
101
|
+
const [cursorTarget, setCursorTarget] = useState<string | undefined>(undefined);
|
|
102
|
+
const [showRipple, setShowRipple] = useState(true);
|
|
103
|
+
const [dragging, setDragging] = useState(false);
|
|
104
|
+
const [cameraTransform, setCameraTransform] = useState(VIEWPORT_TRANSFORM_IDENTITY);
|
|
105
|
+
|
|
106
|
+
// Reset the cursor when the step changes so a prior step's target never
|
|
107
|
+
// lingers; the new step's interactions re-set it at their scheduled time.
|
|
108
|
+
// One handler feeds both step-change callbacks: authored steps deliver
|
|
109
|
+
// data, card steps deliver the card — the wiring needs only the index,
|
|
110
|
+
// and it must track BOTH so card-step interactions (the outro's
|
|
111
|
+
// cursor-clear and viewport-reset housekeeping) reach the scheduler.
|
|
112
|
+
// Mirrors the packed embed entry's handler exactly, so the two outputs
|
|
113
|
+
// cannot diverge on cursor semantics.
|
|
114
|
+
const handleStepChange = useCallback((_dataOrCard: unknown, index: number) => {
|
|
115
|
+
setStepIndex(index);
|
|
116
|
+
setCursorTarget(undefined);
|
|
117
|
+
}, []);
|
|
118
|
+
|
|
119
|
+
useStepInteractions({
|
|
120
|
+
stepIndex,
|
|
121
|
+
narrationManifest: bundle.narrationManifest,
|
|
122
|
+
containerRef,
|
|
123
|
+
cameraRef,
|
|
124
|
+
setCursorTarget,
|
|
125
|
+
setShowRipple,
|
|
126
|
+
setDragging,
|
|
127
|
+
setViewportTransform: setCameraTransform,
|
|
128
|
+
steps: bundle.steps,
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
const fit = computeContainFit(
|
|
132
|
+
frameWidth,
|
|
133
|
+
frameHeight,
|
|
134
|
+
viewport.widthPx,
|
|
135
|
+
viewport.heightPx,
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
return (
|
|
139
|
+
// The letterbox/pillarbox canvas: painted with the theme surface token
|
|
140
|
+
// so uncovered margins read as an intentional page, not dead pixels.
|
|
141
|
+
<div
|
|
142
|
+
style={{
|
|
143
|
+
position: "absolute",
|
|
144
|
+
inset: 0,
|
|
145
|
+
background: "var(--scenar-surface)",
|
|
146
|
+
}}
|
|
147
|
+
>
|
|
148
|
+
{/* The canonical box, contain-fit into the frame. `transform` does
|
|
149
|
+
not affect layout, so the box is positioned at its scaled offset
|
|
150
|
+
and scaled from its top-left corner — one scale factor, owned by
|
|
151
|
+
this boundary. */}
|
|
152
|
+
<div
|
|
153
|
+
ref={containerRef}
|
|
154
|
+
style={
|
|
155
|
+
{
|
|
156
|
+
position: "absolute",
|
|
157
|
+
left: fit.offsetX,
|
|
158
|
+
top: fit.offsetY,
|
|
159
|
+
width: viewport.widthPx,
|
|
160
|
+
height: viewport.heightPx,
|
|
161
|
+
transform: `scale(${fit.scale})`,
|
|
162
|
+
transformOrigin: "top left",
|
|
163
|
+
overflow: "hidden",
|
|
164
|
+
"--scenar-shell-height": `${viewport.heightPx}px`,
|
|
165
|
+
} as React.CSSProperties
|
|
166
|
+
}
|
|
167
|
+
>
|
|
168
|
+
<ViewportTransformLayer transform={cameraTransform} contentRef={cameraRef}>
|
|
169
|
+
<ScenarioPlayer
|
|
170
|
+
bundle={bundle}
|
|
171
|
+
captions={captions}
|
|
172
|
+
onStepChange={handleStepChange}
|
|
173
|
+
onCardStepChange={handleStepChange}
|
|
174
|
+
>
|
|
175
|
+
{stage
|
|
176
|
+
? (data: T, i: number) => <ScenarioStage>{children(data, i)}</ScenarioStage>
|
|
177
|
+
: children}
|
|
178
|
+
</ScenarioPlayer>
|
|
179
|
+
{cursor && (
|
|
180
|
+
<Cursor
|
|
181
|
+
target={cursorTarget}
|
|
182
|
+
containerRef={cameraRef}
|
|
183
|
+
showRipple={showRipple}
|
|
184
|
+
isDragging={dragging}
|
|
185
|
+
/>
|
|
186
|
+
)}
|
|
187
|
+
</ViewportTransformLayer>
|
|
188
|
+
</div>
|
|
189
|
+
</div>
|
|
190
|
+
);
|
|
191
|
+
}
|
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
VideoExportProvider,
|
|
21
21
|
ScenarioPlayer,
|
|
22
22
|
} from "@scenar/react";
|
|
23
|
+
import { PresentationStack, type RenderViewport } from "./PresentationStack.js";
|
|
23
24
|
import { msToFrames, useScenarioTimeline } from "./useScenarioTimeline.js";
|
|
24
25
|
|
|
25
26
|
const DEFAULT_FPS = 30;
|
|
@@ -69,6 +70,29 @@ interface ScenarioCompositionProps<T> {
|
|
|
69
70
|
* render` stages them at (`soundtrack/sfx/<name>.mp3`).
|
|
70
71
|
*/
|
|
71
72
|
sfxSrcs?: Record<SfxSound, string>;
|
|
73
|
+
/**
|
|
74
|
+
* Mount the full presentation stack around the player — the same stack
|
|
75
|
+
* packed embeds mount: a definite canonical box contain-fit into the
|
|
76
|
+
* video frame (the geometry `DemoViewport` provides in browsers but
|
|
77
|
+
* deliberately passes through under `isVideoExport`), the
|
|
78
|
+
* `ViewportTransformLayer` camera, the `Cursor`, and TimeSource-driven
|
|
79
|
+
* step interactions. Without it, only step content renders:
|
|
80
|
+
* interactions' visible effects are absent from the MP4 and surfaces
|
|
81
|
+
* that size through the shell-geometry contract
|
|
82
|
+
* (`--scenar-shell-height`) collapse (scenar#35). Omitted, the
|
|
83
|
+
* composition keeps its prior bare-player output.
|
|
84
|
+
*/
|
|
85
|
+
viewport?: RenderViewport;
|
|
86
|
+
/**
|
|
87
|
+
* Wrap each step's content in `<ScenarioStage>` (staged bundles — the
|
|
88
|
+
* `scenar pack --stage` presentation). Only meaningful with `viewport`.
|
|
89
|
+
*/
|
|
90
|
+
stage?: boolean;
|
|
91
|
+
/**
|
|
92
|
+
* Render the cursor overlay for interactions. Defaults to true; only
|
|
93
|
+
* meaningful with `viewport`.
|
|
94
|
+
*/
|
|
95
|
+
cursor?: boolean;
|
|
72
96
|
}
|
|
73
97
|
|
|
74
98
|
/**
|
|
@@ -120,6 +144,9 @@ export function ScenarioComposition<T>({
|
|
|
120
144
|
useStaticFile: useStaticFileProp = true,
|
|
121
145
|
captions = false,
|
|
122
146
|
sfxSrcs = DEFAULT_SFX_SRCS,
|
|
147
|
+
viewport,
|
|
148
|
+
stage = false,
|
|
149
|
+
cursor = true,
|
|
123
150
|
}: ScenarioCompositionProps<T>) {
|
|
124
151
|
const videoConfig = useVideoConfig();
|
|
125
152
|
const fps = fpsProp ?? videoConfig.fps ?? DEFAULT_FPS;
|
|
@@ -223,9 +250,25 @@ export function ScenarioComposition<T>({
|
|
|
223
250
|
stepStartTimesMs={timeline.stepStartTimesMs}
|
|
224
251
|
>
|
|
225
252
|
<VideoExportProvider presenterMedia={presenterMedia}>
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
253
|
+
{viewport ? (
|
|
254
|
+
// The full presentation stack (scenar#35). Rendered below the
|
|
255
|
+
// TimeSourceProvider on purpose: its useStepInteractions call
|
|
256
|
+
// must see the time source or it silently selects the
|
|
257
|
+
// wall-clock browser scheduler (the capture mount's lesson).
|
|
258
|
+
<PresentationStack
|
|
259
|
+
bundle={playerBundle}
|
|
260
|
+
captions={captions}
|
|
261
|
+
viewport={viewport}
|
|
262
|
+
stage={stage}
|
|
263
|
+
cursor={cursor}
|
|
264
|
+
>
|
|
265
|
+
{children}
|
|
266
|
+
</PresentationStack>
|
|
267
|
+
) : (
|
|
268
|
+
<ScenarioPlayer bundle={playerBundle} captions={captions}>
|
|
269
|
+
{children}
|
|
270
|
+
</ScenarioPlayer>
|
|
271
|
+
)}
|
|
229
272
|
</VideoExportProvider>
|
|
230
273
|
</TimeSourceProvider>
|
|
231
274
|
|
package/src/index.ts
CHANGED
|
@@ -20,6 +20,12 @@ export { applyTitleCards } from "@scenar/core";
|
|
|
20
20
|
// Composition
|
|
21
21
|
export { ScenarioComposition } from "./ScenarioComposition.js";
|
|
22
22
|
|
|
23
|
+
// The video-export presentation stack (scenar#35). The composition mounts
|
|
24
|
+
// it when given `viewport`; the pieces are exported for custom entries
|
|
25
|
+
// that assemble their own tree.
|
|
26
|
+
export type { RenderViewport } from "./PresentationStack.js";
|
|
27
|
+
export { PresentationStack, computeContainFit } from "./PresentationStack.js";
|
|
28
|
+
|
|
23
29
|
// Timeline
|
|
24
30
|
export type { ScenarioTimeline, AudioClip } from "./useScenarioTimeline.js";
|
|
25
31
|
export {
|