@taole/giftstage 0.1.33 → 0.1.35
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/dist/assets/webcodecs-mp4.worker-DeS4ARzq.js +398 -0
- package/dist/core/audio-manager.d.ts +9 -2
- package/dist/core/gift-stage.d.ts +26 -0
- package/dist/core/render-manager.d.ts +32 -0
- package/dist/gift-stage.cjs.js +753 -634
- package/dist/gift-stage.es.js +6580 -5348
- package/dist/gpu/webgl1-backend.d.ts +17 -1
- package/dist/gpu/webgl2-backend.d.ts +29 -1
- package/dist/gpu/webgpu-backend.d.ts +45 -5
- package/dist/parsers/svga-sprite-table.d.ts +7 -1
- package/dist/parsers/vap-config-parser.d.ts +11 -9
- package/dist/renderers/alpha-video-renderer.d.ts +1 -0
- package/dist/renderers/image-renderer.d.ts +5 -0
- package/dist/renderers/svga-batch-renderer.d.ts +172 -1
- package/dist/renderers/vap-renderer.d.ts +24 -0
- package/dist/shaders/index.d.ts +5 -0
- package/dist/types/index.d.ts +93 -4
- package/dist/utils/gift-transform.d.ts +4 -3
- package/dist/utils/resource-cache.d.ts +13 -0
- package/dist/video/webcodecs-mp4-core.d.ts +51 -0
- package/dist/video/webcodecs-mp4-playback.d.ts +24 -0
- package/package.json +1 -1
- package/dist/assets/webcodecs-mp4.worker-BvV8Fiyc.js +0 -369
package/dist/types/index.d.ts
CHANGED
|
@@ -26,14 +26,16 @@ export interface TextureOpts {
|
|
|
26
26
|
sourcePremultiplied?: boolean;
|
|
27
27
|
}
|
|
28
28
|
export type ShaderType = 'svga-batch-premul'
|
|
29
|
-
/** WebGPU / WebGL2: SVGA premultiplied atlas sprites batched as instances
|
|
29
|
+
/** WebGPU / WebGL2: SVGA premultiplied atlas sprites batched as packed instances. */
|
|
30
30
|
| 'svga-instanced-premul' | 'vap'
|
|
31
31
|
/** Main-context VAP mixed resource draw: resource texture multiplied by raw-video mask. */
|
|
32
32
|
| 'vap-mix-overlay'
|
|
33
33
|
/** WebGPU / WebGL2: batched VAP sharing one texture; see `instanceCount` / `instanceData` (32 floats/instance). */
|
|
34
34
|
| 'vap-instanced' | 'alpha-video'
|
|
35
35
|
/** WebGPU: batched draws sharing one texture (e.g. atlas); see `instanceCount` / `instanceData`. */
|
|
36
|
-
| 'alpha-video-instanced' | 'rgba-textured' | 'clip-mask'
|
|
36
|
+
| 'alpha-video-instanced' | 'rgba-textured' | 'clip-mask'
|
|
37
|
+
/** WebGPU / WebGL2: one local-space clip mesh transformed by per-instance affine data. */
|
|
38
|
+
| 'clip-mask-instanced';
|
|
37
39
|
export type StencilMode = 'none' | 'write' | 'test' | 'clear';
|
|
38
40
|
export interface DrawCommand {
|
|
39
41
|
shader: ShaderType;
|
|
@@ -44,13 +46,15 @@ export interface DrawCommand {
|
|
|
44
46
|
uniforms: Record<string, Float32Array | number>;
|
|
45
47
|
stencil?: StencilMode;
|
|
46
48
|
/**
|
|
47
|
-
* `svga-instanced-premul`: the first `instanceCount * 13`
|
|
48
|
-
* (
|
|
49
|
+
* `svga-instanced-premul`: the first `instanceCount * 13` words are consumed
|
|
50
|
+
* (pre-scaled transform, alpha, UV rect, and two packed framebuffer clip pairs).
|
|
49
51
|
*
|
|
50
52
|
* `alpha-video-instanced`: the first `instanceCount * 12` floats are consumed
|
|
51
53
|
* (display rect + atlasRemap + opacity/source-alpha-mode vec4).
|
|
52
54
|
* `vap-instanced`: the first `instanceCount * 32` floats are consumed
|
|
53
55
|
* (mat4 + atlasRemap + rgb/alpha rects + opacity/source-alpha-mode vec4).
|
|
56
|
+
* `clip-mask-instanced`: the first `instanceCount * 6` floats are consumed
|
|
57
|
+
* (a, b, c, d, tx, ty affine transform).
|
|
54
58
|
* Renderers may pass a larger reused scratch array to avoid allocating a subarray view per draw.
|
|
55
59
|
*/
|
|
56
60
|
instanceCount?: number;
|
|
@@ -80,6 +84,15 @@ export interface GPUBackend {
|
|
|
80
84
|
clearStencil?(): void;
|
|
81
85
|
setScissor(x: number, y: number, w: number, h: number): void;
|
|
82
86
|
resetScissor(): void;
|
|
87
|
+
/** Internal performance-harness diagnostics; absent on backends without native buffer uploads. */
|
|
88
|
+
getPerformanceSnapshot?(): {
|
|
89
|
+
bufferWrites: number;
|
|
90
|
+
bufferWriteBytes: number;
|
|
91
|
+
storageBufferWrites: number;
|
|
92
|
+
storageBufferWriteBytes: number;
|
|
93
|
+
};
|
|
94
|
+
/** Resets the internal performance-harness diagnostics for a new sampling window. */
|
|
95
|
+
resetPerformanceSnapshot?(): void;
|
|
83
96
|
destroy(): void;
|
|
84
97
|
}
|
|
85
98
|
export interface SVGAParams {
|
|
@@ -148,6 +161,8 @@ export interface SVGASpriteTable {
|
|
|
148
161
|
shapesTable: SVGAShape[][];
|
|
149
162
|
shapesIndex: Int32Array;
|
|
150
163
|
hasAnyShapes: boolean;
|
|
164
|
+
/** Internal parser-derived fast-path flag; undefined only for legacy/incomplete data. */
|
|
165
|
+
allFramesInsideViewBox?: boolean;
|
|
151
166
|
}
|
|
152
167
|
export interface SVGAAudio {
|
|
153
168
|
audioKey: string;
|
|
@@ -271,6 +286,47 @@ export interface ResolvedSVGASlotTexture {
|
|
|
271
286
|
logicalWidth: number;
|
|
272
287
|
logicalHeight: number;
|
|
273
288
|
}
|
|
289
|
+
/**
|
|
290
|
+
* SVGABatchRenderer-internal: one packed instance row for a `stopped` slot's static batch
|
|
291
|
+
* cache (P1-10). `row` mirrors the exact `FRAME_DATA_FLOATS` layout `packFrameData` produces.
|
|
292
|
+
* `isOverride` mirrors whether the sprite used a `svgaSlots` texture override (drawn immediately,
|
|
293
|
+
* breaking any running atlas batch) vs. a normal atlas sprite (batched with same-texture sprites).
|
|
294
|
+
*/
|
|
295
|
+
export interface SVGAStaticBatchCacheEntry {
|
|
296
|
+
row: Float32Array;
|
|
297
|
+
handle: GPUTextureHandle;
|
|
298
|
+
isOverride: boolean;
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* SVGABatchRenderer-internal: cached packed-instance-row list for a `stopped` SVGA slot, plus
|
|
302
|
+
* the exact snapshot of every input it was built from. Any mismatch against the slot's current
|
|
303
|
+
* values invalidates the cache (see `SVGABatchRenderer`'s match check) — favors over-invalidating
|
|
304
|
+
* to guarantee correctness over maximizing hit rate.
|
|
305
|
+
*/
|
|
306
|
+
export interface SVGAStaticBatchCache {
|
|
307
|
+
entries: SVGAStaticBatchCacheEntry[];
|
|
308
|
+
/** `VideoEntity` reference the cache was built against (covers spriteTable/atlasRects identity). */
|
|
309
|
+
entity: unknown;
|
|
310
|
+
/** `entity.atlasTextureHandles` (if any) or `entity.atlasTextureHandle`, whichever is in use. */
|
|
311
|
+
atlasHandlesDep: unknown;
|
|
312
|
+
/**
|
|
313
|
+
* `slot.svgaSlotTextures` map reference. `svgaSlots` overrides are immutable post-load, but
|
|
314
|
+
* tracked defensively so a future change to that invariant can't silently go stale here.
|
|
315
|
+
*/
|
|
316
|
+
svgaSlotTextures: unknown;
|
|
317
|
+
currentFrame: number;
|
|
318
|
+
opacity: number;
|
|
319
|
+
x: number;
|
|
320
|
+
y: number;
|
|
321
|
+
width: number;
|
|
322
|
+
height: number;
|
|
323
|
+
scale: number;
|
|
324
|
+
rotation: number;
|
|
325
|
+
sx: number;
|
|
326
|
+
sy: number;
|
|
327
|
+
offsetX: number;
|
|
328
|
+
offsetY: number;
|
|
329
|
+
}
|
|
274
330
|
export interface ResolvedVAPResource {
|
|
275
331
|
source: CanvasImageSource;
|
|
276
332
|
width: number;
|
|
@@ -566,6 +622,11 @@ export interface GiftSlot {
|
|
|
566
622
|
accumulatedTime: number;
|
|
567
623
|
lastTickTime: number;
|
|
568
624
|
loopCount: number;
|
|
625
|
+
/**
|
|
626
|
+
* SVGABatchRenderer internal: most recent frame whose complete set of referenced clip meshes
|
|
627
|
+
* is ready. A later frame with a mesh miss renders this frame until the missing meshes land.
|
|
628
|
+
*/
|
|
629
|
+
svgaClipReadyFrame?: number | null;
|
|
569
630
|
videoEntity: VideoEntity | null;
|
|
570
631
|
loadAbortController?: AbortController | null;
|
|
571
632
|
/** HTMLVideoElement finite-playback watchdog; cleared on normal completion or resource release. */
|
|
@@ -613,6 +674,32 @@ export interface GiftSlot {
|
|
|
613
674
|
map: unknown;
|
|
614
675
|
size: number;
|
|
615
676
|
};
|
|
677
|
+
/**
|
|
678
|
+
* RenderManager internal: cached coincident-visual-duplicate key + the exact field snapshot it
|
|
679
|
+
* was built from. Rebuilt only when one of those fields actually changes, avoiding a template
|
|
680
|
+
* string array + `join('|')` allocation on every dirty frame for every visible slot.
|
|
681
|
+
*/
|
|
682
|
+
coincidentVisualKeyCache?: {
|
|
683
|
+
key: string;
|
|
684
|
+
resourceKey: string;
|
|
685
|
+
frameKey: string;
|
|
686
|
+
svgaSlotKey: string;
|
|
687
|
+
objectFit: string;
|
|
688
|
+
x: number;
|
|
689
|
+
y: number;
|
|
690
|
+
width: number;
|
|
691
|
+
height: number;
|
|
692
|
+
scale: number;
|
|
693
|
+
opacity: number;
|
|
694
|
+
rotation: number | undefined;
|
|
695
|
+
originX: string | number;
|
|
696
|
+
originY: string | number;
|
|
697
|
+
};
|
|
698
|
+
/**
|
|
699
|
+
* SVGABatchRenderer internal: cached packed instance rows for a `stopped` slot with no
|
|
700
|
+
* shape/clip sprites this frame (P1-10). Rebuilt whenever any tracked input changes.
|
|
701
|
+
*/
|
|
702
|
+
svgaStaticBatchCache?: SVGAStaticBatchCache | null;
|
|
616
703
|
onFrame?: (frame: number, gift: GiftHandle) => void;
|
|
617
704
|
onComplete?: (gift: GiftHandle) => void;
|
|
618
705
|
}
|
|
@@ -654,6 +741,8 @@ export interface VAPFrameObj {
|
|
|
654
741
|
mt?: number;
|
|
655
742
|
frame: [number, number, number, number];
|
|
656
743
|
mFrame: [number, number, number, number];
|
|
744
|
+
/** `String(srcId)` precomputed once by `buildVAPFrameMap` to avoid per-draw re-stringification. */
|
|
745
|
+
srcKey?: string;
|
|
657
746
|
}
|
|
658
747
|
export interface FlyAnimation {
|
|
659
748
|
giftId: string;
|
|
@@ -20,10 +20,11 @@ export declare function resolveGiftTransformOrigin(slot: GiftSlot, out?: GiftTra
|
|
|
20
20
|
export declare function fillGiftTransformMatrix(slot: GiftSlot, x: number, y: number, width: number, height: number, out: Float32Array): Float32Array;
|
|
21
21
|
/** Apply the gift's parent rotation to an existing local-to-stage affine transform. */
|
|
22
22
|
export declare function rotateGiftAffine(slot: GiftSlot, transform: Affine2D, out?: Affine2D): Affine2D;
|
|
23
|
-
|
|
24
|
-
export declare function getRotatedRectAABB(slot: GiftSlot, x: number, y: number, width: number, height: number): {
|
|
23
|
+
export interface RectAABB {
|
|
25
24
|
x: number;
|
|
26
25
|
y: number;
|
|
27
26
|
w: number;
|
|
28
27
|
h: number;
|
|
29
|
-
}
|
|
28
|
+
}
|
|
29
|
+
/** AABB of a rotated stage-space rectangle, suitable for the backend scissor. */
|
|
30
|
+
export declare function getRotatedRectAABB(slot: GiftSlot, x: number, y: number, width: number, height: number, out?: RectAABB): RectAABB;
|
|
@@ -10,6 +10,19 @@ export declare class AsyncLimiter {
|
|
|
10
10
|
run<T>(task: () => Promise<T>): Promise<T>;
|
|
11
11
|
private drain;
|
|
12
12
|
}
|
|
13
|
+
/**
|
|
14
|
+
* Structural estimate; avoids JSON.stringify of the whole sprites tree (main-thread spike).
|
|
15
|
+
*
|
|
16
|
+
* When the worker-binary path is used, `imageBlob` is the *entire* deserialized SGPB buffer
|
|
17
|
+
* (see `deserializeSVGAWorkerPayload`), not just the image bytes — `spriteTable.frameData`/
|
|
18
|
+
* `clipPathIndex`/`shapesIndex` are typed-array *views* sharing that same underlying
|
|
19
|
+
* `ArrayBuffer` (see `readSpriteTable` in `svga-sprite-table-binary.ts`). Adding their
|
|
20
|
+
* `byteLength` again on top of `imageBlob.byteLength` would double-count them, systematically
|
|
21
|
+
* inflating the estimate and causing the memory LRU to evict/re-parse too eagerly. Only add
|
|
22
|
+
* them when they do *not* share `imageBlob`'s buffer (the legacy `images` record path, where
|
|
23
|
+
* the sprite table is a separate allocation).
|
|
24
|
+
*/
|
|
25
|
+
export declare function estimateSVGAWorkerPayloadBytes(payload: SVGAWorkerMoviePayload): number;
|
|
13
26
|
export declare class ResourceCacheManager {
|
|
14
27
|
private readonly limiter;
|
|
15
28
|
private readonly pending;
|
|
@@ -32,6 +32,57 @@ export declare function decodeH264ToFrames(codec: string, description: ArrayBuff
|
|
|
32
32
|
frames: VideoFrame[];
|
|
33
33
|
timesSec: Float64Array;
|
|
34
34
|
}>;
|
|
35
|
+
export interface IncrementalH264DecoderCallbacks {
|
|
36
|
+
onFrame: (index: number, frame: VideoFrame) => void;
|
|
37
|
+
onDecoded?: () => void;
|
|
38
|
+
onDropped?: () => void;
|
|
39
|
+
onEos?: () => void;
|
|
40
|
+
onError?: (error: unknown) => void;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Playhead-driven incremental H.264 decode: feeds chunks in decode order only up to
|
|
44
|
+
* `needIdx + decodeAheadFrames` (via {@link ensure}), so memory stays bounded by how far ahead
|
|
45
|
+
* of playback the caller asks for instead of decoding (and retaining) the entire clip upfront.
|
|
46
|
+
* Originally the Worker's demux/decode pump (see `webcodecs-mp4.worker.ts`); lifted here so the
|
|
47
|
+
* main-thread WebCodecs fallback (no Worker available, or `useWorker: false`) can reuse the exact
|
|
48
|
+
* same backpressure/rewind semantics instead of decoding the whole file into memory at once —
|
|
49
|
+
* see P1-10 optimization notes / `WebCodecsMp4Playback.createOnMainThread`.
|
|
50
|
+
*/
|
|
51
|
+
export declare class IncrementalH264Decoder {
|
|
52
|
+
private readonly decodeOrder;
|
|
53
|
+
private readonly presTimes;
|
|
54
|
+
private readonly timescale;
|
|
55
|
+
private readonly codec;
|
|
56
|
+
private readonly description;
|
|
57
|
+
private readonly duration;
|
|
58
|
+
private readonly callbacks;
|
|
59
|
+
private decoder;
|
|
60
|
+
private nextChunk;
|
|
61
|
+
private chunksFed;
|
|
62
|
+
private targetNeedIdx;
|
|
63
|
+
private lastPostedMax;
|
|
64
|
+
private minPosted;
|
|
65
|
+
private playbackNeedFloor;
|
|
66
|
+
/** Remaining output budget from the latest `ensure`; decremented only when a frame is posted. */
|
|
67
|
+
private freeSlots;
|
|
68
|
+
private eosClosed;
|
|
69
|
+
private pumpScheduled;
|
|
70
|
+
private pumping;
|
|
71
|
+
private destroyed;
|
|
72
|
+
private readonly onDecoderDequeue;
|
|
73
|
+
constructor(decodeOrder: Sample[], presTimes: Float64Array, timescale: number, codec: string, description: ArrayBuffer | undefined, duration: number, callbacks: IncrementalH264DecoderCallbacks, initialNeedIdx: number);
|
|
74
|
+
/** Raise the decode target and/or update main-thread ring headroom; resumes the pump if idle. */
|
|
75
|
+
ensure(needIdx: number, freeSlots: number): void;
|
|
76
|
+
/** Restart decode from the nearest keyframe at/before t=0 (backward seek / loop wrap). */
|
|
77
|
+
rewind(): void;
|
|
78
|
+
destroy(): void;
|
|
79
|
+
private schedulePump;
|
|
80
|
+
private doRewind;
|
|
81
|
+
private openDecoder;
|
|
82
|
+
private feedOneChunk;
|
|
83
|
+
private finalizeEos;
|
|
84
|
+
private runPump;
|
|
85
|
+
}
|
|
35
86
|
export declare function frameIndexForTime(timesSec: Float64Array, clipDurationSec: number, t: number): number;
|
|
36
87
|
export declare function computeClipDurationSeconds(metaDurationSec: number, timesSec: Float64Array, frames: VideoFrame[]): number;
|
|
37
88
|
/** Clip duration from demux metadata + presentation timeline only (no decoded frames). */
|
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
export interface WebCodecsPerformanceSnapshot {
|
|
2
|
+
decodedFrames: number;
|
|
3
|
+
postedFrames: number;
|
|
4
|
+
closedFrames: number;
|
|
5
|
+
residentPeak: number;
|
|
6
|
+
workerMessages: number;
|
|
7
|
+
}
|
|
8
|
+
export declare function resetWebCodecsPerformanceSnapshot(): void;
|
|
9
|
+
export declare function getWebCodecsPerformanceSnapshot(): WebCodecsPerformanceSnapshot;
|
|
1
10
|
/**
|
|
2
11
|
* Presentation-index → VideoFrame with bounded size.
|
|
3
12
|
* Eviction follows the playhead (see {@link setPlaybackHint}) so we keep a window around the
|
|
@@ -11,6 +20,7 @@ export declare class VideoFrameRing {
|
|
|
11
20
|
private playbackHint;
|
|
12
21
|
constructor(maxSize: number);
|
|
13
22
|
setPlaybackHint(idx: number): void;
|
|
23
|
+
private evictConsumedFrames;
|
|
14
24
|
minKey(): number | undefined;
|
|
15
25
|
set(idx: number, frame: VideoFrame): void;
|
|
16
26
|
private evictDownToCapacity;
|
|
@@ -48,6 +58,8 @@ export declare class WebCodecsMp4Playback {
|
|
|
48
58
|
private readonly timesSec;
|
|
49
59
|
private readonly worker;
|
|
50
60
|
private readonly workerMsgId;
|
|
61
|
+
/** Main-thread (no-Worker) counterpart of `worker`: same incremental pump, called in-process. */
|
|
62
|
+
private readonly mainThreadDecoder;
|
|
51
63
|
private readonly streaming;
|
|
52
64
|
private readonly ring;
|
|
53
65
|
private readonly frames;
|
|
@@ -64,7 +76,19 @@ export declare class WebCodecsMp4Playback {
|
|
|
64
76
|
*/
|
|
65
77
|
static create(source: string | ArrayBuffer, signal?: AbortSignal, opts?: WebCodecsMp4CreateOptions): Promise<WebCodecsMp4Playback>;
|
|
66
78
|
private static createViaWorker;
|
|
79
|
+
/**
|
|
80
|
+
* Same incremental/backpressured decode as the Worker path (see `IncrementalH264Decoder`),
|
|
81
|
+
* called in-process instead of via `postMessage`. Bounds resident `VideoFrame`s to
|
|
82
|
+
* `maxDecodedFrames` via a `VideoFrameRing` instead of decoding the whole clip into a
|
|
83
|
+
* `VideoFrame[]` that stays fully resident for the playback's lifetime (P1-11) — the previous
|
|
84
|
+
* behavior made the main-thread fallback (used when Workers are unavailable or `useWorker:
|
|
85
|
+
* false`, typically the lowest-end devices) the worst case for decode-time memory peaks.
|
|
86
|
+
*/
|
|
67
87
|
private static createOnMainThread;
|
|
88
|
+
/** Send a `rewind` through whichever transport is active (Worker `postMessage` or in-process). */
|
|
89
|
+
private sendRewind;
|
|
90
|
+
/** Send an `ensure` through whichever transport is active (Worker `postMessage` or in-process). */
|
|
91
|
+
private sendEnsure;
|
|
68
92
|
ensureDecodedThrough(tSec: number): void;
|
|
69
93
|
getFrameForTime(tSec: number): VideoFrame | null;
|
|
70
94
|
destroy(): void;
|
package/package.json
CHANGED
|
@@ -1,369 +0,0 @@
|
|
|
1
|
-
let y = null;
|
|
2
|
-
function v() {
|
|
3
|
-
return y || (y = import("./mp4box.all-Bcqr6y1c.js").catch((e) => {
|
|
4
|
-
throw y = null, e;
|
|
5
|
-
})), y;
|
|
6
|
-
}
|
|
7
|
-
const C = 800;
|
|
8
|
-
function S(e) {
|
|
9
|
-
return e.startsWith("avc1") || e.startsWith("avc3");
|
|
10
|
-
}
|
|
11
|
-
function I(e) {
|
|
12
|
-
const t = new Uint8Array(e);
|
|
13
|
-
if (t.byteLength < 8) return e;
|
|
14
|
-
const n = t[4], r = t[5], o = t[6], i = t[7];
|
|
15
|
-
return n === 97 && r === 118 && o === 99 && i === 67 ? e.slice(8) : e;
|
|
16
|
-
}
|
|
17
|
-
async function F(e) {
|
|
18
|
-
const t = e.avcC;
|
|
19
|
-
if (!t) return;
|
|
20
|
-
const { DataStream: n } = await v(), r = new n();
|
|
21
|
-
t.write(r);
|
|
22
|
-
const o = r.byteLength;
|
|
23
|
-
if (o <= 0) return;
|
|
24
|
-
const i = r.buffer.slice(0, o);
|
|
25
|
-
return I(i);
|
|
26
|
-
}
|
|
27
|
-
async function P(e) {
|
|
28
|
-
const { createFile: t } = await v();
|
|
29
|
-
return new Promise((n, r) => {
|
|
30
|
-
const o = t(), i = e.slice(0);
|
|
31
|
-
i.fileStart = 0, o.onError = (d) => {
|
|
32
|
-
r(new Error(`mp4box: ${d}`));
|
|
33
|
-
}, o.onReady = (d) => {
|
|
34
|
-
var a, c, l, f, h, m, g;
|
|
35
|
-
const s = (a = d.videoTracks) == null ? void 0 : a[0];
|
|
36
|
-
if (!s) {
|
|
37
|
-
r(new Error("mp4box: no video track"));
|
|
38
|
-
return;
|
|
39
|
-
}
|
|
40
|
-
if (!S(s.codec)) {
|
|
41
|
-
r(new Error(`mp4box: unsupported video codec ${s.codec}`));
|
|
42
|
-
return;
|
|
43
|
-
}
|
|
44
|
-
if (s.nb_samples > C) {
|
|
45
|
-
r(new Error(`mp4box: too many samples (${s.nb_samples})`));
|
|
46
|
-
return;
|
|
47
|
-
}
|
|
48
|
-
const p = [];
|
|
49
|
-
o.onSamples = (q, B, k) => {
|
|
50
|
-
p.push(...k);
|
|
51
|
-
}, o.setExtractionOptions(s.id, null, {
|
|
52
|
-
nbSamples: Math.max(s.nb_samples, 1)
|
|
53
|
-
}), o.start();
|
|
54
|
-
const b = s.timescale > 0 ? s.duration / s.timescale : d.duration / (d.timescale || 1);
|
|
55
|
-
if (p.length === 0) {
|
|
56
|
-
r(new Error("mp4box: extraction produced no samples"));
|
|
57
|
-
return;
|
|
58
|
-
}
|
|
59
|
-
n({
|
|
60
|
-
codec: s.codec,
|
|
61
|
-
timescale: s.timescale,
|
|
62
|
-
durationSec: Number.isFinite(b) ? b : 0,
|
|
63
|
-
videoWidth: (f = (l = (c = s.video) == null ? void 0 : c.width) != null ? l : s.track_width) != null ? f : 0,
|
|
64
|
-
videoHeight: (g = (m = (h = s.video) == null ? void 0 : h.height) != null ? m : s.track_height) != null ? g : 0,
|
|
65
|
-
samples: p
|
|
66
|
-
});
|
|
67
|
-
}, o.appendBuffer(i), o.flush();
|
|
68
|
-
});
|
|
69
|
-
}
|
|
70
|
-
const x = 25e3;
|
|
71
|
-
async function D(e, t, n) {
|
|
72
|
-
const r = performance.now();
|
|
73
|
-
let o = 0;
|
|
74
|
-
for (; e.decodeQueueSize > 0; )
|
|
75
|
-
if (await new Promise((i) => queueMicrotask(i)), o++, o % 120 === 0 && await new Promise((i) => setTimeout(i, 0)), performance.now() - r > n) {
|
|
76
|
-
console.warn("[GiftStage:WC] decodeQueue drain gave up", {
|
|
77
|
-
label: t,
|
|
78
|
-
decodeQueueSize: e.decodeQueueSize,
|
|
79
|
-
spins: o
|
|
80
|
-
});
|
|
81
|
-
break;
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
async function E(e, t) {
|
|
85
|
-
console.info("[GiftStage:WC] VideoDecoder.flush…", t);
|
|
86
|
-
try {
|
|
87
|
-
await Promise.race([
|
|
88
|
-
e.flush(),
|
|
89
|
-
new Promise((n, r) => {
|
|
90
|
-
setTimeout(
|
|
91
|
-
() => r(
|
|
92
|
-
new Error(
|
|
93
|
-
`VideoDecoder.flush() timed out after ${x}ms (${JSON.stringify(t)})`
|
|
94
|
-
)
|
|
95
|
-
),
|
|
96
|
-
x
|
|
97
|
-
);
|
|
98
|
-
})
|
|
99
|
-
]);
|
|
100
|
-
} catch (n) {
|
|
101
|
-
try {
|
|
102
|
-
e.close();
|
|
103
|
-
} catch (r) {
|
|
104
|
-
}
|
|
105
|
-
throw n;
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
function _(e, t, n) {
|
|
109
|
-
if (e.length === 0) return 0;
|
|
110
|
-
const r = e.length;
|
|
111
|
-
if (n <= e[0]) return 0;
|
|
112
|
-
const o = e[r - 1];
|
|
113
|
-
if (n >= t || n >= o) return r - 1;
|
|
114
|
-
let i = 0, d = r - 1;
|
|
115
|
-
for (; i + 1 < d; ) {
|
|
116
|
-
const a = i + d >>> 1;
|
|
117
|
-
e[a] <= n ? i = a : d = a;
|
|
118
|
-
}
|
|
119
|
-
return i;
|
|
120
|
-
}
|
|
121
|
-
function N(e, t) {
|
|
122
|
-
if (t.length === 0) return 1e-6;
|
|
123
|
-
const n = t.length, r = n - 1, o = t[r], i = n >= 2 ? t[r] - t[r - 1] : 1 / 30, d = o + i;
|
|
124
|
-
let a = e > 0 ? e : d;
|
|
125
|
-
if (!(a > 0) && n >= 2) {
|
|
126
|
-
const c = (t[n - 1] - t[0]) / (n - 1);
|
|
127
|
-
a = t[n - 1] + (c > 0 ? c : 1 / 30);
|
|
128
|
-
}
|
|
129
|
-
return a > 0 || (a = Math.max(d, 1 / 30)), a;
|
|
130
|
-
}
|
|
131
|
-
function T(e, t) {
|
|
132
|
-
var n, r;
|
|
133
|
-
if (e.length === 0) return new Float64Array(0);
|
|
134
|
-
const o = e.map((d, a) => a);
|
|
135
|
-
o.sort((d, a) => {
|
|
136
|
-
var c, l, f, h;
|
|
137
|
-
const m = e[d], g = e[a], s = (l = (c = m.cts) != null ? c : m.dts) != null ? l : 0, p = (h = (f = g.cts) != null ? f : g.dts) != null ? h : 0;
|
|
138
|
-
return s !== p ? s - p : d - a;
|
|
139
|
-
});
|
|
140
|
-
const i = new Float64Array(o.length);
|
|
141
|
-
for (let d = 0; d < o.length; d++) {
|
|
142
|
-
const a = e[o[d]];
|
|
143
|
-
i[d] = ((r = (n = a.cts) != null ? n : a.dts) != null ? r : 0) / t;
|
|
144
|
-
}
|
|
145
|
-
return i;
|
|
146
|
-
}
|
|
147
|
-
function O(e) {
|
|
148
|
-
return [...e].sort((t, n) => {
|
|
149
|
-
var r, o, i, d;
|
|
150
|
-
const a = ((o = (r = t.dts) != null ? r : t.cts) != null ? o : 0) - ((d = (i = n.dts) != null ? i : n.cts) != null ? d : 0);
|
|
151
|
-
return a !== 0 ? a : 0;
|
|
152
|
-
});
|
|
153
|
-
}
|
|
154
|
-
function W(e, t) {
|
|
155
|
-
if (e.length === 0) return 0;
|
|
156
|
-
const n = t / 1e6;
|
|
157
|
-
let r = 0, o = 1 / 0;
|
|
158
|
-
for (let i = 0; i < e.length; i++) {
|
|
159
|
-
const d = Math.abs(e[i] - n);
|
|
160
|
-
d < o && (o = d, r = i);
|
|
161
|
-
}
|
|
162
|
-
return r;
|
|
163
|
-
}
|
|
164
|
-
function A(e) {
|
|
165
|
-
for (let t = 0; t < e.length; t++)
|
|
166
|
-
if (e[t].is_sync) return t;
|
|
167
|
-
return 0;
|
|
168
|
-
}
|
|
169
|
-
async function V(e, t) {
|
|
170
|
-
var n;
|
|
171
|
-
const r = {
|
|
172
|
-
codec: e,
|
|
173
|
-
hardwareAcceleration: "prefer-software",
|
|
174
|
-
...t && t.byteLength > 0 ? { description: t } : {}
|
|
175
|
-
};
|
|
176
|
-
let o = await VideoDecoder.isConfigSupported(r), i = r;
|
|
177
|
-
if (!o.supported) {
|
|
178
|
-
const d = {
|
|
179
|
-
codec: e,
|
|
180
|
-
...t && t.byteLength > 0 ? { description: t } : {}
|
|
181
|
-
};
|
|
182
|
-
if (o = await VideoDecoder.isConfigSupported(d), !o.supported)
|
|
183
|
-
throw new Error(`WebCodecs: unsupported config ${e}`);
|
|
184
|
-
i = d;
|
|
185
|
-
}
|
|
186
|
-
return (n = o.config) != null ? n : i;
|
|
187
|
-
}
|
|
188
|
-
let u = null;
|
|
189
|
-
self.onmessage = (e) => {
|
|
190
|
-
const t = e.data;
|
|
191
|
-
if (t) {
|
|
192
|
-
if (t.type === "init") {
|
|
193
|
-
H(t);
|
|
194
|
-
return;
|
|
195
|
-
}
|
|
196
|
-
if (!(!u || t.id !== u.id)) {
|
|
197
|
-
if (t.type === "ensure") {
|
|
198
|
-
u.targetNeedIdx = Math.max(u.targetNeedIdx, t.needIdx), w(u);
|
|
199
|
-
return;
|
|
200
|
-
}
|
|
201
|
-
if (t.type === "rewind") {
|
|
202
|
-
Q(u);
|
|
203
|
-
return;
|
|
204
|
-
}
|
|
205
|
-
t.type === "destroy" && M(u);
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
};
|
|
209
|
-
function M(e) {
|
|
210
|
-
if (e.destroyed = !0, e.decoder && e.decoder.state !== "closed")
|
|
211
|
-
try {
|
|
212
|
-
e.decoder.close();
|
|
213
|
-
} catch (t) {
|
|
214
|
-
}
|
|
215
|
-
e.decoder = null, u = null;
|
|
216
|
-
}
|
|
217
|
-
function w(e) {
|
|
218
|
-
e.pumpScheduled || e.destroyed || (e.pumpScheduled = !0, queueMicrotask(() => {
|
|
219
|
-
e.pumpScheduled = !1, U(e);
|
|
220
|
-
}));
|
|
221
|
-
}
|
|
222
|
-
async function H(e) {
|
|
223
|
-
u && M(u);
|
|
224
|
-
const t = e.id;
|
|
225
|
-
try {
|
|
226
|
-
const n = await P(e.buffer);
|
|
227
|
-
if (n.samples.length === 0) {
|
|
228
|
-
self.postMessage({ type: "error", id: t, message: "mp4box: no samples" });
|
|
229
|
-
return;
|
|
230
|
-
}
|
|
231
|
-
const r = O(n.samples), o = T(n.samples, n.timescale), i = N(n.durationSec, o), d = n.videoWidth || 0, a = n.videoHeight || 0, c = new Float64Array(o.length);
|
|
232
|
-
c.set(o), self.postMessage(
|
|
233
|
-
{
|
|
234
|
-
type: "meta",
|
|
235
|
-
id: t,
|
|
236
|
-
duration: i,
|
|
237
|
-
videoWidth: d,
|
|
238
|
-
videoHeight: a,
|
|
239
|
-
frameCount: o.length,
|
|
240
|
-
timesSec: c
|
|
241
|
-
},
|
|
242
|
-
{ transfer: [c.buffer] }
|
|
243
|
-
);
|
|
244
|
-
const l = _(o, i, 0), f = Math.min(
|
|
245
|
-
o.length - 1,
|
|
246
|
-
Math.max(0, l + Math.max(0, e.decodeAheadFrames))
|
|
247
|
-
);
|
|
248
|
-
u = {
|
|
249
|
-
id: t,
|
|
250
|
-
destroyed: !1,
|
|
251
|
-
decodeOrder: r,
|
|
252
|
-
presTimes: o,
|
|
253
|
-
timescale: n.timescale,
|
|
254
|
-
codec: n.codec,
|
|
255
|
-
description: n.samples[0] ? await F(
|
|
256
|
-
n.samples[0].description
|
|
257
|
-
) : void 0,
|
|
258
|
-
duration: i,
|
|
259
|
-
videoWidth: d,
|
|
260
|
-
videoHeight: a,
|
|
261
|
-
decoder: null,
|
|
262
|
-
nextChunk: 0,
|
|
263
|
-
chunksFed: 0,
|
|
264
|
-
targetNeedIdx: f,
|
|
265
|
-
lastPostedMax: -1,
|
|
266
|
-
minPosted: Number.POSITIVE_INFINITY,
|
|
267
|
-
playbackNeedFloor: l,
|
|
268
|
-
eosClosed: !1,
|
|
269
|
-
pumpScheduled: !1,
|
|
270
|
-
pumping: !1
|
|
271
|
-
}, w(u);
|
|
272
|
-
} catch (n) {
|
|
273
|
-
self.postMessage({
|
|
274
|
-
type: "error",
|
|
275
|
-
id: t,
|
|
276
|
-
message: n instanceof Error ? n.message : String(n)
|
|
277
|
-
});
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
async function L(e) {
|
|
281
|
-
if (e.decoder && e.decoder.state !== "closed") return;
|
|
282
|
-
const t = await V(e.codec, e.description), n = new VideoDecoder({
|
|
283
|
-
output: (r) => {
|
|
284
|
-
if (e.destroyed) {
|
|
285
|
-
try {
|
|
286
|
-
r.close();
|
|
287
|
-
} catch (i) {
|
|
288
|
-
}
|
|
289
|
-
return;
|
|
290
|
-
}
|
|
291
|
-
const o = W(e.presTimes, r.timestamp);
|
|
292
|
-
e.lastPostedMax = Math.max(e.lastPostedMax, o), e.minPosted = Math.min(e.minPosted, o), self.postMessage({ type: "frame", id: e.id, index: o, frame: r }, { transfer: [r] });
|
|
293
|
-
},
|
|
294
|
-
error: (r) => {
|
|
295
|
-
console.error("[GiftStage:WC] worker VideoDecoder error", r);
|
|
296
|
-
}
|
|
297
|
-
});
|
|
298
|
-
n.configure(t), e.decoder = n;
|
|
299
|
-
}
|
|
300
|
-
async function Q(e) {
|
|
301
|
-
if (e.destroyed) return;
|
|
302
|
-
if (e.decoder && e.decoder.state !== "closed")
|
|
303
|
-
try {
|
|
304
|
-
e.decoder.close();
|
|
305
|
-
} catch (n) {
|
|
306
|
-
}
|
|
307
|
-
e.decoder = null, e.nextChunk = A(e.decodeOrder), e.chunksFed = 0, e.lastPostedMax = -1, e.minPosted = Number.POSITIVE_INFINITY, e.eosClosed = !1;
|
|
308
|
-
const t = _(e.presTimes, e.duration, 0);
|
|
309
|
-
e.playbackNeedFloor = t, e.targetNeedIdx = Math.max(
|
|
310
|
-
e.targetNeedIdx,
|
|
311
|
-
Math.min(e.presTimes.length - 1, Math.max(0, t))
|
|
312
|
-
), w(e);
|
|
313
|
-
}
|
|
314
|
-
function $(e) {
|
|
315
|
-
var t, n;
|
|
316
|
-
const r = e.decoder, o = e.nextChunk;
|
|
317
|
-
if (o >= e.decodeOrder.length) return;
|
|
318
|
-
const i = e.decodeOrder[o];
|
|
319
|
-
e.nextChunk = o + 1;
|
|
320
|
-
const d = i.data;
|
|
321
|
-
if (!d || d.byteLength === 0) return;
|
|
322
|
-
const a = (n = (t = i.cts) != null ? t : i.dts) != null ? n : 0, c = Math.round(a / e.timescale * 1e6), l = i.duration && e.timescale > 0 ? Math.round(i.duration / e.timescale * 1e6) : void 0, f = new Uint8Array(d.byteLength);
|
|
323
|
-
f.set(d);
|
|
324
|
-
const h = new EncodedVideoChunk({
|
|
325
|
-
type: i.is_sync ? "key" : "delta",
|
|
326
|
-
timestamp: c,
|
|
327
|
-
duration: l,
|
|
328
|
-
data: f
|
|
329
|
-
});
|
|
330
|
-
r.decode(h), e.chunksFed++;
|
|
331
|
-
}
|
|
332
|
-
async function z(e) {
|
|
333
|
-
if (!e.decoder || e.eosClosed) return;
|
|
334
|
-
const t = e.decoder;
|
|
335
|
-
await D(t, "before flush", 1e4);
|
|
336
|
-
const n = t.decodeQueueSize;
|
|
337
|
-
await E(t, {
|
|
338
|
-
chunksDecoded: e.chunksFed,
|
|
339
|
-
outputsBeforeFlush: e.lastPostedMax + 1,
|
|
340
|
-
queueBeforeFlush: n
|
|
341
|
-
});
|
|
342
|
-
try {
|
|
343
|
-
t.close();
|
|
344
|
-
} catch (r) {
|
|
345
|
-
}
|
|
346
|
-
e.decoder = null, e.eosClosed = !0, self.postMessage({ type: "eos", id: e.id });
|
|
347
|
-
}
|
|
348
|
-
async function U(e) {
|
|
349
|
-
if (!e.destroyed && !e.pumping) {
|
|
350
|
-
e.pumping = !0;
|
|
351
|
-
try {
|
|
352
|
-
for (; !e.destroyed; ) {
|
|
353
|
-
if (e.eosClosed)
|
|
354
|
-
return;
|
|
355
|
-
const t = e.nextChunk < e.decodeOrder.length, n = Number.isFinite(e.minPosted) && e.minPosted <= e.playbackNeedFloor, r = e.lastPostedMax < e.targetNeedIdx || !n;
|
|
356
|
-
if (!t && !e.eosClosed) {
|
|
357
|
-
await z(e);
|
|
358
|
-
continue;
|
|
359
|
-
}
|
|
360
|
-
if (!r && t || !r && !t)
|
|
361
|
-
return;
|
|
362
|
-
await L(e), $(e), e.chunksFed % 4 === 0 && await new Promise((o) => setTimeout(o, 0));
|
|
363
|
-
}
|
|
364
|
-
} finally {
|
|
365
|
-
e.pumping = !1;
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
//# sourceMappingURL=webcodecs-mp4.worker-BvV8Fiyc.js.map
|