@editframe/assets 0.16.8-beta.0 → 0.18.3-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.
@@ -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 };
@@ -1,5 +1,7 @@
1
1
  import { ReadableStream as ReadableStreamNode } from 'node:stream/web';
2
- import * as MP4Box from "mp4box";
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): Promise<VideoAsset>;
30
- videoDecoder: VideoDecoder;
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
- latestSeekCts: number;
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;