@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.
@@ -0,0 +1,62 @@
1
+ /**
2
+ * FrameBuffer manages a collection of decoded VideoFrames with automatic LRU eviction.
3
+ *
4
+ * This class extracts the frame buffering logic from VideoAsset to improve
5
+ * testability and separation of concerns while maintaining the exact same
6
+ * memory management behavior.
7
+ */
8
+ export declare class FrameBuffer {
9
+ private _frames;
10
+ private readonly _maxSize;
11
+ /**
12
+ * Creates a new FrameBuffer with the specified maximum size.
13
+ *
14
+ * @param maxSize Maximum number of frames to keep in buffer (default: 30)
15
+ */
16
+ constructor(maxSize?: number);
17
+ /**
18
+ * Gets the maximum size of the buffer.
19
+ */
20
+ get maxSize(): number;
21
+ /**
22
+ * Gets the current number of frames in the buffer.
23
+ */
24
+ get size(): number;
25
+ /**
26
+ * Gets a copy of the frames array for compatibility.
27
+ * Note: Returns a shallow copy to prevent external modification while allowing access.
28
+ */
29
+ get frames(): VideoFrame[];
30
+ /**
31
+ * Adds a frame to the buffer. If the buffer exceeds maxSize after adding,
32
+ * the oldest frames will be automatically pruned and closed.
33
+ *
34
+ * @param frame The VideoFrame to add to the buffer
35
+ */
36
+ add(frame: VideoFrame): void;
37
+ /**
38
+ * Finds a frame in the buffer by its timestamp.
39
+ *
40
+ * @param timestamp The timestamp to search for
41
+ * @returns The VideoFrame with matching timestamp, or undefined if not found
42
+ */
43
+ findByTimestamp(timestamp: number): VideoFrame | undefined;
44
+ /**
45
+ * Checks if the buffer contains the specified frame instance.
46
+ *
47
+ * @param frame The VideoFrame instance to check for
48
+ * @returns true if the frame is in the buffer, false otherwise
49
+ */
50
+ includes(frame: VideoFrame): boolean;
51
+ /**
52
+ * Removes all frames from the buffer and closes them to free memory.
53
+ * This method is safe to call even if some frames are already closed.
54
+ */
55
+ clear(): void;
56
+ /**
57
+ * Prunes the buffer to the maximum size by removing and closing the oldest frames.
58
+ * This maintains the LRU (Least Recently Used) eviction policy where oldest
59
+ * frames are removed first.
60
+ */
61
+ pruneToSize(): void;
62
+ }
@@ -0,0 +1,89 @@
1
+ /**
2
+ * FrameBuffer manages a collection of decoded VideoFrames with automatic LRU eviction.
3
+ *
4
+ * This class extracts the frame buffering logic from VideoAsset to improve
5
+ * testability and separation of concerns while maintaining the exact same
6
+ * memory management behavior.
7
+ */
8
+ var FrameBuffer = class {
9
+ /**
10
+ * Creates a new FrameBuffer with the specified maximum size.
11
+ *
12
+ * @param maxSize Maximum number of frames to keep in buffer (default: 30)
13
+ */
14
+ constructor(maxSize = 30) {
15
+ this._frames = [];
16
+ this._maxSize = maxSize;
17
+ }
18
+ /**
19
+ * Gets the maximum size of the buffer.
20
+ */
21
+ get maxSize() {
22
+ return this._maxSize;
23
+ }
24
+ /**
25
+ * Gets the current number of frames in the buffer.
26
+ */
27
+ get size() {
28
+ return this._frames.length;
29
+ }
30
+ /**
31
+ * Gets a copy of the frames array for compatibility.
32
+ * Note: Returns a shallow copy to prevent external modification while allowing access.
33
+ */
34
+ get frames() {
35
+ return [...this._frames];
36
+ }
37
+ /**
38
+ * Adds a frame to the buffer. If the buffer exceeds maxSize after adding,
39
+ * the oldest frames will be automatically pruned and closed.
40
+ *
41
+ * @param frame The VideoFrame to add to the buffer
42
+ */
43
+ add(frame) {
44
+ this._frames.push(frame);
45
+ this.pruneToSize();
46
+ }
47
+ /**
48
+ * Finds a frame in the buffer by its timestamp.
49
+ *
50
+ * @param timestamp The timestamp to search for
51
+ * @returns The VideoFrame with matching timestamp, or undefined if not found
52
+ */
53
+ findByTimestamp(timestamp) {
54
+ return this._frames.find((frame) => frame.timestamp === timestamp);
55
+ }
56
+ /**
57
+ * Checks if the buffer contains the specified frame instance.
58
+ *
59
+ * @param frame The VideoFrame instance to check for
60
+ * @returns true if the frame is in the buffer, false otherwise
61
+ */
62
+ includes(frame) {
63
+ return this._frames.includes(frame);
64
+ }
65
+ /**
66
+ * Removes all frames from the buffer and closes them to free memory.
67
+ * This method is safe to call even if some frames are already closed.
68
+ */
69
+ clear() {
70
+ for (const frame of this._frames) try {
71
+ frame.close();
72
+ } catch (error) {}
73
+ this._frames = [];
74
+ }
75
+ /**
76
+ * Prunes the buffer to the maximum size by removing and closing the oldest frames.
77
+ * This maintains the LRU (Least Recently Used) eviction policy where oldest
78
+ * frames are removed first.
79
+ */
80
+ pruneToSize() {
81
+ while (this._frames.length > this._maxSize) {
82
+ const oldestFrame = this._frames.shift();
83
+ if (oldestFrame) try {
84
+ oldestFrame.close();
85
+ } catch (error) {}
86
+ }
87
+ }
88
+ };
89
+ export { FrameBuffer };
package/dist/MP4File.d.ts CHANGED
@@ -1,6 +1,14 @@
1
1
  import * as MP4Box from "mp4box";
2
+ export interface MP4FileOptions {
3
+ readyTimeoutMs?: number;
4
+ sampleWaitTimeoutMs?: number;
5
+ }
2
6
  export declare class MP4File extends MP4Box.ISOFile {
3
- readyPromise: Promise<void>;
7
+ private timeoutId?;
8
+ private readonly readyTimeoutMs;
9
+ private readonly sampleWaitTimeoutMs;
10
+ readonly readyPromise: Promise<void>;
11
+ constructor(options?: MP4FileOptions);
4
12
  setSegmentOptions(id: number, user: any, options: MP4Box.SegmentOptions): void;
5
13
  /**
6
14
  * Fragments all tracks in a file into separate array buffers.
package/dist/MP4File.js CHANGED
@@ -1,223 +1,209 @@
1
1
  import * as MP4Box from "mp4box";
2
2
  import debug from "debug";
3
3
  const log = debug("ef:av:mp4file");
4
- class MP4File extends MP4Box.ISOFile {
5
- constructor() {
6
- super(...arguments);
7
- this.readyPromise = new Promise((resolve, reject) => {
8
- this.onReady = () => resolve();
9
- this.onError = reject;
10
- });
11
- this.waitingForSamples = [];
12
- this._hasSeenLastSamples = false;
13
- this._arrayBufferFileStart = 0;
14
- }
15
- setSegmentOptions(id, user, options) {
16
- const trak = this.getTrackById(id);
17
- if (trak) {
18
- trak.nextSample = 0;
19
- this.fragmentedTracks.push({
20
- id,
21
- user,
22
- trak,
23
- segmentStream: null,
24
- nb_samples: "nbSamples" in options && options.nbSamples || 1e3,
25
- rapAlignement: ("rapAlignement" in options && options.rapAlignement) ?? true
26
- });
27
- }
28
- }
29
- /**
30
- * Fragments all tracks in a file into separate array buffers.
31
- */
32
- async fragmentAllTracks() {
33
- const trackBuffers = {};
34
- for await (const segment of this.fragmentIterator()) {
35
- (trackBuffers[segment.track] ??= []).push(segment.data);
36
- }
37
- return trackBuffers;
38
- }
39
- async *fragmentIterator() {
40
- await this.readyPromise;
41
- const trackInfo = {};
42
- for (const videoTrack of this.getInfo().videoTracks) {
43
- trackInfo[videoTrack.id] = { index: 0 };
44
- this.setSegmentOptions(videoTrack.id, null, {
45
- rapAlignement: true
46
- });
47
- }
48
- for (const audioTrack of this.getInfo().audioTracks) {
49
- trackInfo[audioTrack.id] = { index: 0 };
50
- const sampleRate = audioTrack.audio.sample_rate;
51
- const probablePacketSize = 1024;
52
- const probableFourSecondsOfSamples = Math.ceil(
53
- sampleRate / probablePacketSize * 4
54
- );
55
- this.setSegmentOptions(audioTrack.id, null, {
56
- nbSamples: probableFourSecondsOfSamples
57
- });
58
- }
59
- const initSegments = this.initializeSegmentation();
60
- for (const initSegment of initSegments) {
61
- yield {
62
- track: initSegment.id,
63
- segment: "init",
64
- data: initSegment.buffer
65
- };
66
- }
67
- const fragmentStartSamples = {};
68
- let finishedReading = false;
69
- do {
70
- for (const fragTrak of this.fragmentedTracks) {
71
- const trak = fragTrak.trak;
72
- if (trak.nextSample === void 0) {
73
- throw new Error("trak.nextSample is undefined");
74
- }
75
- if (trak.samples === void 0) {
76
- throw new Error("trak.samples is undefined");
77
- }
78
- log("trak.nextSample", fragTrak.id, trak.nextSample);
79
- log("trak.samples.length", fragTrak.id, trak.samples.length);
80
- while (trak.nextSample < trak.samples.length) {
81
- let result = void 0;
82
- const fragTrakNextSample = trak.samples[trak.nextSample];
83
- if (fragTrakNextSample) {
84
- fragmentStartSamples[fragTrak.id] ||= fragTrakNextSample;
85
- }
86
- try {
87
- result = this.createFragment(
88
- fragTrak.id,
89
- trak.nextSample,
90
- fragTrak.segmentStream
91
- );
92
- } catch (error) {
93
- console.error("Failed to createFragment", error);
94
- }
95
- if (result) {
96
- fragTrak.segmentStream = result;
97
- trak.nextSample++;
98
- } else {
99
- finishedReading = await this.waitForMoreSamples();
100
- break;
101
- }
102
- const nextSample = trak.samples[trak.nextSample];
103
- const emitSegment = (
104
- // if rapAlignement is true, we emit a fragment when we have a rap sample coming up next
105
- fragTrak.rapAlignement === true && nextSample?.is_sync || // if rapAlignement is false, we emit a fragment when we have the required number of samples
106
- !fragTrak.rapAlignement && trak.nextSample % fragTrak.nb_samples === 0 || // // if this is the last sample, we emit the fragment
107
- // finished ||
108
- // if we have more samples than the number of samples requested, we emit the fragment
109
- trak.nextSample >= trak.samples.length
110
- );
111
- if (emitSegment) {
112
- const trackInfoForFrag = trackInfo[fragTrak.id];
113
- if (!trackInfoForFrag) {
114
- throw new Error("trackInfoForFrag is undefined");
115
- }
116
- const startSample = fragmentStartSamples[fragTrak.id];
117
- const endSample = trak.samples[trak.nextSample - 1];
118
- if (!startSample || !endSample) {
119
- throw new Error("startSample or endSample is undefined");
120
- }
121
- log(
122
- `Yielding fragment #${trackInfoForFrag.index} for track=${fragTrak.id}`,
123
- `startTime=${startSample.cts}`,
124
- `endTime=${endSample.cts + endSample.duration}`
125
- );
126
- yield {
127
- track: fragTrak.id,
128
- segment: trackInfoForFrag.index,
129
- data: fragTrak.segmentStream.buffer,
130
- cts: startSample.cts,
131
- dts: startSample.dts,
132
- duration: endSample.cts - startSample.cts + endSample.duration
133
- };
134
- trackInfoForFrag.index += 1;
135
- fragTrak.segmentStream = null;
136
- delete fragmentStartSamples[fragTrak.id];
137
- }
138
- }
139
- }
140
- finishedReading = await this.waitForMoreSamples();
141
- } while (!finishedReading);
142
- for (const fragTrak of this.fragmentedTracks) {
143
- const trak = fragTrak.trak;
144
- if (trak.nextSample === void 0) {
145
- throw new Error("trak.nextSample is undefined");
146
- }
147
- if (trak.samples === void 0) {
148
- throw new Error("trak.samples is undefined");
149
- }
150
- while (trak.nextSample < trak.samples.length) {
151
- let result = void 0;
152
- try {
153
- result = this.createFragment(
154
- fragTrak.id,
155
- trak.nextSample,
156
- fragTrak.segmentStream
157
- );
158
- } catch (error) {
159
- console.error("Failed to createFragment", error);
160
- }
161
- if (result) {
162
- fragTrak.segmentStream = result;
163
- trak.nextSample++;
164
- } else {
165
- finishedReading = await this.waitForMoreSamples();
166
- break;
167
- }
168
- const nextSample = trak.samples[trak.nextSample];
169
- const emitSegment = (
170
- // if rapAlignement is true, we emit a fragment when we have a rap sample coming up next
171
- fragTrak.rapAlignement === true && nextSample?.is_sync || // if rapAlignement is false, we emit a fragment when we have the required number of samples
172
- !fragTrak.rapAlignement && trak.nextSample % fragTrak.nb_samples === 0 || // if we have more samples than the number of samples requested, we emit the fragment
173
- trak.nextSample >= trak.samples.length
174
- );
175
- if (emitSegment) {
176
- const trackInfoForFrag = trackInfo[fragTrak.id];
177
- if (!trackInfoForFrag) {
178
- throw new Error("trackInfoForFrag is undefined");
179
- }
180
- const startSample = fragmentStartSamples[fragTrak.id];
181
- const endSample = trak.samples[trak.nextSample - 1];
182
- if (!startSample || !endSample) {
183
- throw new Error("startSample or endSample is undefined");
184
- }
185
- log(
186
- `Yielding fragment #${trackInfoForFrag.index} for track=${fragTrak.id}`,
187
- `startTime=${startSample.cts}`,
188
- `endTime=${endSample.cts + endSample.duration}`
189
- );
190
- yield {
191
- track: fragTrak.id,
192
- segment: trackInfoForFrag.index,
193
- data: fragTrak.segmentStream.buffer,
194
- cts: startSample.cts,
195
- dts: startSample.dts,
196
- duration: endSample.cts - startSample.cts + endSample.duration
197
- };
198
- trackInfoForFrag.index += 1;
199
- fragTrak.segmentStream = null;
200
- delete fragmentStartSamples[fragTrak.id];
201
- }
202
- }
203
- }
204
- }
205
- waitForMoreSamples() {
206
- if (this._hasSeenLastSamples) {
207
- return Promise.resolve(true);
208
- }
209
- return new Promise((resolve) => {
210
- this.waitingForSamples.push(resolve);
211
- });
212
- }
213
- processSamples(last) {
214
- this._hasSeenLastSamples = last;
215
- for (const observer of this.waitingForSamples) {
216
- observer(last);
217
- }
218
- this.waitingForSamples = [];
219
- }
220
- }
221
- export {
222
- MP4File
4
+ var MP4File = class extends MP4Box.ISOFile {
5
+ constructor(options = {}) {
6
+ super();
7
+ this.waitingForSamples = [];
8
+ this._hasSeenLastSamples = false;
9
+ this._arrayBufferFileStart = 0;
10
+ this.readyTimeoutMs = options.readyTimeoutMs ?? 100;
11
+ this.sampleWaitTimeoutMs = options.sampleWaitTimeoutMs ?? 100;
12
+ this.readyPromise = new Promise((resolve, reject) => {
13
+ this.onReady = () => {
14
+ if (this.timeoutId) {
15
+ clearTimeout(this.timeoutId);
16
+ this.timeoutId = void 0;
17
+ }
18
+ resolve();
19
+ };
20
+ this.onError = (error) => {
21
+ if (this.timeoutId) {
22
+ clearTimeout(this.timeoutId);
23
+ this.timeoutId = void 0;
24
+ }
25
+ reject(error);
26
+ };
27
+ this.timeoutId = setTimeout(() => {
28
+ this.timeoutId = void 0;
29
+ reject(/* @__PURE__ */ new Error("MP4File ready timeout - file may be invalid or incomplete"));
30
+ }, this.readyTimeoutMs);
31
+ });
32
+ this.readyPromise.catch(() => {});
33
+ }
34
+ setSegmentOptions(id, user, options) {
35
+ const trak = this.getTrackById(id);
36
+ if (trak) {
37
+ trak.nextSample = 0;
38
+ this.fragmentedTracks.push({
39
+ id,
40
+ user,
41
+ trak,
42
+ segmentStream: null,
43
+ nb_samples: "nbSamples" in options && options.nbSamples || 1e3,
44
+ rapAlignement: ("rapAlignement" in options && options.rapAlignement) ?? true
45
+ });
46
+ }
47
+ }
48
+ /**
49
+ * Fragments all tracks in a file into separate array buffers.
50
+ */
51
+ async fragmentAllTracks() {
52
+ const trackBuffers = {};
53
+ try {
54
+ for await (const segment of this.fragmentIterator()) (trackBuffers[segment.track] ??= []).push(segment.data);
55
+ } catch (error) {
56
+ console.warn("fragmentAllTracks failed:", error);
57
+ }
58
+ return trackBuffers;
59
+ }
60
+ async *fragmentIterator() {
61
+ try {
62
+ await this.readyPromise;
63
+ } catch (error) {
64
+ console.warn("MP4File not ready:", error);
65
+ return;
66
+ }
67
+ const trackInfo = {};
68
+ const videoTracks = this.getInfo().videoTracks;
69
+ const audioTracks = this.getInfo().audioTracks;
70
+ if (videoTracks.length === 0 && audioTracks.length === 0) {
71
+ console.warn("No video or audio tracks found in MP4 file");
72
+ return;
73
+ }
74
+ for (const videoTrack of videoTracks) {
75
+ trackInfo[videoTrack.id] = { index: 0 };
76
+ this.setSegmentOptions(videoTrack.id, null, { rapAlignement: true });
77
+ }
78
+ for (const audioTrack of audioTracks) {
79
+ trackInfo[audioTrack.id] = { index: 0 };
80
+ const sampleRate = audioTrack.audio.sample_rate;
81
+ const probablePacketSize = 1024;
82
+ const probableFourSecondsOfSamples = Math.ceil(sampleRate / probablePacketSize * 4);
83
+ this.setSegmentOptions(audioTrack.id, null, { nbSamples: probableFourSecondsOfSamples });
84
+ }
85
+ if (this.fragmentedTracks.length === 0) {
86
+ console.warn("No fragmented tracks set up");
87
+ return;
88
+ }
89
+ const initSegments = this.initializeSegmentation();
90
+ for (const initSegment of initSegments) yield {
91
+ track: initSegment.id,
92
+ segment: "init",
93
+ data: initSegment.buffer
94
+ };
95
+ const fragmentStartSamples = {};
96
+ let finishedReading = false;
97
+ do {
98
+ /**
99
+ * For each track marked for fragmentation, check if the next sample is
100
+ * there (i.e. if the sample information is known (i.e. moof has arrived)
101
+ * and if it has been downloaded) and create a fragment with it
102
+ */
103
+ for (const fragTrak of this.fragmentedTracks) {
104
+ const trak = fragTrak.trak;
105
+ if (trak.nextSample === void 0) throw new Error("trak.nextSample is undefined");
106
+ if (trak.samples === void 0) throw new Error("trak.samples is undefined");
107
+ log("trak.nextSample", fragTrak.id, trak.nextSample);
108
+ log("trak.samples.length", fragTrak.id, trak.samples.length);
109
+ while (trak.nextSample < trak.samples.length) {
110
+ let result = void 0;
111
+ const fragTrakNextSample = trak.samples[trak.nextSample];
112
+ if (fragTrakNextSample) fragmentStartSamples[fragTrak.id] ||= fragTrakNextSample;
113
+ try {
114
+ result = this.createFragment(fragTrak.id, trak.nextSample, fragTrak.segmentStream);
115
+ } catch (error) {
116
+ console.error("Failed to createFragment", error);
117
+ }
118
+ if (result) {
119
+ fragTrak.segmentStream = result;
120
+ trak.nextSample++;
121
+ } else {
122
+ finishedReading = await this.waitForMoreSamples();
123
+ break;
124
+ }
125
+ const nextSample = trak.samples[trak.nextSample];
126
+ const emitSegment = fragTrak.rapAlignement === true && nextSample?.is_sync || !fragTrak.rapAlignement && trak.nextSample % fragTrak.nb_samples === 0 || trak.nextSample >= trak.samples.length;
127
+ if (emitSegment) {
128
+ const trackInfoForFrag = trackInfo[fragTrak.id];
129
+ if (!trackInfoForFrag) throw new Error("trackInfoForFrag is undefined");
130
+ const startSample = fragmentStartSamples[fragTrak.id];
131
+ const endSample = trak.samples[trak.nextSample - 1];
132
+ if (!startSample || !endSample) throw new Error("startSample or endSample is undefined");
133
+ log(`Yielding fragment #${trackInfoForFrag.index} for track=${fragTrak.id}`, `startTime=${startSample.cts}`, `endTime=${endSample.cts + endSample.duration}`);
134
+ yield {
135
+ track: fragTrak.id,
136
+ segment: trackInfoForFrag.index,
137
+ data: fragTrak.segmentStream.buffer,
138
+ cts: startSample.cts,
139
+ dts: startSample.dts,
140
+ duration: endSample.cts - startSample.cts + endSample.duration
141
+ };
142
+ trackInfoForFrag.index += 1;
143
+ fragTrak.segmentStream = null;
144
+ delete fragmentStartSamples[fragTrak.id];
145
+ }
146
+ }
147
+ }
148
+ finishedReading = await this.waitForMoreSamples();
149
+ } while (!finishedReading);
150
+ for (const fragTrak of this.fragmentedTracks) {
151
+ const trak = fragTrak.trak;
152
+ if (trak.nextSample === void 0) throw new Error("trak.nextSample is undefined");
153
+ if (trak.samples === void 0) throw new Error("trak.samples is undefined");
154
+ while (trak.nextSample < trak.samples.length) {
155
+ let result = void 0;
156
+ try {
157
+ result = this.createFragment(fragTrak.id, trak.nextSample, fragTrak.segmentStream);
158
+ } catch (error) {
159
+ console.error("Failed to createFragment", error);
160
+ }
161
+ if (result) {
162
+ fragTrak.segmentStream = result;
163
+ trak.nextSample++;
164
+ } else {
165
+ finishedReading = await this.waitForMoreSamples();
166
+ break;
167
+ }
168
+ const nextSample = trak.samples[trak.nextSample];
169
+ const emitSegment = fragTrak.rapAlignement === true && nextSample?.is_sync || !fragTrak.rapAlignement && trak.nextSample % fragTrak.nb_samples === 0 || trak.nextSample >= trak.samples.length;
170
+ if (emitSegment) {
171
+ const trackInfoForFrag = trackInfo[fragTrak.id];
172
+ if (!trackInfoForFrag) throw new Error("trackInfoForFrag is undefined");
173
+ const startSample = fragmentStartSamples[fragTrak.id];
174
+ const endSample = trak.samples[trak.nextSample - 1];
175
+ if (!startSample || !endSample) throw new Error("startSample or endSample is undefined");
176
+ log(`Yielding fragment #${trackInfoForFrag.index} for track=${fragTrak.id}`, `startTime=${startSample.cts}`, `endTime=${endSample.cts + endSample.duration}`);
177
+ yield {
178
+ track: fragTrak.id,
179
+ segment: trackInfoForFrag.index,
180
+ data: fragTrak.segmentStream.buffer,
181
+ cts: startSample.cts,
182
+ dts: startSample.dts,
183
+ duration: endSample.cts - startSample.cts + endSample.duration
184
+ };
185
+ trackInfoForFrag.index += 1;
186
+ fragTrak.segmentStream = null;
187
+ delete fragmentStartSamples[fragTrak.id];
188
+ }
189
+ }
190
+ }
191
+ }
192
+ waitForMoreSamples() {
193
+ if (this._hasSeenLastSamples) return Promise.resolve(true);
194
+ return new Promise((resolve) => {
195
+ this.waitingForSamples.push(resolve);
196
+ setTimeout(() => {
197
+ const index = this.waitingForSamples.indexOf(resolve);
198
+ if (index !== -1) this.waitingForSamples.splice(index, 1);
199
+ resolve(true);
200
+ }, this.sampleWaitTimeoutMs);
201
+ });
202
+ }
203
+ processSamples(last) {
204
+ this._hasSeenLastSamples = last;
205
+ for (const observer of this.waitingForSamples) observer(last);
206
+ this.waitingForSamples = [];
207
+ }
223
208
  };
209
+ export { MP4File };