@remotion/webcodecs 4.0.330 → 4.0.331
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/esm/index.mjs +23 -11
- package/dist/esm/worker.mjs +506 -0
- package/dist/extract-frames-on-web-worker.d.ts +12 -0
- package/dist/extract-frames-on-web-worker.js +15 -0
- package/dist/extract-frames-on-worker.d.ts +0 -0
- package/dist/extract-frames-on-worker.js +1 -0
- package/dist/extract-frames.d.ts +6 -8
- package/dist/extract-frames.js +3 -145
- package/dist/index.d.ts +2 -2
- package/dist/internal-extract-frames.d.ts +16 -0
- package/dist/internal-extract-frames.js +156 -0
- package/dist/worker.d.ts +1 -0
- package/dist/worker.js +5 -0
- package/package.json +14 -5
package/dist/esm/index.mjs
CHANGED
|
@@ -5593,20 +5593,23 @@ var convertMedia = async function({
|
|
|
5593
5593
|
});
|
|
5594
5594
|
};
|
|
5595
5595
|
// src/extract-frames.ts
|
|
5596
|
+
import { parseMedia } from "@remotion/media-parser";
|
|
5597
|
+
|
|
5598
|
+
// src/internal-extract-frames.ts
|
|
5596
5599
|
import {
|
|
5597
|
-
hasBeenAborted,
|
|
5598
5600
|
MediaParserAbortError as MediaParserAbortError4,
|
|
5599
|
-
|
|
5600
|
-
|
|
5601
|
+
WEBCODECS_TIMESCALE,
|
|
5602
|
+
hasBeenAborted,
|
|
5603
|
+
mediaParserController as mediaParserController2
|
|
5601
5604
|
} from "@remotion/media-parser";
|
|
5602
|
-
import { parseMediaOnWebWorker } from "@remotion/media-parser/worker";
|
|
5603
5605
|
var internalExtractFrames = ({
|
|
5604
5606
|
src,
|
|
5605
5607
|
onFrame,
|
|
5606
5608
|
signal,
|
|
5607
5609
|
timestampsInSeconds,
|
|
5608
5610
|
acknowledgeRemotionLicense,
|
|
5609
|
-
logLevel
|
|
5611
|
+
logLevel,
|
|
5612
|
+
parseMediaImplementation
|
|
5610
5613
|
}) => {
|
|
5611
5614
|
const controller = mediaParserController2();
|
|
5612
5615
|
const expectedFrames = [];
|
|
@@ -5619,7 +5622,7 @@ var internalExtractFrames = ({
|
|
|
5619
5622
|
let dur = null;
|
|
5620
5623
|
let lastFrame;
|
|
5621
5624
|
let lastFrameEmitted;
|
|
5622
|
-
|
|
5625
|
+
parseMediaImplementation({
|
|
5623
5626
|
src: new URL(src, window.location.href),
|
|
5624
5627
|
acknowledgeRemotionLicense,
|
|
5625
5628
|
controller,
|
|
@@ -5656,11 +5659,17 @@ var internalExtractFrames = ({
|
|
|
5656
5659
|
onFrame(lastFrame);
|
|
5657
5660
|
lastFrameEmitted = lastFrame;
|
|
5658
5661
|
expectedFrames.shift();
|
|
5662
|
+
if (lastFrame) {
|
|
5663
|
+
lastFrame.close();
|
|
5664
|
+
}
|
|
5659
5665
|
lastFrame = frame;
|
|
5660
5666
|
return;
|
|
5661
5667
|
}
|
|
5662
5668
|
expectedFrames.shift();
|
|
5663
5669
|
onFrame(frame);
|
|
5670
|
+
if (lastFrame && lastFrame !== lastFrameEmitted) {
|
|
5671
|
+
lastFrame.close();
|
|
5672
|
+
}
|
|
5664
5673
|
lastFrameEmitted = frame;
|
|
5665
5674
|
lastFrame = frame;
|
|
5666
5675
|
},
|
|
@@ -5681,14 +5690,14 @@ var internalExtractFrames = ({
|
|
|
5681
5690
|
if (!sam) {
|
|
5682
5691
|
throw new Error("Sample is undefined");
|
|
5683
5692
|
}
|
|
5684
|
-
await decoder.waitForQueueToBeLessThan(
|
|
5693
|
+
await decoder.waitForQueueToBeLessThan(20);
|
|
5685
5694
|
Log.trace(logLevel, "Decoding sample", sam.timestamp);
|
|
5686
5695
|
await decoder.decode(sam);
|
|
5687
5696
|
}
|
|
5688
5697
|
};
|
|
5689
5698
|
return async (sample) => {
|
|
5690
5699
|
const nextTimestampWeWant = timestampTargets[0];
|
|
5691
|
-
Log.trace(logLevel,
|
|
5700
|
+
Log.trace(logLevel, `Received ${sample.type} sample with dts`, sample.decodingTimestamp, "and cts", sample.timestamp);
|
|
5692
5701
|
if (sample.type === "key") {
|
|
5693
5702
|
await decoder.flush();
|
|
5694
5703
|
queued.length = 0;
|
|
@@ -5735,12 +5744,15 @@ var internalExtractFrames = ({
|
|
|
5735
5744
|
});
|
|
5736
5745
|
return resolvers.promise;
|
|
5737
5746
|
};
|
|
5747
|
+
|
|
5748
|
+
// src/extract-frames.ts
|
|
5738
5749
|
var extractFrames = (options) => {
|
|
5739
5750
|
return internalExtractFrames({
|
|
5740
5751
|
...options,
|
|
5741
5752
|
signal: options.signal ?? null,
|
|
5742
5753
|
acknowledgeRemotionLicense: options.acknowledgeRemotionLicense ?? false,
|
|
5743
|
-
logLevel: options.logLevel ?? "info"
|
|
5754
|
+
logLevel: options.logLevel ?? "info",
|
|
5755
|
+
parseMediaImplementation: parseMedia
|
|
5744
5756
|
});
|
|
5745
5757
|
};
|
|
5746
5758
|
// src/get-available-audio-codecs.ts
|
|
@@ -5762,7 +5774,7 @@ var getAvailableAudioCodecs = ({
|
|
|
5762
5774
|
import {
|
|
5763
5775
|
hasBeenAborted as hasBeenAborted2,
|
|
5764
5776
|
mediaParserController as mediaParserController3,
|
|
5765
|
-
parseMedia
|
|
5777
|
+
parseMedia as parseMedia2
|
|
5766
5778
|
} from "@remotion/media-parser";
|
|
5767
5779
|
var extractOverlappingAudioSamples = ({
|
|
5768
5780
|
sample,
|
|
@@ -5820,7 +5832,7 @@ var getPartialAudioData = async ({
|
|
|
5820
5832
|
if (fromSeconds > 0) {
|
|
5821
5833
|
controller.seek(fromSeconds);
|
|
5822
5834
|
}
|
|
5823
|
-
await
|
|
5835
|
+
await parseMedia2({
|
|
5824
5836
|
acknowledgeRemotionLicense: true,
|
|
5825
5837
|
src,
|
|
5826
5838
|
controller,
|
|
@@ -0,0 +1,506 @@
|
|
|
1
|
+
var __create = Object.create;
|
|
2
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __toESM = (mod, isNodeMode, target) => {
|
|
7
|
+
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
8
|
+
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
9
|
+
for (let key of __getOwnPropNames(mod))
|
|
10
|
+
if (!__hasOwnProp.call(to, key))
|
|
11
|
+
__defProp(to, key, {
|
|
12
|
+
get: () => mod[key],
|
|
13
|
+
enumerable: true
|
|
14
|
+
});
|
|
15
|
+
return to;
|
|
16
|
+
};
|
|
17
|
+
var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
|
|
18
|
+
|
|
19
|
+
// src/extract-frames-on-web-worker.ts
|
|
20
|
+
import { parseMediaOnWebWorker } from "@remotion/media-parser/worker";
|
|
21
|
+
|
|
22
|
+
// src/internal-extract-frames.ts
|
|
23
|
+
import {
|
|
24
|
+
MediaParserAbortError,
|
|
25
|
+
WEBCODECS_TIMESCALE,
|
|
26
|
+
hasBeenAborted,
|
|
27
|
+
mediaParserController
|
|
28
|
+
} from "@remotion/media-parser";
|
|
29
|
+
|
|
30
|
+
// src/create/with-resolvers.ts
|
|
31
|
+
var withResolvers = function() {
|
|
32
|
+
let resolve;
|
|
33
|
+
let reject;
|
|
34
|
+
const promise = new Promise((res, rej) => {
|
|
35
|
+
resolve = res;
|
|
36
|
+
reject = rej;
|
|
37
|
+
});
|
|
38
|
+
return { promise, resolve, reject };
|
|
39
|
+
};
|
|
40
|
+
var withResolversAndWaitForReturn = () => {
|
|
41
|
+
const { promise, reject, resolve } = withResolvers();
|
|
42
|
+
const { promise: returnPromise, resolve: resolveReturn } = withResolvers();
|
|
43
|
+
return {
|
|
44
|
+
getPromiseToImmediatelyReturn: () => {
|
|
45
|
+
resolveReturn(undefined);
|
|
46
|
+
return promise;
|
|
47
|
+
},
|
|
48
|
+
reject: (reason) => {
|
|
49
|
+
returnPromise.then(() => reject(reason));
|
|
50
|
+
},
|
|
51
|
+
resolve
|
|
52
|
+
};
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
// src/flush-pending.ts
|
|
56
|
+
var makeFlushPending = () => {
|
|
57
|
+
const { promise, resolve, reject } = withResolvers();
|
|
58
|
+
return {
|
|
59
|
+
promise,
|
|
60
|
+
resolve,
|
|
61
|
+
reject
|
|
62
|
+
};
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
// src/create/event-emitter.ts
|
|
66
|
+
class IoEventEmitter {
|
|
67
|
+
listeners = {
|
|
68
|
+
input: [],
|
|
69
|
+
output: [],
|
|
70
|
+
processed: [],
|
|
71
|
+
progress: []
|
|
72
|
+
};
|
|
73
|
+
addEventListener(name, callback) {
|
|
74
|
+
this.listeners[name].push(callback);
|
|
75
|
+
}
|
|
76
|
+
removeEventListener(name, callback) {
|
|
77
|
+
this.listeners[name] = this.listeners[name].filter((l) => l !== callback);
|
|
78
|
+
}
|
|
79
|
+
dispatchEvent(dispatchName, context) {
|
|
80
|
+
this.listeners[dispatchName].forEach((callback) => {
|
|
81
|
+
callback({ detail: context });
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// src/log.ts
|
|
87
|
+
import { MediaParserInternals } from "@remotion/media-parser";
|
|
88
|
+
var { Log } = MediaParserInternals;
|
|
89
|
+
|
|
90
|
+
// src/io-manager/make-timeout-promise.ts
|
|
91
|
+
var makeTimeoutPromise = ({
|
|
92
|
+
label,
|
|
93
|
+
ms,
|
|
94
|
+
controller
|
|
95
|
+
}) => {
|
|
96
|
+
const { promise, reject, resolve } = withResolvers();
|
|
97
|
+
let timeout = null;
|
|
98
|
+
const set = () => {
|
|
99
|
+
timeout = setTimeout(() => {
|
|
100
|
+
reject(new Error(`${label()} (timed out after ${ms}ms)`));
|
|
101
|
+
}, ms);
|
|
102
|
+
};
|
|
103
|
+
set();
|
|
104
|
+
const onPause = () => {
|
|
105
|
+
if (timeout) {
|
|
106
|
+
clearTimeout(timeout);
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
const onResume = () => {
|
|
110
|
+
set();
|
|
111
|
+
};
|
|
112
|
+
if (controller) {
|
|
113
|
+
controller.addEventListener("pause", onPause);
|
|
114
|
+
controller.addEventListener("resume", onResume);
|
|
115
|
+
}
|
|
116
|
+
return {
|
|
117
|
+
timeoutPromise: promise,
|
|
118
|
+
clear: () => {
|
|
119
|
+
if (timeout) {
|
|
120
|
+
clearTimeout(timeout);
|
|
121
|
+
}
|
|
122
|
+
resolve();
|
|
123
|
+
if (controller) {
|
|
124
|
+
controller.removeEventListener("pause", onPause);
|
|
125
|
+
controller.removeEventListener("resume", onResume);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
// src/io-manager/io-synchronizer.ts
|
|
132
|
+
var makeIoSynchronizer = ({
|
|
133
|
+
logLevel,
|
|
134
|
+
label,
|
|
135
|
+
controller
|
|
136
|
+
}) => {
|
|
137
|
+
const eventEmitter = new IoEventEmitter;
|
|
138
|
+
let lastInput = 0;
|
|
139
|
+
let lastOutput = 0;
|
|
140
|
+
let inputsSinceLastOutput = 0;
|
|
141
|
+
let inputs = [];
|
|
142
|
+
let resolvers = [];
|
|
143
|
+
const getQueuedItems = () => {
|
|
144
|
+
inputs = inputs.filter((input) => Math.floor(input) > Math.floor(lastOutput) + 1);
|
|
145
|
+
return inputs.length;
|
|
146
|
+
};
|
|
147
|
+
const printState = (prefix) => {
|
|
148
|
+
Log.trace(logLevel, `[${label}] ${prefix}, state: Last input = ${lastInput} Last output = ${lastOutput} Inputs since last output = ${inputsSinceLastOutput}, Queue = ${getQueuedItems()}`);
|
|
149
|
+
};
|
|
150
|
+
const inputItem = (timestamp) => {
|
|
151
|
+
lastInput = timestamp;
|
|
152
|
+
inputsSinceLastOutput++;
|
|
153
|
+
inputs.push(timestamp);
|
|
154
|
+
eventEmitter.dispatchEvent("input", {
|
|
155
|
+
timestamp
|
|
156
|
+
});
|
|
157
|
+
printState("Input item");
|
|
158
|
+
};
|
|
159
|
+
const onOutput = (timestamp) => {
|
|
160
|
+
lastOutput = timestamp;
|
|
161
|
+
inputsSinceLastOutput = 0;
|
|
162
|
+
eventEmitter.dispatchEvent("output", {
|
|
163
|
+
timestamp
|
|
164
|
+
});
|
|
165
|
+
printState("Got output");
|
|
166
|
+
};
|
|
167
|
+
const waitForOutput = () => {
|
|
168
|
+
const { promise, resolve } = withResolvers();
|
|
169
|
+
const on = () => {
|
|
170
|
+
eventEmitter.removeEventListener("output", on);
|
|
171
|
+
resolve();
|
|
172
|
+
resolvers = resolvers.filter((resolver) => resolver !== resolve);
|
|
173
|
+
};
|
|
174
|
+
eventEmitter.addEventListener("output", on);
|
|
175
|
+
resolvers.push(resolve);
|
|
176
|
+
return promise;
|
|
177
|
+
};
|
|
178
|
+
const makeErrorBanner = () => {
|
|
179
|
+
return [
|
|
180
|
+
`Waited too long for ${label} to finish:`,
|
|
181
|
+
`${getQueuedItems()} queued items`,
|
|
182
|
+
`inputs: ${JSON.stringify(inputs)}`,
|
|
183
|
+
`last output: ${lastOutput}`
|
|
184
|
+
];
|
|
185
|
+
};
|
|
186
|
+
const waitForQueueSize = async (queueSize) => {
|
|
187
|
+
if (getQueuedItems() <= queueSize) {
|
|
188
|
+
return Promise.resolve();
|
|
189
|
+
}
|
|
190
|
+
const { timeoutPromise, clear } = makeTimeoutPromise({
|
|
191
|
+
label: () => [
|
|
192
|
+
...makeErrorBanner(),
|
|
193
|
+
`wanted: <${queueSize} queued items`,
|
|
194
|
+
`Report this at https://remotion.dev/report`
|
|
195
|
+
].join(`
|
|
196
|
+
`),
|
|
197
|
+
ms: 1e4,
|
|
198
|
+
controller
|
|
199
|
+
});
|
|
200
|
+
if (controller) {
|
|
201
|
+
controller._internals._mediaParserController._internals.signal.addEventListener("abort", clear);
|
|
202
|
+
}
|
|
203
|
+
await Promise.race([
|
|
204
|
+
timeoutPromise,
|
|
205
|
+
(async () => {
|
|
206
|
+
while (getQueuedItems() > queueSize) {
|
|
207
|
+
await waitForOutput();
|
|
208
|
+
}
|
|
209
|
+
})()
|
|
210
|
+
]).finally(() => clear());
|
|
211
|
+
if (controller) {
|
|
212
|
+
controller._internals._mediaParserController._internals.signal.removeEventListener("abort", clear);
|
|
213
|
+
}
|
|
214
|
+
};
|
|
215
|
+
const clearQueue = () => {
|
|
216
|
+
inputs.length = 0;
|
|
217
|
+
lastInput = 0;
|
|
218
|
+
lastOutput = 0;
|
|
219
|
+
inputsSinceLastOutput = 0;
|
|
220
|
+
resolvers.forEach((resolver) => {
|
|
221
|
+
return resolver();
|
|
222
|
+
});
|
|
223
|
+
resolvers.length = 0;
|
|
224
|
+
inputs.length = 0;
|
|
225
|
+
};
|
|
226
|
+
return {
|
|
227
|
+
inputItem,
|
|
228
|
+
onOutput,
|
|
229
|
+
waitForQueueSize,
|
|
230
|
+
clearQueue
|
|
231
|
+
};
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
// src/create-video-decoder.ts
|
|
235
|
+
var internalCreateVideoDecoder = ({
|
|
236
|
+
onFrame,
|
|
237
|
+
onError,
|
|
238
|
+
controller,
|
|
239
|
+
config,
|
|
240
|
+
logLevel
|
|
241
|
+
}) => {
|
|
242
|
+
if (controller && controller._internals._mediaParserController._internals.signal.aborted) {
|
|
243
|
+
throw new Error("Not creating audio decoder, already aborted");
|
|
244
|
+
}
|
|
245
|
+
const ioSynchronizer = makeIoSynchronizer({
|
|
246
|
+
logLevel,
|
|
247
|
+
label: "Video decoder",
|
|
248
|
+
controller
|
|
249
|
+
});
|
|
250
|
+
let mostRecentSampleReceived = null;
|
|
251
|
+
const videoDecoder = new VideoDecoder({
|
|
252
|
+
async output(frame) {
|
|
253
|
+
try {
|
|
254
|
+
await onFrame(frame);
|
|
255
|
+
} catch (err) {
|
|
256
|
+
onError(err);
|
|
257
|
+
frame.close();
|
|
258
|
+
}
|
|
259
|
+
ioSynchronizer.onOutput(frame.timestamp);
|
|
260
|
+
},
|
|
261
|
+
error(error) {
|
|
262
|
+
onError(error);
|
|
263
|
+
}
|
|
264
|
+
});
|
|
265
|
+
const close = () => {
|
|
266
|
+
if (controller) {
|
|
267
|
+
controller._internals._mediaParserController._internals.signal.removeEventListener("abort", onAbort);
|
|
268
|
+
}
|
|
269
|
+
if (videoDecoder.state === "closed") {
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
videoDecoder.close();
|
|
273
|
+
};
|
|
274
|
+
const onAbort = () => {
|
|
275
|
+
close();
|
|
276
|
+
};
|
|
277
|
+
if (controller) {
|
|
278
|
+
controller._internals._mediaParserController._internals.signal.addEventListener("abort", onAbort);
|
|
279
|
+
}
|
|
280
|
+
videoDecoder.configure(config);
|
|
281
|
+
const decode = async (sample) => {
|
|
282
|
+
if (videoDecoder.state === "closed") {
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
try {
|
|
286
|
+
await controller?._internals._mediaParserController._internals.checkForAbortAndPause();
|
|
287
|
+
} catch (err) {
|
|
288
|
+
onError(err);
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
mostRecentSampleReceived = sample.timestamp;
|
|
292
|
+
const encodedChunk = sample instanceof EncodedVideoChunk ? sample : new EncodedVideoChunk(sample);
|
|
293
|
+
videoDecoder.decode(encodedChunk);
|
|
294
|
+
ioSynchronizer.inputItem(sample.timestamp);
|
|
295
|
+
};
|
|
296
|
+
let flushPending = null;
|
|
297
|
+
let lastReset = null;
|
|
298
|
+
return {
|
|
299
|
+
decode,
|
|
300
|
+
close,
|
|
301
|
+
flush: () => {
|
|
302
|
+
if (flushPending) {
|
|
303
|
+
throw new Error("Flush already pending");
|
|
304
|
+
}
|
|
305
|
+
const pendingFlush = makeFlushPending();
|
|
306
|
+
flushPending = pendingFlush;
|
|
307
|
+
Promise.resolve().then(() => {
|
|
308
|
+
return videoDecoder.flush();
|
|
309
|
+
}).catch(() => {}).finally(() => {
|
|
310
|
+
pendingFlush.resolve();
|
|
311
|
+
flushPending = null;
|
|
312
|
+
});
|
|
313
|
+
return pendingFlush.promise;
|
|
314
|
+
},
|
|
315
|
+
waitForQueueToBeLessThan: ioSynchronizer.waitForQueueSize,
|
|
316
|
+
reset: () => {
|
|
317
|
+
lastReset = Date.now();
|
|
318
|
+
flushPending?.resolve();
|
|
319
|
+
ioSynchronizer.clearQueue();
|
|
320
|
+
videoDecoder.reset();
|
|
321
|
+
videoDecoder.configure(config);
|
|
322
|
+
},
|
|
323
|
+
checkReset: () => {
|
|
324
|
+
const initTime = Date.now();
|
|
325
|
+
return {
|
|
326
|
+
wasReset: () => lastReset !== null && lastReset > initTime
|
|
327
|
+
};
|
|
328
|
+
},
|
|
329
|
+
getMostRecentSampleInput() {
|
|
330
|
+
return mostRecentSampleReceived;
|
|
331
|
+
}
|
|
332
|
+
};
|
|
333
|
+
};
|
|
334
|
+
var createVideoDecoder = ({
|
|
335
|
+
onFrame,
|
|
336
|
+
onError,
|
|
337
|
+
controller,
|
|
338
|
+
track,
|
|
339
|
+
logLevel
|
|
340
|
+
}) => {
|
|
341
|
+
return internalCreateVideoDecoder({
|
|
342
|
+
onFrame,
|
|
343
|
+
onError,
|
|
344
|
+
controller: controller ?? null,
|
|
345
|
+
config: track,
|
|
346
|
+
logLevel: logLevel ?? "info"
|
|
347
|
+
});
|
|
348
|
+
};
|
|
349
|
+
|
|
350
|
+
// src/internal-extract-frames.ts
|
|
351
|
+
var internalExtractFrames = ({
|
|
352
|
+
src,
|
|
353
|
+
onFrame,
|
|
354
|
+
signal,
|
|
355
|
+
timestampsInSeconds,
|
|
356
|
+
acknowledgeRemotionLicense,
|
|
357
|
+
logLevel,
|
|
358
|
+
parseMediaImplementation
|
|
359
|
+
}) => {
|
|
360
|
+
const controller = mediaParserController();
|
|
361
|
+
const expectedFrames = [];
|
|
362
|
+
const resolvers = withResolvers();
|
|
363
|
+
const abortListener = () => {
|
|
364
|
+
controller.abort();
|
|
365
|
+
resolvers.reject(new MediaParserAbortError("Aborted by user"));
|
|
366
|
+
};
|
|
367
|
+
signal?.addEventListener("abort", abortListener, { once: true });
|
|
368
|
+
let dur = null;
|
|
369
|
+
let lastFrame;
|
|
370
|
+
let lastFrameEmitted;
|
|
371
|
+
parseMediaImplementation({
|
|
372
|
+
src: new URL(src, window.location.href),
|
|
373
|
+
acknowledgeRemotionLicense,
|
|
374
|
+
controller,
|
|
375
|
+
logLevel,
|
|
376
|
+
onDurationInSeconds(durationInSeconds) {
|
|
377
|
+
dur = durationInSeconds;
|
|
378
|
+
},
|
|
379
|
+
onVideoTrack: async ({ track, container }) => {
|
|
380
|
+
const timestampTargetsUnsorted = typeof timestampsInSeconds === "function" ? await timestampsInSeconds({
|
|
381
|
+
track,
|
|
382
|
+
container,
|
|
383
|
+
durationInSeconds: dur
|
|
384
|
+
}) : timestampsInSeconds;
|
|
385
|
+
const timestampTargets = timestampTargetsUnsorted.sort((a, b) => a - b);
|
|
386
|
+
if (timestampTargets.length === 0) {
|
|
387
|
+
throw new Error("expected at least one timestamp to extract but found zero");
|
|
388
|
+
}
|
|
389
|
+
controller.seek(timestampTargets[0]);
|
|
390
|
+
const decoder = createVideoDecoder({
|
|
391
|
+
onFrame: (frame) => {
|
|
392
|
+
Log.trace(logLevel, "Received frame with timestamp", frame.timestamp);
|
|
393
|
+
if (expectedFrames.length === 0) {
|
|
394
|
+
frame.close();
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
if (frame.timestamp < expectedFrames[0] - 1) {
|
|
398
|
+
if (lastFrame) {
|
|
399
|
+
lastFrame.close();
|
|
400
|
+
}
|
|
401
|
+
lastFrame = frame;
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
if (expectedFrames[0] + 6667 < frame.timestamp && lastFrame) {
|
|
405
|
+
onFrame(lastFrame);
|
|
406
|
+
lastFrameEmitted = lastFrame;
|
|
407
|
+
expectedFrames.shift();
|
|
408
|
+
if (lastFrame) {
|
|
409
|
+
lastFrame.close();
|
|
410
|
+
}
|
|
411
|
+
lastFrame = frame;
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
414
|
+
expectedFrames.shift();
|
|
415
|
+
onFrame(frame);
|
|
416
|
+
if (lastFrame && lastFrame !== lastFrameEmitted) {
|
|
417
|
+
lastFrame.close();
|
|
418
|
+
}
|
|
419
|
+
lastFrameEmitted = frame;
|
|
420
|
+
lastFrame = frame;
|
|
421
|
+
},
|
|
422
|
+
onError: (e) => {
|
|
423
|
+
controller.abort();
|
|
424
|
+
try {
|
|
425
|
+
decoder.close();
|
|
426
|
+
} catch {}
|
|
427
|
+
resolvers.reject(e);
|
|
428
|
+
},
|
|
429
|
+
track
|
|
430
|
+
});
|
|
431
|
+
const queued = [];
|
|
432
|
+
const doProcess = async () => {
|
|
433
|
+
expectedFrames.push(timestampTargets.shift() * WEBCODECS_TIMESCALE);
|
|
434
|
+
while (queued.length > 0) {
|
|
435
|
+
const sam = queued.shift();
|
|
436
|
+
if (!sam) {
|
|
437
|
+
throw new Error("Sample is undefined");
|
|
438
|
+
}
|
|
439
|
+
await decoder.waitForQueueToBeLessThan(20);
|
|
440
|
+
Log.trace(logLevel, "Decoding sample", sam.timestamp);
|
|
441
|
+
await decoder.decode(sam);
|
|
442
|
+
}
|
|
443
|
+
};
|
|
444
|
+
return async (sample) => {
|
|
445
|
+
const nextTimestampWeWant = timestampTargets[0];
|
|
446
|
+
Log.trace(logLevel, `Received ${sample.type} sample with dts`, sample.decodingTimestamp, "and cts", sample.timestamp);
|
|
447
|
+
if (sample.type === "key") {
|
|
448
|
+
await decoder.flush();
|
|
449
|
+
queued.length = 0;
|
|
450
|
+
}
|
|
451
|
+
queued.push(sample);
|
|
452
|
+
if (sample.decodingTimestamp >= timestampTargets[timestampTargets.length - 1] * WEBCODECS_TIMESCALE) {
|
|
453
|
+
await doProcess();
|
|
454
|
+
await decoder.flush();
|
|
455
|
+
controller.abort();
|
|
456
|
+
return;
|
|
457
|
+
}
|
|
458
|
+
if (nextTimestampWeWant === undefined) {
|
|
459
|
+
throw new Error("this should not happen");
|
|
460
|
+
}
|
|
461
|
+
if (sample.decodingTimestamp >= nextTimestampWeWant * WEBCODECS_TIMESCALE) {
|
|
462
|
+
await doProcess();
|
|
463
|
+
if (timestampTargets.length === 0) {
|
|
464
|
+
await decoder.flush();
|
|
465
|
+
controller.abort();
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
return async () => {
|
|
469
|
+
await doProcess();
|
|
470
|
+
await decoder.flush();
|
|
471
|
+
if (lastFrame && lastFrameEmitted !== lastFrame) {
|
|
472
|
+
lastFrame.close();
|
|
473
|
+
}
|
|
474
|
+
};
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
}).then(() => {
|
|
478
|
+
resolvers.resolve();
|
|
479
|
+
}).catch((e) => {
|
|
480
|
+
if (!hasBeenAborted(e)) {
|
|
481
|
+
resolvers.reject(e);
|
|
482
|
+
} else {
|
|
483
|
+
resolvers.resolve();
|
|
484
|
+
}
|
|
485
|
+
}).finally(() => {
|
|
486
|
+
if (lastFrame && lastFrameEmitted !== lastFrame) {
|
|
487
|
+
lastFrame.close();
|
|
488
|
+
}
|
|
489
|
+
signal?.removeEventListener("abort", abortListener);
|
|
490
|
+
});
|
|
491
|
+
return resolvers.promise;
|
|
492
|
+
};
|
|
493
|
+
|
|
494
|
+
// src/extract-frames-on-web-worker.ts
|
|
495
|
+
var extractFramesOnWebWorker = (options) => {
|
|
496
|
+
return internalExtractFrames({
|
|
497
|
+
...options,
|
|
498
|
+
signal: options.signal ?? null,
|
|
499
|
+
acknowledgeRemotionLicense: options.acknowledgeRemotionLicense ?? false,
|
|
500
|
+
logLevel: options.logLevel ?? "info",
|
|
501
|
+
parseMediaImplementation: parseMediaOnWebWorker
|
|
502
|
+
});
|
|
503
|
+
};
|
|
504
|
+
export {
|
|
505
|
+
extractFramesOnWebWorker
|
|
506
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type MediaParserLogLevel } from '@remotion/media-parser';
|
|
2
|
+
import { type ExtractFramesTimestampsInSecondsFn } from './internal-extract-frames';
|
|
3
|
+
export type ExtractFramesOnWebWorkerProps = {
|
|
4
|
+
src: string;
|
|
5
|
+
timestampsInSeconds: number[] | ExtractFramesTimestampsInSecondsFn;
|
|
6
|
+
onFrame: (frame: VideoFrame) => void;
|
|
7
|
+
signal?: AbortSignal;
|
|
8
|
+
acknowledgeRemotionLicense?: boolean;
|
|
9
|
+
logLevel?: MediaParserLogLevel;
|
|
10
|
+
};
|
|
11
|
+
export type ExtractFramesOnWebWorker = (options: ExtractFramesOnWebWorkerProps) => Promise<void>;
|
|
12
|
+
export declare const extractFramesOnWebWorker: ExtractFramesOnWebWorker;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.extractFramesOnWebWorker = void 0;
|
|
4
|
+
const worker_1 = require("@remotion/media-parser/worker");
|
|
5
|
+
const internal_extract_frames_1 = require("./internal-extract-frames");
|
|
6
|
+
const extractFramesOnWebWorker = (options) => {
|
|
7
|
+
return (0, internal_extract_frames_1.internalExtractFrames)({
|
|
8
|
+
...options,
|
|
9
|
+
signal: options.signal ?? null,
|
|
10
|
+
acknowledgeRemotionLicense: options.acknowledgeRemotionLicense ?? false,
|
|
11
|
+
logLevel: options.logLevel ?? 'info',
|
|
12
|
+
parseMediaImplementation: worker_1.parseMediaOnWebWorker,
|
|
13
|
+
});
|
|
14
|
+
};
|
|
15
|
+
exports.extractFramesOnWebWorker = extractFramesOnWebWorker;
|
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";
|
package/dist/extract-frames.d.ts
CHANGED
|
@@ -1,14 +1,12 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
container: MediaParserContainer;
|
|
5
|
-
durationInSeconds: number | null;
|
|
6
|
-
}) => Promise<number[]> | number[];
|
|
7
|
-
export declare const extractFrames: (options: {
|
|
1
|
+
import { type MediaParserLogLevel } from '@remotion/media-parser';
|
|
2
|
+
import { type ExtractFramesTimestampsInSecondsFn } from './internal-extract-frames';
|
|
3
|
+
export type ExtractFramesProps = {
|
|
8
4
|
src: string;
|
|
9
5
|
timestampsInSeconds: number[] | ExtractFramesTimestampsInSecondsFn;
|
|
10
6
|
onFrame: (frame: VideoFrame) => void;
|
|
11
7
|
signal?: AbortSignal;
|
|
12
8
|
acknowledgeRemotionLicense?: boolean;
|
|
13
9
|
logLevel?: MediaParserLogLevel;
|
|
14
|
-
}
|
|
10
|
+
};
|
|
11
|
+
export type ExtractFrames = (options: ExtractFramesProps) => Promise<void>;
|
|
12
|
+
export declare const extractFrames: ExtractFrames;
|
package/dist/extract-frames.js
CHANGED
|
@@ -2,156 +2,14 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.extractFrames = void 0;
|
|
4
4
|
const media_parser_1 = require("@remotion/media-parser");
|
|
5
|
-
const
|
|
6
|
-
const create_video_decoder_1 = require("./create-video-decoder");
|
|
7
|
-
const with_resolvers_1 = require("./create/with-resolvers");
|
|
8
|
-
const log_1 = require("./log");
|
|
9
|
-
const internalExtractFrames = ({ src, onFrame, signal, timestampsInSeconds, acknowledgeRemotionLicense, logLevel, }) => {
|
|
10
|
-
const controller = (0, media_parser_1.mediaParserController)();
|
|
11
|
-
const expectedFrames = [];
|
|
12
|
-
const resolvers = (0, with_resolvers_1.withResolvers)();
|
|
13
|
-
const abortListener = () => {
|
|
14
|
-
controller.abort();
|
|
15
|
-
resolvers.reject(new media_parser_1.MediaParserAbortError('Aborted by user'));
|
|
16
|
-
};
|
|
17
|
-
signal?.addEventListener('abort', abortListener, { once: true });
|
|
18
|
-
let dur = null;
|
|
19
|
-
let lastFrame;
|
|
20
|
-
let lastFrameEmitted;
|
|
21
|
-
(0, worker_1.parseMediaOnWebWorker)({
|
|
22
|
-
src: new URL(src, window.location.href),
|
|
23
|
-
acknowledgeRemotionLicense,
|
|
24
|
-
controller,
|
|
25
|
-
logLevel,
|
|
26
|
-
onDurationInSeconds(durationInSeconds) {
|
|
27
|
-
dur = durationInSeconds;
|
|
28
|
-
},
|
|
29
|
-
onVideoTrack: async ({ track, container }) => {
|
|
30
|
-
const timestampTargetsUnsorted = typeof timestampsInSeconds === 'function'
|
|
31
|
-
? await timestampsInSeconds({
|
|
32
|
-
track,
|
|
33
|
-
container,
|
|
34
|
-
durationInSeconds: dur,
|
|
35
|
-
})
|
|
36
|
-
: timestampsInSeconds;
|
|
37
|
-
const timestampTargets = timestampTargetsUnsorted.sort((a, b) => a - b);
|
|
38
|
-
if (timestampTargets.length === 0) {
|
|
39
|
-
throw new Error('expected at least one timestamp to extract but found zero');
|
|
40
|
-
}
|
|
41
|
-
controller.seek(timestampTargets[0]);
|
|
42
|
-
const decoder = (0, create_video_decoder_1.createVideoDecoder)({
|
|
43
|
-
onFrame: (frame) => {
|
|
44
|
-
log_1.Log.trace(logLevel, 'Received frame with timestamp', frame.timestamp);
|
|
45
|
-
if (expectedFrames.length === 0) {
|
|
46
|
-
frame.close();
|
|
47
|
-
return;
|
|
48
|
-
}
|
|
49
|
-
if (frame.timestamp < expectedFrames[0] - 1) {
|
|
50
|
-
if (lastFrame) {
|
|
51
|
-
lastFrame.close();
|
|
52
|
-
}
|
|
53
|
-
lastFrame = frame;
|
|
54
|
-
return;
|
|
55
|
-
}
|
|
56
|
-
// A WebM might have a timestamp of 67000 but we request 66666
|
|
57
|
-
// See a test with this problem in it-tests/rendering/frame-accuracy.test.ts
|
|
58
|
-
// Solution: We allow a 10.000ms - 3.333ms = 6.667ms difference between the requested timestamp and the actual timestamp
|
|
59
|
-
if (expectedFrames[0] + 6667 < frame.timestamp && lastFrame) {
|
|
60
|
-
onFrame(lastFrame);
|
|
61
|
-
lastFrameEmitted = lastFrame;
|
|
62
|
-
expectedFrames.shift();
|
|
63
|
-
lastFrame = frame;
|
|
64
|
-
return;
|
|
65
|
-
}
|
|
66
|
-
expectedFrames.shift();
|
|
67
|
-
onFrame(frame);
|
|
68
|
-
lastFrameEmitted = frame;
|
|
69
|
-
lastFrame = frame;
|
|
70
|
-
},
|
|
71
|
-
onError: (e) => {
|
|
72
|
-
controller.abort();
|
|
73
|
-
try {
|
|
74
|
-
decoder.close();
|
|
75
|
-
}
|
|
76
|
-
catch { }
|
|
77
|
-
resolvers.reject(e);
|
|
78
|
-
},
|
|
79
|
-
track,
|
|
80
|
-
});
|
|
81
|
-
const queued = [];
|
|
82
|
-
const doProcess = async () => {
|
|
83
|
-
expectedFrames.push(timestampTargets.shift() * media_parser_1.WEBCODECS_TIMESCALE);
|
|
84
|
-
while (queued.length > 0) {
|
|
85
|
-
const sam = queued.shift();
|
|
86
|
-
if (!sam) {
|
|
87
|
-
throw new Error('Sample is undefined');
|
|
88
|
-
}
|
|
89
|
-
await decoder.waitForQueueToBeLessThan(10);
|
|
90
|
-
log_1.Log.trace(logLevel, 'Decoding sample', sam.timestamp);
|
|
91
|
-
await decoder.decode(sam);
|
|
92
|
-
}
|
|
93
|
-
};
|
|
94
|
-
return async (sample) => {
|
|
95
|
-
const nextTimestampWeWant = timestampTargets[0];
|
|
96
|
-
log_1.Log.trace(logLevel, 'Received sample with dts', sample.decodingTimestamp, 'and cts', sample.timestamp);
|
|
97
|
-
if (sample.type === 'key') {
|
|
98
|
-
await decoder.flush();
|
|
99
|
-
queued.length = 0;
|
|
100
|
-
}
|
|
101
|
-
queued.push(sample);
|
|
102
|
-
if (sample.decodingTimestamp >=
|
|
103
|
-
timestampTargets[timestampTargets.length - 1] * media_parser_1.WEBCODECS_TIMESCALE) {
|
|
104
|
-
await doProcess();
|
|
105
|
-
await decoder.flush();
|
|
106
|
-
controller.abort();
|
|
107
|
-
return;
|
|
108
|
-
}
|
|
109
|
-
if (nextTimestampWeWant === undefined) {
|
|
110
|
-
throw new Error('this should not happen');
|
|
111
|
-
}
|
|
112
|
-
if (sample.decodingTimestamp >=
|
|
113
|
-
nextTimestampWeWant * media_parser_1.WEBCODECS_TIMESCALE) {
|
|
114
|
-
await doProcess();
|
|
115
|
-
if (timestampTargets.length === 0) {
|
|
116
|
-
await decoder.flush();
|
|
117
|
-
controller.abort();
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
return async () => {
|
|
121
|
-
await doProcess();
|
|
122
|
-
await decoder.flush();
|
|
123
|
-
if (lastFrame && lastFrameEmitted !== lastFrame) {
|
|
124
|
-
lastFrame.close();
|
|
125
|
-
}
|
|
126
|
-
};
|
|
127
|
-
};
|
|
128
|
-
},
|
|
129
|
-
})
|
|
130
|
-
.then(() => {
|
|
131
|
-
resolvers.resolve();
|
|
132
|
-
})
|
|
133
|
-
.catch((e) => {
|
|
134
|
-
if (!(0, media_parser_1.hasBeenAborted)(e)) {
|
|
135
|
-
resolvers.reject(e);
|
|
136
|
-
}
|
|
137
|
-
else {
|
|
138
|
-
resolvers.resolve();
|
|
139
|
-
}
|
|
140
|
-
})
|
|
141
|
-
.finally(() => {
|
|
142
|
-
if (lastFrame && lastFrameEmitted !== lastFrame) {
|
|
143
|
-
lastFrame.close();
|
|
144
|
-
}
|
|
145
|
-
signal?.removeEventListener('abort', abortListener);
|
|
146
|
-
});
|
|
147
|
-
return resolvers.promise;
|
|
148
|
-
};
|
|
5
|
+
const internal_extract_frames_1 = require("./internal-extract-frames");
|
|
149
6
|
const extractFrames = (options) => {
|
|
150
|
-
return internalExtractFrames({
|
|
7
|
+
return (0, internal_extract_frames_1.internalExtractFrames)({
|
|
151
8
|
...options,
|
|
152
9
|
signal: options.signal ?? null,
|
|
153
10
|
acknowledgeRemotionLicense: options.acknowledgeRemotionLicense ?? false,
|
|
154
11
|
logLevel: options.logLevel ?? 'info',
|
|
12
|
+
parseMediaImplementation: media_parser_1.parseMedia,
|
|
155
13
|
});
|
|
156
14
|
};
|
|
157
15
|
exports.extractFrames = extractFrames;
|
package/dist/index.d.ts
CHANGED
|
@@ -13,8 +13,7 @@ export { createVideoDecoder } from './create-video-decoder';
|
|
|
13
13
|
export type { WebCodecsVideoDecoder } from './create-video-decoder';
|
|
14
14
|
export { defaultOnAudioTrackHandler } from './default-on-audio-track-handler';
|
|
15
15
|
export { defaultOnVideoTrackHandler } from './default-on-video-track-handler';
|
|
16
|
-
export { extractFrames } from './extract-frames';
|
|
17
|
-
export type { ExtractFramesTimestampsInSecondsFn } from './extract-frames';
|
|
16
|
+
export { extractFrames, ExtractFrames, ExtractFramesProps, } from './extract-frames';
|
|
18
17
|
export { getAvailableAudioCodecs } from './get-available-audio-codecs';
|
|
19
18
|
export type { ConvertMediaAudioCodec } from './get-available-audio-codecs';
|
|
20
19
|
export { getAvailableContainers } from './get-available-containers';
|
|
@@ -24,6 +23,7 @@ export type { ConvertMediaVideoCodec } from './get-available-video-codecs';
|
|
|
24
23
|
export { getDefaultAudioCodec } from './get-default-audio-codec';
|
|
25
24
|
export { getDefaultVideoCodec } from './get-default-video-codec';
|
|
26
25
|
export { getPartialAudioData, GetPartialAudioDataProps, } from './get-partial-audio-data';
|
|
26
|
+
export type { ExtractFramesTimestampsInSecondsFn } from './internal-extract-frames';
|
|
27
27
|
export type { AudioOperation, ConvertMediaOnAudioTrackHandler, } from './on-audio-track-handler';
|
|
28
28
|
export type { ConvertMediaOnVideoTrackHandler, VideoOperation, } from './on-video-track-handler';
|
|
29
29
|
export type { ResizeOperation } from './resizing/mode';
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { MediaParserContainer, MediaParserLogLevel, MediaParserVideoTrack, ParseMedia } from '@remotion/media-parser';
|
|
2
|
+
import type { ParseMediaOnWorker } from '@remotion/media-parser/worker';
|
|
3
|
+
export type ExtractFramesTimestampsInSecondsFn = (options: {
|
|
4
|
+
track: MediaParserVideoTrack;
|
|
5
|
+
container: MediaParserContainer;
|
|
6
|
+
durationInSeconds: number | null;
|
|
7
|
+
}) => Promise<number[]> | number[];
|
|
8
|
+
export declare const internalExtractFrames: ({ src, onFrame, signal, timestampsInSeconds, acknowledgeRemotionLicense, logLevel, parseMediaImplementation, }: {
|
|
9
|
+
timestampsInSeconds: number[] | ExtractFramesTimestampsInSecondsFn;
|
|
10
|
+
src: string;
|
|
11
|
+
onFrame: (frame: VideoFrame) => void;
|
|
12
|
+
signal: AbortSignal | null;
|
|
13
|
+
acknowledgeRemotionLicense: boolean;
|
|
14
|
+
logLevel: MediaParserLogLevel;
|
|
15
|
+
parseMediaImplementation: ParseMediaOnWorker | ParseMedia;
|
|
16
|
+
}) => Promise<void>;
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.internalExtractFrames = void 0;
|
|
4
|
+
const media_parser_1 = require("@remotion/media-parser");
|
|
5
|
+
const create_video_decoder_1 = require("./create-video-decoder");
|
|
6
|
+
const with_resolvers_1 = require("./create/with-resolvers");
|
|
7
|
+
const log_1 = require("./log");
|
|
8
|
+
const internalExtractFrames = ({ src, onFrame, signal, timestampsInSeconds, acknowledgeRemotionLicense, logLevel, parseMediaImplementation, }) => {
|
|
9
|
+
const controller = (0, media_parser_1.mediaParserController)();
|
|
10
|
+
const expectedFrames = [];
|
|
11
|
+
const resolvers = (0, with_resolvers_1.withResolvers)();
|
|
12
|
+
const abortListener = () => {
|
|
13
|
+
controller.abort();
|
|
14
|
+
resolvers.reject(new media_parser_1.MediaParserAbortError('Aborted by user'));
|
|
15
|
+
};
|
|
16
|
+
signal?.addEventListener('abort', abortListener, { once: true });
|
|
17
|
+
let dur = null;
|
|
18
|
+
let lastFrame;
|
|
19
|
+
let lastFrameEmitted;
|
|
20
|
+
parseMediaImplementation({
|
|
21
|
+
src: new URL(src, window.location.href),
|
|
22
|
+
acknowledgeRemotionLicense,
|
|
23
|
+
controller,
|
|
24
|
+
logLevel,
|
|
25
|
+
onDurationInSeconds(durationInSeconds) {
|
|
26
|
+
dur = durationInSeconds;
|
|
27
|
+
},
|
|
28
|
+
onVideoTrack: async ({ track, container }) => {
|
|
29
|
+
const timestampTargetsUnsorted = typeof timestampsInSeconds === 'function'
|
|
30
|
+
? await timestampsInSeconds({
|
|
31
|
+
track,
|
|
32
|
+
container,
|
|
33
|
+
durationInSeconds: dur,
|
|
34
|
+
})
|
|
35
|
+
: timestampsInSeconds;
|
|
36
|
+
const timestampTargets = timestampTargetsUnsorted.sort((a, b) => a - b);
|
|
37
|
+
if (timestampTargets.length === 0) {
|
|
38
|
+
throw new Error('expected at least one timestamp to extract but found zero');
|
|
39
|
+
}
|
|
40
|
+
controller.seek(timestampTargets[0]);
|
|
41
|
+
const decoder = (0, create_video_decoder_1.createVideoDecoder)({
|
|
42
|
+
onFrame: (frame) => {
|
|
43
|
+
log_1.Log.trace(logLevel, 'Received frame with timestamp', frame.timestamp);
|
|
44
|
+
if (expectedFrames.length === 0) {
|
|
45
|
+
frame.close();
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
if (frame.timestamp < expectedFrames[0] - 1) {
|
|
49
|
+
if (lastFrame) {
|
|
50
|
+
lastFrame.close();
|
|
51
|
+
}
|
|
52
|
+
lastFrame = frame;
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
// A WebM might have a timestamp of 67000 but we request 66666
|
|
56
|
+
// See a test with this problem in it-tests/rendering/frame-accuracy.test.ts
|
|
57
|
+
// Solution: We allow a 10.000ms - 3.333ms = 6.667ms difference between the requested timestamp and the actual timestamp
|
|
58
|
+
if (expectedFrames[0] + 6667 < frame.timestamp && lastFrame) {
|
|
59
|
+
onFrame(lastFrame);
|
|
60
|
+
lastFrameEmitted = lastFrame;
|
|
61
|
+
expectedFrames.shift();
|
|
62
|
+
if (lastFrame) {
|
|
63
|
+
lastFrame.close();
|
|
64
|
+
}
|
|
65
|
+
lastFrame = frame;
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
expectedFrames.shift();
|
|
69
|
+
onFrame(frame);
|
|
70
|
+
if (lastFrame && lastFrame !== lastFrameEmitted) {
|
|
71
|
+
lastFrame.close();
|
|
72
|
+
}
|
|
73
|
+
lastFrameEmitted = frame;
|
|
74
|
+
lastFrame = frame;
|
|
75
|
+
},
|
|
76
|
+
onError: (e) => {
|
|
77
|
+
controller.abort();
|
|
78
|
+
try {
|
|
79
|
+
decoder.close();
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
// Ignore
|
|
83
|
+
}
|
|
84
|
+
resolvers.reject(e);
|
|
85
|
+
},
|
|
86
|
+
track,
|
|
87
|
+
});
|
|
88
|
+
const queued = [];
|
|
89
|
+
const doProcess = async () => {
|
|
90
|
+
expectedFrames.push(timestampTargets.shift() * media_parser_1.WEBCODECS_TIMESCALE);
|
|
91
|
+
while (queued.length > 0) {
|
|
92
|
+
const sam = queued.shift();
|
|
93
|
+
if (!sam) {
|
|
94
|
+
throw new Error('Sample is undefined');
|
|
95
|
+
}
|
|
96
|
+
await decoder.waitForQueueToBeLessThan(20);
|
|
97
|
+
log_1.Log.trace(logLevel, 'Decoding sample', sam.timestamp);
|
|
98
|
+
await decoder.decode(sam);
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
return async (sample) => {
|
|
102
|
+
const nextTimestampWeWant = timestampTargets[0];
|
|
103
|
+
log_1.Log.trace(logLevel, `Received ${sample.type} sample with dts`, sample.decodingTimestamp, 'and cts', sample.timestamp);
|
|
104
|
+
if (sample.type === 'key') {
|
|
105
|
+
await decoder.flush();
|
|
106
|
+
queued.length = 0;
|
|
107
|
+
}
|
|
108
|
+
queued.push(sample);
|
|
109
|
+
if (sample.decodingTimestamp >=
|
|
110
|
+
timestampTargets[timestampTargets.length - 1] * media_parser_1.WEBCODECS_TIMESCALE) {
|
|
111
|
+
await doProcess();
|
|
112
|
+
await decoder.flush();
|
|
113
|
+
controller.abort();
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
if (nextTimestampWeWant === undefined) {
|
|
117
|
+
throw new Error('this should not happen');
|
|
118
|
+
}
|
|
119
|
+
if (sample.decodingTimestamp >=
|
|
120
|
+
nextTimestampWeWant * media_parser_1.WEBCODECS_TIMESCALE) {
|
|
121
|
+
await doProcess();
|
|
122
|
+
if (timestampTargets.length === 0) {
|
|
123
|
+
await decoder.flush();
|
|
124
|
+
controller.abort();
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return async () => {
|
|
128
|
+
await doProcess();
|
|
129
|
+
await decoder.flush();
|
|
130
|
+
if (lastFrame && lastFrameEmitted !== lastFrame) {
|
|
131
|
+
lastFrame.close();
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
};
|
|
135
|
+
},
|
|
136
|
+
})
|
|
137
|
+
.then(() => {
|
|
138
|
+
resolvers.resolve();
|
|
139
|
+
})
|
|
140
|
+
.catch((e) => {
|
|
141
|
+
if (!(0, media_parser_1.hasBeenAborted)(e)) {
|
|
142
|
+
resolvers.reject(e);
|
|
143
|
+
}
|
|
144
|
+
else {
|
|
145
|
+
resolvers.resolve();
|
|
146
|
+
}
|
|
147
|
+
})
|
|
148
|
+
.finally(() => {
|
|
149
|
+
if (lastFrame && lastFrameEmitted !== lastFrame) {
|
|
150
|
+
lastFrame.close();
|
|
151
|
+
}
|
|
152
|
+
signal?.removeEventListener('abort', abortListener);
|
|
153
|
+
});
|
|
154
|
+
return resolvers.promise;
|
|
155
|
+
};
|
|
156
|
+
exports.internalExtractFrames = internalExtractFrames;
|
package/dist/worker.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { ExtractFramesOnWebWorker, ExtractFramesOnWebWorkerProps, extractFramesOnWebWorker, } from './extract-frames-on-web-worker';
|
package/dist/worker.js
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.extractFramesOnWebWorker = void 0;
|
|
4
|
+
var extract_frames_on_web_worker_1 = require("./extract-frames-on-web-worker");
|
|
5
|
+
Object.defineProperty(exports, "extractFramesOnWebWorker", { enumerable: true, get: function () { return extract_frames_on_web_worker_1.extractFramesOnWebWorker; } });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@remotion/webcodecs",
|
|
3
|
-
"version": "4.0.
|
|
3
|
+
"version": "4.0.331",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"types": "dist/index.d.ts",
|
|
6
6
|
"module": "dist/esm/index.mjs",
|
|
@@ -19,8 +19,8 @@
|
|
|
19
19
|
"author": "Jonny Burger <jonny@remotion.dev>",
|
|
20
20
|
"license": "Remotion License (See https://remotion.dev/docs/webcodecs#license)",
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"@remotion/media-parser": "4.0.
|
|
23
|
-
"@remotion/licensing": "4.0.
|
|
22
|
+
"@remotion/media-parser": "4.0.331",
|
|
23
|
+
"@remotion/licensing": "4.0.331"
|
|
24
24
|
},
|
|
25
25
|
"peerDependencies": {},
|
|
26
26
|
"devDependencies": {
|
|
@@ -29,8 +29,8 @@
|
|
|
29
29
|
"vite": "5.4.19",
|
|
30
30
|
"@playwright/test": "1.51.1",
|
|
31
31
|
"eslint": "9.19.0",
|
|
32
|
-
"@remotion/
|
|
33
|
-
"@remotion/
|
|
32
|
+
"@remotion/example-videos": "4.0.331",
|
|
33
|
+
"@remotion/eslint-config-internal": "4.0.331"
|
|
34
34
|
},
|
|
35
35
|
"keywords": [],
|
|
36
36
|
"publishConfig": {
|
|
@@ -55,6 +55,12 @@
|
|
|
55
55
|
"module": "./dist/esm/buffer.mjs",
|
|
56
56
|
"import": "./dist/esm/buffer.mjs"
|
|
57
57
|
},
|
|
58
|
+
"./worker": {
|
|
59
|
+
"types": "./dist/worker.d.ts",
|
|
60
|
+
"require": "./dist/worker.js",
|
|
61
|
+
"module": "./dist/esm/worker.mjs",
|
|
62
|
+
"import": "./dist/esm/worker.mjs"
|
|
63
|
+
},
|
|
58
64
|
"./package.json": "./package.json"
|
|
59
65
|
},
|
|
60
66
|
"typesVersions": {
|
|
@@ -62,6 +68,9 @@
|
|
|
62
68
|
"web-fs": [
|
|
63
69
|
"dist/writers/web-fs.d.ts"
|
|
64
70
|
],
|
|
71
|
+
"worker": [
|
|
72
|
+
"dist/worker.d.ts"
|
|
73
|
+
],
|
|
65
74
|
"buffer": [
|
|
66
75
|
"dist/writers/buffer.d.ts"
|
|
67
76
|
]
|