@editframe/assets 0.16.7-beta.0 → 0.17.6-beta.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/dist/DecoderManager.d.ts +62 -0
- package/dist/DecoderManager.js +114 -0
- package/dist/EncodedAsset.d.ts +58 -16
- package/dist/EncodedAsset.js +436 -565
- package/dist/FrameBuffer.d.ts +62 -0
- package/dist/FrameBuffer.js +89 -0
- package/dist/MP4File.d.ts +9 -1
- package/dist/MP4File.js +205 -219
- package/dist/MP4SampleAnalyzer.d.ts +59 -0
- package/dist/MP4SampleAnalyzer.js +119 -0
- package/dist/Probe.d.ts +1 -0
- package/dist/Probe.js +273 -301
- package/dist/SeekStrategy.d.ts +82 -0
- package/dist/SeekStrategy.js +101 -0
- package/dist/VideoRenderOptions.js +31 -33
- package/dist/idempotentTask.js +78 -78
- package/dist/index.js +1 -15
- package/dist/md5.js +35 -51
- package/dist/memoize.js +9 -12
- package/dist/mp4FileWritable.js +16 -18
- package/dist/tasks/cacheImage.js +13 -15
- package/dist/tasks/findOrCreateCaptions.js +18 -21
- package/dist/tasks/generateTrack.js +45 -63
- package/dist/tasks/generateTrackFragmentIndex.js +88 -101
- package/package.json +4 -4
- package/types.json +1 -1
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import * as MP4Box from "mp4box";
|
|
2
|
+
/**
|
|
3
|
+
* Manages VideoDecoder lifecycle, configuration, and queue management.
|
|
4
|
+
* Handles codec configuration, state transitions, and async queue operations.
|
|
5
|
+
*/
|
|
6
|
+
export declare class DecoderManager {
|
|
7
|
+
private readonly mp4boxFile;
|
|
8
|
+
private readonly defaultVideoTrack;
|
|
9
|
+
private readonly decoder;
|
|
10
|
+
constructor(mp4boxFile: MP4Box.ISOFile, defaultVideoTrack: any, // MP4Box track type
|
|
11
|
+
onOutput: (frame: VideoFrame) => void, onError: (error: Error) => void);
|
|
12
|
+
/**
|
|
13
|
+
* Gets the decoder instance for direct access to decode operations
|
|
14
|
+
*/
|
|
15
|
+
get videoDecoder(): VideoDecoder;
|
|
16
|
+
/**
|
|
17
|
+
* Builds the decoder configuration from MP4 track data
|
|
18
|
+
*/
|
|
19
|
+
get decoderConfiguration(): VideoDecoderConfig;
|
|
20
|
+
/**
|
|
21
|
+
* Configures the video decoder with the appropriate codec, dimensions, and hardware acceleration settings.
|
|
22
|
+
* If the decoder is already configured, it will be reset before being reconfigured.
|
|
23
|
+
*/
|
|
24
|
+
configureDecoder(): void;
|
|
25
|
+
/**
|
|
26
|
+
* Waits until the video decoder queue is drained (empty).
|
|
27
|
+
* Uses recursive async pattern to handle multiple pending frames.
|
|
28
|
+
*/
|
|
29
|
+
waitUntilVideoQueueDrained(): Promise<void>;
|
|
30
|
+
/**
|
|
31
|
+
* Gets the current decoder state
|
|
32
|
+
*/
|
|
33
|
+
get state(): string;
|
|
34
|
+
/**
|
|
35
|
+
* Gets the current decode queue size
|
|
36
|
+
*/
|
|
37
|
+
get decodeQueueSize(): number;
|
|
38
|
+
/**
|
|
39
|
+
* Decodes an encoded video chunk
|
|
40
|
+
*/
|
|
41
|
+
decode(chunk: EncodedVideoChunk): void;
|
|
42
|
+
/**
|
|
43
|
+
* Flushes the decoder
|
|
44
|
+
*/
|
|
45
|
+
flush(): Promise<void>;
|
|
46
|
+
/**
|
|
47
|
+
* Resets the decoder
|
|
48
|
+
*/
|
|
49
|
+
reset(): void;
|
|
50
|
+
/**
|
|
51
|
+
* Closes the decoder and releases resources
|
|
52
|
+
*/
|
|
53
|
+
close(): void;
|
|
54
|
+
/**
|
|
55
|
+
* Adds event listener to the decoder
|
|
56
|
+
*/
|
|
57
|
+
addEventListener(type: string, listener: EventListener, options?: AddEventListenerOptions): void;
|
|
58
|
+
/**
|
|
59
|
+
* Removes event listener from the decoder
|
|
60
|
+
*/
|
|
61
|
+
removeEventListener(type: string, listener: EventListener): void;
|
|
62
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import * as MP4Box from "mp4box";
|
|
2
|
+
/**
|
|
3
|
+
* Manages VideoDecoder lifecycle, configuration, and queue management.
|
|
4
|
+
* Handles codec configuration, state transitions, and async queue operations.
|
|
5
|
+
*/
|
|
6
|
+
var DecoderManager = class {
|
|
7
|
+
constructor(mp4boxFile, defaultVideoTrack, onOutput, onError) {
|
|
8
|
+
this.mp4boxFile = mp4boxFile;
|
|
9
|
+
this.defaultVideoTrack = defaultVideoTrack;
|
|
10
|
+
this.decoder = new VideoDecoder({
|
|
11
|
+
output: onOutput,
|
|
12
|
+
error: onError
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Gets the decoder instance for direct access to decode operations
|
|
17
|
+
*/
|
|
18
|
+
get videoDecoder() {
|
|
19
|
+
return this.decoder;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Builds the decoder configuration from MP4 track data
|
|
23
|
+
*/
|
|
24
|
+
get decoderConfiguration() {
|
|
25
|
+
if (!this.defaultVideoTrack) throw new Error("No default video track found");
|
|
26
|
+
let description = new Uint8Array();
|
|
27
|
+
const trak = this.mp4boxFile.getTrackById(this.defaultVideoTrack.id);
|
|
28
|
+
for (const entry of trak.mdia.minf.stbl.stsd.entries) if (entry.avcC ?? entry.hvcC) {
|
|
29
|
+
const stream = new MP4Box.DataStream(void 0, 0, MP4Box.DataStream.BIG_ENDIAN);
|
|
30
|
+
if (entry.avcC) entry.avcC.write(stream);
|
|
31
|
+
else entry.hvcC.write(stream);
|
|
32
|
+
description = new Uint8Array(stream.buffer, 8);
|
|
33
|
+
break;
|
|
34
|
+
}
|
|
35
|
+
return {
|
|
36
|
+
codec: this.defaultVideoTrack.codec,
|
|
37
|
+
codedWidth: this.defaultVideoTrack.track_width,
|
|
38
|
+
codedHeight: this.defaultVideoTrack.track_height,
|
|
39
|
+
optimizeForLatency: true,
|
|
40
|
+
description
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Configures the video decoder with the appropriate codec, dimensions, and hardware acceleration settings.
|
|
45
|
+
* If the decoder is already configured, it will be reset before being reconfigured.
|
|
46
|
+
*/
|
|
47
|
+
configureDecoder() {
|
|
48
|
+
if (this.decoder.state === "configured") this.decoder.reset();
|
|
49
|
+
this.decoder.configure(this.decoderConfiguration);
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Waits until the video decoder queue is drained (empty).
|
|
53
|
+
* Uses recursive async pattern to handle multiple pending frames.
|
|
54
|
+
*/
|
|
55
|
+
async waitUntilVideoQueueDrained() {
|
|
56
|
+
if (this.decoder.decodeQueueSize === 0) return;
|
|
57
|
+
await new Promise((resolve) => {
|
|
58
|
+
this.decoder.addEventListener("dequeue", () => {
|
|
59
|
+
resolve();
|
|
60
|
+
}, { once: true });
|
|
61
|
+
});
|
|
62
|
+
await this.waitUntilVideoQueueDrained();
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Gets the current decoder state
|
|
66
|
+
*/
|
|
67
|
+
get state() {
|
|
68
|
+
return this.decoder.state;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Gets the current decode queue size
|
|
72
|
+
*/
|
|
73
|
+
get decodeQueueSize() {
|
|
74
|
+
return this.decoder.decodeQueueSize;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Decodes an encoded video chunk
|
|
78
|
+
*/
|
|
79
|
+
decode(chunk) {
|
|
80
|
+
if (this.decoder.state === "closed") return;
|
|
81
|
+
this.decoder.decode(chunk);
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Flushes the decoder
|
|
85
|
+
*/
|
|
86
|
+
async flush() {
|
|
87
|
+
return this.decoder.flush();
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Resets the decoder
|
|
91
|
+
*/
|
|
92
|
+
reset() {
|
|
93
|
+
this.decoder.reset();
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Closes the decoder and releases resources
|
|
97
|
+
*/
|
|
98
|
+
close() {
|
|
99
|
+
this.decoder.close();
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Adds event listener to the decoder
|
|
103
|
+
*/
|
|
104
|
+
addEventListener(type, listener, options) {
|
|
105
|
+
this.decoder.addEventListener(type, listener, options);
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Removes event listener from the decoder
|
|
109
|
+
*/
|
|
110
|
+
removeEventListener(type, listener) {
|
|
111
|
+
this.decoder.removeEventListener(type, listener);
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
export { DecoderManager };
|
package/dist/EncodedAsset.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { ReadableStream as ReadableStreamNode } from 'node:stream/web';
|
|
2
|
-
import
|
|
2
|
+
import { MP4SampleAnalyzer } from './MP4SampleAnalyzer.js';
|
|
3
|
+
import { DecoderManager } from './DecoderManager.js';
|
|
4
|
+
import type * as MP4Box from "mp4box";
|
|
3
5
|
type AnyReadableStream = ReadableStream<Uint8Array> | ReadableStreamNode<Uint8Array>;
|
|
4
6
|
type FrameCallback = (frame: VideoFrame) => void;
|
|
5
7
|
export interface FetchContext {
|
|
@@ -26,14 +28,47 @@ export declare class ISOFileAsset extends FileAsset {
|
|
|
26
28
|
get containerFormat(): "mp4";
|
|
27
29
|
}
|
|
28
30
|
export declare class VideoAsset extends ISOFileAsset {
|
|
29
|
-
static createFromReadableStream(id: string, stream: AnyReadableStream, file: File
|
|
30
|
-
|
|
31
|
+
static createFromReadableStream(id: string, stream: AnyReadableStream, file: File, options?: {
|
|
32
|
+
startTimeOffsetMs?: number;
|
|
33
|
+
}): Promise<VideoAsset>;
|
|
34
|
+
/**
|
|
35
|
+
* Creates a VideoAsset from a complete MP4 file (like JIT transcoded segments).
|
|
36
|
+
*
|
|
37
|
+
* This is used for JIT transcoded segments which are complete MP4 files that always
|
|
38
|
+
* start on keyframes, unlike fragmented MP4s used in the asset pipeline.
|
|
39
|
+
*/
|
|
40
|
+
static createFromCompleteMP4(id: string, file: File, options?: {
|
|
41
|
+
startTimeOffsetMs?: number;
|
|
42
|
+
}): Promise<VideoAsset>;
|
|
43
|
+
private readonly frameBuffer;
|
|
44
|
+
private readonly seekStrategy;
|
|
45
|
+
readonly sampleAnalyzer: MP4SampleAnalyzer;
|
|
46
|
+
readonly decoderManager: DecoderManager;
|
|
31
47
|
lastDecodedSample?: MP4Box.Sample;
|
|
32
48
|
lastSoughtFrame?: VideoFrame;
|
|
33
|
-
decodedFrames: VideoFrame[];
|
|
34
49
|
requestedSampleNumber: number;
|
|
35
50
|
outCursor: number;
|
|
36
51
|
sampleCursor: number;
|
|
52
|
+
latestSeekCts: number;
|
|
53
|
+
/**
|
|
54
|
+
* Indicates this is a JIT segment that always starts on a keyframe.
|
|
55
|
+
* Used to optimize seeking behavior for JIT transcoded segments.
|
|
56
|
+
*/
|
|
57
|
+
isJitSegment: boolean;
|
|
58
|
+
/**
|
|
59
|
+
* Flag to track if this VideoAsset is being replaced
|
|
60
|
+
* Used to gracefully abort ongoing operations during recreation
|
|
61
|
+
*/
|
|
62
|
+
private isBeingReplaced;
|
|
63
|
+
/**
|
|
64
|
+
* Active seek operations that should be aborted if VideoAsset is replaced
|
|
65
|
+
*/
|
|
66
|
+
private activeSeekAbortController?;
|
|
67
|
+
/**
|
|
68
|
+
* FFmpeg start_time offset in milliseconds from the processed video.
|
|
69
|
+
* Applied during seeking to correct for timing shifts introduced by FFmpeg processing.
|
|
70
|
+
*/
|
|
71
|
+
startTimeOffsetMs?: number;
|
|
37
72
|
/**
|
|
38
73
|
* **Only use this function in tests to reset a VideoAsset to its initial state.**
|
|
39
74
|
*
|
|
@@ -45,6 +80,9 @@ export declare class VideoAsset extends ISOFileAsset {
|
|
|
45
80
|
removeEventListener(type: "frame", callback: FrameCallback): void;
|
|
46
81
|
emit(type: "frame", frame: VideoFrame): void;
|
|
47
82
|
constructor(localName: string, mp4boxFile: MP4Box.ISOFile, file: File);
|
|
83
|
+
get videoDecoder(): VideoDecoder;
|
|
84
|
+
get decodedFrames(): VideoFrame[];
|
|
85
|
+
set decodedFrames(frames: VideoFrame[]);
|
|
48
86
|
get videoCodec(): string;
|
|
49
87
|
get fragmentInfo(): {
|
|
50
88
|
offset: number;
|
|
@@ -58,17 +96,7 @@ export declare class VideoAsset extends ISOFileAsset {
|
|
|
58
96
|
get canDecodeNextSample(): boolean;
|
|
59
97
|
decodeNextSample(): Promise<void>;
|
|
60
98
|
decodeSlice(start: number, end: number): Promise<void>;
|
|
61
|
-
get decoderConfiguration():
|
|
62
|
-
codec: string;
|
|
63
|
-
codedWidth: number;
|
|
64
|
-
codedHeight: number;
|
|
65
|
-
optimizeForLatency: true;
|
|
66
|
-
description: Uint8Array;
|
|
67
|
-
};
|
|
68
|
-
/**
|
|
69
|
-
* Configures the video decoder with the appropriate codec, dimensions, and hardware acceleration settings.
|
|
70
|
-
* If the decoder is already configured, it will be reset before being reconfigured.
|
|
71
|
-
*/
|
|
99
|
+
get decoderConfiguration(): VideoDecoderConfig;
|
|
72
100
|
configureDecoder(): void;
|
|
73
101
|
getSample(index?: number): MP4Box.Sample;
|
|
74
102
|
get timescale(): number;
|
|
@@ -78,7 +106,21 @@ export declare class VideoAsset extends ISOFileAsset {
|
|
|
78
106
|
seekingWillEmitNewFrame(seconds: number): boolean;
|
|
79
107
|
seekingWillSkipPictureGroup(seconds: number): boolean;
|
|
80
108
|
seekingWillGoBackwards(seconds: number): boolean;
|
|
81
|
-
|
|
109
|
+
/**
|
|
110
|
+
* Optimized flush decision for JIT segments that always start on keyframes.
|
|
111
|
+
* JIT segments have better keyframe distribution and shorter duration,
|
|
112
|
+
* so we can be less aggressive about flushing.
|
|
113
|
+
*/
|
|
114
|
+
shouldFlushForJitSegment(seconds: number, _shouldFlushPictureGroup: boolean, shouldFlushBackwards: boolean): boolean;
|
|
115
|
+
/**
|
|
116
|
+
* Finds the optimal sample cursor position for segments with keyframes.
|
|
117
|
+
* Uses optimal keyframe selection for both single and multi-keyframe segments.
|
|
118
|
+
*/
|
|
119
|
+
findOptimalSampleCursorForJitSeek(targetSample: MP4Box.Sample): number;
|
|
120
|
+
/**
|
|
121
|
+
* Marks this VideoAsset as being replaced, which will abort any ongoing seek operations
|
|
122
|
+
*/
|
|
123
|
+
markAsBeingReplaced(): void;
|
|
82
124
|
seekToTime(seconds: number): Promise<VideoFrame | undefined>;
|
|
83
125
|
get defaultVideoTrack(): MP4Box.VideoTrackInfo | undefined;
|
|
84
126
|
get defaultVideoTrak(): MP4Box.TrakBox;
|