@glissade/player 0.1.0 → 0.3.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 +23 -0
- package/dist/index.d.ts +31 -6
- package/dist/index.js +29 -3
- package/package.json +4 -4
package/README.md
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# @glissade/player
|
|
2
|
+
|
|
3
|
+
The embed runtime: `Player` (play/pause/seek/loop/rate — time-based, never frame-counted), the **Driver** seam (`InputDriver<T>`: clock, scroll, or anything that writes a value), and `mount()` — scene + timeline + canvas in one call. v2 machines attach here: `player.attach(machine)` steps them on every host tick, even while linear playback is paused, with hard target-disjointness validation.
|
|
4
|
+
|
|
5
|
+
```sh
|
|
6
|
+
npm i @glissade/player
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
import { mount } from '@glissade/player';
|
|
11
|
+
|
|
12
|
+
const { player } = mount(scene, doc, canvas, { loop: true, autoplay: true });
|
|
13
|
+
player.seek(1.5); // pure: identical to having played there
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Part of glissade
|
|
17
|
+
|
|
18
|
+
*(glide & slide)* — programmatic motion graphics for TypeScript: realtime-first in any web page, deterministic headless video export from the same code, a visual studio over the same document. No generator functions.
|
|
19
|
+
|
|
20
|
+
- [Repository & full README](https://github.com/tyevco/glissade)
|
|
21
|
+
- [Getting started](https://github.com/tyevco/glissade/blob/main/docs/getting-started.md) · [Concepts](https://github.com/tyevco/glissade/blob/main/docs/concepts.md) · [Interactivity](https://github.com/tyevco/glissade/blob/main/docs/interactivity.md)
|
|
22
|
+
|
|
23
|
+
Apache-2.0.
|
package/dist/index.d.ts
CHANGED
|
@@ -10,14 +10,19 @@ import { Canvas2DBackend } from "@glissade/backend-canvas2d";
|
|
|
10
10
|
* clock is the default Driver, not a privileged one — this seam is the v2
|
|
11
11
|
* interactivity path (§2.9).
|
|
12
12
|
*/
|
|
13
|
-
interface
|
|
14
|
-
/** Begin writing. Call write(
|
|
15
|
-
start(write: (
|
|
13
|
+
interface InputDriver<T = number> {
|
|
14
|
+
/** Begin writing. Call write(value) whenever the driven value changes. */
|
|
15
|
+
start(write: (value: T) => void, ctx: DriverContext): void;
|
|
16
16
|
stop(): void;
|
|
17
17
|
}
|
|
18
|
+
/** v1 alias (v2 §C.1): the playhead was always just the T = number case. */
|
|
19
|
+
type Driver = InputDriver<number>;
|
|
18
20
|
interface DriverContext {
|
|
19
|
-
/**
|
|
20
|
-
|
|
21
|
+
/**
|
|
22
|
+
* Timeline duration — present when driving a playhead; ABSENT in input mode
|
|
23
|
+
* (machines have no duration, v2 §C.1).
|
|
24
|
+
*/
|
|
25
|
+
duration?: number;
|
|
21
26
|
visibility: () => 'visible' | 'hidden';
|
|
22
27
|
}
|
|
23
28
|
/**
|
|
@@ -55,6 +60,20 @@ interface PlayerInit {
|
|
|
55
60
|
/** Injected clock; defaults to the rAF clockDriver. Tests pass a manual driver. */
|
|
56
61
|
driver?: Driver;
|
|
57
62
|
visibility?: () => 'visible' | 'hidden';
|
|
63
|
+
/** Track targets of the bound timeline — the v2 §A.1 disjointness set for attach(). */
|
|
64
|
+
targets?: Iterable<string>;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* The structural shape of an attached machine (v2 addendum §A.5). Defined
|
|
68
|
+
* structurally so @glissade/player never imports @glissade/interact (§7.1).
|
|
69
|
+
*/
|
|
70
|
+
interface AttachedMachine {
|
|
71
|
+
step(now: number): void;
|
|
72
|
+
readonly targets?: ReadonlySet<string>;
|
|
73
|
+
}
|
|
74
|
+
/** §A.1: machine and Player track-target sets must be statically disjoint. */
|
|
75
|
+
declare class TargetOverlapError extends Error {
|
|
76
|
+
constructor(overlaps: string[]);
|
|
58
77
|
}
|
|
59
78
|
interface Player {
|
|
60
79
|
readonly playhead: Playhead;
|
|
@@ -69,6 +88,12 @@ interface Player {
|
|
|
69
88
|
seek(t: number): void;
|
|
70
89
|
/** Register a marker callback; fired only when continuous playback crosses it. */
|
|
71
90
|
onMarker(name: string, cb: (marker: Marker) => void): () => void;
|
|
91
|
+
/**
|
|
92
|
+
* Wire a machine to the host clock (v2 §A.5): step(now) on every tick, in
|
|
93
|
+
* attach order, before evaluation — even while linear playback is paused.
|
|
94
|
+
* Returns detach. Throws TargetOverlapError on a non-disjoint target set.
|
|
95
|
+
*/
|
|
96
|
+
attach(machine: AttachedMachine): () => void;
|
|
72
97
|
dispose(): void;
|
|
73
98
|
}
|
|
74
99
|
declare function createPlayer(init: PlayerInit, opts?: PlayerOptions): Player;
|
|
@@ -83,4 +108,4 @@ interface Mounted {
|
|
|
83
108
|
}
|
|
84
109
|
declare function mount(scene: Scene, doc: Timeline, canvas: HTMLCanvasElement | OffscreenCanvas, opts?: PlayerOptions): Mounted;
|
|
85
110
|
//#endregion
|
|
86
|
-
export { type Driver, type DriverContext, type LoopMode, type Mounted, type PlayHandle, type Player, type PlayerInit, type PlayerOptions, type ScrollDriverOptions, clockDriver, createPlayer, mount, scrollDriver };
|
|
111
|
+
export { type AttachedMachine, type Driver, type DriverContext, type InputDriver, type LoopMode, type Mounted, type PlayHandle, type Player, type PlayerInit, type PlayerOptions, type ScrollDriverOptions, TargetOverlapError, clockDriver, createPlayer, mount, scrollDriver };
|
package/dist/index.js
CHANGED
|
@@ -34,7 +34,7 @@ function scrollDriver(opts) {
|
|
|
34
34
|
const el = opts.source;
|
|
35
35
|
return {
|
|
36
36
|
start(write, ctx) {
|
|
37
|
-
const range = opts.range ?? [0, ctx.duration];
|
|
37
|
+
const range = opts.range ?? [0, ctx.duration ?? 1];
|
|
38
38
|
const progress = () => {
|
|
39
39
|
if (el instanceof Element) {
|
|
40
40
|
const max = axis === "y" ? el.scrollHeight - el.clientHeight : el.scrollWidth - el.clientWidth;
|
|
@@ -61,6 +61,13 @@ function scrollDriver(opts) {
|
|
|
61
61
|
}
|
|
62
62
|
//#endregion
|
|
63
63
|
//#region src/player.ts
|
|
64
|
+
/** §A.1: machine and Player track-target sets must be statically disjoint. */
|
|
65
|
+
var TargetOverlapError = class extends Error {
|
|
66
|
+
constructor(overlaps) {
|
|
67
|
+
super(`machine targets overlap an already-bound target set: ${overlaps.join(", ")} — concurrent writers to one property is the silent last-writer-wins §2.2 exists to kill`);
|
|
68
|
+
this.name = "TargetOverlapError";
|
|
69
|
+
}
|
|
70
|
+
};
|
|
64
71
|
function createPlayer(init, opts = {}) {
|
|
65
72
|
const { playhead, duration } = init;
|
|
66
73
|
const markers = init.markers ?? [];
|
|
@@ -70,6 +77,8 @@ function createPlayer(init, opts = {}) {
|
|
|
70
77
|
let rate = opts.rate ?? 1;
|
|
71
78
|
let playing = false;
|
|
72
79
|
let driverRunning = false;
|
|
80
|
+
const ownTargets = new Set(init.targets ?? []);
|
|
81
|
+
const machines = [];
|
|
73
82
|
let base = 0;
|
|
74
83
|
let elapsedOrigin = null;
|
|
75
84
|
let lastElapsed = 0;
|
|
@@ -90,6 +99,7 @@ function createPlayer(init, opts = {}) {
|
|
|
90
99
|
}
|
|
91
100
|
function onElapsed(elapsed) {
|
|
92
101
|
lastElapsed = elapsed;
|
|
102
|
+
for (const m of machines) m.step(elapsed);
|
|
93
103
|
if (!playing) return;
|
|
94
104
|
elapsedOrigin ??= elapsed;
|
|
95
105
|
const prev = playhead.peek();
|
|
@@ -170,6 +180,20 @@ function createPlayer(init, opts = {}) {
|
|
|
170
180
|
elapsedOrigin = playing ? lastElapsed : null;
|
|
171
181
|
playhead.set(base);
|
|
172
182
|
},
|
|
183
|
+
attach(machine) {
|
|
184
|
+
if (machine.targets) {
|
|
185
|
+
const overlaps = [];
|
|
186
|
+
for (const t of machine.targets) if (ownTargets.has(t)) overlaps.push(`'${t}' (player timeline)`);
|
|
187
|
+
else if (machines.some((m) => m.targets?.has(t))) overlaps.push(`'${t}' (another machine)`);
|
|
188
|
+
if (overlaps.length > 0) throw new TargetOverlapError(overlaps);
|
|
189
|
+
}
|
|
190
|
+
machines.push(machine);
|
|
191
|
+
ensureDriver();
|
|
192
|
+
return () => {
|
|
193
|
+
const i = machines.indexOf(machine);
|
|
194
|
+
if (i >= 0) machines.splice(i, 1);
|
|
195
|
+
};
|
|
196
|
+
},
|
|
173
197
|
onMarker(name, cb) {
|
|
174
198
|
let set = callbacks.get(name);
|
|
175
199
|
if (!set) {
|
|
@@ -181,6 +205,7 @@ function createPlayer(init, opts = {}) {
|
|
|
181
205
|
},
|
|
182
206
|
dispose() {
|
|
183
207
|
if (playing) settle(false);
|
|
208
|
+
machines.length = 0;
|
|
184
209
|
if (driverRunning) driver.stop();
|
|
185
210
|
driverRunning = false;
|
|
186
211
|
}
|
|
@@ -200,7 +225,8 @@ function mount(scene, doc, canvas, opts = {}) {
|
|
|
200
225
|
const player = createPlayer({
|
|
201
226
|
playhead: scene.playhead,
|
|
202
227
|
duration: compiled.duration,
|
|
203
|
-
markers: compiled.markers
|
|
228
|
+
markers: compiled.markers,
|
|
229
|
+
targets: compiled.tracks.keys()
|
|
204
230
|
}, opts);
|
|
205
231
|
const renderNow = () => backend.render(evaluate(scene, doc, scene.playhead.peek()));
|
|
206
232
|
let scheduled = false;
|
|
@@ -233,4 +259,4 @@ function mount(scene, doc, canvas, opts = {}) {
|
|
|
233
259
|
};
|
|
234
260
|
}
|
|
235
261
|
//#endregion
|
|
236
|
-
export { clockDriver, createPlayer, mount, scrollDriver };
|
|
262
|
+
export { TargetOverlapError, clockDriver, createPlayer, mount, scrollDriver };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@glissade/player",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "glissade embed runtime: Player, Drivers (clock, scroll), mount().",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -15,9 +15,9 @@
|
|
|
15
15
|
"dist"
|
|
16
16
|
],
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"@glissade/
|
|
19
|
-
"@glissade/
|
|
20
|
-
"@glissade/scene": "0.
|
|
18
|
+
"@glissade/core": "0.3.0",
|
|
19
|
+
"@glissade/backend-canvas2d": "0.3.0",
|
|
20
|
+
"@glissade/scene": "0.3.0"
|
|
21
21
|
},
|
|
22
22
|
"repository": {
|
|
23
23
|
"type": "git",
|