@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.
Files changed (74) hide show
  1. package/dist/can-render-types.d.ts +2 -2
  2. package/dist/esm/index.mjs +274 -94
  3. package/dist/index.d.ts +1 -1
  4. package/dist/mediabunny-mappings.d.ts +6 -4
  5. package/dist/register-mp3-encoder.d.ts +1 -0
  6. package/dist/render-media-on-web.d.ts +1 -1
  7. package/package.json +9 -8
  8. package/dist/add-sample.js +0 -20
  9. package/dist/artifact.js +0 -56
  10. package/dist/audio.js +0 -42
  11. package/dist/can-use-webfs-target.js +0 -19
  12. package/dist/compose.js +0 -85
  13. package/dist/create-scaffold.js +0 -104
  14. package/dist/drawing/border-radius.js +0 -151
  15. package/dist/drawing/calculate-object-fit.js +0 -208
  16. package/dist/drawing/calculate-transforms.js +0 -127
  17. package/dist/drawing/clamp-rect-to-parent-bounds.js +0 -18
  18. package/dist/drawing/do-rects-intersect.js +0 -6
  19. package/dist/drawing/draw-background.js +0 -62
  20. package/dist/drawing/draw-border.js +0 -353
  21. package/dist/drawing/draw-box-shadow.js +0 -103
  22. package/dist/drawing/draw-dom-element.js +0 -85
  23. package/dist/drawing/draw-element.js +0 -84
  24. package/dist/drawing/draw-outline.js +0 -93
  25. package/dist/drawing/draw-rounded.js +0 -34
  26. package/dist/drawing/drawn-fn.js +0 -1
  27. package/dist/drawing/fit-svg-into-its-dimensions.js +0 -35
  28. package/dist/drawing/get-clipped-background.d.ts +0 -8
  29. package/dist/drawing/get-clipped-background.js +0 -14
  30. package/dist/drawing/get-padding-box.js +0 -30
  31. package/dist/drawing/get-pretransform-rect.js +0 -49
  32. package/dist/drawing/handle-3d-transform.js +0 -26
  33. package/dist/drawing/handle-mask.js +0 -21
  34. package/dist/drawing/has-transform.js +0 -14
  35. package/dist/drawing/mask-image.js +0 -14
  36. package/dist/drawing/opacity.js +0 -7
  37. package/dist/drawing/overflow.js +0 -14
  38. package/dist/drawing/parse-linear-gradient.js +0 -260
  39. package/dist/drawing/parse-transform-origin.js +0 -7
  40. package/dist/drawing/precompose.d.ts +0 -11
  41. package/dist/drawing/precompose.js +0 -14
  42. package/dist/drawing/process-node.js +0 -122
  43. package/dist/drawing/round-to-expand-rect.js +0 -7
  44. package/dist/drawing/text/apply-text-transform.js +0 -12
  45. package/dist/drawing/text/draw-text.js +0 -53
  46. package/dist/drawing/text/find-line-breaks.text.js +0 -118
  47. package/dist/drawing/text/get-collapsed-text.d.ts +0 -1
  48. package/dist/drawing/text/get-collapsed-text.js +0 -46
  49. package/dist/drawing/text/handle-text-node.js +0 -24
  50. package/dist/drawing/transform-in-3d.js +0 -177
  51. package/dist/drawing/transform-rect-with-matrix.js +0 -19
  52. package/dist/drawing/transform.js +0 -10
  53. package/dist/drawing/turn-svg-into-drawable.js +0 -41
  54. package/dist/frame-range.js +0 -15
  55. package/dist/get-audio-encoding-config.js +0 -18
  56. package/dist/get-biggest-bounding-client-rect.js +0 -43
  57. package/dist/index.js +0 -2
  58. package/dist/internal-state.js +0 -36
  59. package/dist/mediabunny-mappings.js +0 -63
  60. package/dist/output-target.js +0 -1
  61. package/dist/props-if-has-props.js +0 -1
  62. package/dist/render-media-on-web.js +0 -304
  63. package/dist/render-operations-queue.js +0 -3
  64. package/dist/render-still-on-web.js +0 -110
  65. package/dist/send-telemetry-event.js +0 -22
  66. package/dist/take-screenshot.js +0 -30
  67. package/dist/throttle-progress.js +0 -43
  68. package/dist/tree-walker-cleanup-after-children.js +0 -33
  69. package/dist/update-time.js +0 -17
  70. package/dist/validate-video-frame.js +0 -34
  71. package/dist/wait-for-ready.js +0 -39
  72. package/dist/walk-tree.js +0 -14
  73. package/dist/web-fs-target.js +0 -41
  74. 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;
@@ -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 = ["aac", "opus"];
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
- const canEncode = await canEncodeAudio(mediabunnyAudioCodec, { bitrate });
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 canEncodeAudio(fallbackMediabunnyCodec, {
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
- const format = containerToMediabunnyContainer(container);
281
- if (!format.getSupportedCodecs().includes(codecToMediabunnyCodec(videoCodec))) {
282
- issues.push({
283
- type: "container-codec-mismatch",
284
- message: `Codec ${videoCodec} is not supported for container ${container}`,
285
- severity: "error"
286
- });
287
- }
288
- const dimensionIssue = validateDimensions({ width, height, codec: videoCodec });
289
- if (dimensionIssue) {
290
- issues.push(dimensionIssue);
291
- }
292
- const canEncodeVideoResult = await canEncodeVideo(codecToMediabunnyCodec(videoCodec), { bitrate: resolvedVideoBitrate });
293
- if (!canEncodeVideoResult) {
294
- issues.push({
295
- type: "video-codec-unsupported",
296
- message: `Video codec "${videoCodec}" cannot be encoded by this browser`,
297
- severity: "error"
298
- });
299
- }
300
- if (transparent && !["vp8", "vp9"].includes(videoCodec)) {
301
- issues.push({
302
- type: "transparent-video-unsupported",
303
- message: `Transparent video requires VP8 or VP9 codec with WebM container. ${videoCodec} does not support alpha channel.`,
304
- severity: "error"
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 = "fixed";
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
- document.body.appendChild(div);
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
- if (codec && !format.getSupportedCodecs().includes(codecToMediabunnyCodec(codec))) {
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: true,
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
- outputWithCleanup.output.addVideoTrack(videoSampleSource.videoSampleSource);
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
- const videoFrame = new VideoFrame(layer.canvas, {
3965
- timestamp
3966
- });
3967
- progress.renderedFrames++;
3968
- throttledOnProgress?.({ ...progress });
3969
- let frameToEncode = videoFrame;
3970
- if (onFrame) {
3971
- const returnedFrame = await onFrame(videoFrame);
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
- frameToEncode = validateVideoFrame({
3976
- originalFrame: videoFrame,
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: layer.canvas,
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
- await Promise.all([
4000
- addVideoSampleAndCloseFrame(frameToEncode, videoSampleSource.videoSampleSource),
4001
- audio && audioSampleSource ? addAudioSample(audio, audioSampleSource.audioSampleSource) : Promise.resolve()
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.videoSampleSource.close();
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
- return webFsTarget.getBlob();
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;