taleem-player 0.1.0 → 0.2.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 +243 -0
- package/dist/taleem-browser.esm.js +92 -0
- package/dist/taleem-browser.umd.js +116 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
|
|
2
|
+
# 📦 taleem-player
|
|
3
|
+
|
|
4
|
+
**taleem-player** is a **headless, time-driven playback engine** for Taleem slide decks.
|
|
5
|
+
|
|
6
|
+
It is designed for:
|
|
7
|
+
|
|
8
|
+
* timed presentations
|
|
9
|
+
* narrated lessons
|
|
10
|
+
* audio / video–synced slides
|
|
11
|
+
* external playback control
|
|
12
|
+
|
|
13
|
+
At its core, `taleem-player` does one thing well:
|
|
14
|
+
|
|
15
|
+
> **Given a valid deck and a clock, decide which slide should be active and when.**
|
|
16
|
+
|
|
17
|
+
It does **not** render slides.
|
|
18
|
+
It does **not** define layouts.
|
|
19
|
+
It does **not** ship CSS.
|
|
20
|
+
|
|
21
|
+
Those responsibilities belong elsewhere.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Core idea
|
|
26
|
+
|
|
27
|
+
A slide deck can be interpreted in **time**, not just order.
|
|
28
|
+
|
|
29
|
+
`taleem-player` treats a deck as a **timeline**, where each slide occupies a
|
|
30
|
+
known interval:
|
|
31
|
+
|
|
32
|
+
```text
|
|
33
|
+
[start ─────────── end)
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
At any given moment:
|
|
37
|
+
|
|
38
|
+
* exactly one slide may be active
|
|
39
|
+
* or no slide (before / after the timeline)
|
|
40
|
+
|
|
41
|
+
The player’s job is to **resolve time → slide** and notify a renderer.
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## What this library does
|
|
46
|
+
|
|
47
|
+
`taleem-player`:
|
|
48
|
+
|
|
49
|
+
* Accepts a **deck-v1 JSON object**
|
|
50
|
+
* Enforces **strict timing validity**
|
|
51
|
+
* Owns a single DOM stage
|
|
52
|
+
* Resolves the active slide for a given time
|
|
53
|
+
* Calls an injected **renderer**
|
|
54
|
+
* Supports **scrubbing, autoplay, pause, stop**
|
|
55
|
+
* Is completely **renderer-agnostic**
|
|
56
|
+
|
|
57
|
+
The public API is intentionally small:
|
|
58
|
+
|
|
59
|
+
```js
|
|
60
|
+
player.renderAt(time)
|
|
61
|
+
player.destroy()
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Higher-level controls (play, pause, keyboard, UI) are built **on top**, not inside.
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## What this library intentionally does NOT do
|
|
69
|
+
|
|
70
|
+
This is not accidental — it is a design boundary.
|
|
71
|
+
|
|
72
|
+
`taleem-player` does **not**:
|
|
73
|
+
|
|
74
|
+
* define slide layouts
|
|
75
|
+
* ship or inject CSS
|
|
76
|
+
* depend on `taleem-slides`
|
|
77
|
+
* interpret slide content
|
|
78
|
+
* infer or auto-fix timings
|
|
79
|
+
* manage audio or narration
|
|
80
|
+
* expose internal state
|
|
81
|
+
* depend on any framework
|
|
82
|
+
|
|
83
|
+
If a deck is invalid, the player **throws**.
|
|
84
|
+
If rendering looks wrong, the renderer is responsible.
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## Strict timing model (important)
|
|
89
|
+
|
|
90
|
+
`taleem-player` only accepts **player-ready decks**.
|
|
91
|
+
|
|
92
|
+
Every slide **must** define:
|
|
93
|
+
|
|
94
|
+
```json
|
|
95
|
+
{
|
|
96
|
+
"start": number,
|
|
97
|
+
"end": number
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Rules:
|
|
102
|
+
|
|
103
|
+
* `start` and `end` are **absolute seconds**
|
|
104
|
+
* `end` must be greater than `start`
|
|
105
|
+
* No implicit inheritance
|
|
106
|
+
* No auto-injection
|
|
107
|
+
* No browser-style forgiveness
|
|
108
|
+
|
|
109
|
+
This strictness is intentional and non-negotiable.
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## Renderer contract
|
|
114
|
+
|
|
115
|
+
`taleem-player` does not know how slides look.
|
|
116
|
+
|
|
117
|
+
Instead, it calls a renderer with this contract:
|
|
118
|
+
|
|
119
|
+
```js
|
|
120
|
+
renderer.render({
|
|
121
|
+
mount, // HTMLElement
|
|
122
|
+
slide, // full slide JSON
|
|
123
|
+
time // absolute time (seconds)
|
|
124
|
+
})
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
That’s it.
|
|
128
|
+
|
|
129
|
+
How the slide is rendered — HTML, SVG, Canvas, WebGL — is **not the player’s concern**.
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
## Relationship to other Taleem libraries
|
|
134
|
+
|
|
135
|
+
`taleem-player` is part of a **layered system**.
|
|
136
|
+
|
|
137
|
+
### Lower-level
|
|
138
|
+
|
|
139
|
+
**taleem-slides**
|
|
140
|
+
Pure slide renderer.
|
|
141
|
+
Converts slide JSON into HTML + CSS.
|
|
142
|
+
|
|
143
|
+
### Higher-level
|
|
144
|
+
|
|
145
|
+
**Taleem demo app**
|
|
146
|
+
Wires together:
|
|
147
|
+
|
|
148
|
+
* `taleem-player`
|
|
149
|
+
* `taleem-slides`
|
|
150
|
+
* playback controls
|
|
151
|
+
* UI
|
|
152
|
+
|
|
153
|
+
### Sibling
|
|
154
|
+
|
|
155
|
+
**taleem-browser**
|
|
156
|
+
Index-based slide viewer (no time).
|
|
157
|
+
|
|
158
|
+
Each library has **one responsibility**.
|
|
159
|
+
They are composed — never merged.
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
## Why this library exists
|
|
164
|
+
|
|
165
|
+
`taleem-browser` treats slides as a **document**.
|
|
166
|
+
`taleem-player` treats slides as a **timeline**.
|
|
167
|
+
|
|
168
|
+
Both are valid interpretations.
|
|
169
|
+
|
|
170
|
+
By separating them:
|
|
171
|
+
|
|
172
|
+
* timed playback does not pollute browsing logic
|
|
173
|
+
* rendering does not pollute playback logic
|
|
174
|
+
* decks remain portable JSON documents
|
|
175
|
+
|
|
176
|
+
This separation allows:
|
|
177
|
+
|
|
178
|
+
* narration sync
|
|
179
|
+
* recorded lessons
|
|
180
|
+
* adaptive playback
|
|
181
|
+
* multiple renderers
|
|
182
|
+
|
|
183
|
+
without rewriting the core.
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
## Typical usage (composition layer)
|
|
188
|
+
|
|
189
|
+
```js
|
|
190
|
+
import { createTaleemPlayer } from "taleem-player";
|
|
191
|
+
import { createSlidesRenderer } from "taleem-slides";
|
|
192
|
+
import "taleem-slides/dist/taleem.css";
|
|
193
|
+
|
|
194
|
+
const renderer = createSlidesRenderer();
|
|
195
|
+
|
|
196
|
+
const player = createTaleemPlayer({
|
|
197
|
+
mount: "#app",
|
|
198
|
+
deck,
|
|
199
|
+
renderer
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
// external clock
|
|
203
|
+
player.renderAt(12.5);
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
The **player** never imports slides.
|
|
207
|
+
The **slides** never import the player.
|
|
208
|
+
The **app** composes them.
|
|
209
|
+
|
|
210
|
+
---
|
|
211
|
+
|
|
212
|
+
## Design philosophy (locked)
|
|
213
|
+
|
|
214
|
+
* Time is external
|
|
215
|
+
* Rendering is replaceable
|
|
216
|
+
* Strictness beats convenience
|
|
217
|
+
* Libraries stay small
|
|
218
|
+
* Composition happens at the edge
|
|
219
|
+
|
|
220
|
+
> **A player decides *when*.
|
|
221
|
+
> A renderer decides *how*.
|
|
222
|
+
> An app decides *why*.**
|
|
223
|
+
|
|
224
|
+
---
|
|
225
|
+
|
|
226
|
+
## Project status
|
|
227
|
+
|
|
228
|
+
**Core complete and stable.**
|
|
229
|
+
|
|
230
|
+
Future work belongs in:
|
|
231
|
+
|
|
232
|
+
* demo applications
|
|
233
|
+
* playback UI layers
|
|
234
|
+
* renderers
|
|
235
|
+
* authoring tools
|
|
236
|
+
|
|
237
|
+
The player itself should change rarely.
|
|
238
|
+
|
|
239
|
+
---
|
|
240
|
+
|
|
241
|
+
## License
|
|
242
|
+
|
|
243
|
+
MIT
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
// src/core/stage.js
|
|
2
|
+
function createStage(mount) {
|
|
3
|
+
if (!mount) throw new Error("taleem-player: mount is required");
|
|
4
|
+
const root = typeof mount === "string" ? document.querySelector(mount) : mount;
|
|
5
|
+
if (!root) throw new Error("taleem-player: mount element not found");
|
|
6
|
+
root.innerHTML = "";
|
|
7
|
+
const stage = document.createElement("div");
|
|
8
|
+
stage.className = "taleem-player-stage";
|
|
9
|
+
stage.style.position = "relative";
|
|
10
|
+
stage.style.width = "100%";
|
|
11
|
+
stage.style.height = "100%";
|
|
12
|
+
root.appendChild(stage);
|
|
13
|
+
function clear() {
|
|
14
|
+
stage.innerHTML = "";
|
|
15
|
+
}
|
|
16
|
+
function destroy() {
|
|
17
|
+
root.innerHTML = "";
|
|
18
|
+
}
|
|
19
|
+
return {
|
|
20
|
+
el: stage,
|
|
21
|
+
clear,
|
|
22
|
+
destroy
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// src/utils/timing.js
|
|
27
|
+
function getDeckDuration(deck) {
|
|
28
|
+
if (!deck?.deck?.length) return 0;
|
|
29
|
+
return Math.max(...deck.deck.map((s) => s.end));
|
|
30
|
+
}
|
|
31
|
+
function getSlideAtTime(deck, time) {
|
|
32
|
+
return deck.deck.find((s) => time >= s.start && time < s.end) || null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// src/utils/assertPlayable.js
|
|
36
|
+
function assertDeckPlayable(deck) {
|
|
37
|
+
if (!deck || deck.version !== "deck-v1") {
|
|
38
|
+
throw new Error("Invalid deck version");
|
|
39
|
+
}
|
|
40
|
+
if (!Array.isArray(deck.deck)) {
|
|
41
|
+
throw new Error("Deck must contain slides");
|
|
42
|
+
}
|
|
43
|
+
deck.deck.forEach((s, i) => {
|
|
44
|
+
if (typeof s.start !== "number" || typeof s.end !== "number") {
|
|
45
|
+
throw new Error(`Slide ${i} has invalid timing`);
|
|
46
|
+
}
|
|
47
|
+
if (s.end <= s.start) {
|
|
48
|
+
throw new Error(`Slide ${i} end must be > start`);
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// src/core/player.js
|
|
55
|
+
function createTaleemPlayer({ mount, deck, renderer }) {
|
|
56
|
+
if (!renderer || typeof renderer.render !== "function") {
|
|
57
|
+
throw new Error("taleem-player: renderer with render() required");
|
|
58
|
+
}
|
|
59
|
+
assertDeckPlayable(deck);
|
|
60
|
+
const stage = createStage(mount);
|
|
61
|
+
let lastSlide = null;
|
|
62
|
+
function renderAt(time) {
|
|
63
|
+
const slide = getSlideAtTime(deck, time);
|
|
64
|
+
if (!slide) {
|
|
65
|
+
stage.clear();
|
|
66
|
+
lastSlide = null;
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
if (slide !== lastSlide) {
|
|
70
|
+
stage.clear();
|
|
71
|
+
lastSlide = slide;
|
|
72
|
+
}
|
|
73
|
+
renderer.render({
|
|
74
|
+
mount: stage.el,
|
|
75
|
+
slide,
|
|
76
|
+
time
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
function destroy() {
|
|
80
|
+
stage.destroy();
|
|
81
|
+
}
|
|
82
|
+
return {
|
|
83
|
+
renderAt,
|
|
84
|
+
destroy
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
export {
|
|
88
|
+
assertDeckPlayable,
|
|
89
|
+
createTaleemPlayer,
|
|
90
|
+
getDeckDuration,
|
|
91
|
+
getSlideAtTime
|
|
92
|
+
};
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
var TaleemBrowser = (() => {
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.js
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
assertDeckPlayable: () => assertDeckPlayable,
|
|
24
|
+
createTaleemPlayer: () => createTaleemPlayer,
|
|
25
|
+
getDeckDuration: () => getDeckDuration,
|
|
26
|
+
getSlideAtTime: () => getSlideAtTime
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
// src/core/stage.js
|
|
30
|
+
function createStage(mount) {
|
|
31
|
+
if (!mount) throw new Error("taleem-player: mount is required");
|
|
32
|
+
const root = typeof mount === "string" ? document.querySelector(mount) : mount;
|
|
33
|
+
if (!root) throw new Error("taleem-player: mount element not found");
|
|
34
|
+
root.innerHTML = "";
|
|
35
|
+
const stage = document.createElement("div");
|
|
36
|
+
stage.className = "taleem-player-stage";
|
|
37
|
+
stage.style.position = "relative";
|
|
38
|
+
stage.style.width = "100%";
|
|
39
|
+
stage.style.height = "100%";
|
|
40
|
+
root.appendChild(stage);
|
|
41
|
+
function clear() {
|
|
42
|
+
stage.innerHTML = "";
|
|
43
|
+
}
|
|
44
|
+
function destroy() {
|
|
45
|
+
root.innerHTML = "";
|
|
46
|
+
}
|
|
47
|
+
return {
|
|
48
|
+
el: stage,
|
|
49
|
+
clear,
|
|
50
|
+
destroy
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// src/utils/timing.js
|
|
55
|
+
function getDeckDuration(deck) {
|
|
56
|
+
if (!deck?.deck?.length) return 0;
|
|
57
|
+
return Math.max(...deck.deck.map((s) => s.end));
|
|
58
|
+
}
|
|
59
|
+
function getSlideAtTime(deck, time) {
|
|
60
|
+
return deck.deck.find((s) => time >= s.start && time < s.end) || null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// src/utils/assertPlayable.js
|
|
64
|
+
function assertDeckPlayable(deck) {
|
|
65
|
+
if (!deck || deck.version !== "deck-v1") {
|
|
66
|
+
throw new Error("Invalid deck version");
|
|
67
|
+
}
|
|
68
|
+
if (!Array.isArray(deck.deck)) {
|
|
69
|
+
throw new Error("Deck must contain slides");
|
|
70
|
+
}
|
|
71
|
+
deck.deck.forEach((s, i) => {
|
|
72
|
+
if (typeof s.start !== "number" || typeof s.end !== "number") {
|
|
73
|
+
throw new Error(`Slide ${i} has invalid timing`);
|
|
74
|
+
}
|
|
75
|
+
if (s.end <= s.start) {
|
|
76
|
+
throw new Error(`Slide ${i} end must be > start`);
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// src/core/player.js
|
|
83
|
+
function createTaleemPlayer({ mount, deck, renderer }) {
|
|
84
|
+
if (!renderer || typeof renderer.render !== "function") {
|
|
85
|
+
throw new Error("taleem-player: renderer with render() required");
|
|
86
|
+
}
|
|
87
|
+
assertDeckPlayable(deck);
|
|
88
|
+
const stage = createStage(mount);
|
|
89
|
+
let lastSlide = null;
|
|
90
|
+
function renderAt(time) {
|
|
91
|
+
const slide = getSlideAtTime(deck, time);
|
|
92
|
+
if (!slide) {
|
|
93
|
+
stage.clear();
|
|
94
|
+
lastSlide = null;
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
if (slide !== lastSlide) {
|
|
98
|
+
stage.clear();
|
|
99
|
+
lastSlide = slide;
|
|
100
|
+
}
|
|
101
|
+
renderer.render({
|
|
102
|
+
mount: stage.el,
|
|
103
|
+
slide,
|
|
104
|
+
time
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
function destroy() {
|
|
108
|
+
stage.destroy();
|
|
109
|
+
}
|
|
110
|
+
return {
|
|
111
|
+
renderAt,
|
|
112
|
+
destroy
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
return __toCommonJS(index_exports);
|
|
116
|
+
})();
|