@remotion/webcodecs 4.0.227 → 4.0.229
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/audio-decoder-config.js +3 -0
- package/dist/audio-decoder.d.ts +3 -3
- package/dist/audio-decoder.js +23 -29
- package/dist/audio-encoder.d.ts +3 -2
- package/dist/audio-encoder.js +17 -29
- package/dist/auto-select-writer.d.ts +3 -0
- package/dist/auto-select-writer.js +20 -0
- package/dist/calculate-progress.d.ts +4 -0
- package/dist/calculate-progress.js +10 -0
- package/dist/can-copy-audio-track.d.ts +8 -0
- package/dist/can-copy-audio-track.js +10 -0
- package/dist/can-copy-video-track.d.ts +8 -0
- package/dist/can-copy-video-track.js +13 -0
- package/dist/can-reencode-audio-track.d.ts +7 -0
- package/dist/can-reencode-audio-track.js +21 -0
- package/dist/can-reencode-audio.d.ts +7 -0
- package/dist/can-reencode-audio.js +21 -0
- package/dist/can-reencode-video-track.d.ts +6 -0
- package/dist/can-reencode-video-track.js +15 -0
- package/dist/can-reencode-video.d.ts +6 -0
- package/dist/can-reencode-video.js +15 -0
- package/dist/codec-id.d.ts +7 -2
- package/dist/codec-id.js +7 -0
- package/dist/convert-encoded-chunk.d.ts +2 -0
- package/dist/convert-encoded-chunk.js +15 -0
- package/dist/convert-media.d.ts +25 -9
- package/dist/convert-media.js +69 -15
- package/dist/esm/index.mjs +602 -297
- package/dist/event-emitter.d.ts +25 -0
- package/dist/event-emitter.js +23 -0
- package/dist/index.d.ts +8 -1
- package/dist/index.js +12 -1
- package/dist/io-manager/event-emitter.d.ts +27 -0
- package/dist/io-manager/event-emitter.js +24 -0
- package/dist/io-manager/io-synchronizer.d.ts +12 -0
- package/dist/io-manager/io-synchronizer.js +95 -0
- package/dist/log.d.ts +10 -0
- package/dist/log.js +6 -0
- package/dist/on-audio-track.d.ts +6 -5
- package/dist/on-audio-track.js +23 -21
- package/dist/on-frame.d.ts +11 -0
- package/dist/on-frame.js +32 -0
- package/dist/on-video-track.d.ts +8 -6
- package/dist/on-video-track.js +30 -21
- package/dist/polyfill-encoded-audio-chunk.d.ts +3 -0
- package/dist/polyfill-encoded-audio-chunk.js +5 -0
- package/dist/resolve-audio-action.d.ts +15 -11
- package/dist/resolve-audio-action.js +23 -21
- package/dist/resolve-video-action.d.ts +14 -11
- package/dist/resolve-video-action.js +17 -24
- package/dist/video-decoder.d.ts +3 -3
- package/dist/video-decoder.js +23 -28
- package/dist/video-encoder-config.d.ts +6 -1
- package/dist/video-encoder-config.js +6 -1
- package/dist/video-encoder.d.ts +4 -3
- package/dist/video-encoder.js +26 -29
- package/dist/wait-until-return.d.ts +4 -0
- package/dist/wait-until-return.js +14 -0
- package/dist/with-resolvers.d.ts +5 -0
- package/dist/with-resolvers.js +16 -1
- package/package.json +4 -4
package/dist/esm/index.mjs
CHANGED
|
@@ -1,24 +1,175 @@
|
|
|
1
|
+
// src/log.ts
|
|
2
|
+
import { MediaParserInternals } from "@remotion/media-parser";
|
|
3
|
+
var { Log } = MediaParserInternals;
|
|
4
|
+
|
|
5
|
+
// src/with-resolvers.ts
|
|
6
|
+
var withResolvers = function() {
|
|
7
|
+
let resolve;
|
|
8
|
+
let reject;
|
|
9
|
+
const promise = new Promise((res, rej) => {
|
|
10
|
+
resolve = res;
|
|
11
|
+
reject = rej;
|
|
12
|
+
});
|
|
13
|
+
return { promise, resolve, reject };
|
|
14
|
+
};
|
|
15
|
+
var withResolversAndWaitForReturn = () => {
|
|
16
|
+
const { promise, reject, resolve } = withResolvers();
|
|
17
|
+
const { promise: returnPromise, resolve: resolveReturn } = withResolvers();
|
|
18
|
+
return {
|
|
19
|
+
getPromiseToImmediatelyReturn: () => {
|
|
20
|
+
resolveReturn(undefined);
|
|
21
|
+
return promise;
|
|
22
|
+
},
|
|
23
|
+
reject: (reason) => {
|
|
24
|
+
returnPromise.then(() => reject(reason));
|
|
25
|
+
},
|
|
26
|
+
resolve
|
|
27
|
+
};
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
// src/io-manager/event-emitter.ts
|
|
31
|
+
class IoEventEmitter {
|
|
32
|
+
listeners = {
|
|
33
|
+
input: [],
|
|
34
|
+
output: [],
|
|
35
|
+
processed: []
|
|
36
|
+
};
|
|
37
|
+
addEventListener(name, callback) {
|
|
38
|
+
this.listeners[name].push(callback);
|
|
39
|
+
}
|
|
40
|
+
removeEventListener(name, callback) {
|
|
41
|
+
this.listeners[name] = this.listeners[name].filter((l) => l !== callback);
|
|
42
|
+
}
|
|
43
|
+
dispatchEvent(dispatchName, context) {
|
|
44
|
+
this.listeners[dispatchName].forEach((callback) => {
|
|
45
|
+
callback({ detail: context });
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// src/io-manager/io-synchronizer.ts
|
|
51
|
+
var makeIoSynchronizer = (logLevel, label) => {
|
|
52
|
+
const eventEmitter = new IoEventEmitter;
|
|
53
|
+
let lastInput = 0;
|
|
54
|
+
let lastInputKeyframe = 0;
|
|
55
|
+
let lastOutput = 0;
|
|
56
|
+
let inputsSinceLastOutput = 0;
|
|
57
|
+
let inputs = [];
|
|
58
|
+
let keyframes = [];
|
|
59
|
+
let unprocessed = 0;
|
|
60
|
+
const getUnprocessed = () => unprocessed;
|
|
61
|
+
const getUnemittedItems = () => {
|
|
62
|
+
inputs = inputs.filter((input) => input > lastOutput);
|
|
63
|
+
return inputs.length;
|
|
64
|
+
};
|
|
65
|
+
const getUnemittedKeyframes = () => {
|
|
66
|
+
keyframes = keyframes.filter((keyframe) => keyframe > lastOutput);
|
|
67
|
+
return keyframes.length;
|
|
68
|
+
};
|
|
69
|
+
const printState = (prefix) => {
|
|
70
|
+
Log.trace(logLevel, `[${label}] ${prefix}, state: Last input = ${lastInput} Last input keyframe = ${lastInputKeyframe} Last output = ${lastOutput} Inputs since last output = ${inputsSinceLastOutput}, Queue = ${getUnemittedItems()} (${getUnemittedKeyframes()} keyframes), Unprocessed = ${getUnprocessed()}`);
|
|
71
|
+
};
|
|
72
|
+
const inputItem = (timestamp, keyFrame) => {
|
|
73
|
+
lastInput = timestamp;
|
|
74
|
+
if (keyFrame) {
|
|
75
|
+
lastInputKeyframe = timestamp;
|
|
76
|
+
keyframes.push(timestamp);
|
|
77
|
+
}
|
|
78
|
+
inputsSinceLastOutput++;
|
|
79
|
+
inputs.push(timestamp);
|
|
80
|
+
eventEmitter.dispatchEvent("input", {
|
|
81
|
+
timestamp,
|
|
82
|
+
keyFrame
|
|
83
|
+
});
|
|
84
|
+
printState("Input item");
|
|
85
|
+
};
|
|
86
|
+
const onOutput = (timestamp) => {
|
|
87
|
+
lastOutput = timestamp;
|
|
88
|
+
inputsSinceLastOutput = 0;
|
|
89
|
+
eventEmitter.dispatchEvent("output", {
|
|
90
|
+
timestamp
|
|
91
|
+
});
|
|
92
|
+
unprocessed++;
|
|
93
|
+
printState("Got output");
|
|
94
|
+
};
|
|
95
|
+
const waitForOutput = () => {
|
|
96
|
+
const { promise, resolve } = withResolvers();
|
|
97
|
+
const on = () => {
|
|
98
|
+
eventEmitter.removeEventListener("output", on);
|
|
99
|
+
resolve();
|
|
100
|
+
};
|
|
101
|
+
eventEmitter.addEventListener("output", on);
|
|
102
|
+
return promise;
|
|
103
|
+
};
|
|
104
|
+
const waitForProcessed = () => {
|
|
105
|
+
const { promise, resolve } = withResolvers();
|
|
106
|
+
const on = () => {
|
|
107
|
+
eventEmitter.removeEventListener("processed", on);
|
|
108
|
+
resolve();
|
|
109
|
+
};
|
|
110
|
+
eventEmitter.addEventListener("processed", on);
|
|
111
|
+
return promise;
|
|
112
|
+
};
|
|
113
|
+
const waitFor = async ({
|
|
114
|
+
_unprocessed,
|
|
115
|
+
unemitted
|
|
116
|
+
}) => {
|
|
117
|
+
while (getUnemittedItems() > unemitted) {
|
|
118
|
+
await waitForOutput();
|
|
119
|
+
}
|
|
120
|
+
while (getUnprocessed() > _unprocessed) {
|
|
121
|
+
await waitForProcessed();
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
const waitForFinish = async () => {
|
|
125
|
+
await waitFor({ _unprocessed: 0, unemitted: 0 });
|
|
126
|
+
};
|
|
127
|
+
const onProcessed = () => {
|
|
128
|
+
eventEmitter.dispatchEvent("processed", {});
|
|
129
|
+
unprocessed--;
|
|
130
|
+
};
|
|
131
|
+
return {
|
|
132
|
+
inputItem,
|
|
133
|
+
onOutput,
|
|
134
|
+
waitFor,
|
|
135
|
+
waitForFinish,
|
|
136
|
+
onProcessed,
|
|
137
|
+
getUnprocessed
|
|
138
|
+
};
|
|
139
|
+
};
|
|
140
|
+
|
|
1
141
|
// src/audio-decoder.ts
|
|
2
142
|
var createAudioDecoder = ({
|
|
3
143
|
onFrame,
|
|
4
144
|
onError,
|
|
5
145
|
signal,
|
|
6
|
-
config
|
|
146
|
+
config,
|
|
147
|
+
logLevel
|
|
7
148
|
}) => {
|
|
8
149
|
if (signal.aborted) {
|
|
9
150
|
throw new Error("Not creating audio decoder, already aborted");
|
|
10
151
|
}
|
|
152
|
+
const ioSynchronizer = makeIoSynchronizer(logLevel, "Audio decoder");
|
|
11
153
|
let outputQueue = Promise.resolve();
|
|
12
|
-
let outputQueueSize = 0;
|
|
13
|
-
let dequeueResolver = () => {
|
|
14
|
-
};
|
|
15
154
|
const audioDecoder = new AudioDecoder({
|
|
16
155
|
output(inputFrame) {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
156
|
+
ioSynchronizer.onOutput(inputFrame.timestamp);
|
|
157
|
+
const abortHandler = () => {
|
|
158
|
+
inputFrame.close();
|
|
159
|
+
};
|
|
160
|
+
signal.addEventListener("abort", abortHandler, { once: true });
|
|
161
|
+
outputQueue = outputQueue.then(() => {
|
|
162
|
+
if (signal.aborted) {
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
return onFrame(inputFrame);
|
|
166
|
+
}).then(() => {
|
|
167
|
+
ioSynchronizer.onProcessed();
|
|
168
|
+
signal.removeEventListener("abort", abortHandler);
|
|
21
169
|
return Promise.resolve();
|
|
170
|
+
}).catch((err) => {
|
|
171
|
+
inputFrame.close();
|
|
172
|
+
onError(err);
|
|
22
173
|
});
|
|
23
174
|
},
|
|
24
175
|
error(error) {
|
|
@@ -36,32 +187,15 @@ var createAudioDecoder = ({
|
|
|
36
187
|
close();
|
|
37
188
|
};
|
|
38
189
|
signal.addEventListener("abort", onAbort);
|
|
39
|
-
const getQueueSize = () => {
|
|
40
|
-
return audioDecoder.decodeQueueSize + outputQueueSize;
|
|
41
|
-
};
|
|
42
190
|
audioDecoder.configure(config);
|
|
43
|
-
const waitForDequeue = async () => {
|
|
44
|
-
await new Promise((r) => {
|
|
45
|
-
dequeueResolver = r;
|
|
46
|
-
audioDecoder.addEventListener("dequeue", () => r(), {
|
|
47
|
-
once: true
|
|
48
|
-
});
|
|
49
|
-
});
|
|
50
|
-
};
|
|
51
|
-
const waitForFinish = async () => {
|
|
52
|
-
while (getQueueSize() > 0) {
|
|
53
|
-
await waitForDequeue();
|
|
54
|
-
}
|
|
55
|
-
};
|
|
56
191
|
const processSample = async (audioSample) => {
|
|
57
192
|
if (audioDecoder.state === "closed") {
|
|
58
193
|
return;
|
|
59
194
|
}
|
|
60
|
-
|
|
61
|
-
await waitForDequeue();
|
|
62
|
-
}
|
|
195
|
+
await ioSynchronizer.waitFor({ unemitted: 100, _unprocessed: 2 });
|
|
63
196
|
const chunk = new EncodedAudioChunk(audioSample);
|
|
64
197
|
audioDecoder.decode(chunk);
|
|
198
|
+
ioSynchronizer.inputItem(chunk.timestamp, audioSample.type === "key");
|
|
65
199
|
};
|
|
66
200
|
let queue = Promise.resolve();
|
|
67
201
|
return {
|
|
@@ -71,11 +205,10 @@ var createAudioDecoder = ({
|
|
|
71
205
|
},
|
|
72
206
|
waitForFinish: async () => {
|
|
73
207
|
await audioDecoder.flush();
|
|
74
|
-
await waitForFinish();
|
|
208
|
+
await ioSynchronizer.waitForFinish();
|
|
75
209
|
await outputQueue;
|
|
76
210
|
},
|
|
77
211
|
close,
|
|
78
|
-
getQueueSize,
|
|
79
212
|
flush: async () => {
|
|
80
213
|
await audioDecoder.flush();
|
|
81
214
|
}
|
|
@@ -87,22 +220,27 @@ var createAudioEncoder = ({
|
|
|
87
220
|
onError,
|
|
88
221
|
codec,
|
|
89
222
|
signal,
|
|
90
|
-
config: audioEncoderConfig
|
|
223
|
+
config: audioEncoderConfig,
|
|
224
|
+
logLevel
|
|
91
225
|
}) => {
|
|
92
226
|
if (signal.aborted) {
|
|
93
227
|
throw new Error("Not creating audio encoder, already aborted");
|
|
94
228
|
}
|
|
229
|
+
const ioSynchronizer = makeIoSynchronizer(logLevel, "Audio encoder");
|
|
95
230
|
let prom = Promise.resolve();
|
|
96
|
-
let outputQueue = 0;
|
|
97
|
-
let dequeueResolver = () => {
|
|
98
|
-
};
|
|
99
231
|
const encoder = new AudioEncoder({
|
|
100
232
|
output: (chunk) => {
|
|
101
|
-
|
|
102
|
-
prom = prom.then(() =>
|
|
103
|
-
|
|
104
|
-
|
|
233
|
+
ioSynchronizer.onOutput(chunk.timestamp);
|
|
234
|
+
prom = prom.then(() => {
|
|
235
|
+
if (signal.aborted) {
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
return onChunk(chunk);
|
|
239
|
+
}).then(() => {
|
|
240
|
+
ioSynchronizer.onProcessed();
|
|
105
241
|
return Promise.resolve();
|
|
242
|
+
}).catch((err) => {
|
|
243
|
+
onError(err);
|
|
106
244
|
});
|
|
107
245
|
},
|
|
108
246
|
error(error) {
|
|
@@ -123,34 +261,17 @@ var createAudioEncoder = ({
|
|
|
123
261
|
if (codec !== "opus") {
|
|
124
262
|
throw new Error('Only `codec: "opus"` is supported currently');
|
|
125
263
|
}
|
|
126
|
-
const getQueueSize = () => {
|
|
127
|
-
return encoder.encodeQueueSize + outputQueue;
|
|
128
|
-
};
|
|
129
264
|
encoder.configure(audioEncoderConfig);
|
|
130
|
-
const waitForDequeue = async () => {
|
|
131
|
-
await new Promise((r) => {
|
|
132
|
-
dequeueResolver = r;
|
|
133
|
-
encoder.addEventListener("dequeue", () => r(), {
|
|
134
|
-
once: true
|
|
135
|
-
});
|
|
136
|
-
});
|
|
137
|
-
};
|
|
138
|
-
const waitForFinish = async () => {
|
|
139
|
-
while (getQueueSize() > 0) {
|
|
140
|
-
await waitForDequeue();
|
|
141
|
-
}
|
|
142
|
-
};
|
|
143
265
|
const encodeFrame = async (audioData) => {
|
|
144
266
|
if (encoder.state === "closed") {
|
|
145
267
|
return;
|
|
146
268
|
}
|
|
147
|
-
|
|
148
|
-
await waitForDequeue();
|
|
149
|
-
}
|
|
269
|
+
await ioSynchronizer.waitFor({ unemitted: 2, _unprocessed: 2 });
|
|
150
270
|
if (encoder.state === "closed") {
|
|
151
271
|
return;
|
|
152
272
|
}
|
|
153
273
|
encoder.encode(audioData);
|
|
274
|
+
ioSynchronizer.inputItem(audioData.timestamp, true);
|
|
154
275
|
};
|
|
155
276
|
let queue = Promise.resolve();
|
|
156
277
|
return {
|
|
@@ -160,32 +281,48 @@ var createAudioEncoder = ({
|
|
|
160
281
|
},
|
|
161
282
|
waitForFinish: async () => {
|
|
162
283
|
await encoder.flush();
|
|
163
|
-
await waitForFinish();
|
|
284
|
+
await ioSynchronizer.waitForFinish();
|
|
164
285
|
await prom;
|
|
165
286
|
},
|
|
166
287
|
close,
|
|
167
|
-
getQueueSize,
|
|
168
288
|
flush: async () => {
|
|
169
289
|
await encoder.flush();
|
|
170
290
|
}
|
|
171
291
|
};
|
|
172
292
|
};
|
|
173
|
-
// src/
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
293
|
+
// src/can-copy-audio-track.ts
|
|
294
|
+
var canCopyAudioTrack = ({
|
|
295
|
+
inputCodec,
|
|
296
|
+
outputCodec,
|
|
297
|
+
container
|
|
298
|
+
}) => {
|
|
299
|
+
if (outputCodec === "opus") {
|
|
300
|
+
return inputCodec === "opus" && container === "webm";
|
|
301
|
+
}
|
|
302
|
+
throw new Error(`Unhandled codec: ${outputCodec}`);
|
|
303
|
+
};
|
|
304
|
+
// src/can-copy-video-track.ts
|
|
305
|
+
var canCopyVideoTrack = ({
|
|
306
|
+
inputCodec,
|
|
307
|
+
outputCodec,
|
|
308
|
+
container
|
|
309
|
+
}) => {
|
|
310
|
+
if (outputCodec === "vp8") {
|
|
311
|
+
return inputCodec === "vp8" && container === "webm";
|
|
312
|
+
}
|
|
313
|
+
if (outputCodec === "vp9") {
|
|
314
|
+
return inputCodec === "vp9" && container === "webm";
|
|
315
|
+
}
|
|
316
|
+
throw new Error(`Unhandled codec: ${outputCodec}`);
|
|
317
|
+
};
|
|
184
318
|
// src/audio-decoder-config.ts
|
|
185
319
|
var getAudioDecoderConfig = async (config) => {
|
|
186
320
|
if (typeof AudioDecoder === "undefined") {
|
|
187
321
|
return null;
|
|
188
322
|
}
|
|
323
|
+
if (typeof EncodedAudioChunk === "undefined") {
|
|
324
|
+
return null;
|
|
325
|
+
}
|
|
189
326
|
if ((await AudioDecoder.isConfigSupported(config)).supported) {
|
|
190
327
|
return config;
|
|
191
328
|
}
|
|
@@ -203,39 +340,175 @@ var getAudioEncoderConfig = async (config) => {
|
|
|
203
340
|
return null;
|
|
204
341
|
};
|
|
205
342
|
|
|
206
|
-
// src/
|
|
207
|
-
var
|
|
208
|
-
|
|
209
|
-
|
|
343
|
+
// src/can-reencode-audio-track.ts
|
|
344
|
+
var canReencodeAudioTrack = async ({
|
|
345
|
+
track,
|
|
346
|
+
audioCodec,
|
|
347
|
+
bitrate
|
|
348
|
+
}) => {
|
|
349
|
+
const audioDecoderConfig = await getAudioDecoderConfig({
|
|
350
|
+
codec: track.codec,
|
|
351
|
+
numberOfChannels: track.numberOfChannels,
|
|
352
|
+
sampleRate: track.sampleRate,
|
|
353
|
+
description: track.description
|
|
354
|
+
});
|
|
355
|
+
const audioEncoderConfig = await getAudioEncoderConfig({
|
|
356
|
+
codec: audioCodec,
|
|
357
|
+
numberOfChannels: track.numberOfChannels,
|
|
358
|
+
sampleRate: track.sampleRate,
|
|
359
|
+
bitrate
|
|
360
|
+
});
|
|
361
|
+
return Boolean(audioDecoderConfig && audioEncoderConfig);
|
|
362
|
+
};
|
|
363
|
+
// src/video-decoder-config.ts
|
|
364
|
+
var getVideoDecoderConfigWithHardwareAcceleration = async (config) => {
|
|
365
|
+
if (typeof VideoDecoder === "undefined") {
|
|
366
|
+
return null;
|
|
210
367
|
}
|
|
211
|
-
|
|
368
|
+
const hardware = {
|
|
369
|
+
...config,
|
|
370
|
+
hardwareAcceleration: "prefer-hardware"
|
|
371
|
+
};
|
|
372
|
+
if ((await VideoDecoder.isConfigSupported(hardware)).supported) {
|
|
373
|
+
return hardware;
|
|
374
|
+
}
|
|
375
|
+
const software = {
|
|
376
|
+
...config,
|
|
377
|
+
hardwareAcceleration: "prefer-software"
|
|
378
|
+
};
|
|
379
|
+
if ((await VideoDecoder.isConfigSupported(software)).supported) {
|
|
380
|
+
return software;
|
|
381
|
+
}
|
|
382
|
+
return null;
|
|
383
|
+
};
|
|
384
|
+
|
|
385
|
+
// src/video-encoder-config.ts
|
|
386
|
+
var getVideoEncoderConfig = async ({
|
|
387
|
+
width,
|
|
388
|
+
height,
|
|
389
|
+
codec
|
|
390
|
+
}) => {
|
|
391
|
+
if (typeof VideoEncoder === "undefined") {
|
|
392
|
+
return null;
|
|
393
|
+
}
|
|
394
|
+
const config = {
|
|
395
|
+
codec: codec === "vp9" ? "vp09.00.10.08" : codec,
|
|
396
|
+
height,
|
|
397
|
+
width
|
|
398
|
+
};
|
|
399
|
+
const hardware = {
|
|
400
|
+
...config,
|
|
401
|
+
hardwareAcceleration: "prefer-hardware"
|
|
402
|
+
};
|
|
403
|
+
if ((await VideoEncoder.isConfigSupported(hardware)).supported) {
|
|
404
|
+
return hardware;
|
|
405
|
+
}
|
|
406
|
+
const software = {
|
|
407
|
+
...config,
|
|
408
|
+
hardwareAcceleration: "prefer-software"
|
|
409
|
+
};
|
|
410
|
+
if ((await VideoEncoder.isConfigSupported(software)).supported) {
|
|
411
|
+
return software;
|
|
412
|
+
}
|
|
413
|
+
return null;
|
|
212
414
|
};
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
415
|
+
|
|
416
|
+
// src/can-reencode-video-track.ts
|
|
417
|
+
var canReencodeVideoTrack = async ({
|
|
418
|
+
videoCodec,
|
|
419
|
+
track
|
|
216
420
|
}) => {
|
|
217
|
-
|
|
218
|
-
|
|
421
|
+
const videoEncoderConfig = await getVideoEncoderConfig({
|
|
422
|
+
codec: videoCodec,
|
|
423
|
+
height: track.displayAspectHeight,
|
|
424
|
+
width: track.displayAspectWidth
|
|
425
|
+
});
|
|
426
|
+
const videoDecoderConfig = await getVideoDecoderConfigWithHardwareAcceleration(track);
|
|
427
|
+
return Boolean(videoDecoderConfig && videoEncoderConfig);
|
|
428
|
+
};
|
|
429
|
+
// src/codec-id.ts
|
|
430
|
+
var availableVideoCodecs = ["vp8", "vp9"];
|
|
431
|
+
var getAvailableVideoCodecs = () => availableVideoCodecs;
|
|
432
|
+
var availableAudioCodecs = ["opus"];
|
|
433
|
+
var getAvailableAudioCodecs = () => availableAudioCodecs;
|
|
434
|
+
// src/convert-media.ts
|
|
435
|
+
import {
|
|
436
|
+
MediaParserInternals as MediaParserInternals2,
|
|
437
|
+
parseMedia
|
|
438
|
+
} from "@remotion/media-parser";
|
|
439
|
+
|
|
440
|
+
// src/auto-select-writer.ts
|
|
441
|
+
import { bufferWriter } from "@remotion/media-parser/buffer";
|
|
442
|
+
import { canUseWebFsWriter, webFsWriter } from "@remotion/media-parser/web-fs";
|
|
443
|
+
var autoSelectWriter = async (writer, logLevel) => {
|
|
444
|
+
if (writer) {
|
|
445
|
+
Log.verbose(logLevel, "Using writer provided by user");
|
|
446
|
+
return writer;
|
|
219
447
|
}
|
|
220
|
-
|
|
221
|
-
|
|
448
|
+
Log.verbose(logLevel, "Determining best writer");
|
|
449
|
+
if (await canUseWebFsWriter()) {
|
|
450
|
+
Log.verbose(logLevel, "Using WebFS writer because it is supported");
|
|
451
|
+
return webFsWriter;
|
|
452
|
+
}
|
|
453
|
+
Log.verbose(logLevel, "Using buffer writer because WebFS writer is not supported");
|
|
454
|
+
return bufferWriter;
|
|
455
|
+
};
|
|
456
|
+
|
|
457
|
+
// src/calculate-progress.ts
|
|
458
|
+
var calculateProgress = ({
|
|
459
|
+
millisecondsWritten,
|
|
460
|
+
expectedOutputDurationInMs
|
|
461
|
+
}) => {
|
|
462
|
+
if (expectedOutputDurationInMs === null) {
|
|
463
|
+
return null;
|
|
222
464
|
}
|
|
223
|
-
return
|
|
465
|
+
return millisecondsWritten / expectedOutputDurationInMs;
|
|
224
466
|
};
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
467
|
+
|
|
468
|
+
// src/error-cause.ts
|
|
469
|
+
var error_cause_default = Error;
|
|
470
|
+
|
|
471
|
+
// src/convert-encoded-chunk.ts
|
|
472
|
+
var convertEncodedChunk = (chunk) => {
|
|
473
|
+
const arr = new Uint8Array(chunk.byteLength);
|
|
474
|
+
chunk.copyTo(arr);
|
|
475
|
+
return {
|
|
476
|
+
data: arr,
|
|
477
|
+
duration: chunk.duration ?? undefined,
|
|
478
|
+
timestamp: chunk.timestamp,
|
|
479
|
+
type: chunk.type
|
|
480
|
+
};
|
|
481
|
+
};
|
|
482
|
+
|
|
483
|
+
// src/resolve-audio-action.ts
|
|
484
|
+
var DEFAULT_BITRATE = 128000;
|
|
485
|
+
var defaultResolveAudioAction = async ({
|
|
228
486
|
track,
|
|
229
487
|
audioCodec,
|
|
230
|
-
|
|
488
|
+
logLevel,
|
|
489
|
+
container
|
|
231
490
|
}) => {
|
|
232
|
-
const
|
|
233
|
-
const canCopy = canCopyAudioTrack(
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
491
|
+
const bitrate = DEFAULT_BITRATE;
|
|
492
|
+
const canCopy = canCopyAudioTrack({
|
|
493
|
+
inputCodec: track.codecWithoutConfig,
|
|
494
|
+
outputCodec: audioCodec,
|
|
495
|
+
container
|
|
237
496
|
});
|
|
238
|
-
|
|
497
|
+
if (canCopy) {
|
|
498
|
+
Log.verbose(logLevel, `Track ${track.trackId} (audio): Can copy = ${canCopy}, action = copy`);
|
|
499
|
+
return Promise.resolve({ type: "copy" });
|
|
500
|
+
}
|
|
501
|
+
const canReencode = await canReencodeAudioTrack({
|
|
502
|
+
audioCodec,
|
|
503
|
+
track,
|
|
504
|
+
bitrate
|
|
505
|
+
});
|
|
506
|
+
if (canReencode) {
|
|
507
|
+
Log.verbose(logLevel, `Track ${track.trackId} (audio): Can re-encode = ${canReencode}, can copy = ${canCopy}, action = reencode`);
|
|
508
|
+
return Promise.resolve({ type: "reencode", bitrate, audioCodec });
|
|
509
|
+
}
|
|
510
|
+
Log.verbose(logLevel, `Track ${track.trackId} (audio): Can re-encode = ${canReencode}, can copy = ${canCopy}, action = drop`);
|
|
511
|
+
return Promise.resolve({ type: "drop" });
|
|
239
512
|
};
|
|
240
513
|
|
|
241
514
|
// src/on-audio-track.ts
|
|
@@ -247,31 +520,19 @@ var makeAudioTrackHandler = ({
|
|
|
247
520
|
abortConversion,
|
|
248
521
|
onMediaStateUpdate,
|
|
249
522
|
onAudioTrack,
|
|
250
|
-
|
|
523
|
+
logLevel,
|
|
524
|
+
container
|
|
251
525
|
}) => async (track) => {
|
|
252
|
-
const
|
|
253
|
-
codec: audioCodec,
|
|
254
|
-
numberOfChannels: track.numberOfChannels,
|
|
255
|
-
sampleRate: track.sampleRate,
|
|
256
|
-
bitrate
|
|
257
|
-
});
|
|
258
|
-
const audioDecoderConfig = await getAudioDecoderConfig({
|
|
259
|
-
codec: track.codec,
|
|
260
|
-
numberOfChannels: track.numberOfChannels,
|
|
261
|
-
sampleRate: track.sampleRate,
|
|
262
|
-
description: track.description
|
|
263
|
-
});
|
|
264
|
-
const audioOperation = await resolveAudioAction({
|
|
265
|
-
audioDecoderConfig,
|
|
266
|
-
audioEncoderConfig,
|
|
526
|
+
const audioOperation = await (onAudioTrack ?? defaultResolveAudioAction)({
|
|
267
527
|
audioCodec,
|
|
268
528
|
track,
|
|
269
|
-
|
|
529
|
+
logLevel,
|
|
530
|
+
container
|
|
270
531
|
});
|
|
271
|
-
if (audioOperation === "drop") {
|
|
532
|
+
if (audioOperation.type === "drop") {
|
|
272
533
|
return null;
|
|
273
534
|
}
|
|
274
|
-
if (audioOperation === "copy") {
|
|
535
|
+
if (audioOperation.type === "copy") {
|
|
275
536
|
const addedTrack = await state.addTrack({
|
|
276
537
|
type: "audio",
|
|
277
538
|
codec: audioCodec,
|
|
@@ -280,11 +541,23 @@ var makeAudioTrackHandler = ({
|
|
|
280
541
|
codecPrivate: track.codecPrivate
|
|
281
542
|
});
|
|
282
543
|
return async (audioSample) => {
|
|
283
|
-
await state.addSample(
|
|
544
|
+
await state.addSample(audioSample, addedTrack.trackNumber, false);
|
|
284
545
|
convertMediaState.encodedAudioFrames++;
|
|
285
546
|
onMediaStateUpdate?.({ ...convertMediaState });
|
|
286
547
|
};
|
|
287
548
|
}
|
|
549
|
+
const audioEncoderConfig = await getAudioEncoderConfig({
|
|
550
|
+
numberOfChannels: track.numberOfChannels,
|
|
551
|
+
sampleRate: track.sampleRate,
|
|
552
|
+
codec: audioOperation.audioCodec,
|
|
553
|
+
bitrate: audioOperation.bitrate
|
|
554
|
+
});
|
|
555
|
+
const audioDecoderConfig = await getAudioDecoderConfig({
|
|
556
|
+
codec: track.codec,
|
|
557
|
+
numberOfChannels: track.numberOfChannels,
|
|
558
|
+
sampleRate: track.sampleRate,
|
|
559
|
+
description: track.description
|
|
560
|
+
});
|
|
288
561
|
if (!audioEncoderConfig) {
|
|
289
562
|
abortConversion(new error_cause_default(`Could not configure audio encoder of track ${track.trackId}`));
|
|
290
563
|
return null;
|
|
@@ -302,7 +575,7 @@ var makeAudioTrackHandler = ({
|
|
|
302
575
|
});
|
|
303
576
|
const audioEncoder = createAudioEncoder({
|
|
304
577
|
onChunk: async (chunk) => {
|
|
305
|
-
await state.addSample(chunk, trackNumber, false);
|
|
578
|
+
await state.addSample(convertEncodedChunk(chunk), trackNumber, false);
|
|
306
579
|
convertMediaState.encodedAudioFrames++;
|
|
307
580
|
onMediaStateUpdate?.({ ...convertMediaState });
|
|
308
581
|
},
|
|
@@ -313,7 +586,8 @@ var makeAudioTrackHandler = ({
|
|
|
313
586
|
},
|
|
314
587
|
codec: audioCodec,
|
|
315
588
|
signal: controller.signal,
|
|
316
|
-
config: audioEncoderConfig
|
|
589
|
+
config: audioEncoderConfig,
|
|
590
|
+
logLevel
|
|
317
591
|
});
|
|
318
592
|
const audioDecoder = createAudioDecoder({
|
|
319
593
|
onFrame: async (frame) => {
|
|
@@ -328,7 +602,8 @@ var makeAudioTrackHandler = ({
|
|
|
328
602
|
}));
|
|
329
603
|
},
|
|
330
604
|
signal: controller.signal,
|
|
331
|
-
config: audioDecoderConfig
|
|
605
|
+
config: audioDecoderConfig,
|
|
606
|
+
logLevel
|
|
332
607
|
});
|
|
333
608
|
state.addWaitForFinishPromise(async () => {
|
|
334
609
|
await audioDecoder.waitForFinish();
|
|
@@ -341,62 +616,97 @@ var makeAudioTrackHandler = ({
|
|
|
341
616
|
};
|
|
342
617
|
};
|
|
343
618
|
|
|
344
|
-
// src/
|
|
345
|
-
var
|
|
346
|
-
|
|
347
|
-
|
|
619
|
+
// src/on-frame.ts
|
|
620
|
+
var onFrame = async ({
|
|
621
|
+
frame,
|
|
622
|
+
onVideoFrame,
|
|
623
|
+
videoEncoder,
|
|
624
|
+
onMediaStateUpdate,
|
|
625
|
+
track,
|
|
626
|
+
convertMediaState
|
|
627
|
+
}) => {
|
|
628
|
+
const newFrame = onVideoFrame ? await onVideoFrame({ frame, track }) : frame;
|
|
629
|
+
if (newFrame.codedHeight !== frame.codedHeight) {
|
|
630
|
+
throw new Error(`Returned VideoFrame of track ${track.trackId} has different codedHeight (${newFrame.codedHeight}) than the input frame (${frame.codedHeight})`);
|
|
348
631
|
}
|
|
349
|
-
if (
|
|
350
|
-
|
|
632
|
+
if (newFrame.codedWidth !== frame.codedWidth) {
|
|
633
|
+
throw new Error(`Returned VideoFrame of track ${track.trackId} has different codedWidth (${newFrame.codedWidth}) than the input frame (${frame.codedWidth})`);
|
|
351
634
|
}
|
|
352
|
-
|
|
353
|
-
};
|
|
354
|
-
var defaultResolveVideoAction = ({
|
|
355
|
-
canReencode,
|
|
356
|
-
canCopy
|
|
357
|
-
}) => {
|
|
358
|
-
if (canCopy) {
|
|
359
|
-
return "copy";
|
|
635
|
+
if (newFrame.displayWidth !== frame.displayWidth) {
|
|
636
|
+
throw new Error(`Returned VideoFrame of track ${track.trackId} has different displayWidth (${newFrame.displayWidth}) than the input frame (${newFrame.displayHeight})`);
|
|
360
637
|
}
|
|
361
|
-
if (
|
|
362
|
-
|
|
638
|
+
if (newFrame.displayHeight !== frame.displayHeight) {
|
|
639
|
+
throw new Error(`Returned VideoFrame of track ${track.trackId} has different displayHeight (${newFrame.displayHeight}) than the input frame (${newFrame.displayHeight})`);
|
|
640
|
+
}
|
|
641
|
+
if (newFrame.timestamp !== frame.timestamp) {
|
|
642
|
+
throw new Error(`Returned VideoFrame of track ${track.trackId} has different timestamp (${newFrame.timestamp}) than the input frame (${newFrame.timestamp}). When calling new VideoFrame(), pass {timestamp: frame.timestamp} as second argument`);
|
|
643
|
+
}
|
|
644
|
+
if (newFrame.duration !== frame.duration) {
|
|
645
|
+
throw new Error(`Returned VideoFrame of track ${track.trackId} has different duration (${newFrame.duration}) than the input frame (${newFrame.duration}). When calling new VideoFrame(), pass {duration: frame.duration} as second argument`);
|
|
646
|
+
}
|
|
647
|
+
await videoEncoder.encodeFrame(newFrame, newFrame.timestamp);
|
|
648
|
+
convertMediaState.decodedVideoFrames++;
|
|
649
|
+
onMediaStateUpdate?.({ ...convertMediaState });
|
|
650
|
+
newFrame.close();
|
|
651
|
+
if (frame !== newFrame) {
|
|
652
|
+
frame.close();
|
|
363
653
|
}
|
|
364
|
-
return "drop";
|
|
365
654
|
};
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
655
|
+
|
|
656
|
+
// src/resolve-video-action.ts
|
|
657
|
+
var defaultResolveVideoAction = async ({
|
|
369
658
|
track,
|
|
370
659
|
videoCodec,
|
|
371
|
-
|
|
660
|
+
logLevel,
|
|
661
|
+
container
|
|
372
662
|
}) => {
|
|
373
|
-
const
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
canCopy
|
|
663
|
+
const canCopy = canCopyVideoTrack({
|
|
664
|
+
inputCodec: track.codecWithoutConfig,
|
|
665
|
+
outputCodec: videoCodec,
|
|
666
|
+
container
|
|
378
667
|
});
|
|
379
|
-
|
|
668
|
+
if (canCopy) {
|
|
669
|
+
Log.verbose(logLevel, `Track ${track.trackId} (video): Can copy, therefore copying`);
|
|
670
|
+
return Promise.resolve({ type: "copy" });
|
|
671
|
+
}
|
|
672
|
+
const canReencode = await canReencodeVideoTrack({ videoCodec, track });
|
|
673
|
+
if (canReencode) {
|
|
674
|
+
Log.verbose(logLevel, `Track ${track.trackId} (video): Cannot copy, but re-enconde, therefore re-encoding`);
|
|
675
|
+
return Promise.resolve({ type: "reencode", videoCodec });
|
|
676
|
+
}
|
|
677
|
+
Log.verbose(logLevel, `Track ${track.trackId} (video): Can neither copy nor re-encode, therefore dropping`);
|
|
678
|
+
return Promise.resolve({ type: "drop" });
|
|
380
679
|
};
|
|
381
680
|
|
|
382
681
|
// src/video-decoder.ts
|
|
383
682
|
var createVideoDecoder = ({
|
|
384
|
-
onFrame,
|
|
683
|
+
onFrame: onFrame2,
|
|
385
684
|
onError,
|
|
386
685
|
signal,
|
|
387
|
-
config
|
|
686
|
+
config,
|
|
687
|
+
logLevel
|
|
388
688
|
}) => {
|
|
689
|
+
const ioSynchronizer = makeIoSynchronizer(logLevel, "Video decoder");
|
|
389
690
|
let outputQueue = Promise.resolve();
|
|
390
|
-
let outputQueueSize = 0;
|
|
391
|
-
let dequeueResolver = () => {
|
|
392
|
-
};
|
|
393
691
|
const videoDecoder = new VideoDecoder({
|
|
394
692
|
output(inputFrame) {
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
693
|
+
ioSynchronizer.onOutput(inputFrame.timestamp);
|
|
694
|
+
const abortHandler = () => {
|
|
695
|
+
inputFrame.close();
|
|
696
|
+
};
|
|
697
|
+
signal.addEventListener("abort", abortHandler, { once: true });
|
|
698
|
+
outputQueue = outputQueue.then(() => {
|
|
699
|
+
if (signal.aborted) {
|
|
700
|
+
return;
|
|
701
|
+
}
|
|
702
|
+
return onFrame2(inputFrame);
|
|
703
|
+
}).then(() => {
|
|
704
|
+
ioSynchronizer.onProcessed();
|
|
705
|
+
signal.removeEventListener("abort", abortHandler);
|
|
399
706
|
return Promise.resolve();
|
|
707
|
+
}).catch((err) => {
|
|
708
|
+
inputFrame.close();
|
|
709
|
+
onError(err);
|
|
400
710
|
});
|
|
401
711
|
},
|
|
402
712
|
error(error) {
|
|
@@ -414,37 +724,20 @@ var createVideoDecoder = ({
|
|
|
414
724
|
close();
|
|
415
725
|
};
|
|
416
726
|
signal.addEventListener("abort", onAbort);
|
|
417
|
-
const getQueueSize = () => {
|
|
418
|
-
return videoDecoder.decodeQueueSize + outputQueueSize;
|
|
419
|
-
};
|
|
420
727
|
videoDecoder.configure(config);
|
|
421
|
-
const waitForDequeue = async () => {
|
|
422
|
-
await new Promise((r) => {
|
|
423
|
-
dequeueResolver = r;
|
|
424
|
-
videoDecoder.addEventListener("dequeue", () => r(), {
|
|
425
|
-
once: true
|
|
426
|
-
});
|
|
427
|
-
});
|
|
428
|
-
};
|
|
429
|
-
const waitForFinish = async () => {
|
|
430
|
-
while (getQueueSize() > 0) {
|
|
431
|
-
await waitForDequeue();
|
|
432
|
-
}
|
|
433
|
-
};
|
|
434
728
|
const processSample = async (sample) => {
|
|
435
729
|
if (videoDecoder.state === "closed") {
|
|
436
730
|
return;
|
|
437
731
|
}
|
|
438
|
-
while (getQueueSize() > 10) {
|
|
439
|
-
await waitForDequeue();
|
|
440
|
-
}
|
|
441
732
|
if (videoDecoder.state === "closed") {
|
|
442
733
|
return;
|
|
443
734
|
}
|
|
735
|
+
await ioSynchronizer.waitFor({ unemitted: 20, _unprocessed: 2 });
|
|
444
736
|
if (sample.type === "key") {
|
|
445
737
|
await videoDecoder.flush();
|
|
446
738
|
}
|
|
447
739
|
videoDecoder.decode(new EncodedVideoChunk(sample));
|
|
740
|
+
ioSynchronizer.inputItem(sample.timestamp, sample.type === "key");
|
|
448
741
|
};
|
|
449
742
|
let inputQueue = Promise.resolve();
|
|
450
743
|
return {
|
|
@@ -454,64 +747,50 @@ var createVideoDecoder = ({
|
|
|
454
747
|
},
|
|
455
748
|
waitForFinish: async () => {
|
|
456
749
|
await videoDecoder.flush();
|
|
457
|
-
await waitForFinish();
|
|
750
|
+
await ioSynchronizer.waitForFinish();
|
|
458
751
|
await outputQueue;
|
|
459
752
|
await inputQueue;
|
|
460
753
|
},
|
|
461
754
|
close,
|
|
462
|
-
getQueueSize,
|
|
463
755
|
flush: async () => {
|
|
464
756
|
await videoDecoder.flush();
|
|
465
757
|
}
|
|
466
758
|
};
|
|
467
759
|
};
|
|
468
760
|
|
|
469
|
-
// src/video-decoder-config.ts
|
|
470
|
-
var getVideoDecoderConfigWithHardwareAcceleration = async (config) => {
|
|
471
|
-
if (typeof VideoDecoder === "undefined") {
|
|
472
|
-
return null;
|
|
473
|
-
}
|
|
474
|
-
const hardware = {
|
|
475
|
-
...config,
|
|
476
|
-
hardwareAcceleration: "prefer-hardware"
|
|
477
|
-
};
|
|
478
|
-
if ((await VideoDecoder.isConfigSupported(hardware)).supported) {
|
|
479
|
-
return hardware;
|
|
480
|
-
}
|
|
481
|
-
const software = {
|
|
482
|
-
...config,
|
|
483
|
-
hardwareAcceleration: "prefer-software"
|
|
484
|
-
};
|
|
485
|
-
if ((await VideoDecoder.isConfigSupported(software)).supported) {
|
|
486
|
-
return software;
|
|
487
|
-
}
|
|
488
|
-
return null;
|
|
489
|
-
};
|
|
490
|
-
|
|
491
761
|
// src/video-encoder.ts
|
|
492
762
|
var createVideoEncoder = ({
|
|
493
763
|
onChunk,
|
|
494
764
|
onError,
|
|
495
765
|
signal,
|
|
496
|
-
config
|
|
766
|
+
config,
|
|
767
|
+
logLevel
|
|
497
768
|
}) => {
|
|
498
769
|
if (signal.aborted) {
|
|
499
770
|
throw new Error("Not creating video encoder, already aborted");
|
|
500
771
|
}
|
|
772
|
+
const ioSynchronizer = makeIoSynchronizer(logLevel, "Video encoder");
|
|
501
773
|
let outputQueue = Promise.resolve();
|
|
502
|
-
let outputQueueSize = 0;
|
|
503
|
-
let dequeueResolver = () => {
|
|
504
|
-
};
|
|
505
774
|
const encoder = new VideoEncoder({
|
|
506
775
|
error(error) {
|
|
507
776
|
onError(error);
|
|
508
777
|
},
|
|
509
778
|
output(chunk) {
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
779
|
+
if (chunk.duration === null) {
|
|
780
|
+
throw new Error("Duration is null");
|
|
781
|
+
}
|
|
782
|
+
const timestamp = chunk.timestamp + chunk.duration;
|
|
783
|
+
ioSynchronizer.onOutput(timestamp);
|
|
784
|
+
outputQueue = outputQueue.then(() => {
|
|
785
|
+
if (signal.aborted) {
|
|
786
|
+
return;
|
|
787
|
+
}
|
|
788
|
+
return onChunk(chunk);
|
|
789
|
+
}).then(() => {
|
|
790
|
+
ioSynchronizer.onProcessed();
|
|
514
791
|
return Promise.resolve();
|
|
792
|
+
}).catch((err) => {
|
|
793
|
+
onError(err);
|
|
515
794
|
});
|
|
516
795
|
}
|
|
517
796
|
});
|
|
@@ -526,37 +805,24 @@ var createVideoEncoder = ({
|
|
|
526
805
|
close();
|
|
527
806
|
};
|
|
528
807
|
signal.addEventListener("abort", onAbort);
|
|
529
|
-
const getQueueSize = () => {
|
|
530
|
-
return encoder.encodeQueueSize + outputQueueSize;
|
|
531
|
-
};
|
|
532
808
|
encoder.configure(config);
|
|
533
809
|
let framesProcessed = 0;
|
|
534
|
-
const waitForDequeue = async () => {
|
|
535
|
-
await new Promise((r) => {
|
|
536
|
-
dequeueResolver = r;
|
|
537
|
-
encoder.addEventListener("dequeue", () => r(), {
|
|
538
|
-
once: true
|
|
539
|
-
});
|
|
540
|
-
});
|
|
541
|
-
};
|
|
542
|
-
const waitForFinish = async () => {
|
|
543
|
-
while (getQueueSize() > 0) {
|
|
544
|
-
await waitForDequeue();
|
|
545
|
-
}
|
|
546
|
-
};
|
|
547
810
|
const encodeFrame = async (frame) => {
|
|
548
811
|
if (encoder.state === "closed") {
|
|
549
812
|
return;
|
|
550
813
|
}
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
814
|
+
await ioSynchronizer.waitFor({
|
|
815
|
+
unemitted: 2,
|
|
816
|
+
_unprocessed: 2
|
|
817
|
+
});
|
|
554
818
|
if (encoder.state === "closed") {
|
|
555
819
|
return;
|
|
556
820
|
}
|
|
821
|
+
const keyFrame = framesProcessed % 40 === 0;
|
|
557
822
|
encoder.encode(frame, {
|
|
558
|
-
keyFrame
|
|
823
|
+
keyFrame
|
|
559
824
|
});
|
|
825
|
+
ioSynchronizer.inputItem(frame.timestamp, keyFrame);
|
|
560
826
|
framesProcessed++;
|
|
561
827
|
};
|
|
562
828
|
let inputQueue = Promise.resolve();
|
|
@@ -568,38 +834,15 @@ var createVideoEncoder = ({
|
|
|
568
834
|
waitForFinish: async () => {
|
|
569
835
|
await encoder.flush();
|
|
570
836
|
await outputQueue;
|
|
571
|
-
await waitForFinish();
|
|
837
|
+
await ioSynchronizer.waitForFinish();
|
|
572
838
|
},
|
|
573
839
|
close,
|
|
574
|
-
getQueueSize,
|
|
575
840
|
flush: async () => {
|
|
576
841
|
await encoder.flush();
|
|
577
842
|
}
|
|
578
843
|
};
|
|
579
844
|
};
|
|
580
845
|
|
|
581
|
-
// src/video-encoder-config.ts
|
|
582
|
-
var getVideoEncoderConfig = async (config) => {
|
|
583
|
-
if (typeof VideoEncoder === "undefined") {
|
|
584
|
-
return null;
|
|
585
|
-
}
|
|
586
|
-
const hardware = {
|
|
587
|
-
...config,
|
|
588
|
-
hardwareAcceleration: "prefer-hardware"
|
|
589
|
-
};
|
|
590
|
-
if ((await VideoEncoder.isConfigSupported(hardware)).supported) {
|
|
591
|
-
return hardware;
|
|
592
|
-
}
|
|
593
|
-
const software = {
|
|
594
|
-
...config,
|
|
595
|
-
hardwareAcceleration: "prefer-software"
|
|
596
|
-
};
|
|
597
|
-
if ((await VideoEncoder.isConfigSupported(software)).supported) {
|
|
598
|
-
return software;
|
|
599
|
-
}
|
|
600
|
-
return null;
|
|
601
|
-
};
|
|
602
|
-
|
|
603
846
|
// src/on-video-track.ts
|
|
604
847
|
var makeVideoTrackHandler = ({
|
|
605
848
|
state,
|
|
@@ -609,25 +852,23 @@ var makeVideoTrackHandler = ({
|
|
|
609
852
|
convertMediaState,
|
|
610
853
|
controller,
|
|
611
854
|
videoCodec,
|
|
612
|
-
onVideoTrack
|
|
855
|
+
onVideoTrack,
|
|
856
|
+
logLevel,
|
|
857
|
+
container
|
|
613
858
|
}) => async (track) => {
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
});
|
|
619
|
-
const videoDecoderConfig = await getVideoDecoderConfigWithHardwareAcceleration(track);
|
|
620
|
-
const videoOperation = await resolveVideoAction({
|
|
621
|
-
videoDecoderConfig,
|
|
622
|
-
videoEncoderConfig,
|
|
859
|
+
if (controller.signal.aborted) {
|
|
860
|
+
throw new error_cause_default("Aborted");
|
|
861
|
+
}
|
|
862
|
+
const videoOperation = await (onVideoTrack ?? defaultResolveVideoAction)({
|
|
623
863
|
track,
|
|
624
864
|
videoCodec,
|
|
625
|
-
|
|
865
|
+
logLevel,
|
|
866
|
+
container
|
|
626
867
|
});
|
|
627
|
-
if (videoOperation === "drop") {
|
|
868
|
+
if (videoOperation.type === "drop") {
|
|
628
869
|
return null;
|
|
629
870
|
}
|
|
630
|
-
if (videoOperation === "copy") {
|
|
871
|
+
if (videoOperation.type === "copy") {
|
|
631
872
|
const videoTrack = await state.addTrack({
|
|
632
873
|
type: "video",
|
|
633
874
|
color: track.color,
|
|
@@ -636,12 +877,18 @@ var makeVideoTrackHandler = ({
|
|
|
636
877
|
codec: track.codecWithoutConfig,
|
|
637
878
|
codecPrivate: track.codecPrivate
|
|
638
879
|
});
|
|
639
|
-
return (sample) => {
|
|
640
|
-
state.addSample(
|
|
880
|
+
return async (sample) => {
|
|
881
|
+
await state.addSample(sample, videoTrack.trackNumber, true);
|
|
641
882
|
convertMediaState.decodedVideoFrames++;
|
|
642
883
|
onMediaStateUpdate?.({ ...convertMediaState });
|
|
643
884
|
};
|
|
644
885
|
}
|
|
886
|
+
const videoEncoderConfig = await getVideoEncoderConfig({
|
|
887
|
+
codec: videoOperation.videoCodec,
|
|
888
|
+
height: track.displayAspectHeight,
|
|
889
|
+
width: track.displayAspectWidth
|
|
890
|
+
});
|
|
891
|
+
const videoDecoderConfig = await getVideoDecoderConfigWithHardwareAcceleration(track);
|
|
645
892
|
if (videoEncoderConfig === null) {
|
|
646
893
|
abortConversion(new error_cause_default(`Could not configure video encoder of track ${track.trackId}`));
|
|
647
894
|
return null;
|
|
@@ -660,7 +907,7 @@ var makeVideoTrackHandler = ({
|
|
|
660
907
|
});
|
|
661
908
|
const videoEncoder = createVideoEncoder({
|
|
662
909
|
onChunk: async (chunk) => {
|
|
663
|
-
await state.addSample(chunk, trackNumber, true);
|
|
910
|
+
await state.addSample(convertEncodedChunk(chunk), trackNumber, true);
|
|
664
911
|
convertMediaState.encodedVideoFrames++;
|
|
665
912
|
onMediaStateUpdate?.({ ...convertMediaState });
|
|
666
913
|
},
|
|
@@ -670,23 +917,28 @@ var makeVideoTrackHandler = ({
|
|
|
670
917
|
}));
|
|
671
918
|
},
|
|
672
919
|
signal: controller.signal,
|
|
673
|
-
config: videoEncoderConfig
|
|
920
|
+
config: videoEncoderConfig,
|
|
921
|
+
logLevel
|
|
674
922
|
});
|
|
675
923
|
const videoDecoder = createVideoDecoder({
|
|
676
924
|
config: videoDecoderConfig,
|
|
677
925
|
onFrame: async (frame) => {
|
|
678
|
-
await
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
926
|
+
await onFrame({
|
|
927
|
+
convertMediaState,
|
|
928
|
+
frame,
|
|
929
|
+
onMediaStateUpdate,
|
|
930
|
+
track,
|
|
931
|
+
videoEncoder,
|
|
932
|
+
onVideoFrame
|
|
933
|
+
});
|
|
683
934
|
},
|
|
684
935
|
onError: (err) => {
|
|
685
936
|
abortConversion(new error_cause_default(`Video decoder of track ${track.trackId} failed (see .cause of this error)`, {
|
|
686
937
|
cause: err
|
|
687
938
|
}));
|
|
688
939
|
},
|
|
689
|
-
signal: controller.signal
|
|
940
|
+
signal: controller.signal,
|
|
941
|
+
logLevel
|
|
690
942
|
});
|
|
691
943
|
state.addWaitForFinishPromise(async () => {
|
|
692
944
|
await videoDecoder.waitForFinish();
|
|
@@ -699,30 +951,27 @@ var makeVideoTrackHandler = ({
|
|
|
699
951
|
};
|
|
700
952
|
};
|
|
701
953
|
|
|
702
|
-
// src/with-resolvers.ts
|
|
703
|
-
var withResolvers = function() {
|
|
704
|
-
let resolve;
|
|
705
|
-
let reject;
|
|
706
|
-
const promise = new Promise((res, rej) => {
|
|
707
|
-
resolve = res;
|
|
708
|
-
reject = rej;
|
|
709
|
-
});
|
|
710
|
-
return { promise, resolve, reject };
|
|
711
|
-
};
|
|
712
|
-
|
|
713
954
|
// src/convert-media.ts
|
|
714
|
-
var convertMedia = async ({
|
|
955
|
+
var convertMedia = async function({
|
|
715
956
|
src,
|
|
716
957
|
onVideoFrame,
|
|
717
|
-
onMediaStateUpdate,
|
|
958
|
+
onMediaStateUpdate: onMediaStateDoNoCallDirectly,
|
|
718
959
|
audioCodec,
|
|
719
|
-
|
|
960
|
+
container,
|
|
720
961
|
videoCodec,
|
|
721
962
|
signal: userPassedAbortSignal,
|
|
722
963
|
onAudioTrack: userAudioResolver,
|
|
723
|
-
onVideoTrack: userVideoResolver
|
|
724
|
-
|
|
725
|
-
|
|
964
|
+
onVideoTrack: userVideoResolver,
|
|
965
|
+
reader,
|
|
966
|
+
fields,
|
|
967
|
+
logLevel = "info",
|
|
968
|
+
writer,
|
|
969
|
+
...more
|
|
970
|
+
}) {
|
|
971
|
+
if (userPassedAbortSignal?.aborted) {
|
|
972
|
+
return Promise.reject(new error_cause_default("Aborted"));
|
|
973
|
+
}
|
|
974
|
+
if (container !== "webm") {
|
|
726
975
|
return Promise.reject(new TypeError('Only `to: "webm"` is supported currently'));
|
|
727
976
|
}
|
|
728
977
|
if (audioCodec !== "opus") {
|
|
@@ -731,7 +980,7 @@ var convertMedia = async ({
|
|
|
731
980
|
if (videoCodec !== "vp8" && videoCodec !== "vp9") {
|
|
732
981
|
return Promise.reject(new TypeError('Only `videoCodec: "vp8"` and `videoCodec: "vp9"` are supported currently'));
|
|
733
982
|
}
|
|
734
|
-
const {
|
|
983
|
+
const { resolve, reject, getPromiseToImmediatelyReturn } = withResolversAndWaitForReturn();
|
|
735
984
|
const controller = new AbortController;
|
|
736
985
|
const abortConversion = (errCause) => {
|
|
737
986
|
reject(errCause);
|
|
@@ -747,10 +996,35 @@ var convertMedia = async ({
|
|
|
747
996
|
decodedAudioFrames: 0,
|
|
748
997
|
decodedVideoFrames: 0,
|
|
749
998
|
encodedVideoFrames: 0,
|
|
750
|
-
encodedAudioFrames: 0
|
|
999
|
+
encodedAudioFrames: 0,
|
|
1000
|
+
bytesWritten: 0,
|
|
1001
|
+
millisecondsWritten: 0,
|
|
1002
|
+
expectedOutputDurationInMs: null,
|
|
1003
|
+
overallProgress: 0
|
|
751
1004
|
};
|
|
752
|
-
const
|
|
753
|
-
|
|
1005
|
+
const onMediaStateUpdate = (newState) => {
|
|
1006
|
+
if (controller.signal.aborted) {
|
|
1007
|
+
return;
|
|
1008
|
+
}
|
|
1009
|
+
onMediaStateDoNoCallDirectly?.(newState);
|
|
1010
|
+
};
|
|
1011
|
+
const state = await MediaParserInternals2.createMedia({
|
|
1012
|
+
writer: await autoSelectWriter(writer, logLevel),
|
|
1013
|
+
onBytesProgress: (bytesWritten) => {
|
|
1014
|
+
convertMediaState.bytesWritten = bytesWritten;
|
|
1015
|
+
onMediaStateUpdate?.(convertMediaState);
|
|
1016
|
+
},
|
|
1017
|
+
onMillisecondsProgress: (millisecondsWritten) => {
|
|
1018
|
+
if (millisecondsWritten > convertMediaState.millisecondsWritten) {
|
|
1019
|
+
convertMediaState.millisecondsWritten = millisecondsWritten;
|
|
1020
|
+
convertMediaState.overallProgress = calculateProgress({
|
|
1021
|
+
millisecondsWritten: convertMediaState.millisecondsWritten,
|
|
1022
|
+
expectedOutputDurationInMs: convertMediaState.expectedOutputDurationInMs
|
|
1023
|
+
});
|
|
1024
|
+
onMediaStateUpdate?.(convertMediaState);
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
});
|
|
754
1028
|
const onVideoTrack = makeVideoTrackHandler({
|
|
755
1029
|
state,
|
|
756
1030
|
onVideoFrame: onVideoFrame ?? null,
|
|
@@ -759,7 +1033,9 @@ var convertMedia = async ({
|
|
|
759
1033
|
convertMediaState,
|
|
760
1034
|
controller,
|
|
761
1035
|
videoCodec,
|
|
762
|
-
onVideoTrack: userVideoResolver ??
|
|
1036
|
+
onVideoTrack: userVideoResolver ?? null,
|
|
1037
|
+
logLevel,
|
|
1038
|
+
container
|
|
763
1039
|
});
|
|
764
1040
|
const onAudioTrack = makeAudioTrackHandler({
|
|
765
1041
|
abortConversion,
|
|
@@ -768,29 +1044,58 @@ var convertMedia = async ({
|
|
|
768
1044
|
convertMediaState,
|
|
769
1045
|
onMediaStateUpdate: onMediaStateUpdate ?? null,
|
|
770
1046
|
state,
|
|
771
|
-
onAudioTrack: userAudioResolver ??
|
|
772
|
-
|
|
1047
|
+
onAudioTrack: userAudioResolver ?? null,
|
|
1048
|
+
logLevel,
|
|
1049
|
+
container
|
|
773
1050
|
});
|
|
774
1051
|
parseMedia({
|
|
775
1052
|
src,
|
|
776
1053
|
onVideoTrack,
|
|
777
1054
|
onAudioTrack,
|
|
778
|
-
signal: controller.signal
|
|
1055
|
+
signal: controller.signal,
|
|
1056
|
+
fields: {
|
|
1057
|
+
...fields,
|
|
1058
|
+
durationInSeconds: true
|
|
1059
|
+
},
|
|
1060
|
+
reader,
|
|
1061
|
+
...more,
|
|
1062
|
+
onDurationInSeconds: (durationInSeconds) => {
|
|
1063
|
+
if (durationInSeconds === null) {
|
|
1064
|
+
return null;
|
|
1065
|
+
}
|
|
1066
|
+
const casted = more;
|
|
1067
|
+
if (casted.onDurationInSeconds) {
|
|
1068
|
+
casted.onDurationInSeconds(durationInSeconds);
|
|
1069
|
+
}
|
|
1070
|
+
const expectedOutputDurationInMs = durationInSeconds * 1000;
|
|
1071
|
+
convertMediaState.expectedOutputDurationInMs = expectedOutputDurationInMs;
|
|
1072
|
+
convertMediaState.overallProgress = calculateProgress({
|
|
1073
|
+
millisecondsWritten: convertMediaState.millisecondsWritten,
|
|
1074
|
+
expectedOutputDurationInMs
|
|
1075
|
+
});
|
|
1076
|
+
onMediaStateUpdate(convertMediaState);
|
|
1077
|
+
}
|
|
779
1078
|
}).then(() => {
|
|
780
1079
|
return state.waitForFinish();
|
|
781
1080
|
}).then(() => {
|
|
782
1081
|
resolve({ save: state.save, remove: state.remove });
|
|
783
1082
|
}).catch((err) => {
|
|
784
1083
|
reject(err);
|
|
785
|
-
})
|
|
1084
|
+
});
|
|
1085
|
+
return getPromiseToImmediatelyReturn().finally(() => {
|
|
786
1086
|
userPassedAbortSignal?.removeEventListener("abort", onUserAbort);
|
|
787
1087
|
});
|
|
788
|
-
return promise;
|
|
789
1088
|
};
|
|
790
1089
|
export {
|
|
1090
|
+
getAvailableVideoCodecs,
|
|
1091
|
+
getAvailableAudioCodecs,
|
|
791
1092
|
createVideoEncoder,
|
|
792
1093
|
createVideoDecoder,
|
|
793
1094
|
createAudioEncoder,
|
|
794
1095
|
createAudioDecoder,
|
|
795
|
-
convertMedia
|
|
1096
|
+
convertMedia,
|
|
1097
|
+
canReencodeVideoTrack,
|
|
1098
|
+
canReencodeAudioTrack,
|
|
1099
|
+
canCopyVideoTrack,
|
|
1100
|
+
canCopyAudioTrack
|
|
796
1101
|
};
|