@motionscript/browser 0.0.0-stage → 0.1.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (120) hide show
  1. package/CHANGELOG.md +5 -0
  2. package/LICENSE +201 -0
  3. package/README.md +68 -3
  4. package/dist/asset-bytes.d.ts +50 -0
  5. package/dist/asset-bytes.d.ts.map +1 -0
  6. package/dist/asset-bytes.js +60 -0
  7. package/dist/asset-bytes.js.map +1 -0
  8. package/dist/audio/bus-graph.d.ts +43 -0
  9. package/dist/audio/bus-graph.d.ts.map +1 -0
  10. package/dist/audio/bus-graph.js +104 -0
  11. package/dist/audio/bus-graph.js.map +1 -0
  12. package/dist/audio/filter-graph.d.ts +58 -0
  13. package/dist/audio/filter-graph.d.ts.map +1 -0
  14. package/dist/audio/filter-graph.js +235 -0
  15. package/dist/audio/filter-graph.js.map +1 -0
  16. package/dist/audio/index.d.ts +5 -0
  17. package/dist/audio/index.d.ts.map +1 -0
  18. package/dist/audio/index.js +12 -0
  19. package/dist/audio/index.js.map +1 -0
  20. package/dist/audio/mixer.d.ts +59 -0
  21. package/dist/audio/mixer.d.ts.map +1 -0
  22. package/dist/audio/mixer.js +193 -0
  23. package/dist/audio/mixer.js.map +1 -0
  24. package/dist/audio/player.d.ts +49 -0
  25. package/dist/audio/player.d.ts.map +1 -0
  26. package/dist/audio/player.js +226 -0
  27. package/dist/audio/player.js.map +1 -0
  28. package/dist/browser/audio.js +2 -0
  29. package/dist/browser/audio.js.map +7 -0
  30. package/dist/browser/chunks/GLTFLoader-PGXD4V5B.js +2 -0
  31. package/dist/browser/chunks/GLTFLoader-PGXD4V5B.js.map +7 -0
  32. package/dist/browser/chunks/SkeletonUtils-XYDMTGTM.js +2 -0
  33. package/dist/browser/chunks/SkeletonUtils-XYDMTGTM.js.map +7 -0
  34. package/dist/browser/chunks/chunk-CTLBXZSU.js +4119 -0
  35. package/dist/browser/chunks/chunk-CTLBXZSU.js.map +7 -0
  36. package/dist/browser/chunks/chunk-FQ32PDIU.js +2 -0
  37. package/dist/browser/chunks/chunk-FQ32PDIU.js.map +7 -0
  38. package/dist/browser/chunks/chunk-FY3BTKY4.js +2 -0
  39. package/dist/browser/chunks/chunk-FY3BTKY4.js.map +7 -0
  40. package/dist/browser/chunks/chunk-NSFLRI4P.js +2 -0
  41. package/dist/browser/chunks/chunk-NSFLRI4P.js.map +7 -0
  42. package/dist/browser/chunks/three.module-Z4EOVUWF.js +2 -0
  43. package/dist/browser/chunks/three.module-Z4EOVUWF.js.map +7 -0
  44. package/dist/browser/esbuild.wasm +0 -0
  45. package/dist/browser/index.js +2172 -0
  46. package/dist/browser/index.js.map +7 -0
  47. package/dist/browser/manifest.json +15 -0
  48. package/dist/concat.d.ts +95 -0
  49. package/dist/concat.d.ts.map +1 -0
  50. package/dist/concat.js +174 -0
  51. package/dist/concat.js.map +1 -0
  52. package/dist/decode/image-levels.d.ts +137 -0
  53. package/dist/decode/image-levels.d.ts.map +1 -0
  54. package/dist/decode/image-levels.js +477 -0
  55. package/dist/decode/image-levels.js.map +1 -0
  56. package/dist/decode/levels.d.ts +37 -0
  57. package/dist/decode/levels.d.ts.map +1 -0
  58. package/dist/decode/levels.js +68 -0
  59. package/dist/decode/levels.js.map +1 -0
  60. package/dist/decode/video-levels.d.ts +37 -0
  61. package/dist/decode/video-levels.d.ts.map +1 -0
  62. package/dist/decode/video-levels.js +95 -0
  63. package/dist/decode/video-levels.js.map +1 -0
  64. package/dist/dynamic-nodes.d.ts +9 -0
  65. package/dist/dynamic-nodes.d.ts.map +1 -0
  66. package/dist/dynamic-nodes.js +56 -0
  67. package/dist/dynamic-nodes.js.map +1 -0
  68. package/dist/engine.d.ts +59 -0
  69. package/dist/engine.d.ts.map +1 -0
  70. package/dist/engine.js +171 -0
  71. package/dist/engine.js.map +1 -0
  72. package/dist/exporter.d.ts +163 -0
  73. package/dist/exporter.d.ts.map +1 -0
  74. package/dist/exporter.js +263 -0
  75. package/dist/exporter.js.map +1 -0
  76. package/dist/getter.d.ts +10 -0
  77. package/dist/getter.d.ts.map +1 -0
  78. package/dist/getter.js +38 -0
  79. package/dist/getter.js.map +1 -0
  80. package/dist/index.d.ts +22 -0
  81. package/dist/index.d.ts.map +1 -0
  82. package/dist/index.js +72 -0
  83. package/dist/index.js.map +1 -0
  84. package/dist/measure-context.d.ts +12 -0
  85. package/dist/measure-context.d.ts.map +1 -0
  86. package/dist/measure-context.js +12 -0
  87. package/dist/measure-context.js.map +1 -0
  88. package/dist/mix-timeline-audio.d.ts +42 -0
  89. package/dist/mix-timeline-audio.d.ts.map +1 -0
  90. package/dist/mix-timeline-audio.js +63 -0
  91. package/dist/mix-timeline-audio.js.map +1 -0
  92. package/dist/output.d.ts +7 -0
  93. package/dist/output.d.ts.map +1 -0
  94. package/dist/output.js +6 -0
  95. package/dist/output.js.map +1 -0
  96. package/dist/render-context.d.ts +30 -0
  97. package/dist/render-context.d.ts.map +1 -0
  98. package/dist/render-context.js +55 -0
  99. package/dist/render-context.js.map +1 -0
  100. package/dist/screenshot.d.ts +45 -0
  101. package/dist/screenshot.d.ts.map +1 -0
  102. package/dist/screenshot.js +32 -0
  103. package/dist/screenshot.js.map +1 -0
  104. package/dist/still.d.ts +151 -0
  105. package/dist/still.d.ts.map +1 -0
  106. package/dist/still.js +222 -0
  107. package/dist/still.js.map +1 -0
  108. package/dist/storage-adapter.d.ts +367 -0
  109. package/dist/storage-adapter.d.ts.map +1 -0
  110. package/dist/storage-adapter.js +1278 -0
  111. package/dist/storage-adapter.js.map +1 -0
  112. package/dist/three/renderer.d.ts +48 -0
  113. package/dist/three/renderer.d.ts.map +1 -0
  114. package/dist/three/renderer.js +177 -0
  115. package/dist/three/renderer.js.map +1 -0
  116. package/dist/ticker.d.ts +42 -0
  117. package/dist/ticker.d.ts.map +1 -0
  118. package/dist/ticker.js +143 -0
  119. package/dist/ticker.js.map +1 -0
  120. package/package.json +74 -3
package/dist/ticker.js ADDED
@@ -0,0 +1,143 @@
1
+ import { Ticker } from "@motionscript/core";
2
+ /**
3
+ * Ticker backed by a Web Audio AudioContext. Scene time advances in
4
+ * lock-step with `audioContext.currentTime`, so any AudioBufferSourceNodes
5
+ * scheduled on the same context play perfectly in sync.
6
+ *
7
+ * When paused we suspend the context (audio stops, currentTime freezes);
8
+ * when playing we resume and a rAF loop publishes ticks at the target fps.
9
+ *
10
+ * A single silent buffer covering the full timeline is started on play(),
11
+ * which keeps the context active and gives the browser a stable audio
12
+ * graph to schedule against even on scenes that contain no audio nodes.
13
+ */
14
+ export class WebTicker extends Ticker {
15
+ context;
16
+ ownsContext;
17
+ fps;
18
+ playStartCtxTime = 0;
19
+ seekOffset = 0;
20
+ speed = 1;
21
+ reverse = false;
22
+ rafId = null;
23
+ silentSource = null;
24
+ constructor(opts) {
25
+ super();
26
+ this.context = opts.context ?? new AudioContext();
27
+ this.ownsContext = !opts.context;
28
+ this.fps = opts.fps;
29
+ }
30
+ getContext() {
31
+ return this.context;
32
+ }
33
+ seek(t) {
34
+ const clamped = Math.max(0, Math.min(t, this.duration));
35
+ this.seekOffset = clamped;
36
+ this.playStartCtxTime = this.context.currentTime;
37
+ this.setCurrentTime(clamped);
38
+ }
39
+ /** Resumes the (possibly suspended) context, anchors elapsed-time tracking to the current `context.currentTime`, and starts the silent ticker + rAF tick loop. */
40
+ async play(speed = 1, reverse = false) {
41
+ this.speed = speed;
42
+ this.reverse = reverse;
43
+ this.seekOffset = this.currentTime;
44
+ if (this.context.state === "suspended") {
45
+ await this.context.resume();
46
+ }
47
+ this.playStartCtxTime = this.context.currentTime;
48
+ this.startSilentTicker();
49
+ super.play(speed, reverse);
50
+ this.startLoop();
51
+ }
52
+ /** Stops the tick loop and silent ticker, then suspends the context — freezing `currentTime` so elapsed-time math stays correct across the pause. */
53
+ pause() {
54
+ this.seekOffset = this.currentTime;
55
+ this.stopLoop();
56
+ this.stopSilentTicker();
57
+ if (this.context.state === "running") {
58
+ this.context.suspend();
59
+ }
60
+ super.pause();
61
+ }
62
+ /** (Re)starts the silent buffer source covering the remaining timeline — keeps the AudioContext active and gives a stable graph for other AudioBufferSourceNodes to schedule against, per the class-level note. */
63
+ startSilentTicker() {
64
+ this.stopSilentTicker();
65
+ const remaining = Math.max(0.001, this.duration - this.seekOffset);
66
+ const sampleRate = this.context.sampleRate;
67
+ const buffer = this.context.createBuffer(1, Math.ceil(remaining * sampleRate), sampleRate);
68
+ const source = this.context.createBufferSource();
69
+ source.buffer = buffer;
70
+ source.connect(this.context.destination);
71
+ source.start();
72
+ this.silentSource = source;
73
+ }
74
+ stopSilentTicker() {
75
+ if (this.silentSource) {
76
+ try {
77
+ this.silentSource.onended = null;
78
+ this.silentSource.stop();
79
+ this.silentSource.disconnect();
80
+ }
81
+ catch {
82
+ // already stopped
83
+ }
84
+ this.silentSource = null;
85
+ }
86
+ }
87
+ /** Drives ticks off `requestAnimationFrame`, deriving scene time from elapsed `context.currentTime` (so it stays sample-accurate) and only firing `tick()` when a full frame interval has elapsed. */
88
+ startLoop() {
89
+ if (this.rafId !== null)
90
+ return;
91
+ const frameDt = 1 / this.fps;
92
+ let lastTickTime = this.currentTime;
93
+ const loop = async () => {
94
+ const elapsed = (this.context.currentTime - this.playStartCtxTime) * this.speed;
95
+ const next = this.reverse
96
+ ? this.seekOffset - elapsed
97
+ : this.seekOffset + elapsed;
98
+ const clamped = Math.max(0, Math.min(next, this.duration));
99
+ const ended = this.reverse ? clamped <= 0 : clamped >= this.duration;
100
+ // Whether this animation frame has carried the playhead onto a new
101
+ // *scene* frame. This loop runs at the display's refresh rate, which
102
+ // on a 120 or 144Hz panel is two or three times the rate anything is
103
+ // rendered at — so the clock advances every time round (the elapsed
104
+ // maths in `play`/`pause` depend on it being exact) while listeners
105
+ // hear about it once per frame. The end of the run always reports,
106
+ // whether or not it landed on a boundary, or the final position would
107
+ // never reach a host. See `Ticker.setCurrentTime`.
108
+ const crossed = Math.abs(clamped - lastTickTime) >= frameDt;
109
+ this.setCurrentTime(clamped, crossed || ended);
110
+ if (crossed) {
111
+ lastTickTime = clamped;
112
+ // Await so that a slow tick (asset load on a scene
113
+ // boundary) doesn't queue a second concurrent tick.
114
+ await this.tick();
115
+ }
116
+ if (ended) {
117
+ this.rafId = null;
118
+ return;
119
+ }
120
+ // If pause() cleared the rafId while we were awaiting tick(),
121
+ // do not resume the loop.
122
+ if (this.rafId === null)
123
+ return;
124
+ this.rafId = requestAnimationFrame(loop);
125
+ };
126
+ this.rafId = requestAnimationFrame(loop);
127
+ }
128
+ stopLoop() {
129
+ if (this.rafId !== null) {
130
+ cancelAnimationFrame(this.rafId);
131
+ this.rafId = null;
132
+ }
133
+ }
134
+ dispose() {
135
+ this.stopLoop();
136
+ this.stopSilentTicker();
137
+ if (this.ownsContext) {
138
+ this.context.close();
139
+ }
140
+ super.dispose();
141
+ }
142
+ }
143
+ //# sourceMappingURL=ticker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ticker.js","sourceRoot":"","sources":["../src/ticker.ts"],"names":[],"mappings":"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","sourcesContent":["import { Ticker } from \"@motionscript/core\";\n\n/**\n * Ticker backed by a Web Audio AudioContext. Scene time advances in\n * lock-step with `audioContext.currentTime`, so any AudioBufferSourceNodes\n * scheduled on the same context play perfectly in sync.\n *\n * When paused we suspend the context (audio stops, currentTime freezes);\n * when playing we resume and a rAF loop publishes ticks at the target fps.\n *\n * A single silent buffer covering the full timeline is started on play(),\n * which keeps the context active and gives the browser a stable audio\n * graph to schedule against even on scenes that contain no audio nodes.\n */\nexport class WebTicker extends Ticker {\n private context: AudioContext;\n private ownsContext: boolean;\n private fps: number;\n\n private playStartCtxTime: number = 0;\n private seekOffset: number = 0;\n private speed: number = 1;\n private reverse: boolean = false;\n\n private rafId: number | null = null;\n private silentSource: AudioBufferSourceNode | null = null;\n\n constructor(opts: { context?: AudioContext; fps: number }) {\n super();\n this.context = opts.context ?? new AudioContext();\n this.ownsContext = !opts.context;\n this.fps = opts.fps;\n }\n\n getContext(): AudioContext {\n return this.context;\n }\n\n seek(t: number): void {\n const clamped = Math.max(0, Math.min(t, this.duration));\n this.seekOffset = clamped;\n this.playStartCtxTime = this.context.currentTime;\n this.setCurrentTime(clamped);\n }\n\n /** Resumes the (possibly suspended) context, anchors elapsed-time tracking to the current `context.currentTime`, and starts the silent ticker + rAF tick loop. */\n async play(speed: number = 1, reverse: boolean = false): Promise<void> {\n this.speed = speed;\n this.reverse = reverse;\n this.seekOffset = this.currentTime;\n\n if (this.context.state === \"suspended\") {\n await this.context.resume();\n }\n\n this.playStartCtxTime = this.context.currentTime;\n this.startSilentTicker();\n super.play(speed, reverse);\n this.startLoop();\n }\n\n /** Stops the tick loop and silent ticker, then suspends the context — freezing `currentTime` so elapsed-time math stays correct across the pause. */\n pause(): void {\n this.seekOffset = this.currentTime;\n this.stopLoop();\n this.stopSilentTicker();\n if (this.context.state === \"running\") {\n this.context.suspend();\n }\n super.pause();\n }\n\n /** (Re)starts the silent buffer source covering the remaining timeline — keeps the AudioContext active and gives a stable graph for other AudioBufferSourceNodes to schedule against, per the class-level note. */\n private startSilentTicker(): void {\n this.stopSilentTicker();\n const remaining = Math.max(0.001, this.duration - this.seekOffset);\n const sampleRate = this.context.sampleRate;\n const buffer = this.context.createBuffer(1, Math.ceil(remaining * sampleRate), sampleRate);\n const source = this.context.createBufferSource();\n source.buffer = buffer;\n source.connect(this.context.destination);\n source.start();\n this.silentSource = source;\n }\n\n private stopSilentTicker(): void {\n if (this.silentSource) {\n try {\n this.silentSource.onended = null;\n this.silentSource.stop();\n this.silentSource.disconnect();\n } catch {\n // already stopped\n }\n this.silentSource = null;\n }\n }\n\n /** Drives ticks off `requestAnimationFrame`, deriving scene time from elapsed `context.currentTime` (so it stays sample-accurate) and only firing `tick()` when a full frame interval has elapsed. */\n private startLoop(): void {\n if (this.rafId !== null) return;\n const frameDt = 1 / this.fps;\n let lastTickTime = this.currentTime;\n\n const loop = async () => {\n const elapsed = (this.context.currentTime - this.playStartCtxTime) * this.speed;\n const next = this.reverse\n ? this.seekOffset - elapsed\n : this.seekOffset + elapsed;\n const clamped = Math.max(0, Math.min(next, this.duration));\n const ended = this.reverse ? clamped <= 0 : clamped >= this.duration;\n // Whether this animation frame has carried the playhead onto a new\n // *scene* frame. This loop runs at the display's refresh rate, which\n // on a 120 or 144Hz panel is two or three times the rate anything is\n // rendered at — so the clock advances every time round (the elapsed\n // maths in `play`/`pause` depend on it being exact) while listeners\n // hear about it once per frame. The end of the run always reports,\n // whether or not it landed on a boundary, or the final position would\n // never reach a host. See `Ticker.setCurrentTime`.\n const crossed = Math.abs(clamped - lastTickTime) >= frameDt;\n this.setCurrentTime(clamped, crossed || ended);\n\n if (crossed) {\n lastTickTime = clamped;\n // Await so that a slow tick (asset load on a scene\n // boundary) doesn't queue a second concurrent tick.\n await this.tick();\n }\n\n if (ended) {\n this.rafId = null;\n return;\n }\n // If pause() cleared the rafId while we were awaiting tick(),\n // do not resume the loop.\n if (this.rafId === null) return;\n this.rafId = requestAnimationFrame(loop);\n };\n\n this.rafId = requestAnimationFrame(loop);\n }\n\n private stopLoop(): void {\n if (this.rafId !== null) {\n cancelAnimationFrame(this.rafId);\n this.rafId = null;\n }\n }\n\n dispose(): void {\n this.stopLoop();\n this.stopSilentTicker();\n if (this.ownsContext) {\n this.context.close();\n }\n super.dispose();\n }\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,77 @@
1
1
  {
2
2
  "name": "@motionscript/browser",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.1.0-alpha.0",
4
+ "type": "module",
5
+ "description": "Browser rendering backend for Motion Script, powered by Skia/CanvasKit.",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/motionscript-dev/motionscript.git",
9
+ "directory": "packages/browser"
10
+ },
11
+ "homepage": "https://motionscript.dev",
12
+ "bugs": {
13
+ "url": "https://github.com/motionscript-dev/motionscript/issues"
14
+ },
15
+ "sideEffects": false,
16
+ "keywords": [],
17
+ "license": "Apache-2.0",
18
+ "publishConfig": {
19
+ "access": "public"
20
+ },
21
+ "main": "dist/index.js",
22
+ "types": "dist/index.d.ts",
23
+ "files": [
24
+ "dist",
25
+ "CHANGELOG.md",
26
+ "!dist/**/*.tsbuildinfo"
27
+ ],
28
+ "exports": {
29
+ ".": {
30
+ "types": "./dist/index.d.ts",
31
+ "default": "./dist/index.js"
32
+ },
33
+ "./audio": {
34
+ "types": "./dist/audio/index.d.ts",
35
+ "default": "./dist/audio/index.js"
36
+ }
37
+ },
38
+ "peerDependencies": {
39
+ "@motionscript/canvaskit": "^0.1.0-alpha.0",
40
+ "@motionscript/core": "^0.1.0-alpha.0"
41
+ },
42
+ "devDependencies": {
43
+ "@types/three": "0.185.1",
44
+ "@vitest/browser": "4.1.7",
45
+ "@vitest/browser-playwright": "^4.1.7",
46
+ "concurrently": "^9.0.0",
47
+ "eslint": "^9.39.4",
48
+ "eslint-import-resolver-typescript": "^4.4.4",
49
+ "eslint-plugin-import-x": "^4.16.1",
50
+ "playwright": "^1.60.0",
51
+ "three": "0.185.1",
52
+ "tsc-alias": "^1.8.16",
53
+ "typescript": "^6.0.3",
54
+ "typescript-eslint": "^8.48.0",
55
+ "vitest": "^4.1.5",
56
+ "@motionscript/core": "0.1.0-alpha.0",
57
+ "@motionscript/canvaskit": "0.1.0-alpha.0"
58
+ },
59
+ "dependencies": {
60
+ "esbuild-wasm": "^0.28.1",
61
+ "mediabunny": "^1.40.1",
62
+ "@motionscript/skia-render": "^0.1.0-alpha.0"
63
+ },
64
+ "motionscriptBrowser": {
65
+ "assets": {
66
+ "esbuild.wasm": "esbuild-wasm/esbuild.wasm"
67
+ }
68
+ },
69
+ "scripts": {
70
+ "build": "eslint . && tsc -p tsconfig.build.json && tsc-alias -p tsconfig.build.json && node ../../scripts/build-browser.mjs",
71
+ "dev": "concurrently \"tsc -p tsconfig.build.json --watch\" \"tsc-alias -p tsconfig.build.json -w\"",
72
+ "clean": "rimraf --glob dist .turbo *.tsbuildinfo",
73
+ "test": "vitest",
74
+ "lint": "eslint .",
75
+ "typecheck": "tsc -p tsconfig.json --noEmit"
76
+ }
6
77
  }