@taole/giftstage 0.1.33 → 0.1.34

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.
@@ -0,0 +1,379 @@
1
+ let w = null;
2
+ function v() {
3
+ return w || (w = import("./mp4box.all-Bcqr6y1c.js").catch((s) => {
4
+ throw w = null, s;
5
+ })), w;
6
+ }
7
+ var M = Object.defineProperty, S = (s, e, t) => e in s ? M(s, e, { enumerable: !0, configurable: !0, writable: !0, value: t }) : s[e] = t, l = (s, e, t) => S(s, typeof e != "symbol" ? e + "" : e, t);
8
+ const P = 800;
9
+ function k(s) {
10
+ return s.startsWith("avc1") || s.startsWith("avc3");
11
+ }
12
+ function F(s) {
13
+ const e = new Uint8Array(s);
14
+ if (e.byteLength < 8) return s;
15
+ const t = e[4], n = e[5], r = e[6], o = e[7];
16
+ return t === 97 && n === 118 && r === 99 && o === 67 ? s.slice(8) : s;
17
+ }
18
+ async function C(s) {
19
+ const e = s.avcC;
20
+ if (!e) return;
21
+ const { DataStream: t } = await v(), n = new t();
22
+ e.write(n);
23
+ const r = n.byteLength;
24
+ if (r <= 0) return;
25
+ const o = n.buffer.slice(0, r);
26
+ return F(o);
27
+ }
28
+ async function E(s) {
29
+ const { createFile: e } = await v();
30
+ return new Promise((t, n) => {
31
+ const r = e(), o = s.slice(0);
32
+ o.fileStart = 0, r.onError = (i) => {
33
+ n(new Error(`mp4box: ${i}`));
34
+ }, r.onReady = (i) => {
35
+ var d, c, f, u, p, g, m;
36
+ const a = (d = i.videoTracks) == null ? void 0 : d[0];
37
+ if (!a) {
38
+ n(new Error("mp4box: no video track"));
39
+ return;
40
+ }
41
+ if (!k(a.codec)) {
42
+ n(new Error(`mp4box: unsupported video codec ${a.codec}`));
43
+ return;
44
+ }
45
+ if (a.nb_samples > P) {
46
+ n(new Error(`mp4box: too many samples (${a.nb_samples})`));
47
+ return;
48
+ }
49
+ const y = [];
50
+ r.onSamples = (Q, $, I) => {
51
+ y.push(...I);
52
+ }, r.setExtractionOptions(a.id, null, {
53
+ nbSamples: Math.max(a.nb_samples, 1)
54
+ }), r.start();
55
+ const _ = a.timescale > 0 ? a.duration / a.timescale : i.duration / (i.timescale || 1);
56
+ if (y.length === 0) {
57
+ n(new Error("mp4box: extraction produced no samples"));
58
+ return;
59
+ }
60
+ t({
61
+ codec: a.codec,
62
+ timescale: a.timescale,
63
+ durationSec: Number.isFinite(_) ? _ : 0,
64
+ videoWidth: (u = (f = (c = a.video) == null ? void 0 : c.width) != null ? f : a.track_width) != null ? u : 0,
65
+ videoHeight: (m = (g = (p = a.video) == null ? void 0 : p.height) != null ? g : a.track_height) != null ? m : 0,
66
+ samples: y
67
+ });
68
+ }, r.appendBuffer(o), r.flush();
69
+ });
70
+ }
71
+ const x = 25e3;
72
+ async function N(s, e, t) {
73
+ const n = performance.now();
74
+ let r = 0;
75
+ for (; s.decodeQueueSize > 0; )
76
+ if (await new Promise((o) => queueMicrotask(o)), r++, r % 120 === 0 && await new Promise((o) => setTimeout(o, 0)), performance.now() - n > t) {
77
+ console.warn("[GiftStage:WC] decodeQueue drain gave up", {
78
+ label: e,
79
+ decodeQueueSize: s.decodeQueueSize,
80
+ spins: r
81
+ });
82
+ break;
83
+ }
84
+ }
85
+ async function D(s, e) {
86
+ console.info("[GiftStage:WC] VideoDecoder.flush…", e);
87
+ try {
88
+ await Promise.race([
89
+ s.flush(),
90
+ new Promise((t, n) => {
91
+ setTimeout(
92
+ () => n(
93
+ new Error(
94
+ `VideoDecoder.flush() timed out after ${x}ms (${JSON.stringify(e)})`
95
+ )
96
+ ),
97
+ x
98
+ );
99
+ })
100
+ ]);
101
+ } catch (t) {
102
+ try {
103
+ s.close();
104
+ } catch (n) {
105
+ }
106
+ throw t;
107
+ }
108
+ }
109
+ class T {
110
+ constructor(e, t, n, r, o, i, d, c) {
111
+ this.decodeOrder = e, this.presTimes = t, this.timescale = n, this.codec = r, this.description = o, this.duration = i, this.callbacks = d, l(this, "decoder", null), l(this, "nextChunk", 0), l(this, "chunksFed", 0), l(this, "targetNeedIdx"), l(this, "lastPostedMax", -1), l(this, "minPosted", Number.POSITIVE_INFINITY), l(this, "playbackNeedFloor"), l(this, "freeSlots", Number.POSITIVE_INFINITY), l(this, "eosClosed", !1), l(this, "pumpScheduled", !1), l(this, "pumping", !1), l(this, "destroyed", !1), this.targetNeedIdx = c, this.playbackNeedFloor = b(t, i, 0), this.schedulePump();
112
+ }
113
+ /** Raise the decode target and/or update main-thread ring headroom; resumes the pump if idle. */
114
+ ensure(e, t) {
115
+ this.destroyed || (this.targetNeedIdx = Math.max(this.targetNeedIdx, e), this.freeSlots = Math.max(0, t), this.schedulePump());
116
+ }
117
+ /** Restart decode from the nearest keyframe at/before t=0 (backward seek / loop wrap). */
118
+ rewind() {
119
+ this.destroyed || this.doRewind();
120
+ }
121
+ destroy() {
122
+ if (this.destroyed = !0, this.decoder && this.decoder.state !== "closed")
123
+ try {
124
+ this.decoder.close();
125
+ } catch (e) {
126
+ }
127
+ this.decoder = null;
128
+ }
129
+ schedulePump() {
130
+ this.pumpScheduled || this.destroyed || (this.pumpScheduled = !0, queueMicrotask(() => {
131
+ this.pumpScheduled = !1, this.runPump();
132
+ }));
133
+ }
134
+ async doRewind() {
135
+ if (this.decoder && this.decoder.state !== "closed")
136
+ try {
137
+ this.decoder.close();
138
+ } catch (t) {
139
+ }
140
+ this.decoder = null, this.nextChunk = H(this.decodeOrder), this.chunksFed = 0, this.lastPostedMax = -1, this.minPosted = Number.POSITIVE_INFINITY, this.eosClosed = !1;
141
+ const e = b(this.presTimes, this.duration, 0);
142
+ this.playbackNeedFloor = e, this.targetNeedIdx = Math.max(
143
+ this.targetNeedIdx,
144
+ Math.min(this.presTimes.length - 1, Math.max(0, e))
145
+ ), this.freeSlots = Number.POSITIVE_INFINITY, this.schedulePump();
146
+ }
147
+ async openDecoder() {
148
+ if (this.decoder && this.decoder.state !== "closed") return;
149
+ const e = await L(this.codec, this.description), t = new VideoDecoder({
150
+ output: (n) => {
151
+ if (this.destroyed) {
152
+ try {
153
+ n.close();
154
+ } catch (o) {
155
+ }
156
+ return;
157
+ }
158
+ const r = A(this.presTimes, n.timestamp);
159
+ this.lastPostedMax = Math.max(this.lastPostedMax, r), this.minPosted = Math.min(this.minPosted, r), this.callbacks.onFrame(r, n);
160
+ },
161
+ error: (n) => {
162
+ this.callbacks.onError ? this.callbacks.onError(n) : console.error("[GiftStage:WC] IncrementalH264Decoder VideoDecoder error", n);
163
+ }
164
+ });
165
+ t.configure(e), this.decoder = t;
166
+ }
167
+ feedOneChunk() {
168
+ var e, t;
169
+ const n = this.decoder, r = this.nextChunk;
170
+ if (r >= this.decodeOrder.length) return;
171
+ const o = this.decodeOrder[r];
172
+ this.nextChunk = r + 1;
173
+ const i = o.data;
174
+ if (!i || i.byteLength === 0) return;
175
+ const d = (t = (e = o.cts) != null ? e : o.dts) != null ? t : 0, c = Math.round(d / this.timescale * 1e6), f = o.duration && this.timescale > 0 ? Math.round(o.duration / this.timescale * 1e6) : void 0, u = new Uint8Array(i.byteLength);
176
+ u.set(i);
177
+ const p = new EncodedVideoChunk({
178
+ type: o.is_sync ? "key" : "delta",
179
+ timestamp: c,
180
+ duration: f,
181
+ data: u
182
+ });
183
+ n.decode(p), this.chunksFed++;
184
+ }
185
+ async finalizeEos() {
186
+ var e, t;
187
+ if (!this.decoder || this.eosClosed) return;
188
+ const n = this.decoder;
189
+ await N(n, "before flush", 1e4);
190
+ const r = n.decodeQueueSize;
191
+ await D(n, {
192
+ chunksDecoded: this.chunksFed,
193
+ outputsBeforeFlush: this.lastPostedMax + 1,
194
+ queueBeforeFlush: r
195
+ });
196
+ try {
197
+ n.close();
198
+ } catch (o) {
199
+ }
200
+ this.decoder = null, this.eosClosed = !0, (t = (e = this.callbacks).onEos) == null || t.call(e);
201
+ }
202
+ async runPump() {
203
+ if (!(this.destroyed || this.pumping)) {
204
+ this.pumping = !0;
205
+ try {
206
+ for (; !this.destroyed; ) {
207
+ if (this.eosClosed) return;
208
+ const e = this.nextChunk < this.decodeOrder.length, t = Number.isFinite(this.minPosted) && this.minPosted <= this.playbackNeedFloor, n = Number.isFinite(this.freeSlots) ? Math.min(this.targetNeedIdx, this.lastPostedMax + Math.max(1, this.freeSlots)) : this.targetNeedIdx, r = this.lastPostedMax < n || !t;
209
+ if (!e && !this.eosClosed) {
210
+ await this.finalizeEos();
211
+ continue;
212
+ }
213
+ if (!r) return;
214
+ await this.openDecoder(), this.feedOneChunk(), this.chunksFed % 4 === 0 && await new Promise((o) => setTimeout(o, 0));
215
+ }
216
+ } finally {
217
+ this.pumping = !1;
218
+ }
219
+ }
220
+ }
221
+ }
222
+ function b(s, e, t) {
223
+ if (s.length === 0) return 0;
224
+ const n = s.length;
225
+ if (t <= s[0]) return 0;
226
+ const r = s[n - 1];
227
+ if (t >= e || t >= r) return n - 1;
228
+ let o = 0, i = n - 1;
229
+ for (; o + 1 < i; ) {
230
+ const d = o + i >>> 1;
231
+ s[d] <= t ? o = d : i = d;
232
+ }
233
+ return o;
234
+ }
235
+ function O(s, e) {
236
+ if (e.length === 0) return 1e-6;
237
+ const t = e.length, n = t - 1, r = e[n], o = t >= 2 ? e[n] - e[n - 1] : 1 / 30, i = r + o;
238
+ let d = s > 0 ? s : i;
239
+ if (!(d > 0) && t >= 2) {
240
+ const c = (e[t - 1] - e[0]) / (t - 1);
241
+ d = e[t - 1] + (c > 0 ? c : 1 / 30);
242
+ }
243
+ return d > 0 || (d = Math.max(i, 1 / 30)), d;
244
+ }
245
+ function V(s, e) {
246
+ var t, n;
247
+ if (s.length === 0) return new Float64Array(0);
248
+ const r = s.map((i, d) => d);
249
+ r.sort((i, d) => {
250
+ var c, f, u, p;
251
+ const g = s[i], m = s[d], a = (f = (c = g.cts) != null ? c : g.dts) != null ? f : 0, y = (p = (u = m.cts) != null ? u : m.dts) != null ? p : 0;
252
+ return a !== y ? a - y : i - d;
253
+ });
254
+ const o = new Float64Array(r.length);
255
+ for (let i = 0; i < r.length; i++) {
256
+ const d = s[r[i]];
257
+ o[i] = ((n = (t = d.cts) != null ? t : d.dts) != null ? n : 0) / e;
258
+ }
259
+ return o;
260
+ }
261
+ function W(s) {
262
+ return [...s].sort((e, t) => {
263
+ var n, r, o, i;
264
+ const d = ((r = (n = e.dts) != null ? n : e.cts) != null ? r : 0) - ((i = (o = t.dts) != null ? o : t.cts) != null ? i : 0);
265
+ return d !== 0 ? d : 0;
266
+ });
267
+ }
268
+ function A(s, e) {
269
+ if (s.length === 0) return 0;
270
+ const t = e / 1e6;
271
+ let n = 0, r = 1 / 0;
272
+ for (let o = 0; o < s.length; o++) {
273
+ const i = Math.abs(s[o] - t);
274
+ i < r && (r = i, n = o);
275
+ }
276
+ return n;
277
+ }
278
+ function H(s) {
279
+ for (let e = 0; e < s.length; e++)
280
+ if (s[e].is_sync) return e;
281
+ return 0;
282
+ }
283
+ async function L(s, e) {
284
+ var t;
285
+ const n = {
286
+ codec: s,
287
+ hardwareAcceleration: "prefer-software",
288
+ ...e && e.byteLength > 0 ? { description: e } : {}
289
+ };
290
+ let r = await VideoDecoder.isConfigSupported(n), o = n;
291
+ if (!r.supported) {
292
+ const i = {
293
+ codec: s,
294
+ ...e && e.byteLength > 0 ? { description: e } : {}
295
+ };
296
+ if (r = await VideoDecoder.isConfigSupported(i), !r.supported)
297
+ throw new Error(`WebCodecs: unsupported config ${s}`);
298
+ o = i;
299
+ }
300
+ return (t = r.config) != null ? t : o;
301
+ }
302
+ let h = null;
303
+ self.onmessage = (s) => {
304
+ const e = s.data;
305
+ if (e) {
306
+ if (e.type === "init") {
307
+ z(e);
308
+ return;
309
+ }
310
+ if (!(!h || e.id !== h.id)) {
311
+ if (e.type === "ensure") {
312
+ h.decoder.ensure(e.needIdx, e.freeSlots);
313
+ return;
314
+ }
315
+ if (e.type === "rewind") {
316
+ h.decoder.rewind();
317
+ return;
318
+ }
319
+ e.type === "destroy" && (h.decoder.destroy(), h = null);
320
+ }
321
+ }
322
+ };
323
+ async function z(s) {
324
+ h && (h.decoder.destroy(), h = null);
325
+ const e = s.id;
326
+ try {
327
+ const t = await E(s.buffer);
328
+ if (t.samples.length === 0) {
329
+ self.postMessage({ type: "error", id: e, message: "mp4box: no samples" });
330
+ return;
331
+ }
332
+ const n = W(t.samples), r = V(t.samples, t.timescale), o = O(t.durationSec, r), i = t.videoWidth || 0, d = t.videoHeight || 0, c = new Float64Array(r.length);
333
+ c.set(r), self.postMessage(
334
+ {
335
+ type: "meta",
336
+ id: e,
337
+ duration: o,
338
+ videoWidth: i,
339
+ videoHeight: d,
340
+ frameCount: r.length,
341
+ timesSec: c
342
+ },
343
+ { transfer: [c.buffer] }
344
+ );
345
+ const f = t.samples[0] ? await C(
346
+ t.samples[0].description
347
+ ) : void 0, u = b(r, o, 0), p = Math.min(
348
+ r.length - 1,
349
+ Math.max(0, u + Math.max(0, s.decodeAheadFrames))
350
+ ), g = new T(
351
+ n,
352
+ r,
353
+ t.timescale,
354
+ t.codec,
355
+ f,
356
+ o,
357
+ {
358
+ onFrame: (m, a) => {
359
+ self.postMessage({ type: "frame", id: e, index: m, frame: a }, { transfer: [a] });
360
+ },
361
+ onEos: () => {
362
+ self.postMessage({ type: "eos", id: e });
363
+ },
364
+ onError: (m) => {
365
+ console.error("[GiftStage:WC] worker VideoDecoder error", m);
366
+ }
367
+ },
368
+ p
369
+ );
370
+ h = { id: e, decoder: g };
371
+ } catch (t) {
372
+ self.postMessage({
373
+ type: "error",
374
+ id: e,
375
+ message: t instanceof Error ? t.message : String(t)
376
+ });
377
+ }
378
+ }
379
+ //# sourceMappingURL=webcodecs-mp4.worker-CDhalNDE.js.map
@@ -1,4 +1,4 @@
1
- import type { GiftSlot, GiftHandle } from '../types';
1
+ import type { GiftSlot } from '../types';
2
2
  export declare class AudioManager {
3
3
  private ctx;
4
4
  private decodedCache;
@@ -11,7 +11,14 @@ export declare class AudioManager {
11
11
  * Frame-synced playback: start audio on startFrame, stop on endFrame.
12
12
  * Matches SVGAPlayer-Web-master Canvas/renderer.js playAudio() pattern.
13
13
  */
14
- tickFrame(slot: GiftSlot, currentFrame: number, _handle: GiftHandle): void;
14
+ tickFrame(slot: GiftSlot, currentFrame: number): void;
15
+ /**
16
+ * Catch-up trigger for a single audio key once its buffer finishes decoding in the background
17
+ * (see `GiftStage`'s deferred SVGA audio decode). Only re-checks entries using `audioKey` —
18
+ * unlike `tickFrame`, which re-scans every audio and would otherwise risk double-starting a
19
+ * sibling audio that already played this frame if called again with an unchanged frame number.
20
+ */
21
+ tickFrameForKey(slot: GiftSlot, currentFrame: number, audioKey: string): void;
15
22
  private startAudio;
16
23
  stopAllForGift(giftId: string): void;
17
24
  setMute(mute: boolean): void;
@@ -36,6 +36,7 @@ export declare class GiftStage {
36
36
  private nextSharedWebCodecsPlaybackId;
37
37
  private nextSharedWebCodecsSurfaceId;
38
38
  private readonly onWindowLayout;
39
+ private readonly onVisibilityChange;
39
40
  constructor(options: GiftStageOptions);
40
41
  private backingStoreDpr;
41
42
  private syncCanvasSize;
@@ -63,6 +64,28 @@ export declare class GiftStage {
63
64
  private deleteSVGAAtlasTextures;
64
65
  private deleteSVGAAtlasResultTextures;
65
66
  private finishLoadSVGA;
67
+ /**
68
+ * Flips a freshly-attached SVGA slot to 'playing' and forces an immediate redraw, instead of
69
+ * waiting for `addGift`'s shared post-load bookkeeping (which only runs once every gift type's
70
+ * loader — including this SVGA path's now-backgrounded audio decode / clip prewarm — settles).
71
+ * Idempotent no-op once the slot has left the 'loading' state (`addGift` skips re-running its
72
+ * own copy of this priming when it sees that already happened).
73
+ */
74
+ private primeSVGAFirstFrame;
75
+ /**
76
+ * Decodes an SVGA's audio blobs after its first frame has already been painted (see
77
+ * `primeSVGAFirstFrame`). Detached from the `finishLoadSVGA` promise chain, so failures/
78
+ * cancellation must be handled here instead of propagating to `loadSVGA`'s caller.
79
+ */
80
+ private decodeSVGAAudioInBackground;
81
+ /**
82
+ * Builds an SVGA's clip meshes after its first frame has already been painted (see
83
+ * `primeSVGAFirstFrame`). Detached from the `finishLoadSVGA` promise chain; the render loop's
84
+ * own on-demand fallback (`SVGABatchRenderer.getClipMeshForSprite`, see P1-9) already covers
85
+ * correctness for any sprite drawn before this finishes, so this is purely a prefetch that lets
86
+ * later frames skip the render loop's slower one-path-at-a-time queueing.
87
+ */
88
+ private prewarmSVGAClipMeshesInBackground;
66
89
  private svgaAtlasMaxSize;
67
90
  private buildSVGAAtlasIfNeeded;
68
91
  private svgaAtlasMaxPages;
@@ -17,6 +17,8 @@ export declare class RenderManager {
17
17
  private lastTime;
18
18
  private running;
19
19
  private globallyPaused;
20
+ /** Set while the document is hidden (`GiftStage.setHidden`); suppresses rAF independent of `globallyPaused`. */
21
+ private suspendedForHidden;
20
22
  private forceNextDraw;
21
23
  private readonly onAnimationFrame;
22
24
  private readonly onHtmlVideoFrame;
@@ -35,6 +37,7 @@ export declare class RenderManager {
35
37
  private readonly activeSharedWebCodecsScratch;
36
38
  constructor(backend: GPUBackend);
37
39
  addSlot(slot: GiftSlot, handle: GiftHandle): void;
40
+ getHandle(id: string): GiftHandle | undefined;
38
41
  releaseSlotResources(slot: GiftSlot): void;
39
42
  removeSlot(id: string): void;
40
43
  getSlot(id: string): GiftSlot | undefined;
@@ -63,6 +66,28 @@ export declare class RenderManager {
63
66
  private clearFrameOnce;
64
67
  pause(): void;
65
68
  resume(): void;
69
+ /**
70
+ * Tab-visibility throttle: stops the rAF loop while hidden (SVGA/WebCodecs `ensure` calls and
71
+ * uploads otherwise keep running, even though most browsers already cap rAF in the
72
+ * background) and resets the render clock on return so the next frame doesn't see a huge
73
+ * stale `dt`. Independent of {@link pause}/{@link resume} — an explicit user pause stays
74
+ * paused after the tab becomes visible again, and an explicit resume while still hidden does
75
+ * not restart rAF until the tab is visible.
76
+ */
77
+ setHidden(hidden: boolean): void;
78
+ private syncAllVideoPlayback;
79
+ /**
80
+ * Whether some slot other than `excludeId` still needs `video` actively playing right now.
81
+ * Shared `<video>` elements (`releaseSharedVideoPlayback` set) must keep decoding while any
82
+ * consumer in the group is still active, even if the slot we're syncing just paused.
83
+ */
84
+ private hasActiveVideoConsumer;
85
+ /**
86
+ * Sync a single slot's underlying `<video>` element to its current active state. Non-shared
87
+ * videos pause/resume 1:1 with the slot; shared videos only pause once every consumer in the
88
+ * group has gone inactive, so one paused copy never stalls playback for the rest.
89
+ */
90
+ syncVideoPlaybackForSlot(slot: GiftSlot): void;
66
91
  prewarmSVGAClipMeshes(entity: NonNullable<GiftSlot['videoEntity']>, options?: {
67
92
  frameBudgetMs?: number;
68
93
  workerTimeSliceMs?: number;