webcodecs-examples 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 +105 -0
- package/dist/assets/file-DCgsDVsh.js +22251 -0
- package/dist/assets/file-DCgsDVsh.js.map +1 -0
- package/dist/assets/video.worker-DrxbiAg9.js +22579 -0
- package/dist/assets/video.worker-DrxbiAg9.js.map +1 -0
- package/dist/index.js +468 -0
- package/dist/index.js.map +1 -0
- package/package.json +55 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,468 @@
|
|
|
1
|
+
import { Muxer as v, ArrayBufferTarget as M } from "mp4-muxer";
|
|
2
|
+
const r = [];
|
|
3
|
+
for (let s = 0; s < 256; ++s)
|
|
4
|
+
r.push((s + 256).toString(16).slice(1));
|
|
5
|
+
function E(s, e = 0) {
|
|
6
|
+
return (r[s[e + 0]] + r[s[e + 1]] + r[s[e + 2]] + r[s[e + 3]] + "-" + r[s[e + 4]] + r[s[e + 5]] + "-" + r[s[e + 6]] + r[s[e + 7]] + "-" + r[s[e + 8]] + r[s[e + 9]] + "-" + r[s[e + 10]] + r[s[e + 11]] + r[s[e + 12]] + r[s[e + 13]] + r[s[e + 14]] + r[s[e + 15]]).toLowerCase();
|
|
7
|
+
}
|
|
8
|
+
let m;
|
|
9
|
+
const D = new Uint8Array(16);
|
|
10
|
+
function F() {
|
|
11
|
+
if (!m) {
|
|
12
|
+
if (typeof crypto > "u" || !crypto.getRandomValues)
|
|
13
|
+
throw new Error("crypto.getRandomValues() not supported. See https://github.com/uuidjs/uuid#getrandomvalues-not-supported");
|
|
14
|
+
m = crypto.getRandomValues.bind(crypto);
|
|
15
|
+
}
|
|
16
|
+
return m(D);
|
|
17
|
+
}
|
|
18
|
+
const A = typeof crypto < "u" && crypto.randomUUID && crypto.randomUUID.bind(crypto), P = { randomUUID: A };
|
|
19
|
+
function S(s, e, t) {
|
|
20
|
+
if (P.randomUUID && !s)
|
|
21
|
+
return P.randomUUID();
|
|
22
|
+
s = s || {};
|
|
23
|
+
const i = s.random || (s.rng || F)();
|
|
24
|
+
return i[6] = i[6] & 15 | 64, i[8] = i[8] & 63 | 128, E(i);
|
|
25
|
+
}
|
|
26
|
+
class f {
|
|
27
|
+
constructor(e, t = [], i) {
|
|
28
|
+
this.worker = new Worker(e, { type: "module" }), this.listeners = {}, this.persistentEvents = t, this.worker.onmessage = this.handleWorkerMessage.bind(this), i && (this.port = i, this.worker.postMessage({ cmd: "port", data: i }, { transfer: [i] }));
|
|
29
|
+
}
|
|
30
|
+
setupPort(e) {
|
|
31
|
+
this.port = e, this.worker.postMessage({ cmd: "port", data: e }, { transfer: [e] });
|
|
32
|
+
}
|
|
33
|
+
handleWorkerMessage(e) {
|
|
34
|
+
this.listeners[e.data.request_id] && (this.listeners[e.data.request_id](e.data.res), this.persistentEvents.includes(e.data.request_id) || delete this.listeners[e.data.request_id]);
|
|
35
|
+
}
|
|
36
|
+
addPersistentListener(e, t) {
|
|
37
|
+
this.persistentEvents.includes(e) || this.persistentEvents.push(e), this.listeners[e] = t;
|
|
38
|
+
}
|
|
39
|
+
async sendMessage(e, t = {}, i = [], n = !0) {
|
|
40
|
+
const o = S(), h = n ? g(t) : t;
|
|
41
|
+
return new Promise((d, l) => {
|
|
42
|
+
try {
|
|
43
|
+
const u = {
|
|
44
|
+
cmd: e,
|
|
45
|
+
request_id: o,
|
|
46
|
+
data: h
|
|
47
|
+
};
|
|
48
|
+
this.worker.postMessage(u, i), this.listeners[o] = (c) => {
|
|
49
|
+
d(c);
|
|
50
|
+
};
|
|
51
|
+
} catch (u) {
|
|
52
|
+
l(new Error("Failed to send message to worker: " + u));
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
terminate() {
|
|
57
|
+
this.worker.terminate(), this.listeners = {};
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
function g(s) {
|
|
61
|
+
if (s === null || typeof s != "object")
|
|
62
|
+
return s;
|
|
63
|
+
const e = [
|
|
64
|
+
ArrayBuffer,
|
|
65
|
+
Blob,
|
|
66
|
+
EncodedVideoChunk,
|
|
67
|
+
ImageBitmap,
|
|
68
|
+
EncodedAudioChunk,
|
|
69
|
+
OffscreenCanvas,
|
|
70
|
+
Uint8Array,
|
|
71
|
+
VideoFrame,
|
|
72
|
+
FileSystemFileHandle,
|
|
73
|
+
MessageChannel,
|
|
74
|
+
MessagePort
|
|
75
|
+
];
|
|
76
|
+
for (const n of e)
|
|
77
|
+
if (s instanceof n)
|
|
78
|
+
return s;
|
|
79
|
+
if (Array.isArray(s))
|
|
80
|
+
return s.map((n) => g(n));
|
|
81
|
+
const t = s && s.__v_raw ? s.__v_raw : s, i = {};
|
|
82
|
+
for (const n in t) {
|
|
83
|
+
if (n.startsWith("__v_"))
|
|
84
|
+
continue;
|
|
85
|
+
const o = t[n];
|
|
86
|
+
typeof o != "function" && (i[n] = g(o));
|
|
87
|
+
}
|
|
88
|
+
return i;
|
|
89
|
+
}
|
|
90
|
+
const a = 30;
|
|
91
|
+
class I {
|
|
92
|
+
constructor(e) {
|
|
93
|
+
this.audioContext = null, this.sourceNode = null, this.isPlaying = !1, this.startTime = 0, this.pauseTime = 0, this.duration = e.duration, this.audioConfig = e.audioConfig, this.encodedChunks = [], this.audioSegments = /* @__PURE__ */ new Map(), this.scheduledNodes = /* @__PURE__ */ new Map(), this.preloadThreshold = 5, this.isPreloading = !1, this.worker = e.worker, this.file = e.file, this.init();
|
|
94
|
+
}
|
|
95
|
+
init() {
|
|
96
|
+
this.audioContext = new AudioContext(), this.seek(0);
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Mux EncodedAudioChunks to an ArrayBuffer for Web Audio API decoding
|
|
100
|
+
* @param chunks - Array of EncodedAudioChunks from a segment
|
|
101
|
+
*/
|
|
102
|
+
async muxEncodedChunksToBuffer(e, t) {
|
|
103
|
+
const i = new v({
|
|
104
|
+
target: new M(),
|
|
105
|
+
fastStart: "in-memory",
|
|
106
|
+
firstTimestampBehavior: "offset",
|
|
107
|
+
audio: {
|
|
108
|
+
codec: "aac",
|
|
109
|
+
sampleRate: t.sampleRate,
|
|
110
|
+
numberOfChannels: t.numberOfChannels
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
for (const n of e)
|
|
114
|
+
i.addAudioChunk(n);
|
|
115
|
+
return await i.finalize(), i.target.buffer;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Fetch EncodedAudioChunks for a specific time segment from the file worker
|
|
119
|
+
* @param time - Time in seconds
|
|
120
|
+
* @returns Array of EncodedAudioChunks
|
|
121
|
+
*/
|
|
122
|
+
async getEncodedChunksForTime(e) {
|
|
123
|
+
const t = Math.floor(e / a), i = await this.worker.sendMessage("get-audio-segment", {
|
|
124
|
+
start: t * a,
|
|
125
|
+
end: t * a + a,
|
|
126
|
+
file: this.file
|
|
127
|
+
});
|
|
128
|
+
return this.encodedChunks = i, i;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Load and decode an audio segment
|
|
132
|
+
* @param time - Time in seconds
|
|
133
|
+
* @returns Decoded AudioBuffer for the segment
|
|
134
|
+
*/
|
|
135
|
+
async loadSegment(e) {
|
|
136
|
+
const t = Math.floor(e / a);
|
|
137
|
+
if (this.audioSegments.has(t))
|
|
138
|
+
return this.audioSegments.get(t);
|
|
139
|
+
const i = await this.getEncodedChunksForTime(t * a);
|
|
140
|
+
if (i.length === 0) return null;
|
|
141
|
+
try {
|
|
142
|
+
const n = performance.now(), o = await this.muxEncodedChunksToBuffer(i, this.audioConfig), h = performance.now(), d = await this.audioContext.decodeAudioData(o), l = performance.now();
|
|
143
|
+
return console.log(`Segment ${t}: Muxing took ${h - n}ms, Decoding took ${l - h}ms`), this.audioSegments.set(t, d), d;
|
|
144
|
+
} catch (n) {
|
|
145
|
+
return console.error("Error loading audio segment:", n), null;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
async startPlayback(e = this.pauseTime) {
|
|
149
|
+
this.clearScheduledNodes();
|
|
150
|
+
const t = await this.loadSegment(e);
|
|
151
|
+
if (!t) return;
|
|
152
|
+
const i = e % a, n = a - i;
|
|
153
|
+
this.scheduleSegment(t, e, i), this.preloadNextSegment(e + n);
|
|
154
|
+
}
|
|
155
|
+
clearScheduledNodes() {
|
|
156
|
+
for (const e of this.scheduledNodes.values())
|
|
157
|
+
e.stop(), e.disconnect();
|
|
158
|
+
this.scheduledNodes.clear();
|
|
159
|
+
}
|
|
160
|
+
getCurrentSegmentIndex() {
|
|
161
|
+
return Math.floor(this.getCurrentTime() / a);
|
|
162
|
+
}
|
|
163
|
+
async preloadNextSegment(e) {
|
|
164
|
+
if (this.isPreloading || e >= this.duration) return;
|
|
165
|
+
const t = Math.floor(e / a);
|
|
166
|
+
if (this.audioSegments.has(t)) {
|
|
167
|
+
this.scheduleSegment(this.audioSegments.get(t), e, 0);
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
this.isPreloading = !0;
|
|
171
|
+
try {
|
|
172
|
+
const i = await this.loadSegment(e);
|
|
173
|
+
if (!i || !this.isPlaying) return;
|
|
174
|
+
this.scheduleSegment(i, e, 0);
|
|
175
|
+
} finally {
|
|
176
|
+
this.isPreloading = !1;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
scheduleSegment(e, t, i) {
|
|
180
|
+
const n = this.audioContext.createBufferSource();
|
|
181
|
+
n.buffer = e, n.connect(this.audioContext.destination);
|
|
182
|
+
const o = this.startTime + (t - this.pauseTime);
|
|
183
|
+
n.start(o, i), this.scheduledNodes.set(t, n), n.onended = () => {
|
|
184
|
+
n.disconnect(), this.scheduledNodes.delete(t);
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
async play() {
|
|
188
|
+
this.startTime = this.audioContext.currentTime, await this.startPlayback(), this.isPlaying = !0;
|
|
189
|
+
}
|
|
190
|
+
async pause() {
|
|
191
|
+
this.clearScheduledNodes(), this.pauseTime = this.getCurrentTime(), this.isPlaying = !1;
|
|
192
|
+
}
|
|
193
|
+
async seek(e) {
|
|
194
|
+
const t = this.isPlaying;
|
|
195
|
+
t && (this.clearScheduledNodes(), this.isPlaying = !1), this.pauseTime = e, t && (this.startTime = this.audioContext.currentTime, this.isPlaying = !0, await this.startPlayback(e));
|
|
196
|
+
}
|
|
197
|
+
checkForPreLoad() {
|
|
198
|
+
if (!this.isPlaying) return;
|
|
199
|
+
const e = this.getCurrentTime(), t = this.getCurrentSegmentIndex();
|
|
200
|
+
e % a >= a - this.preloadThreshold && !this.isPreloading && !this.audioSegments.has(t + 1) && this.preloadNextSegment((t + 1) * a);
|
|
201
|
+
}
|
|
202
|
+
getCurrentTime() {
|
|
203
|
+
return this.isPlaying ? this.pauseTime + (this.audioContext.currentTime - this.startTime) : this.pauseTime;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
class x {
|
|
207
|
+
constructor() {
|
|
208
|
+
this.listeners = {};
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Add an event listener and return its unique ID
|
|
212
|
+
*/
|
|
213
|
+
on(e, t) {
|
|
214
|
+
this.listeners[e] || (this.listeners[e] = {});
|
|
215
|
+
const i = S();
|
|
216
|
+
return this.listeners[e][i] = t, i;
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Remove a specific listener by its ID
|
|
220
|
+
*/
|
|
221
|
+
off(e, t) {
|
|
222
|
+
this.listeners[e] && delete this.listeners[e][t];
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Emit an event to all registered listeners
|
|
226
|
+
*/
|
|
227
|
+
emit(e, t) {
|
|
228
|
+
this.listeners[e] && Object.values(this.listeners[e]).forEach((i) => {
|
|
229
|
+
i(t);
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Remove all listeners for a specific event
|
|
234
|
+
*/
|
|
235
|
+
removeAllListeners(e) {
|
|
236
|
+
this.listeners[e] && (this.listeners[e] = {});
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
const _ = "/assets/video.worker-DrxbiAg9.js";
|
|
240
|
+
class C extends x {
|
|
241
|
+
constructor(e) {
|
|
242
|
+
super(), this.offscreenCanvas = null, this.duration = 0, this.canvas = e.canvas, this.fileWorkerPort = e.fileWorkerPort, this.worker = new f(_);
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Send a message to the worker and wait for a response
|
|
246
|
+
*/
|
|
247
|
+
/**
|
|
248
|
+
* Initialize the video player
|
|
249
|
+
*/
|
|
250
|
+
async initialize() {
|
|
251
|
+
this.offscreenCanvas = this.canvas.transferControlToOffscreen();
|
|
252
|
+
const e = await this.worker.sendMessage("init", {
|
|
253
|
+
canvas: this.offscreenCanvas,
|
|
254
|
+
fileWorkerPort: this.fileWorkerPort
|
|
255
|
+
}, [this.offscreenCanvas, this.fileWorkerPort]);
|
|
256
|
+
this.emit("initialized", e);
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Seek to a specific time
|
|
260
|
+
*/
|
|
261
|
+
async seek(e) {
|
|
262
|
+
await this.worker.sendMessage("seek", { time: e });
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Get debug information from the video worker
|
|
266
|
+
*/
|
|
267
|
+
async getDebugInfo() {
|
|
268
|
+
return await this.worker.sendMessage("get-debug-info", {});
|
|
269
|
+
}
|
|
270
|
+
async setTrackData(e, t) {
|
|
271
|
+
await this.worker.sendMessage("set-track-data", {
|
|
272
|
+
videoMetadata: e,
|
|
273
|
+
duration: t
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Clean up resources
|
|
278
|
+
*/
|
|
279
|
+
terminate() {
|
|
280
|
+
this.worker.sendMessage("terminate").catch(console.error), this.worker.terminate(), this.offscreenCanvas = null, this.emit("terminated");
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Update the current frame (animation loop)
|
|
286
|
+
*/
|
|
287
|
+
render(e) {
|
|
288
|
+
this.worker.sendMessage("render", { time: e });
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
class b extends x {
|
|
292
|
+
/**
|
|
293
|
+
* Create a new Clock
|
|
294
|
+
* @param audioPlayer - Audio player with Web Audio timeline
|
|
295
|
+
* @param videoWorker - Video worker for passive rendering
|
|
296
|
+
* @param duration - Total video duration in seconds
|
|
297
|
+
*/
|
|
298
|
+
constructor(e, t, i) {
|
|
299
|
+
super(), this.isPlaying = !1, this.animationFrame = null, this.TARGET_FPS = 30, this.lastFrameTime = 0, this.audioPlayer = e, this.videoWorker = t, this.duration = i, this.FRAME_INTERVAL = 1e3 / this.TARGET_FPS;
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Start playback
|
|
303
|
+
*
|
|
304
|
+
* Starts the audio player and begins the tick loop.
|
|
305
|
+
* The tick loop queries the audio timeline and drives video rendering.
|
|
306
|
+
*/
|
|
307
|
+
async play() {
|
|
308
|
+
this.isPlaying || (this.isPlaying = !0, await this.audioPlayer.play(), this.lastFrameTime = performance.now(), this.tick(), this.emit("play"));
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* Pause playback
|
|
312
|
+
*
|
|
313
|
+
* Pauses audio and stops the tick loop.
|
|
314
|
+
*/
|
|
315
|
+
pause() {
|
|
316
|
+
this.isPlaying && (this.isPlaying = !1, this.audioPlayer.pause(), this.animationFrame && (cancelAnimationFrame(this.animationFrame), this.animationFrame = null), this.emit("pause"));
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* Seek to a specific time
|
|
320
|
+
*
|
|
321
|
+
* @param time - Time in seconds
|
|
322
|
+
*/
|
|
323
|
+
async seek(e) {
|
|
324
|
+
const t = Math.max(0, Math.min(e, this.duration));
|
|
325
|
+
this.videoWorker.seek(t), await this.audioPlayer.seek(t), this.emit("seek", t);
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* Get the current playback time
|
|
329
|
+
*
|
|
330
|
+
* Queries the audio player's timeline, which is the source of truth.
|
|
331
|
+
*
|
|
332
|
+
* @returns Current time in seconds
|
|
333
|
+
*/
|
|
334
|
+
getCurrentTime() {
|
|
335
|
+
return this.audioPlayer.getCurrentTime();
|
|
336
|
+
}
|
|
337
|
+
/**
|
|
338
|
+
* Check if currently playing
|
|
339
|
+
*/
|
|
340
|
+
playing() {
|
|
341
|
+
return this.isPlaying;
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* Main tick loop
|
|
345
|
+
*
|
|
346
|
+
* This runs at TARGET_FPS and:
|
|
347
|
+
* 1. Queries the current time from audio timeline
|
|
348
|
+
* 2. Emits tick event for UI updates
|
|
349
|
+
* 3. Tells video worker to render at this time
|
|
350
|
+
* 4. Checks for end of video
|
|
351
|
+
*
|
|
352
|
+
* The video worker is passive - it just renders whatever time we tell it.
|
|
353
|
+
* The audio timeline is the source of truth for the current time.
|
|
354
|
+
*/
|
|
355
|
+
tick() {
|
|
356
|
+
if (!this.isPlaying) return;
|
|
357
|
+
const e = performance.now();
|
|
358
|
+
if (e - this.lastFrameTime < this.FRAME_INTERVAL) {
|
|
359
|
+
this.animationFrame = requestAnimationFrame(() => this.tick());
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
this.lastFrameTime = e;
|
|
363
|
+
const i = this.audioPlayer.getCurrentTime();
|
|
364
|
+
if (i >= this.duration - 0.1) {
|
|
365
|
+
this.pause(), this.emit("ended");
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
this.emit("tick", i), this.videoWorker.render(i), this.audioPlayer.checkForPreLoad(), this.animationFrame = requestAnimationFrame(() => this.tick());
|
|
369
|
+
}
|
|
370
|
+
/**
|
|
371
|
+
* Update duration (if needed after initialization)
|
|
372
|
+
*/
|
|
373
|
+
setDuration(e) {
|
|
374
|
+
this.duration = e;
|
|
375
|
+
}
|
|
376
|
+
/**
|
|
377
|
+
* Clean up resources
|
|
378
|
+
*/
|
|
379
|
+
destroy() {
|
|
380
|
+
this.animationFrame && (cancelAnimationFrame(this.animationFrame), this.animationFrame = null), this.isPlaying = !1;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
const T = "/assets/file-DCgsDVsh.js";
|
|
384
|
+
class z {
|
|
385
|
+
constructor(e) {
|
|
386
|
+
this.canvas = null, this.duration = 0, this.renderer = null, this.audioPlayer = null, this.worker = null, this.clock = null, this.trackData = null, this.params = e, this.worker = new f(T), this.file = e.src, this.canvas = e.canvas, this.duration = 0;
|
|
387
|
+
}
|
|
388
|
+
async play() {
|
|
389
|
+
if (!this.clock)
|
|
390
|
+
throw new Error("Player not initialized. Call initialize() first.");
|
|
391
|
+
await this.clock.play();
|
|
392
|
+
}
|
|
393
|
+
async pause() {
|
|
394
|
+
if (!this.clock)
|
|
395
|
+
throw new Error("Player not initialized. Call initialize() first.");
|
|
396
|
+
this.clock.pause();
|
|
397
|
+
}
|
|
398
|
+
async seek(e) {
|
|
399
|
+
if (!this.clock)
|
|
400
|
+
throw new Error("Player not initialized. Call initialize() first.");
|
|
401
|
+
await this.clock.seek(e);
|
|
402
|
+
}
|
|
403
|
+
getCurrentTime() {
|
|
404
|
+
var e;
|
|
405
|
+
return ((e = this.clock) == null ? void 0 : e.getCurrentTime()) || 0;
|
|
406
|
+
}
|
|
407
|
+
async getDebugInfo() {
|
|
408
|
+
var t, i, n, o, h, d, l, u, c, y, k, w, p;
|
|
409
|
+
const e = this.renderer ? await this.renderer.getDebugInfo() : null;
|
|
410
|
+
return {
|
|
411
|
+
trackData: {
|
|
412
|
+
duration: this.duration,
|
|
413
|
+
audio: this.audioPlayer ? {
|
|
414
|
+
codec: (t = this.audioPlayer.audioConfig) == null ? void 0 : t.codec,
|
|
415
|
+
sampleRate: (i = this.audioPlayer.audioConfig) == null ? void 0 : i.sampleRate,
|
|
416
|
+
numberOfChannels: (n = this.audioPlayer.audioConfig) == null ? void 0 : n.numberOfChannels,
|
|
417
|
+
startTime: this.audioPlayer.startTime,
|
|
418
|
+
pauseTime: this.audioPlayer.pauseTime,
|
|
419
|
+
isPlaying: this.audioPlayer.isPlaying,
|
|
420
|
+
loadedSegments: this.audioPlayer.audioSegments.size,
|
|
421
|
+
scheduledNodeCount: this.audioPlayer.scheduledNodes.size
|
|
422
|
+
} : null,
|
|
423
|
+
video: e ? {
|
|
424
|
+
duration: (o = this.renderer) == null ? void 0 : o.duration,
|
|
425
|
+
codec: (d = (h = this.trackData) == null ? void 0 : h.video) == null ? void 0 : d.codec,
|
|
426
|
+
width: (u = (l = this.trackData) == null ? void 0 : l.video) == null ? void 0 : u.codedWidth,
|
|
427
|
+
height: (y = (c = this.trackData) == null ? void 0 : c.video) == null ? void 0 : y.codedHeight,
|
|
428
|
+
frameRate: (w = (k = this.trackData) == null ? void 0 : k.video) == null ? void 0 : w.frameRate,
|
|
429
|
+
...e
|
|
430
|
+
} : null
|
|
431
|
+
},
|
|
432
|
+
clock: {
|
|
433
|
+
isPlaying: (p = this.clock) == null ? void 0 : p.playing(),
|
|
434
|
+
currentTime: this.getCurrentTime()
|
|
435
|
+
}
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
terminate() {
|
|
439
|
+
this.clock && (this.clock.destroy(), this.clock = null), this.audioPlayer && this.audioPlayer.pause(), this.renderer && (this.renderer instanceof C && this.renderer.terminate(), this.renderer = null);
|
|
440
|
+
}
|
|
441
|
+
async initialize() {
|
|
442
|
+
console.log("Initializing"), this.worker = new f(T);
|
|
443
|
+
const e = new MessageChannel();
|
|
444
|
+
await this.worker.sendMessage("init", {
|
|
445
|
+
file: this.file,
|
|
446
|
+
videoPort: e.port1
|
|
447
|
+
}, [e.port1]);
|
|
448
|
+
const t = await this.worker.sendMessage("get-tracks", {});
|
|
449
|
+
console.log("Track data", t), this.trackData = t, this.duration = t.duration, this.renderer = new C({
|
|
450
|
+
src: this.file,
|
|
451
|
+
canvas: this.canvas,
|
|
452
|
+
fileWorkerPort: e.port2
|
|
453
|
+
}), await this.renderer.initialize(), await this.renderer.setTrackData(t.video, t.duration), this.audioPlayer = new I({
|
|
454
|
+
worker: this.worker,
|
|
455
|
+
audioConfig: t.audio,
|
|
456
|
+
duration: t.duration,
|
|
457
|
+
file: this.file
|
|
458
|
+
}), this.clock = new b(this.audioPlayer, this.renderer, this.duration);
|
|
459
|
+
}
|
|
460
|
+
on(e, t) {
|
|
461
|
+
this.clock.on(e, t);
|
|
462
|
+
}
|
|
463
|
+
// Add more methods as needed
|
|
464
|
+
}
|
|
465
|
+
export {
|
|
466
|
+
z as WebCodecsPlayer
|
|
467
|
+
};
|
|
468
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../../../node_modules/uuid/dist/esm-browser/stringify.js","../../../node_modules/uuid/dist/esm-browser/rng.js","../../../node_modules/uuid/dist/esm-browser/native.js","../../../node_modules/uuid/dist/esm-browser/v4.js","../src/utils/WorkerController.ts","../src/player/renderers/audio/audio.ts","../src/utils/EventEmitter.ts","../src/player/renderers/video/video.ts","../src/player/clock.ts","../src/player/player.ts"],"sourcesContent":["import validate from './validate.js';\nconst byteToHex = [];\nfor (let i = 0; i < 256; ++i) {\n byteToHex.push((i + 0x100).toString(16).slice(1));\n}\nexport function unsafeStringify(arr, offset = 0) {\n return (byteToHex[arr[offset + 0]] +\n byteToHex[arr[offset + 1]] +\n byteToHex[arr[offset + 2]] +\n byteToHex[arr[offset + 3]] +\n '-' +\n byteToHex[arr[offset + 4]] +\n byteToHex[arr[offset + 5]] +\n '-' +\n byteToHex[arr[offset + 6]] +\n byteToHex[arr[offset + 7]] +\n '-' +\n byteToHex[arr[offset + 8]] +\n byteToHex[arr[offset + 9]] +\n '-' +\n byteToHex[arr[offset + 10]] +\n byteToHex[arr[offset + 11]] +\n byteToHex[arr[offset + 12]] +\n byteToHex[arr[offset + 13]] +\n byteToHex[arr[offset + 14]] +\n byteToHex[arr[offset + 15]]).toLowerCase();\n}\nfunction stringify(arr, offset = 0) {\n const uuid = unsafeStringify(arr, offset);\n if (!validate(uuid)) {\n throw TypeError('Stringified UUID is invalid');\n }\n return uuid;\n}\nexport default stringify;\n","let getRandomValues;\nconst rnds8 = new Uint8Array(16);\nexport default function rng() {\n if (!getRandomValues) {\n if (typeof crypto === 'undefined' || !crypto.getRandomValues) {\n throw new Error('crypto.getRandomValues() not supported. See https://github.com/uuidjs/uuid#getrandomvalues-not-supported');\n }\n getRandomValues = crypto.getRandomValues.bind(crypto);\n }\n return getRandomValues(rnds8);\n}\n","const randomUUID = typeof crypto !== 'undefined' && crypto.randomUUID && crypto.randomUUID.bind(crypto);\nexport default { randomUUID };\n","import native from './native.js';\nimport rng from './rng.js';\nimport { unsafeStringify } from './stringify.js';\nfunction v4(options, buf, offset) {\n if (native.randomUUID && !buf && !options) {\n return native.randomUUID();\n }\n options = options || {};\n const rnds = options.random || (options.rng || rng)();\n rnds[6] = (rnds[6] & 0x0f) | 0x40;\n rnds[8] = (rnds[8] & 0x3f) | 0x80;\n if (buf) {\n offset = offset || 0;\n for (let i = 0; i < 16; ++i) {\n buf[offset + i] = rnds[i];\n }\n return buf;\n }\n return unsafeStringify(rnds);\n}\nexport default v4;\n","import { v4 as uuidv4 } from 'uuid';\n\n\nexport interface WorkerMessage {\n cmd: string;\n data: any;\n request_id: string;\n}\n\nexport interface WorkerResponse {\n request_id: string;\n res: any;\n}\n\ninterface GenericObject {\n [key: string]: any;\n}\n\n\n\nexport type WorkerEventHandler = (data: any) => void;\n\nexport class WorkerController {\n private worker: Worker;\n private listeners: Record<string, WorkerEventHandler>;\n private persistentEvents: string[];\n private port?: MessagePort;\n\n constructor(workerUrl: string | URL, persistentEvents: string[] = [], port?: MessagePort) {\n this.worker = new Worker(workerUrl, { type: 'module' });\n this.listeners = {};\n this.persistentEvents = persistentEvents;\n\n this.worker.onmessage = this.handleWorkerMessage.bind(this);\n\n if(port){\n this.port = port;\n this.worker.postMessage({cmd: 'port', data: port}, {transfer: [port]});\n }\n }\n\n\n setupPort(port: MessagePort){\n this.port = port;\n this.worker.postMessage({cmd: 'port', data: port}, {transfer: [port]});\n }\n\n private handleWorkerMessage(event: MessageEvent<WorkerResponse>): void {\n\n if (this.listeners[event.data.request_id]) {\n this.listeners[event.data.request_id](event.data.res);\n\n // Clean up non-persistent listeners\n if (!this.persistentEvents.includes(event.data.request_id)) {\n delete this.listeners[event.data.request_id];\n }\n }\n }\n\n public addPersistentListener(eventName: string, handler: WorkerEventHandler): void {\n if (!this.persistentEvents.includes(eventName)) {\n this.persistentEvents.push(eventName);\n }\n this.listeners[eventName] = handler;\n }\n\n public async sendMessage<T>(\n cmd: string, \n data: any = {}, \n transfer: Transferable[] = [],\n sanitize: boolean = true\n ): Promise<T> {\n const request_id = uuidv4();\n const sanitizedData = sanitize ? sanitizeForWorker(data) : data;\n\n return new Promise((resolve, reject) => {\n try {\n const message: WorkerMessage = {\n cmd,\n request_id,\n data: sanitizedData\n };\n\n\n \n\n\n this.worker.postMessage(message, transfer);\n \n this.listeners[request_id] = (response: T) => {\n resolve(response);\n };\n } catch (error) {\n reject(new Error('Failed to send message to worker: ' + error));\n }\n });\n }\n\n public terminate(): void {\n this.worker.terminate();\n this.listeners = {};\n }\n} \n\n\n\nfunction sanitizeForWorker(obj: GenericObject): GenericObject {\n // Handle null or primitive values\n if (obj === null || typeof obj !== 'object') {\n return obj;\n }\n\n\n const special_types = [\n ArrayBuffer,\n Blob,\n EncodedVideoChunk,\n ImageBitmap,\n EncodedAudioChunk,\n OffscreenCanvas,\n Uint8Array,\n VideoFrame,\n FileSystemFileHandle,\n MessageChannel,\n MessagePort\n ]\n\n // Handle special cases\n\n for (const type of special_types) {\n if(obj instanceof type) {\n return obj;\n }\n }\n\n // Handle arrays\n if (Array.isArray(obj)) {\n return obj.map(item => sanitizeForWorker(item));\n }\n\n // Get raw object if it's a Vue Proxy\n const rawObj = obj && obj.__v_raw ? obj.__v_raw : obj;\n\n // Create a new object to store sanitized properties\n const sanitized: GenericObject = {};\n\n // Process each property\n for (const key in rawObj) {\n // Skip Vue internal properties\n if (key.startsWith('__v_')) {\n continue;\n }\n\n const value = rawObj[key];\n\n // Skip functions\n if (typeof value === 'function') {\n continue;\n }\n\n // Recursively sanitize nested objects\n sanitized[key ] = sanitizeForWorker(value);\n }\n\n return sanitized;\n}\n","import {ArrayBufferTarget, Muxer} from \"mp4-muxer\";\nimport EventEmitter from \"../../../utils/EventEmitter\";\nimport { WorkerController } from \"../../../utils/WorkerController\";\nimport { VideoWorker } from \"../video/video\";\nexport interface AudioTrackData {\n codec: string,\n sampleRate: number ,\n numberOfChannels: number\n}\n\n// Duration of each audio segment (time-based blocks that contain multiple EncodedAudioChunks)\nconst SEGMENT_DURATION = 30; // seconds\n\n\nexport interface AudioPlayerArgs {\n\n audioConfig: AudioTrackData;\n duration: number;\n worker: WorkerController;\n file: File;\n}\n\n\nexport class WebAudioPlayer {\n\n\n audioContext: AudioContext | null;\n sourceNode: AudioBufferSourceNode | null;\n isPlaying: boolean;\n startTime: number;\n pauseTime: number;\n duration: number;\n encodedChunks: EncodedAudioChunk[]; // EncodedAudioChunks from current segment\n audioSegments: Map<number, AudioBuffer>; // Decoded audio segments (segmentIndex -> AudioBuffer)\n scheduledNodes: Map<number, AudioBufferSourceNode>;\n preloadThreshold: number; // Seconds before segment end to trigger preload\n file: File;\n\n worker: WorkerController;\n isPreloading: boolean;\n audioConfig: AudioTrackData | null;\n constructor(args: AudioPlayerArgs) {\n this.audioContext = null;\n this.sourceNode = null;\n this.isPlaying = false;\n this.startTime = 0;\n this.pauseTime = 0;\n this.duration = args.duration;\n this.audioConfig = args.audioConfig;\n\n this.encodedChunks = [];\n this.audioSegments = new Map(); // Cache for decoded audio segments\n this.scheduledNodes = new Map(); // Track scheduled audio nodes\n this.preloadThreshold = 5; // Seconds before segment end to trigger preload\n this.isPreloading = false;\n\n //Audio Renderer gets its own worker to avoid using the video worker to get audio chunks while the video renderer is running / fetching video chunks\n this.worker = args.worker;\n this.file = args.file;\n\n\n this.init();\n }\n\n init() {\n this.audioContext = new AudioContext();\n\n this.seek(0);\n \n }\n\n /**\n * Mux EncodedAudioChunks to an ArrayBuffer for Web Audio API decoding\n * @param chunks - Array of EncodedAudioChunks from a segment\n */\n async muxEncodedChunksToBuffer(chunks: EncodedAudioChunk[], config: AudioTrackData) {\n // Create MP4 muxer\n const muxer = new Muxer({\n target: new ArrayBufferTarget(),\n fastStart: 'in-memory',\n firstTimestampBehavior: 'offset',\n audio: {\n codec: 'aac',\n sampleRate: config.sampleRate,\n numberOfChannels: config.numberOfChannels\n }\n });\n\n // Add EncodedAudioChunks to muxer\n for (const chunk of chunks) {\n muxer.addAudioChunk(chunk);\n }\n\n // Finalize and get array buffer\n await muxer.finalize();\n return muxer.target.buffer;\n }\n\n /**\n * Fetch EncodedAudioChunks for a specific time segment from the file worker\n * @param time - Time in seconds\n * @returns Array of EncodedAudioChunks\n */\n async getEncodedChunksForTime(time: number) {\n const segmentIndex = Math.floor(time / SEGMENT_DURATION);\n\n const chunks = <EncodedAudioChunk[]> await this.worker.sendMessage('get-audio-segment', {\n start: segmentIndex * SEGMENT_DURATION,\n end: segmentIndex * SEGMENT_DURATION + SEGMENT_DURATION,\n file: this.file\n });\n\n this.encodedChunks = chunks;\n\n return chunks;\n }\n\n /**\n * Load and decode an audio segment\n * @param time - Time in seconds\n * @returns Decoded AudioBuffer for the segment\n */\n async loadSegment(time: number) {\n const segmentIndex = Math.floor(time / SEGMENT_DURATION);\n\n // Check cache first\n if (this.audioSegments.has(segmentIndex)) {\n return this.audioSegments.get(segmentIndex);\n }\n\n // Fetch EncodedAudioChunks for this segment\n const encodedChunks = await this.getEncodedChunksForTime(segmentIndex * SEGMENT_DURATION);\n if (encodedChunks.length === 0) return null;\n\n try {\n const a = performance.now();\n\n // Mux EncodedAudioChunks to AAC buffer\n const muxedBuffer = await this.muxEncodedChunksToBuffer(encodedChunks, this.audioConfig!);\n\n const b = performance.now();\n\n // Decode to AudioBuffer for Web Audio API\n const audioBuffer = await this.audioContext!.decodeAudioData(muxedBuffer);\n\n const c = performance.now();\n console.log(`Segment ${segmentIndex}: Muxing took ${b - a}ms, Decoding took ${c - b}ms`);\n\n // Cache the decoded segment\n this.audioSegments.set(segmentIndex, audioBuffer);\n\n return audioBuffer;\n } catch (error) {\n console.error('Error loading audio segment:', error);\n return null;\n }\n }\n\n\n\n async startPlayback(startFrom = this.pauseTime) {\n // Clear any previously scheduled nodes\n this.clearScheduledNodes();\n\n const currentSegment = await this.loadSegment(startFrom);\n\n if (!currentSegment) return;\n\n const segmentOffset = startFrom % SEGMENT_DURATION;\n const timeUntilEnd = SEGMENT_DURATION - segmentOffset;\n\n // Schedule current segment\n this.scheduleSegment(currentSegment, startFrom, segmentOffset);\n\n // Pre-load and schedule next segment\n this.preloadNextSegment(startFrom + timeUntilEnd);\n }\n\n clearScheduledNodes() {\n // Clear both audio nodes and preload timeouts\n for (const node of this.scheduledNodes.values()) {\n node.stop();\n node.disconnect();\n }\n this.scheduledNodes.clear();\n }\n\n getCurrentSegmentIndex() {\n return Math.floor(this.getCurrentTime() / SEGMENT_DURATION);\n }\n\n async preloadNextSegment(startTime: number) {\n if (this.isPreloading || startTime >= this.duration) return;\n\n const nextSegmentIndex = Math.floor(startTime / SEGMENT_DURATION);\n\n // Check if we already have this segment cached\n if (this.audioSegments.has(nextSegmentIndex)) {\n this.scheduleSegment(this.audioSegments.get(nextSegmentIndex), startTime, 0);\n return;\n }\n this.isPreloading = true;\n try {\n const nextSegment = await this.loadSegment(startTime);\n if (!nextSegment || !this.isPlaying) return;\n\n this.scheduleSegment(nextSegment, startTime, 0);\n\n // Instead of setTimeout, we'll check during playback updates\n } finally {\n this.isPreloading = false;\n }\n }\n\n scheduleSegment(audioBuffer: AudioBuffer, startTime: number, offset: number) {\n const sourceNode = this.audioContext!.createBufferSource();\n sourceNode.buffer = audioBuffer;\n sourceNode.connect(this.audioContext!.destination);\n const playbackTime = this.startTime + (startTime - this.pauseTime);\n sourceNode.start(playbackTime, offset);\n this.scheduledNodes.set(startTime, sourceNode);\n\n // Clean up completed nodes\n sourceNode.onended = () => {\n sourceNode.disconnect();\n this.scheduledNodes.delete(startTime);\n };\n }\n\n async play() {\n this.startTime = this.audioContext!.currentTime;\n await this.startPlayback();\n this.isPlaying = true;\n }\n\n async pause() {\n this.clearScheduledNodes();\n this.pauseTime = this.getCurrentTime();\n this.isPlaying = false;\n }\n\n \n async seek(time: number) {\n const wasPlaying = this.isPlaying; \n if (wasPlaying) {\n this.clearScheduledNodes();\n this.isPlaying = false;\n }\n this.pauseTime = time;\n if (wasPlaying) {\n this.startTime = this.audioContext!.currentTime;\n this.isPlaying = true;\n await this.startPlayback(time);\n\n }\n }\n\n\n checkForPreLoad() {\n if (!this.isPlaying) return;\n const currentTime = this.getCurrentTime();\n // Check if we need to preload the next segment\n const currentSegmentIndex = this.getCurrentSegmentIndex();\n const timeInCurrentSegment = currentTime % SEGMENT_DURATION;\n\n if (timeInCurrentSegment >= (SEGMENT_DURATION - this.preloadThreshold) &&\n !this.isPreloading &&\n !this.audioSegments.has(currentSegmentIndex + 1)) {\n this.preloadNextSegment((currentSegmentIndex + 1) * SEGMENT_DURATION);\n } \n }\n\n getCurrentTime() {\n if (!this.isPlaying) return this.pauseTime;\n return this.pauseTime + (this.audioContext!.currentTime - this.startTime);\n }\n\n\n}\n\n","import { v4 as uuidv4 } from 'uuid';\n\ntype Listener = (args?: any) => void;\n\nexport default class EventEmitter {\n listeners: Record<string, Record<string, Listener>> = {};\n\n /**\n * Add an event listener and return its unique ID\n */\n on(event: string, listener: Listener): string {\n\n if (!this.listeners[event]) {\n this.listeners[event] = {};\n }\n\n const id = uuidv4();\n this.listeners[event][id] = listener;\n return id;\n }\n\n /**\n * Remove a specific listener by its ID\n */\n off(event: string, listenerId: string): void {\n if (this.listeners[event]) {\n delete this.listeners[event][listenerId];\n }\n }\n\n /**\n * Emit an event to all registered listeners\n */\n emit(event: string, args?: any): void {\n if (this.listeners[event]) {\n Object.values(this.listeners[event]).forEach(listener => {\n listener(args);\n });\n }\n }\n\n /**\n * Remove all listeners for a specific event\n */\n removeAllListeners(event: string): void {\n if (this.listeners[event]) {\n this.listeners[event] = {};\n }\n }\n} ","import EventEmitter from \"../../../utils/EventEmitter\";\nimport workerUrl from './video.worker.ts?worker&url';\nimport { WorkerController } from \"../../../utils/WorkerController\";\n\nexport interface VideoWorkerParams {\n src: File;\n canvas: HTMLCanvasElement;\n fileWorkerPort: MessagePort;\n}\n\n\n\n/**\n * OffscreenVideoWorker is a wrapper around the video.worker.ts\n * It handles communication with the worker and provides a simple interface.\n */\nexport class VideoWorker extends EventEmitter {\n private canvas: HTMLCanvasElement;\n private offscreenCanvas: OffscreenCanvas | null = null;\n public duration: number = 0;\n private worker: WorkerController;\n private fileWorkerPort: MessagePort;\n\n constructor(params: VideoWorkerParams) {\n super();\n this.canvas = params.canvas;\n this.fileWorkerPort = params.fileWorkerPort;\n this.worker = new WorkerController(workerUrl);\n }\n \n\n /**\n * Send a message to the worker and wait for a response\n */\n\n\n /**\n * Initialize the video player\n */\n async initialize(): Promise<void> {\n // Create the offscreen canvas\n this.offscreenCanvas = this.canvas.transferControlToOffscreen();\n\n // Initialize the worker with the offscreen canvas and file worker port\n const initialized = await this.worker.sendMessage('init', {\n canvas: this.offscreenCanvas,\n fileWorkerPort: this.fileWorkerPort\n }, [this.offscreenCanvas, this.fileWorkerPort]);\n\n\n // Emit initialization event\n this.emit('initialized', initialized);\n }\n\n\n\n /**\n * Seek to a specific time\n */\n async seek(time: number): Promise<void> {\n // Send seek command to worker\n await this.worker.sendMessage('seek', { time });\n }\n\n /**\n * Get debug information from the video worker\n */\n async getDebugInfo(): Promise<any> {\n return await this.worker.sendMessage('get-debug-info', {});\n }\n\n\n async setTrackData(videoMetadata: any, duration: number): Promise<void> {\n await this.worker.sendMessage('set-track-data', {\n videoMetadata,\n duration\n });\n }\n /**\n * Clean up resources\n */\n terminate(): void {\n\n // Terminate the worker\n this.worker.sendMessage('terminate').catch(console.error);\n \n // Clean up\n this.worker.terminate();\n this.offscreenCanvas = null;\n \n // Emit terminate event\n this.emit('terminated');\n }\n\n /**\n\n /**\n * Update the current frame (animation loop)\n */\n\n\n render(time: number): void {\n\n // Send render command to worker\n this.worker.sendMessage('render', { time: time });\n }\n}","import EventEmitter from '../utils/EventEmitter';\nimport { WebAudioPlayer } from './renderers/audio/audio';\nimport { VideoWorker } from './renderers/video/video';\n\n/**\n * Clock - Time Management for Video Playback\n *\n * The Clock is the central coordinator for playback timing. It:\n * - Uses the audio timeline as the source of truth (Web Audio API currentTime)\n * - Manages the requestAnimationFrame loop\n * - Emits tick events for UI updates\n * - Coordinates video rendering at the correct time\n * - Provides getCurrentTime() for on-demand queries\n *\n * Architecture:\n *\n * Clock (this class)\n * │\n * ├─> Queries audioPlayer.getCurrentTime() ← Audio timeline is source of truth\n * ├─> Emits 'tick' events with current time\n * ├─> Calls videoWorker.render(time) passively\n * └─> Provides getCurrentTime() for external queries\n *\n * Why use the audio timeline?\n * - Web Audio API provides high-precision timing via AudioContext.currentTime\n * - Hardware accelerated and synchronized with audio output\n * - More reliable than performance.now() for A/V sync\n */\nexport class Clock extends EventEmitter {\n\n private audioPlayer: WebAudioPlayer;\n private videoWorker: VideoWorker;\n private isPlaying: boolean = false;\n private animationFrame: number | null = null;\n private duration: number;\n\n // Frame rate management\n private readonly TARGET_FPS = 30; // Target 30fps for smooth playback\n private readonly FRAME_INTERVAL: number;\n private lastFrameTime = 0;\n\n /**\n * Create a new Clock\n * @param audioPlayer - Audio player with Web Audio timeline\n * @param videoWorker - Video worker for passive rendering\n * @param duration - Total video duration in seconds\n */\n constructor(audioPlayer: WebAudioPlayer, videoWorker: VideoWorker, duration: number) {\n super();\n\n this.audioPlayer = audioPlayer;\n this.videoWorker = videoWorker;\n this.duration = duration;\n this.FRAME_INTERVAL = 1000 / this.TARGET_FPS;\n }\n\n /**\n * Start playback\n *\n * Starts the audio player and begins the tick loop.\n * The tick loop queries the audio timeline and drives video rendering.\n */\n async play(): Promise<void> {\n if (this.isPlaying) return;\n\n this.isPlaying = true;\n\n // Start audio playback (this starts the timeline)\n await this.audioPlayer.play();\n\n // Start the tick loop\n this.lastFrameTime = performance.now();\n this.tick();\n\n this.emit('play');\n }\n\n /**\n * Pause playback\n *\n * Pauses audio and stops the tick loop.\n */\n pause(): void {\n if (!this.isPlaying) return;\n\n this.isPlaying = false;\n\n // Pause audio\n this.audioPlayer.pause();\n\n // Stop the tick loop\n if (this.animationFrame) {\n cancelAnimationFrame(this.animationFrame);\n this.animationFrame = null;\n }\n\n this.emit('pause');\n }\n\n /**\n * Seek to a specific time\n *\n * @param time - Time in seconds\n */\n async seek(time: number): Promise<void> {\n const clampedTime = Math.max(0, Math.min(time, this.duration));\n\n // Seek both video and audio\n this.videoWorker.seek(clampedTime);\n await this.audioPlayer.seek(clampedTime);\n\n this.emit('seek', clampedTime);\n }\n\n /**\n * Get the current playback time\n *\n * Queries the audio player's timeline, which is the source of truth.\n *\n * @returns Current time in seconds\n */\n getCurrentTime(): number {\n return this.audioPlayer.getCurrentTime();\n }\n\n /**\n * Check if currently playing\n */\n playing(): boolean {\n return this.isPlaying;\n }\n\n /**\n * Main tick loop\n *\n * This runs at TARGET_FPS and:\n * 1. Queries the current time from audio timeline\n * 2. Emits tick event for UI updates\n * 3. Tells video worker to render at this time\n * 4. Checks for end of video\n *\n * The video worker is passive - it just renders whatever time we tell it.\n * The audio timeline is the source of truth for the current time.\n */\n private tick(): void {\n if (!this.isPlaying) return;\n\n const now = performance.now();\n const elapsed = now - this.lastFrameTime;\n\n // Frame rate throttling: only update at TARGET_FPS\n // This prevents unnecessary rendering and saves CPU/battery\n if (elapsed < this.FRAME_INTERVAL) {\n this.animationFrame = requestAnimationFrame(() => this.tick());\n return;\n }\n\n this.lastFrameTime = now;\n\n // Get current time from audio timeline (source of truth)\n const currentTime = this.audioPlayer.getCurrentTime();\n\n // Check if we've reached the end\n if (currentTime >= this.duration - 0.1) {\n this.pause();\n this.emit('ended');\n return;\n }\n\n // Emit tick event for UI updates\n // UI should listen to this rather than polling getCurrentTime()\n this.emit('tick', currentTime);\n\n // Tell video worker to render at this time (passive)\n // Video worker doesn't track time itself - it just renders whatever we tell it\n this.videoWorker.render(currentTime);\n this.audioPlayer.checkForPreLoad();\n\n // Schedule next tick\n this.animationFrame = requestAnimationFrame(() => this.tick());\n }\n\n /**\n * Update duration (if needed after initialization)\n */\n setDuration(duration: number): void {\n this.duration = duration;\n }\n\n /**\n * Clean up resources\n */\n destroy(): void {\n if (this.animationFrame) {\n cancelAnimationFrame(this.animationFrame);\n this.animationFrame = null;\n }\n this.isPlaying = false;\n }\n}\n","import { VideoTrackData } from \"./renderers/video/decoder\";\nimport EventEmitter from \"../utils/EventEmitter\";\nimport { WorkerController } from '../utils/WorkerController';\nimport { AudioTrackData, WebAudioPlayer } from \"./renderers/audio/audio\";\nimport { VideoWorker } from \"./renderers/video/video\";\nimport { Clock } from \"./clock\";\nimport workerUrl from './file.ts?worker&url';\n// Note to Claude: Do not edit this file or make suggestions unless I specifically ask you to.\n\nexport interface WebCodecsPlayerParams {\n src: File;\n canvas: HTMLCanvasElement;\n}\n\n\nexport interface TrackData {\n duration: number,\n audio?: AudioTrackData\n video?: VideoTrackData\n}\n\n\n\n\n\nexport class WebCodecsPlayer {\n private canvas: HTMLCanvasElement | null = null;\n private params: WebCodecsPlayerParams;\n private file: File;\n duration: number = 0;\n private renderer: VideoWorker | null = null;\n private audioPlayer: WebAudioPlayer | null = null;\n private worker: WorkerController | null = null;\n private clock: Clock | null = null;\n private trackData: TrackData | null = null;\n\n constructor(params: WebCodecsPlayerParams) {\n \n this.params = params;\n this.worker = new WorkerController(workerUrl);\n this.file = params.src;\n this.canvas = params.canvas;\n this.duration = 0;\n }\n\n\n\n async play() {\n if (!this.clock) {\n throw new Error('Player not initialized. Call initialize() first.');\n }\n\n await this.clock.play();\n }\n\n async pause() {\n if (!this.clock) {\n throw new Error('Player not initialized. Call initialize() first.');\n }\n\n this.clock.pause();\n }\n\n async seek(time: number) {\n if (!this.clock) {\n throw new Error('Player not initialized. Call initialize() first.');\n }\n\n await this.clock.seek(time);\n }\n\n getCurrentTime(): number {\n return this.clock?.getCurrentTime() || 0;\n }\n\n async getDebugInfo() {\n const videoDebugInfo = this.renderer ? await this.renderer.getDebugInfo() : null;\n\n return {\n trackData: {\n duration: this.duration,\n audio: this.audioPlayer ? {\n codec: this.audioPlayer.audioConfig?.codec,\n sampleRate: this.audioPlayer.audioConfig?.sampleRate,\n numberOfChannels: this.audioPlayer.audioConfig?.numberOfChannels,\n startTime: this.audioPlayer.startTime,\n pauseTime: this.audioPlayer.pauseTime,\n isPlaying: this.audioPlayer.isPlaying,\n loadedSegments: this.audioPlayer.audioSegments.size,\n scheduledNodeCount: this.audioPlayer.scheduledNodes.size\n } : null,\n video: videoDebugInfo ? {\n duration: this.renderer?.duration,\n codec: this.trackData?.video?.codec,\n width: this.trackData?.video?.codedWidth,\n height: this.trackData?.video?.codedHeight,\n frameRate: this.trackData?.video?.frameRate,\n ...videoDebugInfo\n } : null\n },\n clock: {\n isPlaying: this.clock?.playing(),\n currentTime: this.getCurrentTime()\n }\n };\n }\n\n terminate(){\n // Clean up clock\n if (this.clock) {\n this.clock.destroy();\n this.clock = null;\n }\n\n // Clean up audio resources\n if (this.audioPlayer) {\n this.audioPlayer.pause();\n // Any additional cleanup for audio...\n }\n\n // Clean up renderer resources\n if (this.renderer) {\n if (this.renderer instanceof VideoWorker) {\n this.renderer.terminate();\n } \n this.renderer = null;\n }\n\n }\n\n async initialize(): Promise<void> {\n\n console.log(\"Initializing\");\n\n // Create file demuxer worker\n this.worker = new WorkerController(workerUrl);\n\n // Create MessageChannel for file worker <-> video worker communication\n const videoChannel = new MessageChannel();\n\n // Initialize file worker with video port\n await this.worker.sendMessage('init', {\n file: this.file,\n videoPort: videoChannel.port1\n }, [videoChannel.port1]);\n\n // Get track metadata from file worker\n const trackData = <TrackData> await this.worker.sendMessage('get-tracks', {});\n console.log(\"Track data\", trackData);\n\n this.trackData = trackData;\n this.duration = trackData.duration;\n\n // Initialize video worker with port to file worker\n this.renderer = new VideoWorker({\n src: this.file,\n canvas: this.canvas!,\n fileWorkerPort: videoChannel.port2\n });\n\n await this.renderer.initialize();\n\n // Send track metadata to video worker\n await this.renderer.setTrackData(trackData.video!, trackData.duration);\n\n // Initialize audio player with file worker\n this.audioPlayer = new WebAudioPlayer({\n worker: this.worker,\n audioConfig: trackData.audio!,\n duration: trackData.duration,\n file: this.file\n });\n\n // Create clock to manage playback timing\n // The clock coordinates audio and video using the audio timeline as source of truth\n this.clock = new Clock(this.audioPlayer, this.renderer, this.duration);\n\n // Forward clock events to external listeners\n\n }\n\n on(event, listener){\n this.clock.on(event, listener)\n }\n\n // Add more methods as needed\n}\n\nexport default WebCodecsPlayer; "],"names":["byteToHex","i","unsafeStringify","arr","offset","getRandomValues","rnds8","rng","randomUUID","native","v4","options","buf","rnds","WorkerController","workerUrl","persistentEvents","port","event","eventName","handler","cmd","data","transfer","sanitize","request_id","uuidv4","sanitizedData","sanitizeForWorker","resolve","reject","message","response","error","obj","special_types","type","item","rawObj","sanitized","key","value","SEGMENT_DURATION","WebAudioPlayer","args","chunks","config","muxer","Muxer","ArrayBufferTarget","chunk","time","segmentIndex","encodedChunks","a","muxedBuffer","b","audioBuffer","c","startFrom","currentSegment","segmentOffset","timeUntilEnd","node","startTime","nextSegmentIndex","nextSegment","sourceNode","playbackTime","wasPlaying","currentTime","currentSegmentIndex","EventEmitter","listener","id","listenerId","VideoWorker","params","initialized","videoMetadata","duration","Clock","audioPlayer","videoWorker","clampedTime","now","WebCodecsPlayer","_a","videoDebugInfo","_b","_c","_d","_f","_e","_h","_g","_j","_i","_l","_k","_m","videoChannel","trackData"],"mappings":"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","x_google_ignoreList":[0,1,2,3]}
|
package/package.json
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "webcodecs-examples",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Production-ready WebCodecs implementation examples",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"import": "./dist/index.js",
|
|
12
|
+
"types": "./dist/index.d.ts"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist"
|
|
17
|
+
],
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "tsc && vite build",
|
|
20
|
+
"dev:player": "vite --config demos/player/vite.config.ts",
|
|
21
|
+
"build:player": "vite build --config demos/player/vite.config.ts",
|
|
22
|
+
"dev": "npm run dev:player",
|
|
23
|
+
"type-check": "tsc --noEmit"
|
|
24
|
+
},
|
|
25
|
+
"repository": {
|
|
26
|
+
"type": "git",
|
|
27
|
+
"url": "https://github.com/sb2702/webcodecs-examples.git"
|
|
28
|
+
},
|
|
29
|
+
"keywords": [
|
|
30
|
+
"webcodecs",
|
|
31
|
+
"video",
|
|
32
|
+
"audio",
|
|
33
|
+
"examples",
|
|
34
|
+
"player",
|
|
35
|
+
"transcoding",
|
|
36
|
+
"editing"
|
|
37
|
+
],
|
|
38
|
+
"author": "Sam Bhattacharyya <https://github.com/sb2702>",
|
|
39
|
+
"license": "MIT",
|
|
40
|
+
"bugs": {
|
|
41
|
+
"url": "https://github.com/sb2702/webcodecs-examples/issues"
|
|
42
|
+
},
|
|
43
|
+
"homepage": "https://www.webcodecsfundamentals.org",
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"@types/node": "^22.10.5",
|
|
46
|
+
"typescript": "^5.8.3",
|
|
47
|
+
"vite": "^6.2.6"
|
|
48
|
+
},
|
|
49
|
+
"dependencies": {
|
|
50
|
+
"localforage": "^1.10.0",
|
|
51
|
+
"mp4-muxer": "^5.2.1",
|
|
52
|
+
"mp4box": "^0.5.4",
|
|
53
|
+
"webcodecs-utils": "^0.1.4"
|
|
54
|
+
}
|
|
55
|
+
}
|