@motion-script/react 0.1.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/README.md +67 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/jsx/jsx-dev-runtime.d.ts +2 -0
- package/dist/jsx/jsx-dev-runtime.d.ts.map +1 -0
- package/dist/jsx/jsx-dev-runtime.js +2 -0
- package/dist/jsx/jsx-runtime.d.ts +2 -0
- package/dist/jsx/jsx-runtime.d.ts.map +1 -0
- package/dist/jsx/jsx-runtime.js +2 -0
- package/dist/motion-script-react.js +178 -0
- package/dist/motion-script-react.js.map +1 -0
- package/dist/ui/provider.d.ts +30 -0
- package/dist/ui/provider.d.ts.map +1 -0
- package/dist/ui/scene.d.ts +58 -0
- package/dist/ui/scene.d.ts.map +1 -0
- package/package.json +65 -0
package/README.md
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# @motion-script/react
|
|
2
|
+
|
|
3
|
+
React bindings for [Motion Script](https://motionscript.dev), drop a scene
|
|
4
|
+
into your app and play it back with a `<canvas>`-based player backed by
|
|
5
|
+
Skia/CanvasKit.
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
import { MotionScriptProvider, MotionPlayer } from '@motion-script/react';
|
|
9
|
+
import { MyScene } from './scenes/my-scene';
|
|
10
|
+
|
|
11
|
+
export function App() {
|
|
12
|
+
return (
|
|
13
|
+
<MotionScriptProvider>
|
|
14
|
+
<MotionPlayer
|
|
15
|
+
initialFrame={0}
|
|
16
|
+
isPlaying
|
|
17
|
+
fps={30}
|
|
18
|
+
viewport={{ width: 1920, height: 1080 }}
|
|
19
|
+
scenes={[new MyScene()]}
|
|
20
|
+
assets={{}}
|
|
21
|
+
/>
|
|
22
|
+
</MotionScriptProvider>
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## What's in here
|
|
28
|
+
|
|
29
|
+
- **`MotionScriptProvider`** — initializes CanvasKit (the Skia/WASM runtime)
|
|
30
|
+
and makes it available to players via context. Wrap your app (or the part of
|
|
31
|
+
it that renders Motion Script content) in this once.
|
|
32
|
+
- **`useMotionScript`** — hook for reading CanvasKit initialization state from
|
|
33
|
+
the provider.
|
|
34
|
+
- **`MotionPlayer`** — renders a `Scene[]` to a `<canvas>`, drives playback
|
|
35
|
+
(play/pause/seek/speed/mute), and exposes an imperative `FrameHandle` (via
|
|
36
|
+
`ref`) for screenshotting, scrubbing, and inspecting build errors and node
|
|
37
|
+
state.
|
|
38
|
+
|
|
39
|
+
This package builds on [`@motion-script/core`](../core) (the scene graph and
|
|
40
|
+
animation runtime) and [`@motion-script/web`](../web) (the Skia/CanvasKit
|
|
41
|
+
rendering backend).
|
|
42
|
+
|
|
43
|
+
## Usage
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
npm install @motion-script/react
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
For a guided setup, scaffold a project instead with:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
npm create motion-script@latest
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
See the [docs](https://motionscript.dev/docs) for the full feature set and API
|
|
56
|
+
reference.
|
|
57
|
+
|
|
58
|
+
## Development
|
|
59
|
+
|
|
60
|
+
From the monorepo root:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
pnpm --filter @motion-script/react build
|
|
64
|
+
pnpm --filter @motion-script/react dev
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
See [CONTRIBUTING.md](../../CONTRIBUTING.md) for the architecture overview.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,oBAAoB,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AACtE,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC1C,YAAY,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAC9C,YAAY,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jsx-dev-runtime.d.ts","sourceRoot":"","sources":["../../src/jsx/jsx-dev-runtime.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,yCAAyC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jsx-runtime.d.ts","sourceRoot":"","sources":["../../src/jsx/jsx-runtime.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,qCAAqC,CAAC"}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { createContext, useContext, useEffect, useImperativeHandle, useRef, useState } from "react";
|
|
2
|
+
import { WebAudioPlayer, WebMasterClock, WebMeasureScope, WebRenderContext, WebStorageAdapter, getCanvasKit } from "@motion-script/web";
|
|
3
|
+
import { jsx } from "react/jsx-runtime";
|
|
4
|
+
import { AssetCatalog, PlaybackController, Precomp, setTheme } from "@motion-script/core";
|
|
5
|
+
//#region src/ui/provider.tsx
|
|
6
|
+
var MotionScriptContext = createContext(null);
|
|
7
|
+
/**
|
|
8
|
+
* Loads CanvasKit once and shares it with descendants via context.
|
|
9
|
+
*
|
|
10
|
+
* Must wrap any tree that renders {@link MotionPlayer}, since the player
|
|
11
|
+
* reads its CanvasKit instance from this provider through {@link useMotionScript}.
|
|
12
|
+
*/
|
|
13
|
+
function MotionScriptProvider({ wsmUrl, children }) {
|
|
14
|
+
const [canvasKit, setCanvasKit] = useState(null);
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
let cancelled = false;
|
|
17
|
+
getCanvasKit(wsmUrl).then((ck) => {
|
|
18
|
+
if (!cancelled) setCanvasKit(ck);
|
|
19
|
+
}).catch(console.error);
|
|
20
|
+
return () => {
|
|
21
|
+
cancelled = true;
|
|
22
|
+
};
|
|
23
|
+
}, [wsmUrl]);
|
|
24
|
+
return /* @__PURE__ */ jsx(MotionScriptContext.Provider, {
|
|
25
|
+
value: {
|
|
26
|
+
canvasKit,
|
|
27
|
+
isInitialized: canvasKit !== null
|
|
28
|
+
},
|
|
29
|
+
children
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Reads the CanvasKit instance and readiness state from the nearest
|
|
34
|
+
* {@link MotionScriptProvider}.
|
|
35
|
+
*
|
|
36
|
+
* @throws If called outside a {@link MotionScriptProvider}.
|
|
37
|
+
*/
|
|
38
|
+
function useMotionScript() {
|
|
39
|
+
const ctx = useContext(MotionScriptContext);
|
|
40
|
+
if (!ctx) throw new Error("useMotionScript must be used inside a MotionScriptProvider");
|
|
41
|
+
return ctx;
|
|
42
|
+
}
|
|
43
|
+
//#endregion
|
|
44
|
+
//#region src/ui/scene.tsx
|
|
45
|
+
/**
|
|
46
|
+
* Renders a MotionScript scene graph to a canvas and drives playback.
|
|
47
|
+
*
|
|
48
|
+
* Must be rendered inside a {@link MotionScriptProvider}, which supplies the
|
|
49
|
+
* shared CanvasKit instance this component depends on.
|
|
50
|
+
*/
|
|
51
|
+
function MotionPlayer({ ref, initialFrame, isPlaying, fps, viewport, scenes, assets, theme, speed = 1, muted = false, onLoadingChange, onFrameChange, onBuildErrors }) {
|
|
52
|
+
const canvasRef = useRef(null);
|
|
53
|
+
const { canvasKit, isInitialized } = useMotionScript();
|
|
54
|
+
const [controller, setController] = useState(null);
|
|
55
|
+
const controllerRef = useRef(null);
|
|
56
|
+
const onLoadingChangeRef = useRef(onLoadingChange);
|
|
57
|
+
const [, setC] = useState(0);
|
|
58
|
+
const initialFrameRef = useRef(initialFrame);
|
|
59
|
+
const onFrameChangeRef = useRef(onFrameChange);
|
|
60
|
+
const onBuildErrorsRef = useRef(onBuildErrors);
|
|
61
|
+
useEffect(() => {
|
|
62
|
+
onLoadingChangeRef.current = onLoadingChange;
|
|
63
|
+
initialFrameRef.current = initialFrame;
|
|
64
|
+
onFrameChangeRef.current = onFrameChange;
|
|
65
|
+
onBuildErrorsRef.current = onBuildErrors;
|
|
66
|
+
});
|
|
67
|
+
useEffect(() => {
|
|
68
|
+
const canvas = canvasRef.current;
|
|
69
|
+
if (!canvas || !canvasKit) return;
|
|
70
|
+
setTheme(theme);
|
|
71
|
+
const catalog = new AssetCatalog(assets);
|
|
72
|
+
const storage = new WebStorageAdapter(canvasKit, catalog, viewport, fps);
|
|
73
|
+
const measure = new WebMeasureScope(storage);
|
|
74
|
+
const audio = new WebAudioPlayer();
|
|
75
|
+
const clock = new WebMasterClock({
|
|
76
|
+
context: audio.getContext(),
|
|
77
|
+
fps
|
|
78
|
+
});
|
|
79
|
+
const renderContext = new WebRenderContext(canvasKit, storage);
|
|
80
|
+
renderContext.mount(canvas);
|
|
81
|
+
const pc = new PlaybackController({
|
|
82
|
+
renderContext,
|
|
83
|
+
measureScope: measure,
|
|
84
|
+
storageAdapter: storage,
|
|
85
|
+
masterClock: clock,
|
|
86
|
+
precomposition: new Precomp(scenes, viewport, fps, catalog, measure),
|
|
87
|
+
audioDevice: audio,
|
|
88
|
+
assets: catalog,
|
|
89
|
+
fps,
|
|
90
|
+
viewport,
|
|
91
|
+
scenes
|
|
92
|
+
});
|
|
93
|
+
if (pc.buildErrors.length > 0) onBuildErrorsRef.current?.(pc.buildErrors);
|
|
94
|
+
pc.onTime((t) => {
|
|
95
|
+
onFrameChangeRef.current?.(Math.trunc(t * fps));
|
|
96
|
+
setC(t);
|
|
97
|
+
});
|
|
98
|
+
controllerRef.current = pc;
|
|
99
|
+
setController(pc);
|
|
100
|
+
onLoadingChangeRef.current?.(true);
|
|
101
|
+
return () => {
|
|
102
|
+
controllerRef.current = null;
|
|
103
|
+
setController(null);
|
|
104
|
+
pc.dispose();
|
|
105
|
+
renderContext.dispose();
|
|
106
|
+
};
|
|
107
|
+
}, [
|
|
108
|
+
canvasKit,
|
|
109
|
+
assets,
|
|
110
|
+
viewport,
|
|
111
|
+
fps,
|
|
112
|
+
scenes,
|
|
113
|
+
theme
|
|
114
|
+
]);
|
|
115
|
+
useEffect(() => {
|
|
116
|
+
if (!controller || isPlaying) return;
|
|
117
|
+
let cancelled = false;
|
|
118
|
+
onLoadingChangeRef.current?.(true);
|
|
119
|
+
controller.seek(initialFrame).then(() => {
|
|
120
|
+
if (cancelled) return;
|
|
121
|
+
onLoadingChangeRef.current?.(false);
|
|
122
|
+
});
|
|
123
|
+
return () => {
|
|
124
|
+
cancelled = true;
|
|
125
|
+
};
|
|
126
|
+
}, [
|
|
127
|
+
controller,
|
|
128
|
+
isPlaying,
|
|
129
|
+
initialFrame
|
|
130
|
+
]);
|
|
131
|
+
useEffect(() => {
|
|
132
|
+
if (!controller) return;
|
|
133
|
+
if (isPlaying) controller.play(speed);
|
|
134
|
+
else controller.pause();
|
|
135
|
+
}, [
|
|
136
|
+
controller,
|
|
137
|
+
isPlaying,
|
|
138
|
+
speed
|
|
139
|
+
]);
|
|
140
|
+
useEffect(() => {
|
|
141
|
+
controller?.setMuted(muted);
|
|
142
|
+
}, [controller, muted]);
|
|
143
|
+
useImperativeHandle(ref, () => ({
|
|
144
|
+
screenshot: async () => controllerRef.current?.screenshot() ?? void 0,
|
|
145
|
+
renderFrame: async (f) => {
|
|
146
|
+
const pc = controllerRef.current;
|
|
147
|
+
if (!pc) return;
|
|
148
|
+
await pc.seek(f);
|
|
149
|
+
},
|
|
150
|
+
seekWhilePlaying: (f) => controllerRef.current?.seekWhilePlaying(f),
|
|
151
|
+
getTreeState: () => controllerRef.current?.getTreeState() ?? null,
|
|
152
|
+
getNodeProps: (nodeId) => controllerRef.current?.getNodeState(nodeId) ?? null,
|
|
153
|
+
getDuration: () => controllerRef.current?.totalDuration ?? 0,
|
|
154
|
+
getSceneDurations: () => controllerRef.current?.tracks.slice() ?? [],
|
|
155
|
+
getBuildErrors: () => controllerRef.current?.buildErrors ?? []
|
|
156
|
+
}), []);
|
|
157
|
+
return /* @__PURE__ */ jsx("div", {
|
|
158
|
+
style: {
|
|
159
|
+
position: "relative",
|
|
160
|
+
width: "100%",
|
|
161
|
+
height: "100%"
|
|
162
|
+
},
|
|
163
|
+
children: /* @__PURE__ */ jsx("canvas", {
|
|
164
|
+
ref: canvasRef,
|
|
165
|
+
width: viewport.width,
|
|
166
|
+
height: viewport.height,
|
|
167
|
+
style: {
|
|
168
|
+
display: isInitialized && controller ? "block" : "none",
|
|
169
|
+
width: "100%",
|
|
170
|
+
height: "100%"
|
|
171
|
+
}
|
|
172
|
+
})
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
//#endregion
|
|
176
|
+
export { MotionPlayer, MotionScriptProvider, useMotionScript };
|
|
177
|
+
|
|
178
|
+
//# sourceMappingURL=motion-script-react.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"motion-script-react.js","names":[],"sources":["../src/ui/provider.tsx","../src/ui/scene.tsx"],"sourcesContent":["import { createContext, useContext, useEffect, useState, type ReactNode } from \"react\";\r\nimport { getCanvasKit } from \"@motion-script/web\";\r\n\r\ntype CanvasKitInstance = Awaited<ReturnType<typeof getCanvasKit>>;\r\n\r\ntype MotionScriptContextValue = {\r\n /** The loaded CanvasKit instance, or `null` until initialization completes. */\r\n canvasKit: CanvasKitInstance | null;\r\n /** Whether {@link CanvasKitInstance} has finished loading and is ready to use. */\r\n isInitialized: boolean;\r\n};\r\n\r\nconst MotionScriptContext = createContext<MotionScriptContextValue | null>(null);\r\n\r\ntype Props = {\r\n /** Optional URL to the CanvasKit `.wasm` binary, forwarded to `getCanvasKit`. */\r\n wsmUrl?: string;\r\n children: ReactNode;\r\n};\r\n\r\n/**\r\n * Loads CanvasKit once and shares it with descendants via context.\r\n *\r\n * Must wrap any tree that renders {@link MotionPlayer}, since the player\r\n * reads its CanvasKit instance from this provider through {@link useMotionScript}.\r\n */\r\nexport function MotionScriptProvider({ wsmUrl, children }: Props) {\r\n const [canvasKit, setCanvasKit] = useState<CanvasKitInstance | null>(null);\r\n\r\n useEffect(() => {\r\n let cancelled = false;\r\n getCanvasKit(wsmUrl)\r\n .then((ck) => {\r\n if (!cancelled) setCanvasKit(ck);\r\n })\r\n .catch(console.error);\r\n return () => {\r\n cancelled = true;\r\n };\r\n }, [wsmUrl]);\r\n\r\n return (\r\n <MotionScriptContext.Provider value={{ canvasKit, isInitialized: canvasKit !== null }}>\r\n {children}\r\n </MotionScriptContext.Provider>\r\n );\r\n}\r\n\r\n/**\r\n * Reads the CanvasKit instance and readiness state from the nearest\r\n * {@link MotionScriptProvider}.\r\n *\r\n * @throws If called outside a {@link MotionScriptProvider}.\r\n */\r\nexport function useMotionScript(): MotionScriptContextValue {\r\n const ctx = useContext(MotionScriptContext);\r\n if (!ctx) {\r\n throw new Error(\"useMotionScript must be used inside a MotionScriptProvider\");\r\n }\r\n return ctx;\r\n}\r\n","import { useEffect, useImperativeHandle, useRef, useState, type Ref } from \"react\";\n\nimport {\n AssetCatalog,\n PlaybackController,\n setTheme,\n type AssetManifest,\n type BuildError,\n type Color,\n type Scene,\n type NodeState,\n type Size2D,\n type TreeState,\n Precomp,\n} from \"@motion-script/core\";\nimport {\n WebAudioPlayer,\n WebMasterClock,\n WebMeasureScope,\n WebRenderContext,\n WebStorageAdapter,\n} from \"@motion-script/web\";\nimport { useMotionScript } from \"./provider\";\n\ntype Props = {\n /** Imperative handle exposing playback and inspection methods; see {@link FrameHandle}. */\n ref?: Ref<FrameHandle>;\n /** Frame to seek to while paused. Ignored while `isPlaying` is true. */\n initialFrame: number;\n /** Whether the controller's clock should be running. */\n isPlaying: boolean;\n /** Frames per second used for playback, seeking, and time/frame conversion. */\n fps: number;\n /** Output canvas size in pixels. */\n viewport: Size2D;\n /** Scenes composed into a single timeline by an internal {@link Precomp}. */\n scenes: Scene[];\n /** Manifest describing the media assets referenced by `scenes`. */\n assets: AssetManifest;\n /** Theme color overrides applied globally before the player mounts. */\n theme?: Record<string, Color>;\n /** Playback rate multiplier passed to the controller. Defaults to `1`. */\n speed?: number;\n /** Whether audio output is muted. Defaults to `false`. */\n muted?: boolean;\n /** Called when the player starts/finishes loading (mount or seek). */\n onLoadingChange?: (loading: boolean) => void;\n /** Called on every clock tick with the current frame number. */\n onFrameChange?: (frame: number) => void;\n /** Called once on mount if the scene graph failed to build. */\n onBuildErrors?: (errors: BuildError[]) => void;\n};\n\n/** Imperative API exposed by {@link MotionPlayer} via its `ref`. */\nexport interface FrameHandle {\n /** Captures the current frame as a data URL, or `undefined` if not yet rendered. */\n screenshot: () => Promise<string | undefined>;\n /** Seeks to and renders a specific frame, resolving once the render completes. */\n renderFrame: (frame: number) => Promise<void>;\n /** Seeks to a frame without interrupting active playback. */\n seekWhilePlaying: (frame: number) => void;\n /** Returns the current state of a node by id, or `null` if not found. */\n getNodeProps: (nodeId: string) => NodeState | null;\n /** Returns a snapshot of the full scene tree state, or `null` before mount. */\n getTreeState: () => TreeState | null;\n /** Total duration of the composed timeline, in seconds. */\n getDuration: () => number;\n /** Duration of each individual scene, in seconds. */\n getSceneDurations: () => number[];\n /** Errors raised while building the scene graph, if any. */\n getBuildErrors: () => BuildError[];\n}\n\n/**\n * Renders a MotionScript scene graph to a canvas and drives playback.\n *\n * Must be rendered inside a {@link MotionScriptProvider}, which supplies the\n * shared CanvasKit instance this component depends on.\n */\nexport function MotionPlayer({\n ref,\n initialFrame,\n isPlaying,\n fps,\n viewport,\n scenes,\n assets,\n theme,\n speed = 1,\n muted = false,\n onLoadingChange,\n onFrameChange,\n onBuildErrors,\n}: Props) {\n const canvasRef = useRef<HTMLCanvasElement>(null);\n const { canvasKit, isInitialized } = useMotionScript();\n\n const [controller, setController] = useState<PlaybackController | null>(null);\n const controllerRef = useRef<PlaybackController | null>(null);\n\n const onLoadingChangeRef = useRef(onLoadingChange);\n // Force a re-render on each time tick; the value itself is unused.\n const [, setC] = useState(0);\n const initialFrameRef = useRef(initialFrame);\n const onFrameChangeRef = useRef(onFrameChange);\n const onBuildErrorsRef = useRef(onBuildErrors);\n\n // Keep callback/value refs current without touching them during render.\n useEffect(() => {\n onLoadingChangeRef.current = onLoadingChange;\n initialFrameRef.current = initialFrame;\n onFrameChangeRef.current = onFrameChange;\n onBuildErrorsRef.current = onBuildErrors;\n });\n\n useEffect(() => {\n const canvas = canvasRef.current;\n if (!canvas || !canvasKit) return;\n setTheme(theme);\n const catalog = new AssetCatalog(assets);\n const storage = new WebStorageAdapter(canvasKit, catalog, viewport, fps);\n const measure = new WebMeasureScope(storage);\n const audio = new WebAudioPlayer();\n const clock = new WebMasterClock({ context: audio.getContext(), fps });\n const renderContext = new WebRenderContext(canvasKit, storage);\n renderContext.mount(canvas);\n\n const pc: PlaybackController = new PlaybackController({\n renderContext,\n measureScope: measure,\n storageAdapter: storage,\n masterClock: clock,\n precomposition: new Precomp(scenes, viewport, fps, catalog, measure),\n audioDevice: audio,\n assets: catalog,\n fps,\n viewport,\n scenes,\n });\n\n if (pc.buildErrors.length > 0) {\n onBuildErrorsRef.current?.(pc.buildErrors);\n }\n\n pc.onTime((t: number) => {\n onFrameChangeRef.current?.(Math.trunc(t * fps));\n setC(t);\n });\n controllerRef.current = pc;\n setController(pc);\n\n onLoadingChangeRef.current?.(true);\n\n return () => {\n controllerRef.current = null;\n setController(null);\n pc.dispose();\n renderContext.dispose();\n };\n }, [canvasKit, assets, viewport, fps, scenes, theme]);\n\n // Apply initialFrame changes while paused (scrubbing). When playing, the\n // controller's own clock drives time, so we ignore prop-driven seeks.\n useEffect(() => {\n if (!controller || isPlaying) return;\n let cancelled = false;\n onLoadingChangeRef.current?.(true);\n controller.seek(initialFrame).then(() => {\n if (cancelled) return;\n onLoadingChangeRef.current?.(false);\n });\n return () => {\n cancelled = true;\n };\n }, [controller, isPlaying, initialFrame]);\n\n useEffect(() => {\n if (!controller) return;\n if (isPlaying) {\n controller.play(speed);\n } else {\n controller.pause();\n }\n }, [controller, isPlaying, speed]);\n\n useEffect(() => {\n controller?.setMuted(muted);\n }, [controller, muted]);\n\n useImperativeHandle(\n ref,\n () => ({\n screenshot: async () => controllerRef.current?.screenshot() ?? undefined,\n renderFrame: async (f: number) => {\n const pc = controllerRef.current;\n if (!pc) return;\n await pc.seek(f);\n },\n seekWhilePlaying: (f: number) => controllerRef.current?.seekWhilePlaying(f),\n getTreeState: () => controllerRef.current?.getTreeState() ?? null,\n getNodeProps: (nodeId: string) => controllerRef.current?.getNodeState(nodeId) ?? null,\n getDuration: () => controllerRef.current?.totalDuration ?? 0,\n getSceneDurations: () => controllerRef.current?.tracks.slice() ?? [],\n getBuildErrors: () => controllerRef.current?.buildErrors ?? [],\n }),\n [],\n );\n\n return (\n <div style={{ position: \"relative\", width: \"100%\", height: \"100%\" }}>\n <canvas\n ref={canvasRef}\n width={viewport.width}\n height={viewport.height}\n style={{\n display: isInitialized && controller ? \"block\" : \"none\",\n width: \"100%\",\n height: \"100%\",\n }}\n />\n </div>\n );\n}\n"],"mappings":";;;;;AAYA,IAAM,sBAAsB,cAA+C,IAAI;;;;;;;AAc/E,SAAgB,qBAAqB,EAAE,QAAQ,YAAmB;CAC9D,MAAM,CAAC,WAAW,gBAAgB,SAAmC,IAAI;CAEzE,gBAAgB;EACZ,IAAI,YAAY;EAChB,aAAa,MAAM,EACd,MAAM,OAAO;GACV,IAAI,CAAC,WAAW,aAAa,EAAE;EACnC,CAAC,EACA,MAAM,QAAQ,KAAK;EACxB,aAAa;GACT,YAAY;EAChB;CACJ,GAAG,CAAC,MAAM,CAAC;CAEX,OACI,oBAAC,oBAAoB,UAArB;EAA8B,OAAO;GAAE;GAAW,eAAe,cAAc;EAAK;EAC/E;CACyB,CAAA;AAEtC;;;;;;;AAQA,SAAgB,kBAA4C;CACxD,MAAM,MAAM,WAAW,mBAAmB;CAC1C,IAAI,CAAC,KACD,MAAM,IAAI,MAAM,4DAA4D;CAEhF,OAAO;AACX;;;;;;;;;ACmBA,SAAgB,aAAa,EACzB,KACA,cACA,WACA,KACA,UACA,QACA,QACA,OACA,QAAQ,GACR,QAAQ,OACR,iBACA,eACA,iBACM;CACN,MAAM,YAAY,OAA0B,IAAI;CAChD,MAAM,EAAE,WAAW,kBAAkB,gBAAgB;CAErD,MAAM,CAAC,YAAY,iBAAiB,SAAoC,IAAI;CAC5E,MAAM,gBAAgB,OAAkC,IAAI;CAE5D,MAAM,qBAAqB,OAAO,eAAe;CAEjD,MAAM,GAAG,QAAQ,SAAS,CAAC;CAC3B,MAAM,kBAAkB,OAAO,YAAY;CAC3C,MAAM,mBAAmB,OAAO,aAAa;CAC7C,MAAM,mBAAmB,OAAO,aAAa;CAG7C,gBAAgB;EACZ,mBAAmB,UAAU;EAC7B,gBAAgB,UAAU;EAC1B,iBAAiB,UAAU;EAC3B,iBAAiB,UAAU;CAC/B,CAAC;CAED,gBAAgB;EACZ,MAAM,SAAS,UAAU;EACzB,IAAI,CAAC,UAAU,CAAC,WAAW;EAC3B,SAAS,KAAK;EACd,MAAM,UAAU,IAAI,aAAa,MAAM;EACvC,MAAM,UAAU,IAAI,kBAAkB,WAAW,SAAS,UAAU,GAAG;EACvE,MAAM,UAAU,IAAI,gBAAgB,OAAO;EAC3C,MAAM,QAAQ,IAAI,eAAe;EACjC,MAAM,QAAQ,IAAI,eAAe;GAAE,SAAS,MAAM,WAAW;GAAG;EAAI,CAAC;EACrE,MAAM,gBAAgB,IAAI,iBAAiB,WAAW,OAAO;EAC7D,cAAc,MAAM,MAAM;EAE1B,MAAM,KAAyB,IAAI,mBAAmB;GAClD;GACA,cAAc;GACd,gBAAgB;GAChB,aAAa;GACb,gBAAgB,IAAI,QAAQ,QAAQ,UAAU,KAAK,SAAS,OAAO;GACnE,aAAa;GACb,QAAQ;GACR;GACA;GACA;EACJ,CAAC;EAED,IAAI,GAAG,YAAY,SAAS,GACxB,iBAAiB,UAAU,GAAG,WAAW;EAG7C,GAAG,QAAQ,MAAc;GACrB,iBAAiB,UAAU,KAAK,MAAM,IAAI,GAAG,CAAC;GAC9C,KAAK,CAAC;EACV,CAAC;EACD,cAAc,UAAU;EACxB,cAAc,EAAE;EAEhB,mBAAmB,UAAU,IAAI;EAEjC,aAAa;GACT,cAAc,UAAU;GACxB,cAAc,IAAI;GAClB,GAAG,QAAQ;GACX,cAAc,QAAQ;EAC1B;CACJ,GAAG;EAAC;EAAW;EAAQ;EAAU;EAAK;EAAQ;CAAK,CAAC;CAIpD,gBAAgB;EACZ,IAAI,CAAC,cAAc,WAAW;EAC9B,IAAI,YAAY;EAChB,mBAAmB,UAAU,IAAI;EACjC,WAAW,KAAK,YAAY,EAAE,WAAW;GACrC,IAAI,WAAW;GACf,mBAAmB,UAAU,KAAK;EACtC,CAAC;EACD,aAAa;GACT,YAAY;EAChB;CACJ,GAAG;EAAC;EAAY;EAAW;CAAY,CAAC;CAExC,gBAAgB;EACZ,IAAI,CAAC,YAAY;EACjB,IAAI,WACA,WAAW,KAAK,KAAK;OAErB,WAAW,MAAM;CAEzB,GAAG;EAAC;EAAY;EAAW;CAAK,CAAC;CAEjC,gBAAgB;EACZ,YAAY,SAAS,KAAK;CAC9B,GAAG,CAAC,YAAY,KAAK,CAAC;CAEtB,oBACI,YACO;EACH,YAAY,YAAY,cAAc,SAAS,WAAW,KAAK,KAAA;EAC/D,aAAa,OAAO,MAAc;GAC9B,MAAM,KAAK,cAAc;GACzB,IAAI,CAAC,IAAI;GACT,MAAM,GAAG,KAAK,CAAC;EACnB;EACA,mBAAmB,MAAc,cAAc,SAAS,iBAAiB,CAAC;EAC1E,oBAAoB,cAAc,SAAS,aAAa,KAAK;EAC7D,eAAe,WAAmB,cAAc,SAAS,aAAa,MAAM,KAAK;EACjF,mBAAmB,cAAc,SAAS,iBAAiB;EAC3D,yBAAyB,cAAc,SAAS,OAAO,MAAM,KAAK,CAAC;EACnE,sBAAsB,cAAc,SAAS,eAAe,CAAC;CACjE,IACA,CAAC,CACL;CAEA,OACI,oBAAC,OAAD;EAAK,OAAO;GAAE,UAAU;GAAY,OAAO;GAAQ,QAAQ;EAAO;YAC9D,oBAAC,UAAD;GACI,KAAK;GACL,OAAO,SAAS;GAChB,QAAQ,SAAS;GACjB,OAAO;IACH,SAAS,iBAAiB,aAAa,UAAU;IACjD,OAAO;IACP,QAAQ;GACZ;EACH,CAAA;CACA,CAAA;AAEb"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { ReactNode } from 'react';
|
|
2
|
+
import { getCanvasKit } from '@motion-script/web';
|
|
3
|
+
type CanvasKitInstance = Awaited<ReturnType<typeof getCanvasKit>>;
|
|
4
|
+
type MotionScriptContextValue = {
|
|
5
|
+
/** The loaded CanvasKit instance, or `null` until initialization completes. */
|
|
6
|
+
canvasKit: CanvasKitInstance | null;
|
|
7
|
+
/** Whether {@link CanvasKitInstance} has finished loading and is ready to use. */
|
|
8
|
+
isInitialized: boolean;
|
|
9
|
+
};
|
|
10
|
+
type Props = {
|
|
11
|
+
/** Optional URL to the CanvasKit `.wasm` binary, forwarded to `getCanvasKit`. */
|
|
12
|
+
wsmUrl?: string;
|
|
13
|
+
children: ReactNode;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Loads CanvasKit once and shares it with descendants via context.
|
|
17
|
+
*
|
|
18
|
+
* Must wrap any tree that renders {@link MotionPlayer}, since the player
|
|
19
|
+
* reads its CanvasKit instance from this provider through {@link useMotionScript}.
|
|
20
|
+
*/
|
|
21
|
+
export declare function MotionScriptProvider({ wsmUrl, children }: Props): import("react").JSX.Element;
|
|
22
|
+
/**
|
|
23
|
+
* Reads the CanvasKit instance and readiness state from the nearest
|
|
24
|
+
* {@link MotionScriptProvider}.
|
|
25
|
+
*
|
|
26
|
+
* @throws If called outside a {@link MotionScriptProvider}.
|
|
27
|
+
*/
|
|
28
|
+
export declare function useMotionScript(): MotionScriptContextValue;
|
|
29
|
+
export {};
|
|
30
|
+
//# sourceMappingURL=provider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../../src/ui/provider.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAkD,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AACvF,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAElD,KAAK,iBAAiB,GAAG,OAAO,CAAC,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC,CAAC;AAElE,KAAK,wBAAwB,GAAG;IAC5B,+EAA+E;IAC/E,SAAS,EAAE,iBAAiB,GAAG,IAAI,CAAC;IACpC,kFAAkF;IAClF,aAAa,EAAE,OAAO,CAAC;CAC1B,CAAC;AAIF,KAAK,KAAK,GAAG;IACT,iFAAiF;IACjF,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,SAAS,CAAC;CACvB,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,EAAE,KAAK,+BAoB/D;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,IAAI,wBAAwB,CAM1D"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { Ref } from 'react';
|
|
2
|
+
import { AssetManifest, BuildError, Color, Scene, NodeState, Size2D, TreeState } from '@motion-script/core';
|
|
3
|
+
type Props = {
|
|
4
|
+
/** Imperative handle exposing playback and inspection methods; see {@link FrameHandle}. */
|
|
5
|
+
ref?: Ref<FrameHandle>;
|
|
6
|
+
/** Frame to seek to while paused. Ignored while `isPlaying` is true. */
|
|
7
|
+
initialFrame: number;
|
|
8
|
+
/** Whether the controller's clock should be running. */
|
|
9
|
+
isPlaying: boolean;
|
|
10
|
+
/** Frames per second used for playback, seeking, and time/frame conversion. */
|
|
11
|
+
fps: number;
|
|
12
|
+
/** Output canvas size in pixels. */
|
|
13
|
+
viewport: Size2D;
|
|
14
|
+
/** Scenes composed into a single timeline by an internal {@link Precomp}. */
|
|
15
|
+
scenes: Scene[];
|
|
16
|
+
/** Manifest describing the media assets referenced by `scenes`. */
|
|
17
|
+
assets: AssetManifest;
|
|
18
|
+
/** Theme color overrides applied globally before the player mounts. */
|
|
19
|
+
theme?: Record<string, Color>;
|
|
20
|
+
/** Playback rate multiplier passed to the controller. Defaults to `1`. */
|
|
21
|
+
speed?: number;
|
|
22
|
+
/** Whether audio output is muted. Defaults to `false`. */
|
|
23
|
+
muted?: boolean;
|
|
24
|
+
/** Called when the player starts/finishes loading (mount or seek). */
|
|
25
|
+
onLoadingChange?: (loading: boolean) => void;
|
|
26
|
+
/** Called on every clock tick with the current frame number. */
|
|
27
|
+
onFrameChange?: (frame: number) => void;
|
|
28
|
+
/** Called once on mount if the scene graph failed to build. */
|
|
29
|
+
onBuildErrors?: (errors: BuildError[]) => void;
|
|
30
|
+
};
|
|
31
|
+
/** Imperative API exposed by {@link MotionPlayer} via its `ref`. */
|
|
32
|
+
export interface FrameHandle {
|
|
33
|
+
/** Captures the current frame as a data URL, or `undefined` if not yet rendered. */
|
|
34
|
+
screenshot: () => Promise<string | undefined>;
|
|
35
|
+
/** Seeks to and renders a specific frame, resolving once the render completes. */
|
|
36
|
+
renderFrame: (frame: number) => Promise<void>;
|
|
37
|
+
/** Seeks to a frame without interrupting active playback. */
|
|
38
|
+
seekWhilePlaying: (frame: number) => void;
|
|
39
|
+
/** Returns the current state of a node by id, or `null` if not found. */
|
|
40
|
+
getNodeProps: (nodeId: string) => NodeState | null;
|
|
41
|
+
/** Returns a snapshot of the full scene tree state, or `null` before mount. */
|
|
42
|
+
getTreeState: () => TreeState | null;
|
|
43
|
+
/** Total duration of the composed timeline, in seconds. */
|
|
44
|
+
getDuration: () => number;
|
|
45
|
+
/** Duration of each individual scene, in seconds. */
|
|
46
|
+
getSceneDurations: () => number[];
|
|
47
|
+
/** Errors raised while building the scene graph, if any. */
|
|
48
|
+
getBuildErrors: () => BuildError[];
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Renders a MotionScript scene graph to a canvas and drives playback.
|
|
52
|
+
*
|
|
53
|
+
* Must be rendered inside a {@link MotionScriptProvider}, which supplies the
|
|
54
|
+
* shared CanvasKit instance this component depends on.
|
|
55
|
+
*/
|
|
56
|
+
export declare function MotionPlayer({ ref, initialFrame, isPlaying, fps, viewport, scenes, assets, theme, speed, muted, onLoadingChange, onFrameChange, onBuildErrors, }: Props): import("react").JSX.Element;
|
|
57
|
+
export {};
|
|
58
|
+
//# sourceMappingURL=scene.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scene.d.ts","sourceRoot":"","sources":["../../src/ui/scene.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAoD,KAAK,GAAG,EAAE,MAAM,OAAO,CAAC;AAEnF,OAAO,EAIH,KAAK,aAAa,EAClB,KAAK,UAAU,EACf,KAAK,KAAK,EACV,KAAK,KAAK,EACV,KAAK,SAAS,EACd,KAAK,MAAM,EACX,KAAK,SAAS,EAEjB,MAAM,qBAAqB,CAAC;AAU7B,KAAK,KAAK,GAAG;IACT,2FAA2F;IAC3F,GAAG,CAAC,EAAE,GAAG,CAAC,WAAW,CAAC,CAAC;IACvB,wEAAwE;IACxE,YAAY,EAAE,MAAM,CAAC;IACrB,wDAAwD;IACxD,SAAS,EAAE,OAAO,CAAC;IACnB,+EAA+E;IAC/E,GAAG,EAAE,MAAM,CAAC;IACZ,oCAAoC;IACpC,QAAQ,EAAE,MAAM,CAAC;IACjB,6EAA6E;IAC7E,MAAM,EAAE,KAAK,EAAE,CAAC;IAChB,mEAAmE;IACnE,MAAM,EAAE,aAAa,CAAC;IACtB,uEAAuE;IACvE,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IAC9B,0EAA0E;IAC1E,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,0DAA0D;IAC1D,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,sEAAsE;IACtE,eAAe,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;IAC7C,gEAAgE;IAChE,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,+DAA+D;IAC/D,aAAa,CAAC,EAAE,CAAC,MAAM,EAAE,UAAU,EAAE,KAAK,IAAI,CAAC;CAClD,CAAC;AAEF,oEAAoE;AACpE,MAAM,WAAW,WAAW;IACxB,oFAAoF;IACpF,UAAU,EAAE,MAAM,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;IAC9C,kFAAkF;IAClF,WAAW,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9C,6DAA6D;IAC7D,gBAAgB,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1C,yEAAyE;IACzE,YAAY,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,SAAS,GAAG,IAAI,CAAC;IACnD,+EAA+E;IAC/E,YAAY,EAAE,MAAM,SAAS,GAAG,IAAI,CAAC;IACrC,2DAA2D;IAC3D,WAAW,EAAE,MAAM,MAAM,CAAC;IAC1B,qDAAqD;IACrD,iBAAiB,EAAE,MAAM,MAAM,EAAE,CAAC;IAClC,4DAA4D;IAC5D,cAAc,EAAE,MAAM,UAAU,EAAE,CAAC;CACtC;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,EACzB,GAAG,EACH,YAAY,EACZ,SAAS,EACT,GAAG,EACH,QAAQ,EACR,MAAM,EACN,MAAM,EACN,KAAK,EACL,KAAS,EACT,KAAa,EACb,eAAe,EACf,aAAa,EACb,aAAa,GAChB,EAAE,KAAK,+BAiIP"}
|
package/package.json
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@motion-script/react",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "React bindings for Motion Script.",
|
|
6
|
+
"license": "Apache-2.0",
|
|
7
|
+
"sideEffects": false,
|
|
8
|
+
"publishConfig": {
|
|
9
|
+
"access": "public"
|
|
10
|
+
},
|
|
11
|
+
"scripts": {
|
|
12
|
+
"dev": "vite",
|
|
13
|
+
"predev": "node ../../scripts/copy-wasm.js",
|
|
14
|
+
"build": "eslint . && tsc -b && vite build",
|
|
15
|
+
"lint": "eslint .",
|
|
16
|
+
"typecheck": "tsc -b",
|
|
17
|
+
"clean": "rimraf --glob dist .turbo *.tsbuildinfo && tsc -b --clean",
|
|
18
|
+
"preview": "vite preview"
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
"dist"
|
|
22
|
+
],
|
|
23
|
+
"module": "./dist/motion-script-react.js",
|
|
24
|
+
"types": "./dist/index.d.ts",
|
|
25
|
+
"exports": {
|
|
26
|
+
".": {
|
|
27
|
+
"types": "./dist/index.d.ts",
|
|
28
|
+
"import": "./dist/motion-script-react.js"
|
|
29
|
+
},
|
|
30
|
+
"./jsx/jsx-runtime": {
|
|
31
|
+
"types": "./dist/jsx/jsx-runtime.d.ts",
|
|
32
|
+
"import": "./dist/jsx/jsx-runtime.js"
|
|
33
|
+
},
|
|
34
|
+
"./jsx/jsx-dev-runtime": {
|
|
35
|
+
"types": "./dist/jsx/jsx-dev-runtime.d.ts",
|
|
36
|
+
"import": "./dist/jsx/jsx-dev-runtime.js"
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
"peerDependencies": {
|
|
40
|
+
"react": "catalog:",
|
|
41
|
+
"react-dom": "catalog:"
|
|
42
|
+
},
|
|
43
|
+
"dependencies": {
|
|
44
|
+
"@motion-script/core": "workspace:*",
|
|
45
|
+
"@motion-script/web": "workspace:*"
|
|
46
|
+
},
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"@eslint/js": "^9.39.1",
|
|
49
|
+
"@types/node": "catalog:",
|
|
50
|
+
"@types/react": "catalog:",
|
|
51
|
+
"@types/react-dom": "catalog:",
|
|
52
|
+
"@vitejs/plugin-react": "catalog:",
|
|
53
|
+
"@motion-script/canvaskit": "workspace:*",
|
|
54
|
+
"eslint": "^9.39.1",
|
|
55
|
+
"eslint-plugin-react-hooks": "^7.0.1",
|
|
56
|
+
"eslint-plugin-react-refresh": "^0.4.24",
|
|
57
|
+
"globals": "^16.5.0",
|
|
58
|
+
"react": "catalog:",
|
|
59
|
+
"react-dom": "catalog:",
|
|
60
|
+
"typescript": "catalog:",
|
|
61
|
+
"typescript-eslint": "^8.48.0",
|
|
62
|
+
"vite": "catalog:",
|
|
63
|
+
"vite-plugin-dts": "catalog:"
|
|
64
|
+
}
|
|
65
|
+
}
|