@remotion/web-renderer 4.0.430 → 4.0.432
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/can-render-types.d.ts +2 -2
- package/dist/esm/index.mjs +274 -94
- package/dist/index.d.ts +1 -1
- package/dist/mediabunny-mappings.d.ts +6 -4
- package/dist/register-mp3-encoder.d.ts +1 -0
- package/dist/render-media-on-web.d.ts +1 -1
- package/package.json +9 -8
- package/dist/add-sample.js +0 -20
- package/dist/artifact.js +0 -56
- package/dist/audio.js +0 -42
- package/dist/can-use-webfs-target.js +0 -19
- package/dist/compose.js +0 -85
- package/dist/create-scaffold.js +0 -104
- package/dist/drawing/border-radius.js +0 -151
- package/dist/drawing/calculate-object-fit.js +0 -208
- package/dist/drawing/calculate-transforms.js +0 -127
- package/dist/drawing/clamp-rect-to-parent-bounds.js +0 -18
- package/dist/drawing/do-rects-intersect.js +0 -6
- package/dist/drawing/draw-background.js +0 -62
- package/dist/drawing/draw-border.js +0 -353
- package/dist/drawing/draw-box-shadow.js +0 -103
- package/dist/drawing/draw-dom-element.js +0 -85
- package/dist/drawing/draw-element.js +0 -84
- package/dist/drawing/draw-outline.js +0 -93
- package/dist/drawing/draw-rounded.js +0 -34
- package/dist/drawing/drawn-fn.js +0 -1
- package/dist/drawing/fit-svg-into-its-dimensions.js +0 -35
- package/dist/drawing/get-clipped-background.d.ts +0 -8
- package/dist/drawing/get-clipped-background.js +0 -14
- package/dist/drawing/get-padding-box.js +0 -30
- package/dist/drawing/get-pretransform-rect.js +0 -49
- package/dist/drawing/handle-3d-transform.js +0 -26
- package/dist/drawing/handle-mask.js +0 -21
- package/dist/drawing/has-transform.js +0 -14
- package/dist/drawing/mask-image.js +0 -14
- package/dist/drawing/opacity.js +0 -7
- package/dist/drawing/overflow.js +0 -14
- package/dist/drawing/parse-linear-gradient.js +0 -260
- package/dist/drawing/parse-transform-origin.js +0 -7
- package/dist/drawing/precompose.d.ts +0 -11
- package/dist/drawing/precompose.js +0 -14
- package/dist/drawing/process-node.js +0 -122
- package/dist/drawing/round-to-expand-rect.js +0 -7
- package/dist/drawing/text/apply-text-transform.js +0 -12
- package/dist/drawing/text/draw-text.js +0 -53
- package/dist/drawing/text/find-line-breaks.text.js +0 -118
- package/dist/drawing/text/get-collapsed-text.d.ts +0 -1
- package/dist/drawing/text/get-collapsed-text.js +0 -46
- package/dist/drawing/text/handle-text-node.js +0 -24
- package/dist/drawing/transform-in-3d.js +0 -177
- package/dist/drawing/transform-rect-with-matrix.js +0 -19
- package/dist/drawing/transform.js +0 -10
- package/dist/drawing/turn-svg-into-drawable.js +0 -41
- package/dist/frame-range.js +0 -15
- package/dist/get-audio-encoding-config.js +0 -18
- package/dist/get-biggest-bounding-client-rect.js +0 -43
- package/dist/index.js +0 -2
- package/dist/internal-state.js +0 -36
- package/dist/mediabunny-mappings.js +0 -63
- package/dist/output-target.js +0 -1
- package/dist/props-if-has-props.js +0 -1
- package/dist/render-media-on-web.js +0 -304
- package/dist/render-operations-queue.js +0 -3
- package/dist/render-still-on-web.js +0 -110
- package/dist/send-telemetry-event.js +0 -22
- package/dist/take-screenshot.js +0 -30
- package/dist/throttle-progress.js +0 -43
- package/dist/tree-walker-cleanup-after-children.js +0 -33
- package/dist/update-time.js +0 -17
- package/dist/validate-video-frame.js +0 -34
- package/dist/wait-for-ready.js +0 -39
- package/dist/walk-tree.js +0 -14
- package/dist/web-fs-target.js +0 -41
- package/dist/with-resolvers.js +0 -9
|
@@ -9,13 +9,13 @@ export type CanRenderIssue = {
|
|
|
9
9
|
export type CanRenderMediaOnWebResult = {
|
|
10
10
|
canRender: boolean;
|
|
11
11
|
issues: CanRenderIssue[];
|
|
12
|
-
resolvedVideoCodec: WebRendererVideoCodec;
|
|
12
|
+
resolvedVideoCodec: WebRendererVideoCodec | null;
|
|
13
13
|
resolvedAudioCodec: WebRendererAudioCodec | null;
|
|
14
14
|
resolvedOutputTarget: WebRendererOutputTarget;
|
|
15
15
|
};
|
|
16
16
|
export type CanRenderMediaOnWebOptions = {
|
|
17
17
|
container?: WebRendererContainer;
|
|
18
|
-
videoCodec?: WebRendererVideoCodec;
|
|
18
|
+
videoCodec?: WebRendererVideoCodec | null;
|
|
19
19
|
audioCodec?: WebRendererAudioCodec | null;
|
|
20
20
|
width: number;
|
|
21
21
|
height: number;
|
package/dist/esm/index.mjs
CHANGED
|
@@ -1,3 +1,26 @@
|
|
|
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 __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
18
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
19
|
+
}) : x)(function(x) {
|
|
20
|
+
if (typeof require !== "undefined")
|
|
21
|
+
return require.apply(this, arguments);
|
|
22
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
23
|
+
});
|
|
1
24
|
var __dispose = Symbol.dispose || /* @__PURE__ */ Symbol.for("Symbol.dispose");
|
|
2
25
|
var __asyncDispose = Symbol.asyncDispose || /* @__PURE__ */ Symbol.for("Symbol.asyncDispose");
|
|
3
26
|
var __using = (stack, value, async) => {
|
|
@@ -91,14 +114,23 @@ var checkWebGLSupport = () => {
|
|
|
91
114
|
|
|
92
115
|
// src/mediabunny-mappings.ts
|
|
93
116
|
import {
|
|
117
|
+
AdtsOutputFormat,
|
|
118
|
+
MkvOutputFormat,
|
|
119
|
+
MovOutputFormat,
|
|
120
|
+
Mp3OutputFormat,
|
|
94
121
|
Mp4OutputFormat,
|
|
122
|
+
OggOutputFormat,
|
|
95
123
|
QUALITY_HIGH,
|
|
96
124
|
QUALITY_LOW,
|
|
97
125
|
QUALITY_MEDIUM,
|
|
98
126
|
QUALITY_VERY_HIGH,
|
|
99
127
|
QUALITY_VERY_LOW,
|
|
128
|
+
WavOutputFormat,
|
|
100
129
|
WebMOutputFormat
|
|
101
130
|
} from "mediabunny";
|
|
131
|
+
var isAudioOnlyContainer = (container) => {
|
|
132
|
+
return container === "wav" || container === "mp3" || container === "aac" || container === "ogg";
|
|
133
|
+
};
|
|
102
134
|
var codecToMediabunnyCodec = (codec) => {
|
|
103
135
|
switch (codec) {
|
|
104
136
|
case "h264":
|
|
@@ -118,9 +150,21 @@ var codecToMediabunnyCodec = (codec) => {
|
|
|
118
150
|
var containerToMediabunnyContainer = (container) => {
|
|
119
151
|
switch (container) {
|
|
120
152
|
case "mp4":
|
|
121
|
-
return new Mp4OutputFormat;
|
|
153
|
+
return new Mp4OutputFormat({ fastStart: "reserve" });
|
|
122
154
|
case "webm":
|
|
123
155
|
return new WebMOutputFormat;
|
|
156
|
+
case "mkv":
|
|
157
|
+
return new MkvOutputFormat;
|
|
158
|
+
case "wav":
|
|
159
|
+
return new WavOutputFormat;
|
|
160
|
+
case "mp3":
|
|
161
|
+
return new Mp3OutputFormat;
|
|
162
|
+
case "aac":
|
|
163
|
+
return new AdtsOutputFormat;
|
|
164
|
+
case "ogg":
|
|
165
|
+
return new OggOutputFormat;
|
|
166
|
+
case "mov":
|
|
167
|
+
return new MovOutputFormat({ fastStart: "reserve" });
|
|
124
168
|
default:
|
|
125
169
|
throw new Error(`Unsupported container: ${container}`);
|
|
126
170
|
}
|
|
@@ -131,10 +175,31 @@ var getDefaultVideoCodecForContainer = (container) => {
|
|
|
131
175
|
return "h264";
|
|
132
176
|
case "webm":
|
|
133
177
|
return "vp8";
|
|
178
|
+
case "mkv":
|
|
179
|
+
case "mov":
|
|
180
|
+
return "h264";
|
|
181
|
+
case "wav":
|
|
182
|
+
case "mp3":
|
|
183
|
+
case "aac":
|
|
184
|
+
case "ogg":
|
|
185
|
+
return null;
|
|
134
186
|
default:
|
|
135
187
|
throw new Error(`Unsupported container: ${container}`);
|
|
136
188
|
}
|
|
137
189
|
};
|
|
190
|
+
var getDefaultContainerForCodec = (codec) => {
|
|
191
|
+
switch (codec) {
|
|
192
|
+
case "h264":
|
|
193
|
+
case "h265":
|
|
194
|
+
case "av1":
|
|
195
|
+
return "mp4";
|
|
196
|
+
case "vp8":
|
|
197
|
+
case "vp9":
|
|
198
|
+
return "webm";
|
|
199
|
+
default:
|
|
200
|
+
throw new Error(`Unsupported codec: ${codec}`);
|
|
201
|
+
}
|
|
202
|
+
};
|
|
138
203
|
var getQualityForWebRendererQuality = (quality) => {
|
|
139
204
|
switch (quality) {
|
|
140
205
|
case "very-low":
|
|
@@ -157,6 +222,18 @@ var getMimeType = (container) => {
|
|
|
157
222
|
return "video/mp4";
|
|
158
223
|
case "webm":
|
|
159
224
|
return "video/webm";
|
|
225
|
+
case "mkv":
|
|
226
|
+
return "video/x-matroska";
|
|
227
|
+
case "wav":
|
|
228
|
+
return "audio/wav";
|
|
229
|
+
case "mp3":
|
|
230
|
+
return "audio/mpeg";
|
|
231
|
+
case "aac":
|
|
232
|
+
return "audio/aac";
|
|
233
|
+
case "ogg":
|
|
234
|
+
return "audio/ogg";
|
|
235
|
+
case "mov":
|
|
236
|
+
return "video/quicktime";
|
|
160
237
|
default:
|
|
161
238
|
throw new Error(`Unsupported container: ${container}`);
|
|
162
239
|
}
|
|
@@ -167,6 +244,18 @@ var getDefaultAudioCodecForContainer = (container) => {
|
|
|
167
244
|
return "aac";
|
|
168
245
|
case "webm":
|
|
169
246
|
return "opus";
|
|
247
|
+
case "mkv":
|
|
248
|
+
return "aac";
|
|
249
|
+
case "wav":
|
|
250
|
+
return "pcm-s16";
|
|
251
|
+
case "mp3":
|
|
252
|
+
return "mp3";
|
|
253
|
+
case "aac":
|
|
254
|
+
return "aac";
|
|
255
|
+
case "ogg":
|
|
256
|
+
return "opus";
|
|
257
|
+
case "mov":
|
|
258
|
+
return "aac";
|
|
170
259
|
default:
|
|
171
260
|
throw new Error(`Unsupported container: ${container}`);
|
|
172
261
|
}
|
|
@@ -179,22 +268,63 @@ var WEB_RENDERER_VIDEO_CODECS = [
|
|
|
179
268
|
"av1"
|
|
180
269
|
];
|
|
181
270
|
var getSupportedVideoCodecsForContainer = (container) => {
|
|
271
|
+
if (isAudioOnlyContainer(container)) {
|
|
272
|
+
return [];
|
|
273
|
+
}
|
|
182
274
|
const format = containerToMediabunnyContainer(container);
|
|
183
275
|
const allSupported = format.getSupportedVideoCodecs();
|
|
184
276
|
return WEB_RENDERER_VIDEO_CODECS.filter((codec) => allSupported.includes(codecToMediabunnyCodec(codec)));
|
|
185
277
|
};
|
|
186
|
-
var WEB_RENDERER_AUDIO_CODECS = [
|
|
278
|
+
var WEB_RENDERER_AUDIO_CODECS = [
|
|
279
|
+
"aac",
|
|
280
|
+
"opus",
|
|
281
|
+
"mp3",
|
|
282
|
+
"vorbis",
|
|
283
|
+
"pcm-s16"
|
|
284
|
+
];
|
|
285
|
+
var audioCodecToMediabunnyAudioCodec = (audioCodec) => {
|
|
286
|
+
switch (audioCodec) {
|
|
287
|
+
case "aac":
|
|
288
|
+
return "aac";
|
|
289
|
+
case "opus":
|
|
290
|
+
return "opus";
|
|
291
|
+
case "mp3":
|
|
292
|
+
return "mp3";
|
|
293
|
+
case "vorbis":
|
|
294
|
+
return "vorbis";
|
|
295
|
+
case "pcm-s16":
|
|
296
|
+
return "pcm-s16";
|
|
297
|
+
default:
|
|
298
|
+
throw new Error(`Unsupported audio codec: ${audioCodec}`);
|
|
299
|
+
}
|
|
300
|
+
};
|
|
187
301
|
var getSupportedAudioCodecsForContainer = (container) => {
|
|
188
302
|
const format = containerToMediabunnyContainer(container);
|
|
189
303
|
const allSupported = format.getSupportedAudioCodecs();
|
|
190
|
-
return WEB_RENDERER_AUDIO_CODECS.filter((codec) => allSupported.includes(codec));
|
|
191
|
-
};
|
|
192
|
-
var audioCodecToMediabunnyAudioCodec = (audioCodec) => {
|
|
193
|
-
return audioCodec;
|
|
304
|
+
return WEB_RENDERER_AUDIO_CODECS.filter((codec) => allSupported.includes(audioCodecToMediabunnyAudioCodec(codec)));
|
|
194
305
|
};
|
|
195
306
|
|
|
196
307
|
// src/resolve-audio-codec.ts
|
|
308
|
+
import { canEncodeAudio as canEncodeAudio2 } from "mediabunny";
|
|
309
|
+
|
|
310
|
+
// src/register-mp3-encoder.ts
|
|
197
311
|
import { canEncodeAudio } from "mediabunny";
|
|
312
|
+
var registrationPromise = null;
|
|
313
|
+
var doRegister = async () => {
|
|
314
|
+
const nativeSupport = await canEncodeAudio("mp3");
|
|
315
|
+
if (!nativeSupport) {
|
|
316
|
+
const { registerMp3Encoder } = await import("@mediabunny/mp3-encoder");
|
|
317
|
+
registerMp3Encoder();
|
|
318
|
+
}
|
|
319
|
+
};
|
|
320
|
+
var ensureMp3EncoderRegistered = () => {
|
|
321
|
+
if (!registrationPromise) {
|
|
322
|
+
registrationPromise = doRegister();
|
|
323
|
+
}
|
|
324
|
+
return registrationPromise;
|
|
325
|
+
};
|
|
326
|
+
|
|
327
|
+
// src/resolve-audio-codec.ts
|
|
198
328
|
var resolveAudioCodec = async (options) => {
|
|
199
329
|
const issues = [];
|
|
200
330
|
const { container, requestedCodec, userSpecifiedAudioCodec, bitrate } = options;
|
|
@@ -209,7 +339,10 @@ var resolveAudioCodec = async (options) => {
|
|
|
209
339
|
return { codec: null, issues };
|
|
210
340
|
}
|
|
211
341
|
const mediabunnyAudioCodec = audioCodecToMediabunnyAudioCodec(audioCodec);
|
|
212
|
-
|
|
342
|
+
if (audioCodec === "mp3") {
|
|
343
|
+
await ensureMp3EncoderRegistered();
|
|
344
|
+
}
|
|
345
|
+
const canEncode = await canEncodeAudio2(mediabunnyAudioCodec, { bitrate });
|
|
213
346
|
if (canEncode) {
|
|
214
347
|
return { codec: audioCodec, issues };
|
|
215
348
|
}
|
|
@@ -223,8 +356,11 @@ var resolveAudioCodec = async (options) => {
|
|
|
223
356
|
}
|
|
224
357
|
for (const fallbackCodec of supportedAudioCodecs) {
|
|
225
358
|
if (fallbackCodec !== audioCodec) {
|
|
359
|
+
if (fallbackCodec === "mp3") {
|
|
360
|
+
await ensureMp3EncoderRegistered();
|
|
361
|
+
}
|
|
226
362
|
const fallbackMediabunnyCodec = audioCodecToMediabunnyAudioCodec(fallbackCodec);
|
|
227
|
-
const canEncodeFallback = await
|
|
363
|
+
const canEncodeFallback = await canEncodeAudio2(fallbackMediabunnyCodec, {
|
|
228
364
|
bitrate
|
|
229
365
|
});
|
|
230
366
|
if (canEncodeFallback) {
|
|
@@ -263,46 +399,61 @@ var validateDimensions = (options) => {
|
|
|
263
399
|
// src/can-render-media-on-web.ts
|
|
264
400
|
var canRenderMediaOnWeb = async (options) => {
|
|
265
401
|
const issues = [];
|
|
266
|
-
if (typeof VideoEncoder === "undefined") {
|
|
267
|
-
issues.push({
|
|
268
|
-
type: "webcodecs-unavailable",
|
|
269
|
-
message: "WebCodecs API is not available in this browser. A modern browser with WebCodecs support is required.",
|
|
270
|
-
severity: "error"
|
|
271
|
-
});
|
|
272
|
-
}
|
|
273
402
|
const container = options.container ?? "mp4";
|
|
274
|
-
const videoCodec = options.videoCodec ?? getDefaultVideoCodecForContainer(container);
|
|
403
|
+
const videoCodec = options.videoCodec ?? getDefaultVideoCodecForContainer(container) ?? null;
|
|
404
|
+
const videoEnabled = !isAudioOnlyContainer(container);
|
|
275
405
|
const transparent = options.transparent ?? false;
|
|
276
406
|
const muted = options.muted ?? false;
|
|
277
407
|
const { width, height } = options;
|
|
278
408
|
const resolvedVideoBitrate = typeof options.videoBitrate === "number" ? options.videoBitrate : getQualityForWebRendererQuality(options.videoBitrate ?? "medium");
|
|
279
409
|
const resolvedAudioBitrate = typeof options.audioBitrate === "number" ? options.audioBitrate : getQualityForWebRendererQuality(options.audioBitrate ?? "medium");
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
410
|
+
if (videoEnabled) {
|
|
411
|
+
if (typeof VideoEncoder === "undefined") {
|
|
412
|
+
issues.push({
|
|
413
|
+
type: "webcodecs-unavailable",
|
|
414
|
+
message: "WebCodecs API is not available in this browser. A modern browser with WebCodecs support is required.",
|
|
415
|
+
severity: "error"
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
if (!videoCodec) {
|
|
419
|
+
issues.push({
|
|
420
|
+
type: "container-codec-mismatch",
|
|
421
|
+
message: `A video codec is required for container ${container}`,
|
|
422
|
+
severity: "error"
|
|
423
|
+
});
|
|
424
|
+
} else {
|
|
425
|
+
const format = containerToMediabunnyContainer(container);
|
|
426
|
+
if (!format.getSupportedCodecs().includes(codecToMediabunnyCodec(videoCodec))) {
|
|
427
|
+
issues.push({
|
|
428
|
+
type: "container-codec-mismatch",
|
|
429
|
+
message: `Codec ${videoCodec} is not supported for container ${container}`,
|
|
430
|
+
severity: "error"
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
const dimensionIssue = validateDimensions({
|
|
434
|
+
width,
|
|
435
|
+
height,
|
|
436
|
+
codec: videoCodec
|
|
437
|
+
});
|
|
438
|
+
if (dimensionIssue) {
|
|
439
|
+
issues.push(dimensionIssue);
|
|
440
|
+
}
|
|
441
|
+
const canEncodeVideoResult = await canEncodeVideo(codecToMediabunnyCodec(videoCodec), { bitrate: resolvedVideoBitrate });
|
|
442
|
+
if (!canEncodeVideoResult) {
|
|
443
|
+
issues.push({
|
|
444
|
+
type: "video-codec-unsupported",
|
|
445
|
+
message: `Video codec "${videoCodec}" cannot be encoded by this browser`,
|
|
446
|
+
severity: "error"
|
|
447
|
+
});
|
|
448
|
+
}
|
|
449
|
+
if (transparent && !["vp8", "vp9"].includes(videoCodec)) {
|
|
450
|
+
issues.push({
|
|
451
|
+
type: "transparent-video-unsupported",
|
|
452
|
+
message: `Transparent video requires VP8 or VP9 codec with WebM container. ${videoCodec} does not support alpha channel.`,
|
|
453
|
+
severity: "error"
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
}
|
|
306
457
|
}
|
|
307
458
|
let resolvedAudioCodec = null;
|
|
308
459
|
if (!muted) {
|
|
@@ -338,7 +489,7 @@ var canRenderMediaOnWeb = async (options) => {
|
|
|
338
489
|
return {
|
|
339
490
|
canRender: issues.filter((i) => i.severity === "error").length === 0,
|
|
340
491
|
issues,
|
|
341
|
-
resolvedVideoCodec: videoCodec,
|
|
492
|
+
resolvedVideoCodec: videoEnabled ? videoCodec : null,
|
|
342
493
|
resolvedAudioCodec,
|
|
343
494
|
resolvedOutputTarget
|
|
344
495
|
};
|
|
@@ -359,6 +510,9 @@ var getEncodableVideoCodecs = async (container, options) => {
|
|
|
359
510
|
};
|
|
360
511
|
var getEncodableAudioCodecs = async (container, options) => {
|
|
361
512
|
const supported = getSupportedAudioCodecsForContainer(container);
|
|
513
|
+
if (supported.includes("mp3")) {
|
|
514
|
+
await ensureMp3EncoderRegistered();
|
|
515
|
+
}
|
|
362
516
|
const resolvedBitrate = options?.audioBitrate ? typeof options.audioBitrate === "number" ? options.audioBitrate : getQualityForWebRendererQuality(options.audioBitrate) : undefined;
|
|
363
517
|
const encodable = await mediabunnyGetEncodableAudioCodecs(supported, {
|
|
364
518
|
bitrate: resolvedBitrate
|
|
@@ -629,6 +783,7 @@ var UpdateTime = ({
|
|
|
629
783
|
}
|
|
630
784
|
}));
|
|
631
785
|
return /* @__PURE__ */ jsx(Internals2.RemotionRootContexts, {
|
|
786
|
+
visualModeEnabled: false,
|
|
632
787
|
audioEnabled,
|
|
633
788
|
videoEnabled,
|
|
634
789
|
logLevel,
|
|
@@ -670,24 +825,27 @@ function createScaffold({
|
|
|
670
825
|
if (!ReactDOM.createRoot) {
|
|
671
826
|
throw new Error("@remotion/web-renderer requires React 18 or higher");
|
|
672
827
|
}
|
|
828
|
+
const wrapper = document.createElement("div");
|
|
829
|
+
wrapper.style.position = "fixed";
|
|
830
|
+
wrapper.style.inset = "0";
|
|
831
|
+
wrapper.style.overflow = "hidden";
|
|
832
|
+
wrapper.style.visibility = "hidden";
|
|
833
|
+
wrapper.style.pointerEvents = "none";
|
|
834
|
+
wrapper.style.zIndex = "-9999";
|
|
673
835
|
const div = document.createElement("div");
|
|
674
|
-
div.style.position = "
|
|
836
|
+
div.style.position = "absolute";
|
|
837
|
+
div.style.top = "0";
|
|
838
|
+
div.style.left = "0";
|
|
675
839
|
div.style.display = "flex";
|
|
676
840
|
div.style.flexDirection = "column";
|
|
677
841
|
div.style.backgroundColor = "transparent";
|
|
678
842
|
div.style.width = `${width}px`;
|
|
679
843
|
div.style.height = `${height}px`;
|
|
680
|
-
div.style.zIndex = "-9999";
|
|
681
|
-
div.style.top = "0";
|
|
682
|
-
div.style.left = "0";
|
|
683
|
-
div.style.right = "0";
|
|
684
|
-
div.style.bottom = "0";
|
|
685
|
-
div.style.visibility = "hidden";
|
|
686
|
-
div.style.pointerEvents = "none";
|
|
687
844
|
const scaffoldClassName = `remotion-scaffold-${Math.random().toString(36).substring(2, 15)}`;
|
|
688
845
|
div.className = scaffoldClassName;
|
|
689
846
|
const cleanupCSS = Internals3.CSSUtils.injectCSS(Internals3.CSSUtils.makeDefaultPreviewCSS(`.${scaffoldClassName}`, "white"));
|
|
690
|
-
|
|
847
|
+
wrapper.appendChild(div);
|
|
848
|
+
document.body.appendChild(wrapper);
|
|
691
849
|
const errorHolder = { error: null };
|
|
692
850
|
const root = ReactDOM.createRoot(div, {
|
|
693
851
|
onUncaughtError: (err) => {
|
|
@@ -781,6 +939,7 @@ function createScaffold({
|
|
|
781
939
|
[Symbol.dispose]: () => {
|
|
782
940
|
root.unmount();
|
|
783
941
|
div.remove();
|
|
942
|
+
wrapper.remove();
|
|
784
943
|
cleanupCSS();
|
|
785
944
|
},
|
|
786
945
|
timeUpdater,
|
|
@@ -3815,7 +3974,8 @@ var internalRenderMediaOnWeb = async ({
|
|
|
3815
3974
|
await cleanupStaleOpfsFiles();
|
|
3816
3975
|
}
|
|
3817
3976
|
const format = containerToMediabunnyContainer(container);
|
|
3818
|
-
|
|
3977
|
+
const videoEnabled = !isAudioOnlyContainer(container);
|
|
3978
|
+
if (videoEnabled && codec && !format.getSupportedCodecs().includes(codecToMediabunnyCodec(codec))) {
|
|
3819
3979
|
return Promise.reject(new Error(`Codec ${codec} is not supported for container ${container}`));
|
|
3820
3980
|
}
|
|
3821
3981
|
const resolvedAudioBitrate = typeof audioBitrate === "number" ? audioBitrate : getQualityForWebRendererQuality(audioBitrate);
|
|
@@ -3863,7 +4023,7 @@ var internalRenderMediaOnWeb = async ({
|
|
|
3863
4023
|
mediaCacheSizeInBytes,
|
|
3864
4024
|
schema: schema ?? null,
|
|
3865
4025
|
audioEnabled: !muted,
|
|
3866
|
-
videoEnabled
|
|
4026
|
+
videoEnabled,
|
|
3867
4027
|
initialFrame: 0,
|
|
3868
4028
|
defaultCodec: resolved.defaultCodec,
|
|
3869
4029
|
defaultOutName: resolved.defaultOutName
|
|
@@ -3904,7 +4064,7 @@ var internalRenderMediaOnWeb = async ({
|
|
|
3904
4064
|
if (signal?.aborted) {
|
|
3905
4065
|
throw new Error("renderMediaOnWeb() was cancelled");
|
|
3906
4066
|
}
|
|
3907
|
-
const videoSampleSource = __using(__stack, makeVideoSampleSourceCleanup({
|
|
4067
|
+
const videoSampleSource = __using(__stack, videoEnabled && codec ? makeVideoSampleSourceCleanup({
|
|
3908
4068
|
codec: codecToMediabunnyCodec(codec),
|
|
3909
4069
|
bitrate: typeof videoBitrate === "number" ? videoBitrate : getQualityForWebRendererQuality(videoBitrate),
|
|
3910
4070
|
sizeChangeBehavior: "deny",
|
|
@@ -3912,15 +4072,23 @@ var internalRenderMediaOnWeb = async ({
|
|
|
3912
4072
|
latencyMode: "quality",
|
|
3913
4073
|
keyFrameInterval: keyframeIntervalInSeconds,
|
|
3914
4074
|
alpha: transparent ? "keep" : "discard"
|
|
3915
|
-
}), 0);
|
|
3916
|
-
|
|
4075
|
+
}) : null, 0);
|
|
4076
|
+
const totalFrames = realFrameRange[1] - realFrameRange[0] + 1;
|
|
4077
|
+
const durationInSeconds = totalFrames / resolved.fps;
|
|
4078
|
+
if (videoSampleSource) {
|
|
4079
|
+
outputWithCleanup.output.addVideoTrack(videoSampleSource.videoSampleSource, {
|
|
4080
|
+
maximumPacketCount: Math.ceil(totalFrames * 1.33)
|
|
4081
|
+
});
|
|
4082
|
+
}
|
|
3917
4083
|
const audioSampleSource = __using(__stack, createAudioSampleSource({
|
|
3918
4084
|
muted,
|
|
3919
4085
|
codec: finalAudioCodec ? audioCodecToMediabunnyAudioCodec(finalAudioCodec) : null,
|
|
3920
4086
|
bitrate: resolvedAudioBitrate
|
|
3921
4087
|
}), 0);
|
|
3922
4088
|
if (audioSampleSource) {
|
|
3923
|
-
outputWithCleanup.output.addAudioTrack(audioSampleSource.audioSampleSource
|
|
4089
|
+
outputWithCleanup.output.addAudioTrack(audioSampleSource.audioSampleSource, {
|
|
4090
|
+
maximumPacketCount: Math.ceil(durationInSeconds * 100 * 1.33)
|
|
4091
|
+
});
|
|
3924
4092
|
}
|
|
3925
4093
|
await outputWithCleanup.output.start();
|
|
3926
4094
|
if (signal?.aborted) {
|
|
@@ -3947,44 +4115,49 @@ var internalRenderMediaOnWeb = async ({
|
|
|
3947
4115
|
if (signal?.aborted) {
|
|
3948
4116
|
throw new Error("renderMediaOnWeb() was cancelled");
|
|
3949
4117
|
}
|
|
3950
|
-
const createFrameStart = performance.now();
|
|
3951
|
-
const layer = await createLayer({
|
|
3952
|
-
element: div,
|
|
3953
|
-
scale,
|
|
3954
|
-
logLevel,
|
|
3955
|
-
internalState,
|
|
3956
|
-
onlyBackgroundClipText: false,
|
|
3957
|
-
cutout: new DOMRect(0, 0, resolved.width, resolved.height)
|
|
3958
|
-
});
|
|
3959
|
-
internalState.addCreateFrameTime(performance.now() - createFrameStart);
|
|
3960
|
-
if (signal?.aborted) {
|
|
3961
|
-
throw new Error("renderMediaOnWeb() was cancelled");
|
|
3962
|
-
}
|
|
3963
4118
|
const timestamp = Math.round((frame - realFrameRange[0]) / resolved.fps * 1e6);
|
|
3964
|
-
|
|
3965
|
-
|
|
3966
|
-
|
|
3967
|
-
|
|
3968
|
-
|
|
3969
|
-
|
|
3970
|
-
|
|
3971
|
-
|
|
4119
|
+
let frameToEncode = null;
|
|
4120
|
+
let layerCanvas = null;
|
|
4121
|
+
if (videoEnabled) {
|
|
4122
|
+
const createFrameStart = performance.now();
|
|
4123
|
+
const layer = await createLayer({
|
|
4124
|
+
element: div,
|
|
4125
|
+
scale,
|
|
4126
|
+
logLevel,
|
|
4127
|
+
internalState,
|
|
4128
|
+
onlyBackgroundClipText: false,
|
|
4129
|
+
cutout: new DOMRect(0, 0, resolved.width, resolved.height)
|
|
4130
|
+
});
|
|
4131
|
+
internalState.addCreateFrameTime(performance.now() - createFrameStart);
|
|
4132
|
+
layerCanvas = layer.canvas;
|
|
3972
4133
|
if (signal?.aborted) {
|
|
3973
4134
|
throw new Error("renderMediaOnWeb() was cancelled");
|
|
3974
4135
|
}
|
|
3975
|
-
|
|
3976
|
-
|
|
3977
|
-
returnedFrame,
|
|
3978
|
-
expectedWidth: Math.round(resolved.width * scale),
|
|
3979
|
-
expectedHeight: Math.round(resolved.height * scale),
|
|
3980
|
-
expectedTimestamp: timestamp
|
|
4136
|
+
const videoFrame = new VideoFrame(layer.canvas, {
|
|
4137
|
+
timestamp
|
|
3981
4138
|
});
|
|
4139
|
+
frameToEncode = videoFrame;
|
|
4140
|
+
if (onFrame) {
|
|
4141
|
+
const returnedFrame = await onFrame(videoFrame);
|
|
4142
|
+
if (signal?.aborted) {
|
|
4143
|
+
throw new Error("renderMediaOnWeb() was cancelled");
|
|
4144
|
+
}
|
|
4145
|
+
frameToEncode = validateVideoFrame({
|
|
4146
|
+
originalFrame: videoFrame,
|
|
4147
|
+
returnedFrame,
|
|
4148
|
+
expectedWidth: Math.round(resolved.width * scale),
|
|
4149
|
+
expectedHeight: Math.round(resolved.height * scale),
|
|
4150
|
+
expectedTimestamp: timestamp
|
|
4151
|
+
});
|
|
4152
|
+
}
|
|
3982
4153
|
}
|
|
4154
|
+
progress.renderedFrames++;
|
|
4155
|
+
throttledOnProgress?.({ ...progress });
|
|
3983
4156
|
const audioCombineStart = performance.now();
|
|
3984
4157
|
const assets = collectAssets.current.collectAssets();
|
|
3985
4158
|
if (onArtifact) {
|
|
3986
4159
|
await artifactsHandler.handle({
|
|
3987
|
-
imageData:
|
|
4160
|
+
imageData: layerCanvas,
|
|
3988
4161
|
frame,
|
|
3989
4162
|
assets,
|
|
3990
4163
|
onArtifact
|
|
@@ -3996,10 +4169,14 @@ var internalRenderMediaOnWeb = async ({
|
|
|
3996
4169
|
const audio = muted ? null : onlyInlineAudio({ assets, fps: resolved.fps, timestamp });
|
|
3997
4170
|
internalState.addAudioMixingTime(performance.now() - audioCombineStart);
|
|
3998
4171
|
const addSampleStart = performance.now();
|
|
3999
|
-
|
|
4000
|
-
|
|
4001
|
-
|
|
4002
|
-
|
|
4172
|
+
const encodingPromises = [];
|
|
4173
|
+
if (frameToEncode && videoSampleSource) {
|
|
4174
|
+
encodingPromises.push(addVideoSampleAndCloseFrame(frameToEncode, videoSampleSource.videoSampleSource));
|
|
4175
|
+
}
|
|
4176
|
+
if (audio && audioSampleSource) {
|
|
4177
|
+
encodingPromises.push(addAudioSample(audio, audioSampleSource.audioSampleSource));
|
|
4178
|
+
}
|
|
4179
|
+
await Promise.all(encodingPromises);
|
|
4003
4180
|
internalState.addAddSampleTime(performance.now() - addSampleStart);
|
|
4004
4181
|
progress.encodedFrames++;
|
|
4005
4182
|
throttledOnProgress?.({ ...progress });
|
|
@@ -4008,7 +4185,7 @@ var internalRenderMediaOnWeb = async ({
|
|
|
4008
4185
|
}
|
|
4009
4186
|
}
|
|
4010
4187
|
onProgress?.({ ...progress });
|
|
4011
|
-
videoSampleSource
|
|
4188
|
+
videoSampleSource?.videoSampleSource.close();
|
|
4012
4189
|
audioSampleSource?.audioSampleSource.close();
|
|
4013
4190
|
await outputWithCleanup.output.finalize();
|
|
4014
4191
|
Internals8.Log.verbose({ logLevel, tag: "web-renderer" }, `Render timings: waitForReady=${internalState.getWaitForReadyTime().toFixed(2)}ms, createFrame=${internalState.getCreateFrameTime().toFixed(2)}ms, addSample=${internalState.getAddSampleTime().toFixed(2)}ms, audioMixing=${internalState.getAudioMixingTime().toFixed(2)}ms`);
|
|
@@ -4022,8 +4199,9 @@ var internalRenderMediaOnWeb = async ({
|
|
|
4022
4199
|
});
|
|
4023
4200
|
await webFsTarget.close();
|
|
4024
4201
|
return {
|
|
4025
|
-
getBlob: () => {
|
|
4026
|
-
|
|
4202
|
+
getBlob: async () => {
|
|
4203
|
+
const file = await webFsTarget.getBlob();
|
|
4204
|
+
return new Blob([file], { type: getMimeType(container) });
|
|
4027
4205
|
},
|
|
4028
4206
|
internalState
|
|
4029
4207
|
};
|
|
@@ -4074,7 +4252,7 @@ var internalRenderMediaOnWeb = async ({
|
|
|
4074
4252
|
};
|
|
4075
4253
|
var renderMediaOnWeb = (options) => {
|
|
4076
4254
|
const container = options.container ?? "mp4";
|
|
4077
|
-
const codec = options.videoCodec ?? getDefaultVideoCodecForContainer(container);
|
|
4255
|
+
const codec = options.videoCodec ?? getDefaultVideoCodecForContainer(container) ?? null;
|
|
4078
4256
|
onlyOneRenderAtATimeQueue.ref = onlyOneRenderAtATimeQueue.ref.catch(() => Promise.resolve()).then(() => internalRenderMediaOnWeb({
|
|
4079
4257
|
...options,
|
|
4080
4258
|
delayRenderTimeoutInMilliseconds: options.delayRenderTimeoutInMilliseconds ?? 30000,
|
|
@@ -4236,11 +4414,13 @@ var renderStillOnWeb = (options) => {
|
|
|
4236
4414
|
export {
|
|
4237
4415
|
renderStillOnWeb,
|
|
4238
4416
|
renderMediaOnWeb,
|
|
4417
|
+
isAudioOnlyContainer,
|
|
4239
4418
|
getSupportedVideoCodecsForContainer,
|
|
4240
4419
|
getSupportedAudioCodecsForContainer,
|
|
4241
4420
|
getEncodableVideoCodecs,
|
|
4242
4421
|
getEncodableAudioCodecs,
|
|
4243
4422
|
getDefaultVideoCodecForContainer,
|
|
4423
|
+
getDefaultContainerForCodec,
|
|
4244
4424
|
getDefaultAudioCodecForContainer,
|
|
4245
4425
|
canRenderMediaOnWeb
|
|
4246
4426
|
};
|
package/dist/index.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ export { canRenderMediaOnWeb } from './can-render-media-on-web';
|
|
|
4
4
|
export type { CanRenderIssue, CanRenderMediaOnWebOptions, CanRenderMediaOnWebResult, } from './can-render-media-on-web';
|
|
5
5
|
export type { FrameRange } from './frame-range';
|
|
6
6
|
export { getEncodableAudioCodecs, getEncodableVideoCodecs, type GetEncodableAudioCodecsOptions, type GetEncodableVideoCodecsOptions, } from './get-encodable-codecs';
|
|
7
|
-
export { getDefaultAudioCodecForContainer, getDefaultVideoCodecForContainer, getSupportedAudioCodecsForContainer, getSupportedVideoCodecsForContainer, } from './mediabunny-mappings';
|
|
7
|
+
export { getDefaultAudioCodecForContainer, getDefaultContainerForCodec, getDefaultVideoCodecForContainer, getSupportedAudioCodecsForContainer, getSupportedVideoCodecsForContainer, isAudioOnlyContainer, } from './mediabunny-mappings';
|
|
8
8
|
export type { WebRendererAudioCodec, WebRendererContainer, WebRendererQuality, WebRendererVideoCodec, } from './mediabunny-mappings';
|
|
9
9
|
export type { WebRendererOutputTarget } from './output-target';
|
|
10
10
|
export { renderMediaOnWeb } from './render-media-on-web';
|
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
import type { Quality } from 'mediabunny';
|
|
2
2
|
import { type OutputFormat } from 'mediabunny';
|
|
3
3
|
export type WebRendererVideoCodec = 'h264' | 'h265' | 'vp8' | 'vp9' | 'av1';
|
|
4
|
-
export type WebRendererContainer = 'mp4' | 'webm';
|
|
5
|
-
export type WebRendererAudioCodec = 'aac' | 'opus';
|
|
4
|
+
export type WebRendererContainer = 'mp4' | 'webm' | 'mkv' | 'mov' | 'wav' | 'mp3' | 'aac' | 'ogg';
|
|
5
|
+
export type WebRendererAudioCodec = 'aac' | 'opus' | 'mp3' | 'vorbis' | 'pcm-s16';
|
|
6
6
|
export type WebRendererQuality = 'very-low' | 'low' | 'medium' | 'high' | 'very-high';
|
|
7
|
+
export declare const isAudioOnlyContainer: (container: WebRendererContainer) => boolean;
|
|
7
8
|
export declare const codecToMediabunnyCodec: (codec: WebRendererVideoCodec) => "av1" | "avc" | "hevc" | "vp8" | "vp9";
|
|
8
9
|
export declare const containerToMediabunnyContainer: (container: WebRendererContainer) => OutputFormat;
|
|
9
|
-
export declare const getDefaultVideoCodecForContainer: (container: WebRendererContainer) => WebRendererVideoCodec;
|
|
10
|
+
export declare const getDefaultVideoCodecForContainer: (container: WebRendererContainer) => WebRendererVideoCodec | null;
|
|
11
|
+
export declare const getDefaultContainerForCodec: (codec: WebRendererVideoCodec) => WebRendererContainer;
|
|
10
12
|
export declare const getQualityForWebRendererQuality: (quality: WebRendererQuality) => Quality;
|
|
11
13
|
export declare const getMimeType: (container: WebRendererContainer) => string;
|
|
12
14
|
export declare const getDefaultAudioCodecForContainer: (container: WebRendererContainer) => WebRendererAudioCodec;
|
|
13
15
|
export declare const getSupportedVideoCodecsForContainer: (container: WebRendererContainer) => WebRendererVideoCodec[];
|
|
14
|
-
export declare const getSupportedAudioCodecsForContainer: (container: WebRendererContainer) => WebRendererAudioCodec[];
|
|
15
16
|
export declare const audioCodecToMediabunnyAudioCodec: (audioCodec: WebRendererAudioCodec) => "aac" | "ac3" | "alaw" | "eac3" | "flac" | "mp3" | "opus" | "pcm-f32" | "pcm-f32be" | "pcm-f64" | "pcm-f64be" | "pcm-s16" | "pcm-s16be" | "pcm-s24" | "pcm-s24be" | "pcm-s32" | "pcm-s32be" | "pcm-s8" | "pcm-u8" | "ulaw" | "vorbis";
|
|
17
|
+
export declare const getSupportedAudioCodecsForContainer: (container: WebRendererContainer) => WebRendererAudioCodec[];
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const ensureMp3EncoderRegistered: () => Promise<void>;
|
|
@@ -36,7 +36,7 @@ type OptionalRenderMediaOnWebOptions<Schema extends $ZodObject> = {
|
|
|
36
36
|
logLevel: LogLevel;
|
|
37
37
|
schema: Schema | undefined;
|
|
38
38
|
mediaCacheSizeInBytes: number | null;
|
|
39
|
-
videoCodec: WebRendererVideoCodec;
|
|
39
|
+
videoCodec: WebRendererVideoCodec | null;
|
|
40
40
|
audioCodec: WebRendererAudioCodec | null;
|
|
41
41
|
audioBitrate: number | WebRendererQuality;
|
|
42
42
|
container: WebRendererContainer;
|