@framv/video 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/LICENSE +21 -0
- package/README.md +78 -0
- package/dist/bundle.esm.js +450 -0
- package/dist/bundle.iife.js +18942 -0
- package/dist/cdn.d.ts +4 -0
- package/dist/cdn.d.ts.map +1 -0
- package/dist/cdn.js +4 -0
- package/dist/cdn.js.map +1 -0
- package/dist/element.d.ts +49 -0
- package/dist/element.d.ts.map +1 -0
- package/dist/element.js +283 -0
- package/dist/element.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/player.d.ts +50 -0
- package/dist/player.d.ts.map +1 -0
- package/dist/player.js +231 -0
- package/dist/player.js.map +1 -0
- package/dist/types.d.ts +3 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +61 -0
package/dist/cdn.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cdn.d.ts","sourceRoot":"","sources":["../src/cdn.ts"],"names":[],"mappings":"AAAA,OAAO,cAAc,CAAC;AACtB,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AACjD,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC"}
|
package/dist/cdn.js
ADDED
package/dist/cdn.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cdn.js","sourceRoot":"","sources":["../src/cdn.ts"],"names":[],"mappings":"AAAA,OAAO,cAAc,CAAC;AACtB,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AACjD,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { Player } from "./player.js";
|
|
2
|
+
/**
|
|
3
|
+
* `<framv-video>` — renders HTML content as a video.
|
|
4
|
+
*
|
|
5
|
+
* Attributes:
|
|
6
|
+
* width — canvas width in pixels (default: 1920)
|
|
7
|
+
* height — canvas height in pixels (default: 1080)
|
|
8
|
+
* fps — frames per second (default: 30)
|
|
9
|
+
* duration — total duration in seconds (default: auto from animations)
|
|
10
|
+
* format — export format: mp4, webm (default: mp4)
|
|
11
|
+
* quality — export quality 0..1 (default: 0.95)
|
|
12
|
+
* controls — always show toolbar
|
|
13
|
+
* autoplay — start playing immediately
|
|
14
|
+
* loop — loop playback
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```html
|
|
18
|
+
* <framv-video width="1920" height="1080" fps="30" duration="5" controls>
|
|
19
|
+
* <div>Your content here</div>
|
|
20
|
+
* </framv-video>
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
export declare class FramvVideoElement extends HTMLElement {
|
|
24
|
+
static observedAttributes: string[];
|
|
25
|
+
private _player;
|
|
26
|
+
private _stage;
|
|
27
|
+
private _toolbar;
|
|
28
|
+
private _btn;
|
|
29
|
+
private _range;
|
|
30
|
+
private _time;
|
|
31
|
+
private _exportBtn;
|
|
32
|
+
private _shadow;
|
|
33
|
+
private _dragging;
|
|
34
|
+
private _exporting;
|
|
35
|
+
constructor();
|
|
36
|
+
get width(): number;
|
|
37
|
+
get height(): number;
|
|
38
|
+
get fps(): number;
|
|
39
|
+
get duration(): number;
|
|
40
|
+
get format(): string;
|
|
41
|
+
get quality(): number;
|
|
42
|
+
get player(): Player | null;
|
|
43
|
+
connectedCallback(): void;
|
|
44
|
+
disconnectedCallback(): void;
|
|
45
|
+
attributeChangedCallback(name: string, _old: string, value: string): void;
|
|
46
|
+
private _adaptSize;
|
|
47
|
+
private _export;
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=element.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"element.d.ts","sourceRoot":"","sources":["../src/element.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AA8DrC;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,qBAAa,iBAAkB,SAAQ,WAAW;IAChD,MAAM,CAAC,kBAAkB,WAA6C;IAEtE,OAAO,CAAC,OAAO,CAAuB;IACtC,OAAO,CAAC,MAAM,CAAkB;IAChC,OAAO,CAAC,QAAQ,CAAkB;IAClC,OAAO,CAAC,IAAI,CAAqB;IACjC,OAAO,CAAC,MAAM,CAAoB;IAClC,OAAO,CAAC,KAAK,CAAmB;IAChC,OAAO,CAAC,UAAU,CAAqB;IACvC,OAAO,CAAC,OAAO,CAAa;IAC5B,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,UAAU,CAAS;;IAO3B,IAAI,KAAK,IAAI,MAAM,CAElB;IACD,IAAI,MAAM,IAAI,MAAM,CAEnB;IACD,IAAI,GAAG,IAAI,MAAM,CAEhB;IACD,IAAI,QAAQ,IAAI,MAAM,CAErB;IACD,IAAI,MAAM,IAAI,MAAM,CAEnB;IACD,IAAI,OAAO,IAAI,MAAM,CAEpB;IACD,IAAI,MAAM,IAAI,MAAM,GAAG,IAAI,CAE1B;IAED,iBAAiB,IAAI,IAAI;IAgFzB,oBAAoB,IAAI,IAAI;IAK5B,wBAAwB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI;IAoBzE,OAAO,CAAC,UAAU;YASJ,OAAO;CAoDtB"}
|
package/dist/element.js
ADDED
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
import { exportElement } from "@framv/core";
|
|
2
|
+
import { Player } from "./player.js";
|
|
3
|
+
const STYLES = `
|
|
4
|
+
:host { display: block; position: relative; overflow: hidden; }
|
|
5
|
+
.framv-stage {
|
|
6
|
+
position: relative;
|
|
7
|
+
width: var(--framv-w, 1920px);
|
|
8
|
+
height: var(--framv-h, 1080px);
|
|
9
|
+
transform-origin: top left;
|
|
10
|
+
overflow: hidden;
|
|
11
|
+
}
|
|
12
|
+
.framv-toolbar {
|
|
13
|
+
display: flex; align-items: center; gap: 10px;
|
|
14
|
+
padding: 8px 12px;
|
|
15
|
+
background: rgba(0,0,0,0.7); backdrop-filter: blur(6px);
|
|
16
|
+
color: #fff; font: 13px/1 system-ui, monospace;
|
|
17
|
+
position: absolute; bottom: 0; left: 0; right: 0;
|
|
18
|
+
opacity: 0; transition: opacity 0.2s;
|
|
19
|
+
z-index: 10;
|
|
20
|
+
}
|
|
21
|
+
:host(:hover) .framv-toolbar,
|
|
22
|
+
:host([controls]) .framv-toolbar { opacity: 1; }
|
|
23
|
+
.framv-toolbar button {
|
|
24
|
+
background: none; border: 1px solid rgba(255,255,255,0.2);
|
|
25
|
+
color: inherit; cursor: pointer;
|
|
26
|
+
padding: 4px 10px; font: inherit; border-radius: 4px;
|
|
27
|
+
}
|
|
28
|
+
.framv-toolbar button:hover { background: rgba(255,255,255,0.15); }
|
|
29
|
+
.framv-toolbar .btn-export {
|
|
30
|
+
border-color: #ff79c6; color: #ff79c6;
|
|
31
|
+
}
|
|
32
|
+
input[type=range] {
|
|
33
|
+
flex: 1; accent-color: #ff79c6; cursor: pointer; height: 3px;
|
|
34
|
+
}
|
|
35
|
+
.framv-time { white-space: nowrap; font-variant-numeric: tabular-nums; opacity: 0.8; min-width: 100px; text-align: center; }
|
|
36
|
+
.framv-badge {
|
|
37
|
+
position: absolute; top: 8px; right: 8px;
|
|
38
|
+
background: rgba(0,0,0,0.6); color: #fff;
|
|
39
|
+
padding: 2px 8px; border-radius: 4px;
|
|
40
|
+
font: 11px system-ui; letter-spacing: 0.5px;
|
|
41
|
+
text-transform: uppercase; z-index: 5;
|
|
42
|
+
}
|
|
43
|
+
.framv-exporting-overlay {
|
|
44
|
+
position: absolute; inset: 0;
|
|
45
|
+
background: rgba(0,0,0,0.7);
|
|
46
|
+
display: flex; align-items: center; justify-content: center;
|
|
47
|
+
color: #fff; font: 16px system-ui; z-index: 20;
|
|
48
|
+
}
|
|
49
|
+
.framv-exporting-overlay .spinner {
|
|
50
|
+
width: 32px; height: 32px; border: 3px solid rgba(255,255,255,0.3);
|
|
51
|
+
border-top-color: #ff79c6; border-radius: 50%;
|
|
52
|
+
animation: framv-spin 0.8s linear infinite;
|
|
53
|
+
margin-right: 12px;
|
|
54
|
+
}
|
|
55
|
+
@keyframes framv-spin { to { transform: rotate(360deg); } }
|
|
56
|
+
`;
|
|
57
|
+
const fmt = (s) => {
|
|
58
|
+
const m = Math.floor(s / 60);
|
|
59
|
+
return `${m}:${String(Math.floor(s % 60)).padStart(2, "0")}`;
|
|
60
|
+
};
|
|
61
|
+
/**
|
|
62
|
+
* `<framv-video>` — renders HTML content as a video.
|
|
63
|
+
*
|
|
64
|
+
* Attributes:
|
|
65
|
+
* width — canvas width in pixels (default: 1920)
|
|
66
|
+
* height — canvas height in pixels (default: 1080)
|
|
67
|
+
* fps — frames per second (default: 30)
|
|
68
|
+
* duration — total duration in seconds (default: auto from animations)
|
|
69
|
+
* format — export format: mp4, webm (default: mp4)
|
|
70
|
+
* quality — export quality 0..1 (default: 0.95)
|
|
71
|
+
* controls — always show toolbar
|
|
72
|
+
* autoplay — start playing immediately
|
|
73
|
+
* loop — loop playback
|
|
74
|
+
*
|
|
75
|
+
* @example
|
|
76
|
+
* ```html
|
|
77
|
+
* <framv-video width="1920" height="1080" fps="30" duration="5" controls>
|
|
78
|
+
* <div>Your content here</div>
|
|
79
|
+
* </framv-video>
|
|
80
|
+
* ```
|
|
81
|
+
*/
|
|
82
|
+
export class FramvVideoElement extends HTMLElement {
|
|
83
|
+
static observedAttributes = ["duration", "width", "height", "format"];
|
|
84
|
+
_player = null;
|
|
85
|
+
_stage;
|
|
86
|
+
_toolbar;
|
|
87
|
+
_btn;
|
|
88
|
+
_range;
|
|
89
|
+
_time;
|
|
90
|
+
_exportBtn;
|
|
91
|
+
_shadow;
|
|
92
|
+
_dragging = false;
|
|
93
|
+
_exporting = false;
|
|
94
|
+
constructor() {
|
|
95
|
+
super();
|
|
96
|
+
this._shadow = this.attachShadow({ mode: "open" });
|
|
97
|
+
}
|
|
98
|
+
get width() {
|
|
99
|
+
return parseInt(this.getAttribute("width") ?? "1920");
|
|
100
|
+
}
|
|
101
|
+
get height() {
|
|
102
|
+
return parseInt(this.getAttribute("height") ?? "1080");
|
|
103
|
+
}
|
|
104
|
+
get fps() {
|
|
105
|
+
return parseInt(this.getAttribute("fps") ?? "30");
|
|
106
|
+
}
|
|
107
|
+
get duration() {
|
|
108
|
+
return parseFloat(this.getAttribute("duration") ?? "5");
|
|
109
|
+
}
|
|
110
|
+
get format() {
|
|
111
|
+
return this.getAttribute("format") ?? "mp4";
|
|
112
|
+
}
|
|
113
|
+
get quality() {
|
|
114
|
+
return parseFloat(this.getAttribute("quality") ?? "0.95");
|
|
115
|
+
}
|
|
116
|
+
get player() {
|
|
117
|
+
return this._player;
|
|
118
|
+
}
|
|
119
|
+
connectedCallback() {
|
|
120
|
+
const w = this.width;
|
|
121
|
+
const h = this.height;
|
|
122
|
+
this._shadow.innerHTML = `
|
|
123
|
+
<style>${STYLES}</style>
|
|
124
|
+
<div class="framv-badge">framv video · ${this.format.toUpperCase()} · ${w}x${h}</div>
|
|
125
|
+
<div class="framv-stage" style="--framv-w:${w}px;--framv-h:${h}px">
|
|
126
|
+
<slot></slot>
|
|
127
|
+
</div>
|
|
128
|
+
<div class="framv-toolbar">
|
|
129
|
+
<button class="btn-play">▶</button>
|
|
130
|
+
<input type="range" min="0" max="1000" value="0" step="1">
|
|
131
|
+
<span class="framv-time">0:00 / 0:00</span>
|
|
132
|
+
<button class="btn-export">⬇ Export ${this.format.toUpperCase()}</button>
|
|
133
|
+
</div>
|
|
134
|
+
`;
|
|
135
|
+
this._stage = this._shadow.querySelector(".framv-stage");
|
|
136
|
+
this._toolbar = this._shadow.querySelector(".framv-toolbar");
|
|
137
|
+
this._btn = this._shadow.querySelector(".btn-play");
|
|
138
|
+
this._range = this._shadow.querySelector("input");
|
|
139
|
+
this._time = this._shadow.querySelector(".framv-time");
|
|
140
|
+
this._exportBtn = this._shadow.querySelector(".btn-export");
|
|
141
|
+
// Adapt stage size to container
|
|
142
|
+
this._adaptSize();
|
|
143
|
+
this._player = new Player(this);
|
|
144
|
+
const d = this.duration;
|
|
145
|
+
if (d > 0)
|
|
146
|
+
this._player.setDuration(d);
|
|
147
|
+
else
|
|
148
|
+
this._player.setDuration(5);
|
|
149
|
+
this._player.on("play", () => {
|
|
150
|
+
this._btn.textContent = "⏸";
|
|
151
|
+
});
|
|
152
|
+
this._player.on("pause", () => {
|
|
153
|
+
this._btn.textContent = "▶";
|
|
154
|
+
});
|
|
155
|
+
this._player.on("ended", () => {
|
|
156
|
+
this._btn.textContent = "↺";
|
|
157
|
+
if (this.hasAttribute("loop")) {
|
|
158
|
+
this._player.seek(0).then(() => this._player.play());
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
this._player.on("timeupdate", (t = 0) => {
|
|
162
|
+
if (!this._dragging)
|
|
163
|
+
this._range.value = String(Math.round((t / (this._player.duration || 1)) * 1000));
|
|
164
|
+
this._time.textContent = `${fmt(t)} / ${fmt(this._player.duration)}`;
|
|
165
|
+
});
|
|
166
|
+
this._btn.addEventListener("click", () => {
|
|
167
|
+
if (this._exporting)
|
|
168
|
+
return;
|
|
169
|
+
if (this._player.playing) {
|
|
170
|
+
this._player.pause();
|
|
171
|
+
}
|
|
172
|
+
else if (this._player.currentTime >= this._player.duration && this._player.duration > 0) {
|
|
173
|
+
this._player.seek(0).then(() => this._player.play());
|
|
174
|
+
}
|
|
175
|
+
else {
|
|
176
|
+
this._player.play();
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
this._range.addEventListener("mousedown", () => {
|
|
180
|
+
this._dragging = true;
|
|
181
|
+
});
|
|
182
|
+
this._range.addEventListener("mouseup", () => {
|
|
183
|
+
this._dragging = false;
|
|
184
|
+
});
|
|
185
|
+
this._range.addEventListener("input", () => {
|
|
186
|
+
const t = (Number(this._range.value) / 1000) * (this._player.duration || 1);
|
|
187
|
+
this._player.seek(t);
|
|
188
|
+
});
|
|
189
|
+
this._exportBtn.addEventListener("click", () => this._export());
|
|
190
|
+
if (this.hasAttribute("autoplay"))
|
|
191
|
+
this._player.play();
|
|
192
|
+
// Resize observer
|
|
193
|
+
new ResizeObserver(() => this._adaptSize()).observe(this);
|
|
194
|
+
}
|
|
195
|
+
disconnectedCallback() {
|
|
196
|
+
this._player?.destroy();
|
|
197
|
+
this._player = null;
|
|
198
|
+
}
|
|
199
|
+
attributeChangedCallback(name, _old, value) {
|
|
200
|
+
if (name === "duration" && this._player) {
|
|
201
|
+
this._player.setDuration(parseFloat(value) || 0);
|
|
202
|
+
}
|
|
203
|
+
if (name === "format" && this._toolbar) {
|
|
204
|
+
const badge = this._shadow.querySelector(".framv-badge");
|
|
205
|
+
if (badge)
|
|
206
|
+
badge.textContent = `framv video · ${this.format.toUpperCase()} · ${this.width}x${this.height}`;
|
|
207
|
+
this._exportBtn.textContent = `⬇ Export ${this.format.toUpperCase()}`;
|
|
208
|
+
}
|
|
209
|
+
if ((name === "width" || name === "height") && this._stage) {
|
|
210
|
+
const w = this.width;
|
|
211
|
+
const h = this.height;
|
|
212
|
+
this._stage.style.setProperty("--framv-w", `${w}px`);
|
|
213
|
+
this._stage.style.setProperty("--framv-h", `${h}px`);
|
|
214
|
+
const badge = this._shadow.querySelector(".framv-badge");
|
|
215
|
+
if (badge)
|
|
216
|
+
badge.textContent = `framv video · ${this.format.toUpperCase()} · ${w}x${h}`;
|
|
217
|
+
this._adaptSize();
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
_adaptSize() {
|
|
221
|
+
const w = this.width;
|
|
222
|
+
const h = this.height;
|
|
223
|
+
const containerW = this.clientWidth || w;
|
|
224
|
+
const scale = Math.min(containerW / w, 1);
|
|
225
|
+
this._stage.style.transform = `scale(${scale})`;
|
|
226
|
+
this.style.minHeight = `${h * scale}px`;
|
|
227
|
+
}
|
|
228
|
+
async _export() {
|
|
229
|
+
if (this._exporting)
|
|
230
|
+
return;
|
|
231
|
+
this._exporting = true;
|
|
232
|
+
const overlay = document.createElement("div");
|
|
233
|
+
overlay.className = "framv-exporting-overlay";
|
|
234
|
+
overlay.innerHTML = '<div class="spinner"></div><span>Exporting... 0%</span>';
|
|
235
|
+
const label = overlay.querySelector("span");
|
|
236
|
+
this._shadow.appendChild(overlay);
|
|
237
|
+
// Build a temporary container with light DOM content for the core renderer
|
|
238
|
+
const container = document.createElement("div");
|
|
239
|
+
container.style.width = `${this.width}px`;
|
|
240
|
+
container.style.height = `${this.height}px`;
|
|
241
|
+
container.style.position = "relative";
|
|
242
|
+
container.style.overflow = "hidden";
|
|
243
|
+
Array.from(this.children).forEach((child) => container.appendChild(child.cloneNode(true)));
|
|
244
|
+
try {
|
|
245
|
+
const blob = await exportElement({
|
|
246
|
+
element: container,
|
|
247
|
+
settings: {
|
|
248
|
+
format: this.format,
|
|
249
|
+
fps: this.fps,
|
|
250
|
+
start: 0,
|
|
251
|
+
end: this.duration,
|
|
252
|
+
width: this.width,
|
|
253
|
+
height: this.height,
|
|
254
|
+
quality: this.quality,
|
|
255
|
+
},
|
|
256
|
+
onProgress: (p) => {
|
|
257
|
+
label.textContent = `Exporting... ${Math.round(p * 100)}%`;
|
|
258
|
+
return true;
|
|
259
|
+
},
|
|
260
|
+
});
|
|
261
|
+
const url = URL.createObjectURL(blob);
|
|
262
|
+
const a = document.createElement("a");
|
|
263
|
+
a.href = url;
|
|
264
|
+
a.download = `framv-video.${this.format}`;
|
|
265
|
+
this._shadow.appendChild(a);
|
|
266
|
+
a.click();
|
|
267
|
+
a.remove();
|
|
268
|
+
URL.revokeObjectURL(url);
|
|
269
|
+
}
|
|
270
|
+
catch (err) {
|
|
271
|
+
console.error("Export failed:", err);
|
|
272
|
+
label.textContent = "Export failed. Check console.";
|
|
273
|
+
}
|
|
274
|
+
finally {
|
|
275
|
+
overlay.remove();
|
|
276
|
+
this._exporting = false;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
if (!customElements.get("framv-video")) {
|
|
281
|
+
customElements.define("framv-video", FramvVideoElement);
|
|
282
|
+
}
|
|
283
|
+
//# sourceMappingURL=element.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"element.js","sourceRoot":"","sources":["../src/element.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AACjD,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,YAAY,EAAE,WAAW,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AACjD,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC"}
|
package/dist/player.d.ts
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { PlayerEvent, PlayerEventCallback } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Player wraps any HTMLElement or SVGSVGElement and provides seek/play/pause
|
|
4
|
+
* controls for its timeline animations.
|
|
5
|
+
*
|
|
6
|
+
* Works with:
|
|
7
|
+
* - CSS @keyframes / Web Animations API on any HTML element
|
|
8
|
+
* - SVG SMIL animations (setCurrentTime / unpauseAnimations / pauseAnimations)
|
|
9
|
+
* - HTMLMediaElement (audio/video) with optional `data-media-start` offset
|
|
10
|
+
* - Custom elements that expose a `draw(time: number)` method
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```ts
|
|
14
|
+
* const player = new Player(document.querySelector('#content')!);
|
|
15
|
+
* player.setDuration(10);
|
|
16
|
+
* await player.seek(2.5);
|
|
17
|
+
* await player.play();
|
|
18
|
+
* player.on('timeupdate', (t) => console.log(t));
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
export declare class Player {
|
|
22
|
+
/** The root element whose animations this player controls. Can be any HTML element (div, section, etc.) or SVGSVGElement. */
|
|
23
|
+
private _element;
|
|
24
|
+
private _currentTime;
|
|
25
|
+
private _duration;
|
|
26
|
+
private _playing;
|
|
27
|
+
private _rafId;
|
|
28
|
+
private _lastRafTime;
|
|
29
|
+
private _listeners;
|
|
30
|
+
constructor(element: HTMLElement | SVGSVGElement);
|
|
31
|
+
get currentTime(): number;
|
|
32
|
+
get duration(): number;
|
|
33
|
+
get playing(): boolean;
|
|
34
|
+
setDuration(duration: number): void;
|
|
35
|
+
play(): Promise<void>;
|
|
36
|
+
pause(): void;
|
|
37
|
+
seek(time: number): Promise<void>;
|
|
38
|
+
destroy(): void;
|
|
39
|
+
on(event: PlayerEvent, cb: PlayerEventCallback): () => void;
|
|
40
|
+
private _startRafLoop;
|
|
41
|
+
private _stopRafLoop;
|
|
42
|
+
private _playElement;
|
|
43
|
+
private _pauseElement;
|
|
44
|
+
private _seekElement;
|
|
45
|
+
private _playMedia;
|
|
46
|
+
private _seekMedia;
|
|
47
|
+
private _getMediaStartTime;
|
|
48
|
+
private _emit;
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=player.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"player.d.ts","sourceRoot":"","sources":["../src/player.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAEnE;;;;;;;;;;;;;;;;;;GAkBG;AACH,qBAAa,MAAM;IACjB,6HAA6H;IAC7H,OAAO,CAAC,QAAQ,CAA8B;IAC9C,OAAO,CAAC,YAAY,CAAK;IACzB,OAAO,CAAC,SAAS,CAAK;IACtB,OAAO,CAAC,QAAQ,CAAS;IAEzB,OAAO,CAAC,MAAM,CAAuB;IACrC,OAAO,CAAC,YAAY,CAAuB;IAE3C,OAAO,CAAC,UAAU,CAAyD;gBAE/D,OAAO,EAAE,WAAW,GAAG,aAAa;IAMhD,IAAI,WAAW,IAAI,MAAM,CAExB;IAED,IAAI,QAAQ,IAAI,MAAM,CAErB;IAED,IAAI,OAAO,IAAI,OAAO,CAErB;IAED,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;IAQ7B,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAS3B,KAAK,IAAI,IAAI;IASP,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IASvC,OAAO,IAAI,IAAI;IAOf,EAAE,CAAC,KAAK,EAAE,WAAW,EAAE,EAAE,EAAE,mBAAmB,GAAG,MAAM,IAAI;IAa3D,OAAO,CAAC,aAAa;IAqCrB,OAAO,CAAC,YAAY;YAUN,YAAY;IAgB1B,OAAO,CAAC,aAAa;YAmBP,YAAY;YAmBZ,UAAU;IAyBxB,OAAO,CAAC,UAAU;IAwBlB,OAAO,CAAC,kBAAkB;IAO1B,OAAO,CAAC,KAAK;CAGd"}
|
package/dist/player.js
ADDED
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Player wraps any HTMLElement or SVGSVGElement and provides seek/play/pause
|
|
3
|
+
* controls for its timeline animations.
|
|
4
|
+
*
|
|
5
|
+
* Works with:
|
|
6
|
+
* - CSS @keyframes / Web Animations API on any HTML element
|
|
7
|
+
* - SVG SMIL animations (setCurrentTime / unpauseAnimations / pauseAnimations)
|
|
8
|
+
* - HTMLMediaElement (audio/video) with optional `data-media-start` offset
|
|
9
|
+
* - Custom elements that expose a `draw(time: number)` method
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```ts
|
|
13
|
+
* const player = new Player(document.querySelector('#content')!);
|
|
14
|
+
* player.setDuration(10);
|
|
15
|
+
* await player.seek(2.5);
|
|
16
|
+
* await player.play();
|
|
17
|
+
* player.on('timeupdate', (t) => console.log(t));
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
export class Player {
|
|
21
|
+
/** The root element whose animations this player controls. Can be any HTML element (div, section, etc.) or SVGSVGElement. */
|
|
22
|
+
_element;
|
|
23
|
+
_currentTime = 0;
|
|
24
|
+
_duration = 0;
|
|
25
|
+
_playing = false;
|
|
26
|
+
_rafId = null;
|
|
27
|
+
_lastRafTime = null;
|
|
28
|
+
_listeners = new Map();
|
|
29
|
+
constructor(element) {
|
|
30
|
+
this._element = element;
|
|
31
|
+
}
|
|
32
|
+
// ─── State ───────────────────────────────────────────────────────────────
|
|
33
|
+
get currentTime() {
|
|
34
|
+
return this._currentTime;
|
|
35
|
+
}
|
|
36
|
+
get duration() {
|
|
37
|
+
return this._duration;
|
|
38
|
+
}
|
|
39
|
+
get playing() {
|
|
40
|
+
return this._playing;
|
|
41
|
+
}
|
|
42
|
+
setDuration(duration) {
|
|
43
|
+
if (duration >= 0) {
|
|
44
|
+
this._duration = duration;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
// ─── Controls ────────────────────────────────────────────────────────────
|
|
48
|
+
async play() {
|
|
49
|
+
if (this._playing)
|
|
50
|
+
return;
|
|
51
|
+
this._playing = true;
|
|
52
|
+
await this._playElement(this._element);
|
|
53
|
+
this._startRafLoop();
|
|
54
|
+
this._emit("play");
|
|
55
|
+
}
|
|
56
|
+
pause() {
|
|
57
|
+
if (!this._playing)
|
|
58
|
+
return;
|
|
59
|
+
this._playing = false;
|
|
60
|
+
this._stopRafLoop();
|
|
61
|
+
this._pauseElement(this._element);
|
|
62
|
+
this._emit("pause");
|
|
63
|
+
}
|
|
64
|
+
async seek(time) {
|
|
65
|
+
const clamped = this._duration > 0 ? Math.max(0, Math.min(time, this._duration)) : Math.max(0, time);
|
|
66
|
+
this._currentTime = clamped;
|
|
67
|
+
await this._seekElement(this._element, clamped);
|
|
68
|
+
this._emit("seek", clamped);
|
|
69
|
+
this._emit("timeupdate", clamped);
|
|
70
|
+
}
|
|
71
|
+
destroy() {
|
|
72
|
+
this.pause();
|
|
73
|
+
this._listeners.clear();
|
|
74
|
+
}
|
|
75
|
+
// ─── Events ──────────────────────────────────────────────────────────────
|
|
76
|
+
on(event, cb) {
|
|
77
|
+
if (!this._listeners.has(event)) {
|
|
78
|
+
this._listeners.set(event, new Set());
|
|
79
|
+
}
|
|
80
|
+
this._listeners.get(event).add(cb);
|
|
81
|
+
return () => {
|
|
82
|
+
this._listeners.get(event)?.delete(cb);
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
// ─── Internal: rAF loop ──────────────────────────────────────────────────
|
|
86
|
+
_startRafLoop() {
|
|
87
|
+
this._lastRafTime = null;
|
|
88
|
+
const tick = (now) => {
|
|
89
|
+
if (!this._playing)
|
|
90
|
+
return;
|
|
91
|
+
if (this._lastRafTime !== null) {
|
|
92
|
+
const delta = (now - this._lastRafTime) / 1000; // seconds
|
|
93
|
+
this._currentTime += delta;
|
|
94
|
+
if (this._duration > 0 && this._currentTime >= this._duration) {
|
|
95
|
+
this._currentTime = this._duration;
|
|
96
|
+
this._emit("timeupdate", this._currentTime);
|
|
97
|
+
this._playing = false;
|
|
98
|
+
this._stopRafLoop();
|
|
99
|
+
this._pauseElement(this._element);
|
|
100
|
+
this._emit("ended");
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
this._lastRafTime = now;
|
|
105
|
+
// Call draw(time) on elements that expose it
|
|
106
|
+
const all = [this._element, ...Array.from(this._element.querySelectorAll("*"))];
|
|
107
|
+
for (const el of all) {
|
|
108
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
109
|
+
if (typeof el.draw === "function")
|
|
110
|
+
el.draw(this._currentTime);
|
|
111
|
+
}
|
|
112
|
+
this._emit("timeupdate", this._currentTime);
|
|
113
|
+
this._rafId = requestAnimationFrame(tick);
|
|
114
|
+
};
|
|
115
|
+
this._rafId = requestAnimationFrame(tick);
|
|
116
|
+
}
|
|
117
|
+
_stopRafLoop() {
|
|
118
|
+
if (this._rafId !== null) {
|
|
119
|
+
cancelAnimationFrame(this._rafId);
|
|
120
|
+
this._rafId = null;
|
|
121
|
+
}
|
|
122
|
+
this._lastRafTime = null;
|
|
123
|
+
}
|
|
124
|
+
// ─── Internal: element control ───────────────────────────────────────────
|
|
125
|
+
async _playElement(element) {
|
|
126
|
+
const all = [element, ...Array.from(element.querySelectorAll("*"))];
|
|
127
|
+
for (const el of all) {
|
|
128
|
+
if (el instanceof SVGSVGElement) {
|
|
129
|
+
el.unpauseAnimations();
|
|
130
|
+
}
|
|
131
|
+
else if (el instanceof HTMLMediaElement) {
|
|
132
|
+
await this._playMedia(el);
|
|
133
|
+
}
|
|
134
|
+
else if (el.getAnimations?.().length > 0) {
|
|
135
|
+
el.getAnimations().forEach((a) => a.play());
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
// Script-driven elements (custom `draw` method) are handled by the rAF loop
|
|
139
|
+
}
|
|
140
|
+
_pauseElement(element) {
|
|
141
|
+
const all = [element, ...Array.from(element.querySelectorAll("*"))];
|
|
142
|
+
for (const el of all) {
|
|
143
|
+
if (el instanceof SVGSVGElement) {
|
|
144
|
+
el.pauseAnimations();
|
|
145
|
+
}
|
|
146
|
+
else if (el instanceof HTMLMediaElement) {
|
|
147
|
+
el.pause();
|
|
148
|
+
const extEl = el;
|
|
149
|
+
if (extEl._autoplayTimeout) {
|
|
150
|
+
clearTimeout(extEl._autoplayTimeout);
|
|
151
|
+
delete extEl._autoplayTimeout;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
else if (el.getAnimations?.().length > 0) {
|
|
155
|
+
el.getAnimations().forEach((a) => a.pause());
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
async _seekElement(element, time) {
|
|
160
|
+
const all = [element, ...Array.from(element.querySelectorAll("*"))];
|
|
161
|
+
const promises = [];
|
|
162
|
+
for (const el of all) {
|
|
163
|
+
if (el instanceof SVGSVGElement) {
|
|
164
|
+
el.setCurrentTime(time);
|
|
165
|
+
}
|
|
166
|
+
else if (el instanceof HTMLMediaElement) {
|
|
167
|
+
promises.push(this._seekMedia(el, time));
|
|
168
|
+
}
|
|
169
|
+
else if (el.getAnimations?.().length > 0) {
|
|
170
|
+
el.getAnimations().forEach((a) => {
|
|
171
|
+
a.currentTime = time * 1000;
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
await Promise.all(promises);
|
|
176
|
+
}
|
|
177
|
+
async _playMedia(el) {
|
|
178
|
+
const startTime = this._getMediaStartTime(el);
|
|
179
|
+
const now = this._currentTime;
|
|
180
|
+
if (startTime > 0) {
|
|
181
|
+
if (now >= startTime) {
|
|
182
|
+
await this._seekMedia(el, now);
|
|
183
|
+
await el.play().catch(() => undefined);
|
|
184
|
+
}
|
|
185
|
+
else {
|
|
186
|
+
const extEl = el;
|
|
187
|
+
extEl._autoplayTimeout = setTimeout(async () => {
|
|
188
|
+
await el.play().catch(() => undefined);
|
|
189
|
+
}, (startTime - now) * 1000);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
else if (startTime < 0) {
|
|
193
|
+
await this._seekMedia(el, now);
|
|
194
|
+
await el.play().catch(() => undefined);
|
|
195
|
+
}
|
|
196
|
+
else {
|
|
197
|
+
await el.play().catch(() => undefined);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
_seekMedia(el, containerTime) {
|
|
201
|
+
return new Promise((resolve) => {
|
|
202
|
+
const startTime = this._getMediaStartTime(el);
|
|
203
|
+
let adjusted;
|
|
204
|
+
if (startTime > 0) {
|
|
205
|
+
adjusted = containerTime >= startTime ? containerTime - startTime : 0;
|
|
206
|
+
}
|
|
207
|
+
else if (startTime < 0) {
|
|
208
|
+
adjusted = containerTime + Math.abs(startTime);
|
|
209
|
+
}
|
|
210
|
+
else {
|
|
211
|
+
adjusted = containerTime;
|
|
212
|
+
}
|
|
213
|
+
const target = el.loop && el.duration > 0 ? adjusted % el.duration : Math.min(adjusted, el.duration || 0);
|
|
214
|
+
const onSeeked = () => {
|
|
215
|
+
el.removeEventListener("seeked", onSeeked);
|
|
216
|
+
resolve();
|
|
217
|
+
};
|
|
218
|
+
el.addEventListener("seeked", onSeeked);
|
|
219
|
+
el.currentTime = target;
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
_getMediaStartTime(el) {
|
|
223
|
+
const attr = el.getAttribute("data-media-start");
|
|
224
|
+
return attr ? parseFloat(attr) : 0;
|
|
225
|
+
}
|
|
226
|
+
// ─── Internal: emit ──────────────────────────────────────────────────────
|
|
227
|
+
_emit(event, time) {
|
|
228
|
+
this._listeners.get(event)?.forEach((cb) => cb(time));
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
//# sourceMappingURL=player.js.map
|