@remotion/webcodecs 4.0.236 → 4.0.239

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 (36) hide show
  1. package/dist/audio-decoder.js +11 -7
  2. package/dist/audio-encoder.js +4 -2
  3. package/dist/auto-select-writer.d.ts +1 -1
  4. package/dist/auto-select-writer.js +22 -5
  5. package/dist/can-copy-audio-track.d.ts +4 -3
  6. package/dist/can-copy-audio-track.js +7 -6
  7. package/dist/can-copy-video-track.d.ts +4 -3
  8. package/dist/can-copy-video-track.js +7 -6
  9. package/dist/convert-media.js +2 -2
  10. package/dist/default-on-audio-track-handler.js +9 -11
  11. package/dist/default-on-video-track-handler.js +6 -15
  12. package/dist/esm/index.mjs +188 -140
  13. package/dist/io-manager/io-synchronizer.d.ts +4 -3
  14. package/dist/io-manager/io-synchronizer.js +25 -10
  15. package/dist/on-audio-track-handler.d.ts +4 -2
  16. package/dist/on-audio-track.d.ts +2 -2
  17. package/dist/on-audio-track.js +13 -6
  18. package/dist/on-frame.js +4 -1
  19. package/dist/on-video-track-handler.d.ts +4 -2
  20. package/dist/on-video-track.d.ts +2 -2
  21. package/dist/on-video-track.js +15 -6
  22. package/dist/video-decoder.js +5 -2
  23. package/dist/video-encoder.js +4 -2
  24. package/package.json +4 -4
  25. package/dist/codec-id.d.ts +0 -10
  26. package/dist/codec-id.js +0 -38
  27. package/dist/create-aac-codecprivate.d.ts +0 -14
  28. package/dist/create-aac-codecprivate.js +0 -72
  29. package/dist/io-manager/event-emitter.d.ts +0 -31
  30. package/dist/io-manager/event-emitter.js +0 -25
  31. package/dist/rotate-video.d.ts +0 -4
  32. package/dist/rotate-video.js +0 -43
  33. package/dist/test/aac-codecprivate.test.js +0 -12
  34. package/dist/with-resolvers.d.ts +0 -10
  35. package/dist/with-resolvers.js +0 -28
  36. /package/dist/test/{aac-codecprivate.test.d.ts → avi-to-mp4.test.d.ts} +0 -0
@@ -134,8 +134,8 @@ var makeIoSynchronizer = ({
134
134
  let inputsSinceLastOutput = 0;
135
135
  let inputs = [];
136
136
  let keyframes = [];
137
- let unprocessed = 0;
138
- const getUnprocessed = () => unprocessed;
137
+ let _unprocessed = 0;
138
+ const getUnprocessed = () => _unprocessed;
139
139
  const getUnemittedItems = () => {
140
140
  inputs = inputs.filter((input) => Math.floor(input) > Math.floor(lastOutput));
141
141
  return inputs.length;
@@ -167,7 +167,7 @@ var makeIoSynchronizer = ({
167
167
  eventEmitter.dispatchEvent("output", {
168
168
  timestamp
169
169
  });
170
- unprocessed++;
170
+ _unprocessed++;
171
171
  printState("Got output");
172
172
  };
173
173
  const waitForOutput = () => {
@@ -189,11 +189,22 @@ var makeIoSynchronizer = ({
189
189
  return promise;
190
190
  };
191
191
  const waitFor = async ({
192
- _unprocessed,
192
+ unprocessed,
193
193
  unemitted,
194
- minimumProgress
194
+ minimumProgress,
195
+ signal
195
196
  }) => {
196
- const { timeoutPromise, clear } = makeTimeoutPromise(`Waited too long for ${label}`, 1e4);
197
+ const { timeoutPromise, clear } = makeTimeoutPromise([
198
+ `Waited too long for ${label}:`,
199
+ `${getUnemittedItems()} unemitted items`,
200
+ `${getUnprocessed()} unprocessed items`,
201
+ `minimum progress ${minimumProgress}`,
202
+ `smallest progress: ${progress.getSmallestProgress()}`,
203
+ `inputs: ${JSON.stringify(inputs)}`,
204
+ `last output: ${lastOutput}`
205
+ ].join(`
206
+ `), 1e4);
207
+ signal.addEventListener("abort", clear);
197
208
  await Promise.race([
198
209
  timeoutPromise,
199
210
  Promise.all([
@@ -203,24 +214,30 @@ var makeIoSynchronizer = ({
203
214
  }
204
215
  })(),
205
216
  (async () => {
206
- while (getUnprocessed() > _unprocessed) {
217
+ while (getUnprocessed() > unprocessed) {
207
218
  await waitForProcessed();
208
219
  }
209
220
  })(),
210
- minimumProgress === null ? Promise.resolve() : (async () => {
221
+ minimumProgress === null || progress.getSmallestProgress() === null ? Promise.resolve() : (async () => {
211
222
  while (progress.getSmallestProgress() < minimumProgress) {
212
223
  await progress.waitForProgress();
213
224
  }
214
225
  })()
215
226
  ])
216
227
  ]).finally(() => clear());
228
+ signal.removeEventListener("abort", clear);
217
229
  };
218
- const waitForFinish = async () => {
219
- await waitFor({ _unprocessed: 0, unemitted: 0, minimumProgress: null });
230
+ const waitForFinish = async (signal) => {
231
+ await waitFor({
232
+ unprocessed: 0,
233
+ unemitted: 0,
234
+ minimumProgress: null,
235
+ signal
236
+ });
220
237
  };
221
238
  const onProcessed = () => {
222
239
  eventEmitter.dispatchEvent("processed", {});
223
- unprocessed--;
240
+ _unprocessed--;
224
241
  };
225
242
  return {
226
243
  inputItem,
@@ -255,23 +272,23 @@ var createAudioDecoder = ({
255
272
  });
256
273
  let outputQueue = Promise.resolve();
257
274
  const audioDecoder = new AudioDecoder({
258
- output(inputFrame) {
259
- ioSynchronizer.onOutput(inputFrame.timestamp);
275
+ output(frame) {
276
+ ioSynchronizer.onOutput(frame.timestamp + (frame.duration ?? 0));
260
277
  const abortHandler = () => {
261
- inputFrame.close();
278
+ frame.close();
262
279
  };
263
280
  signal.addEventListener("abort", abortHandler, { once: true });
264
281
  outputQueue = outputQueue.then(() => {
265
282
  if (signal.aborted) {
266
283
  return;
267
284
  }
268
- return onFrame(inputFrame);
285
+ return onFrame(frame);
269
286
  }).then(() => {
270
287
  ioSynchronizer.onProcessed();
271
288
  signal.removeEventListener("abort", abortHandler);
272
289
  return Promise.resolve();
273
290
  }).catch((err) => {
274
- inputFrame.close();
291
+ frame.close();
275
292
  onError(err);
276
293
  });
277
294
  },
@@ -295,10 +312,12 @@ var createAudioDecoder = ({
295
312
  if (audioDecoder.state === "closed") {
296
313
  return;
297
314
  }
315
+ progressTracker.setPossibleLowestTimestamp(Math.min(audioSample.timestamp, audioSample.dts ?? Infinity, audioSample.cts ?? Infinity));
298
316
  await ioSynchronizer.waitFor({
299
317
  unemitted: 20,
300
- _unprocessed: 20,
301
- minimumProgress: audioSample.timestamp - 1e7
318
+ unprocessed: 20,
319
+ minimumProgress: audioSample.timestamp - 1e7,
320
+ signal
302
321
  });
303
322
  const chunk = new EncodedAudioChunk(audioSample);
304
323
  audioDecoder.decode(chunk);
@@ -319,7 +338,7 @@ var createAudioDecoder = ({
319
338
  } catch {
320
339
  }
321
340
  await queue;
322
- await ioSynchronizer.waitForFinish();
341
+ await ioSynchronizer.waitForFinish(signal);
323
342
  await outputQueue;
324
343
  },
325
344
  close,
@@ -416,10 +435,12 @@ var createAudioEncoder = ({
416
435
  if (encoder.state === "closed") {
417
436
  return;
418
437
  }
438
+ progressTracker.setPossibleLowestTimestamp(audioData.timestamp);
419
439
  await ioSynchronizer.waitFor({
420
440
  unemitted: 20,
421
- _unprocessed: 20,
422
- minimumProgress: audioData.timestamp - 1e7
441
+ unprocessed: 20,
442
+ minimumProgress: audioData.timestamp - 1e7,
443
+ signal
423
444
  });
424
445
  if (encoder.state === "closed") {
425
446
  return;
@@ -446,7 +467,7 @@ var createAudioEncoder = ({
446
467
  },
447
468
  waitForFinish: async () => {
448
469
  await encoder.flush();
449
- await ioSynchronizer.waitForFinish();
470
+ await ioSynchronizer.waitForFinish(signal);
450
471
  await prom;
451
472
  },
452
473
  close,
@@ -458,39 +479,41 @@ var createAudioEncoder = ({
458
479
  // src/can-copy-audio-track.ts
459
480
  var canCopyAudioTrack = ({
460
481
  inputCodec,
461
- container
482
+ outputContainer,
483
+ inputContainer
462
484
  }) => {
463
- if (container === "webm") {
485
+ if (outputContainer === "webm") {
464
486
  return inputCodec === "opus";
465
487
  }
466
- if (container === "mp4") {
467
- return inputCodec === "aac";
488
+ if (outputContainer === "mp4") {
489
+ return inputCodec === "aac" && (inputContainer === "mp4" || inputContainer === "avi");
468
490
  }
469
- if (container === "wav") {
491
+ if (outputContainer === "wav") {
470
492
  return false;
471
493
  }
472
- throw new Error(`Unhandled codec: ${container}`);
494
+ throw new Error(`Unhandled container: ${outputContainer}`);
473
495
  };
474
496
  // src/can-copy-video-track.ts
475
497
  var canCopyVideoTrack = ({
476
498
  inputCodec,
477
- container,
499
+ outputContainer,
478
500
  inputRotation,
479
- rotationToApply
501
+ rotationToApply,
502
+ inputContainer
480
503
  }) => {
481
504
  if (normalizeVideoRotation(inputRotation) !== normalizeVideoRotation(rotationToApply)) {
482
505
  return false;
483
506
  }
484
- if (container === "webm") {
507
+ if (outputContainer === "webm") {
485
508
  return inputCodec === "vp8" || inputCodec === "vp9";
486
509
  }
487
- if (container === "mp4") {
488
- return inputCodec === "h264";
510
+ if (outputContainer === "mp4") {
511
+ return inputCodec === "h264" && (inputContainer === "mp4" || inputContainer === "avi");
489
512
  }
490
- if (container === "wav") {
513
+ if (outputContainer === "wav") {
491
514
  return false;
492
515
  }
493
- throw new Error(`Unhandled codec: ${container}`);
516
+ throw new Error(`Unhandled codec: ${outputContainer}`);
494
517
  };
495
518
  // src/audio-decoder-config.ts
496
519
  var getAudioDecoderConfig = async (config) => {
@@ -671,24 +694,45 @@ var canReencodeVideoTrack = async ({
671
694
  };
672
695
  // src/convert-media.ts
673
696
  import {
674
- MediaParserInternals as MediaParserInternals8,
697
+ MediaParserInternals as MediaParserInternals9,
675
698
  parseMedia
676
699
  } from "@remotion/media-parser";
677
700
 
678
701
  // src/auto-select-writer.ts
679
- import { bufferWriter } from "@remotion/media-parser/buffer";
680
702
  import { canUseWebFsWriter, webFsWriter } from "@remotion/media-parser/web-fs";
703
+ import {
704
+ MediaParserInternals as MediaParserInternals4
705
+ } from "@remotion/media-parser";
706
+ import { bufferWriter } from "@remotion/media-parser/buffer";
681
707
  var autoSelectWriter = async (writer, logLevel) => {
682
708
  if (writer) {
683
709
  Log.verbose(logLevel, "Using writer provided by user");
684
710
  return writer;
685
711
  }
686
712
  Log.verbose(logLevel, "Determining best writer");
687
- if (await canUseWebFsWriter()) {
688
- Log.verbose(logLevel, "Using WebFS writer because it is supported");
689
- return webFsWriter;
713
+ const isOffline = !navigator.onLine;
714
+ if (isOffline) {
715
+ Log.verbose(logLevel, "Offline mode detected, using buffer writer");
716
+ return bufferWriter;
717
+ }
718
+ try {
719
+ const {
720
+ promise: timeout,
721
+ reject,
722
+ resolve
723
+ } = MediaParserInternals4.withResolvers();
724
+ const time = setTimeout(() => reject(new Error("WebFS check timeout")), 2000);
725
+ const webFsSupported = await Promise.race([canUseWebFsWriter(), timeout]);
726
+ resolve();
727
+ clearTimeout(time);
728
+ if (webFsSupported) {
729
+ Log.verbose(logLevel, "Using WebFS writer because it is supported");
730
+ return webFsWriter;
731
+ }
732
+ } catch (err) {
733
+ Log.verbose(logLevel, `WebFS check failed: ${err}. Falling back to buffer writer`);
690
734
  }
691
- Log.verbose(logLevel, "Using buffer writer because WebFS writer is not supported");
735
+ Log.verbose(logLevel, "Using buffer writer because WebFS writer is not supported or unavailable");
692
736
  return bufferWriter;
693
737
  };
694
738
 
@@ -716,7 +760,7 @@ var generateOutputFilename = (source, container) => {
716
760
 
717
761
  // src/on-audio-track.ts
718
762
  import {
719
- MediaParserInternals as MediaParserInternals5
763
+ MediaParserInternals as MediaParserInternals6
720
764
  } from "@remotion/media-parser";
721
765
 
722
766
  // src/convert-encoded-chunk.ts
@@ -735,59 +779,56 @@ var convertEncodedChunk = (chunk, trackId) => {
735
779
  };
736
780
 
737
781
  // src/default-on-audio-track-handler.ts
738
- import { MediaParserInternals as MediaParserInternals4 } from "@remotion/media-parser";
739
-
740
- // src/get-default-audio-codec.ts
741
- var getDefaultAudioCodec = ({
742
- container
743
- }) => {
744
- if (container === "webm") {
745
- return "opus";
746
- }
747
- if (container === "mp4") {
748
- return "aac";
749
- }
750
- if (container === "wav") {
751
- return "wav";
752
- }
753
- throw new Error(`Unhandled container: ${container}`);
754
- };
755
-
756
- // src/default-on-audio-track-handler.ts
782
+ import { MediaParserInternals as MediaParserInternals5 } from "@remotion/media-parser";
757
783
  var DEFAULT_BITRATE = 128000;
758
784
  var defaultOnAudioTrackHandler = async ({
759
785
  track,
760
786
  defaultAudioCodec,
761
787
  logLevel,
762
- container
788
+ canCopyTrack
763
789
  }) => {
764
790
  const bitrate = DEFAULT_BITRATE;
765
- const canCopy = canCopyAudioTrack({
766
- inputCodec: track.codecWithoutConfig,
767
- container
768
- });
769
- if (canCopy) {
770
- MediaParserInternals4.Log.verbose(logLevel, `Track ${track.trackId} (audio): Can copy track, therefore copying`);
791
+ if (canCopyTrack) {
792
+ MediaParserInternals5.Log.verbose(logLevel, `Track ${track.trackId} (audio): Can copy track, therefore copying`);
771
793
  return Promise.resolve({ type: "copy" });
772
794
  }
773
- const audioCodec = defaultAudioCodec ?? getDefaultAudioCodec({ container });
795
+ if (defaultAudioCodec === null) {
796
+ MediaParserInternals5.Log.verbose(logLevel, `Track ${track.trackId} (audio): Container does not support audio, dropping audio`);
797
+ return Promise.resolve({ type: "drop" });
798
+ }
774
799
  const canReencode = await canReencodeAudioTrack({
775
- audioCodec,
800
+ audioCodec: defaultAudioCodec,
776
801
  track,
777
802
  bitrate
778
803
  });
779
804
  if (canReencode) {
780
- MediaParserInternals4.Log.verbose(logLevel, `Track ${track.trackId} (audio): Cannot copy, but re-encode, therefore re-encoding`);
805
+ MediaParserInternals5.Log.verbose(logLevel, `Track ${track.trackId} (audio): Cannot copy, but re-encode, therefore re-encoding`);
781
806
  return Promise.resolve({
782
807
  type: "reencode",
783
808
  bitrate,
784
- audioCodec
809
+ audioCodec: defaultAudioCodec
785
810
  });
786
811
  }
787
- MediaParserInternals4.Log.verbose(logLevel, `Track ${track.trackId} (audio): Can neither re-encode nor copy, failing render`);
812
+ MediaParserInternals5.Log.verbose(logLevel, `Track ${track.trackId} (audio): Can neither re-encode nor copy, failing render`);
788
813
  return Promise.resolve({ type: "fail" });
789
814
  };
790
815
 
816
+ // src/get-default-audio-codec.ts
817
+ var getDefaultAudioCodec = ({
818
+ container
819
+ }) => {
820
+ if (container === "webm") {
821
+ return "opus";
822
+ }
823
+ if (container === "mp4") {
824
+ return "aac";
825
+ }
826
+ if (container === "wav") {
827
+ return "wav";
828
+ }
829
+ throw new Error(`Unhandled container: ${container}`);
830
+ };
831
+
791
832
  // src/on-audio-track.ts
792
833
  var makeAudioTrackHandler = ({
793
834
  state,
@@ -797,20 +838,27 @@ var makeAudioTrackHandler = ({
797
838
  onMediaStateUpdate,
798
839
  onAudioTrack,
799
840
  logLevel,
800
- container,
841
+ outputContainer,
801
842
  progressTracker
802
- }) => async (track) => {
843
+ }) => async ({ track, container: inputContainer }) => {
844
+ const canCopyTrack = canCopyAudioTrack({
845
+ inputCodec: track.codecWithoutConfig,
846
+ outputContainer,
847
+ inputContainer
848
+ });
803
849
  const audioOperation = await (onAudioTrack ?? defaultOnAudioTrackHandler)({
804
- defaultAudioCodec: audioCodec,
850
+ defaultAudioCodec: audioCodec ?? getDefaultAudioCodec({ container: outputContainer }),
805
851
  track,
806
852
  logLevel,
807
- container
853
+ outputContainer,
854
+ inputContainer,
855
+ canCopyTrack
808
856
  });
809
857
  if (audioOperation.type === "drop") {
810
858
  return null;
811
859
  }
812
860
  if (audioOperation.type === "fail") {
813
- throw new error_cause_default(`Audio track with ID ${track.trackId} could resolved with {"type": "fail"}. This could mean that this audio track could neither be copied to the output container or re-encoded. You have the option to drop the track instead of failing it: https://remotion.dev/docs/webcodecs/track-transformation`);
861
+ throw new error_cause_default(`Audio track with ID ${track.trackId} resolved with {"type": "fail"}. This could mean that this audio track could neither be copied to the output container or re-encoded. You have the option to drop the track instead of failing it: https://remotion.dev/docs/webcodecs/track-transformation`);
814
862
  }
815
863
  if (audioOperation.type === "copy") {
816
864
  const addedTrack = await state.addTrack({
@@ -827,7 +875,6 @@ var makeAudioTrackHandler = ({
827
875
  chunk: audioSample,
828
876
  trackNumber: addedTrack.trackNumber,
829
877
  isVideo: false,
830
- timescale: track.timescale,
831
878
  codecPrivate: track.codecPrivate
832
879
  });
833
880
  onMediaStateUpdate?.((prevState) => {
@@ -858,7 +905,7 @@ var makeAudioTrackHandler = ({
858
905
  abortConversion(new error_cause_default(`Could not configure audio decoder of track ${track.trackId}`));
859
906
  return null;
860
907
  }
861
- const codecPrivate = audioOperation.audioCodec === "aac" ? MediaParserInternals5.createAacCodecPrivate({
908
+ const codecPrivate = audioOperation.audioCodec === "aac" ? MediaParserInternals6.createAacCodecPrivate({
862
909
  audioObjectType: 2,
863
910
  sampleRate: track.sampleRate,
864
911
  channelConfiguration: track.numberOfChannels
@@ -880,7 +927,6 @@ var makeAudioTrackHandler = ({
880
927
  chunk: convertEncodedChunk(chunk, trackNumber),
881
928
  trackNumber,
882
929
  isVideo: false,
883
- timescale: track.timescale,
884
930
  codecPrivate
885
931
  });
886
932
  onMediaStateUpdate?.((prevState) => {
@@ -940,63 +986,54 @@ var arrayBufferToUint8Array = (buffer) => {
940
986
  };
941
987
 
942
988
  // src/default-on-video-track-handler.ts
943
- import { MediaParserInternals as MediaParserInternals6 } from "@remotion/media-parser";
944
-
945
- // src/get-default-video-codec.ts
946
- var getDefaultVideoCodec = ({
947
- container
948
- }) => {
949
- if (container === "webm") {
950
- return "vp8";
951
- }
952
- if (container === "mp4") {
953
- return "h264";
954
- }
955
- if (container === "wav") {
956
- return null;
957
- }
958
- throw new Error(`Unhandled container: ${container}`);
959
- };
960
-
961
- // src/default-on-video-track-handler.ts
989
+ import { MediaParserInternals as MediaParserInternals7 } from "@remotion/media-parser";
962
990
  var defaultOnVideoTrackHandler = async ({
963
991
  track,
964
992
  defaultVideoCodec,
965
993
  logLevel,
966
- container,
967
- rotate
994
+ rotate,
995
+ canCopyTrack
968
996
  }) => {
969
- const canCopy = canCopyVideoTrack({
970
- inputCodec: track.codecWithoutConfig,
971
- container,
972
- inputRotation: track.rotation,
973
- rotationToApply: rotate
974
- });
975
- if (canCopy) {
976
- MediaParserInternals6.Log.verbose(logLevel, `Track ${track.trackId} (video): Can copy, therefore copying`);
997
+ if (canCopyTrack) {
998
+ MediaParserInternals7.Log.verbose(logLevel, `Track ${track.trackId} (video): Can copy, therefore copying`);
977
999
  return Promise.resolve({ type: "copy" });
978
1000
  }
979
- const videoCodec = defaultVideoCodec ?? getDefaultVideoCodec({ container });
980
- if (videoCodec === null) {
981
- MediaParserInternals6.Log.verbose(logLevel, `Track ${track.trackId} (video): No default video codec, therefore dropping`);
1001
+ if (defaultVideoCodec === null) {
1002
+ MediaParserInternals7.Log.verbose(logLevel, `Track ${track.trackId} (video): Is audio container, therefore dropping video`);
982
1003
  return Promise.resolve({ type: "drop" });
983
1004
  }
984
1005
  const canReencode = await canReencodeVideoTrack({
985
- videoCodec,
1006
+ videoCodec: defaultVideoCodec,
986
1007
  track
987
1008
  });
988
1009
  if (canReencode) {
989
- MediaParserInternals6.Log.verbose(logLevel, `Track ${track.trackId} (video): Cannot copy, but re-enconde, therefore re-encoding`);
1010
+ MediaParserInternals7.Log.verbose(logLevel, `Track ${track.trackId} (video): Cannot copy, but re-enconde, therefore re-encoding`);
990
1011
  return Promise.resolve({
991
1012
  type: "reencode",
992
- videoCodec,
1013
+ videoCodec: defaultVideoCodec,
993
1014
  rotation: rotate - track.rotation
994
1015
  });
995
1016
  }
996
- MediaParserInternals6.Log.verbose(logLevel, `Track ${track.trackId} (video): Can neither copy nor re-encode, therefore failing`);
1017
+ MediaParserInternals7.Log.verbose(logLevel, `Track ${track.trackId} (video): Can neither copy nor re-encode, therefore failing`);
997
1018
  return Promise.resolve({ type: "fail" });
998
1019
  };
999
1020
 
1021
+ // src/get-default-video-codec.ts
1022
+ var getDefaultVideoCodec = ({
1023
+ container
1024
+ }) => {
1025
+ if (container === "webm") {
1026
+ return "vp8";
1027
+ }
1028
+ if (container === "mp4") {
1029
+ return "h264";
1030
+ }
1031
+ if (container === "wav") {
1032
+ return null;
1033
+ }
1034
+ throw new Error(`Unhandled container: ${container}`);
1035
+ };
1036
+
1000
1037
  // src/convert-to-correct-videoframe.ts
1001
1038
  var needsToCorrectVideoFrame = ({
1002
1039
  videoFrame,
@@ -1056,7 +1093,7 @@ var onFrame = async ({
1056
1093
  if (userProcessedFrame.displayHeight !== rotated.displayHeight) {
1057
1094
  throw new Error(`Returned VideoFrame of track ${track.trackId} has different displayHeight (${userProcessedFrame.displayHeight}) than the input frame (${userProcessedFrame.displayHeight})`);
1058
1095
  }
1059
- if (userProcessedFrame.timestamp !== rotated.timestamp) {
1096
+ if (userProcessedFrame.timestamp !== rotated.timestamp && !isSafari()) {
1060
1097
  throw new Error(`Returned VideoFrame of track ${track.trackId} has different timestamp (${userProcessedFrame.timestamp}) than the input frame (${rotated.timestamp}). When calling new VideoFrame(), pass {timestamp: frame.timestamp} as second argument`);
1061
1098
  }
1062
1099
  if ((userProcessedFrame.duration ?? 0) !== (rotated.duration ?? 0)) {
@@ -1135,10 +1172,12 @@ var createVideoDecoder = ({
1135
1172
  if (videoDecoder.state === "closed") {
1136
1173
  return;
1137
1174
  }
1175
+ progress.setPossibleLowestTimestamp(Math.min(sample.timestamp, sample.dts ?? Infinity, sample.cts ?? Infinity));
1138
1176
  await ioSynchronizer.waitFor({
1139
1177
  unemitted: 20,
1140
- _unprocessed: 2,
1141
- minimumProgress: sample.timestamp - 5000000
1178
+ unprocessed: 2,
1179
+ minimumProgress: sample.timestamp - 5000000,
1180
+ signal
1142
1181
  });
1143
1182
  if (sample.type === "key") {
1144
1183
  await videoDecoder.flush();
@@ -1155,7 +1194,7 @@ var createVideoDecoder = ({
1155
1194
  waitForFinish: async () => {
1156
1195
  await videoDecoder.flush();
1157
1196
  Log.verbose(logLevel, "Flushed video decoder");
1158
- await ioSynchronizer.waitForFinish();
1197
+ await ioSynchronizer.waitForFinish(signal);
1159
1198
  Log.verbose(logLevel, "IO synchro finished");
1160
1199
  await outputQueue;
1161
1200
  Log.verbose(logLevel, "Output queue finished");
@@ -1226,10 +1265,12 @@ var createVideoEncoder = ({
1226
1265
  if (encoder.state === "closed") {
1227
1266
  return;
1228
1267
  }
1268
+ progress.setPossibleLowestTimestamp(frame.timestamp);
1229
1269
  await ioSynchronizer.waitFor({
1230
1270
  unemitted: 10,
1231
- _unprocessed: 10,
1232
- minimumProgress: frame.timestamp - 5000000
1271
+ unprocessed: 10,
1272
+ minimumProgress: frame.timestamp - 5000000,
1273
+ signal
1233
1274
  });
1234
1275
  if (encoder.state === "closed") {
1235
1276
  return;
@@ -1250,7 +1291,7 @@ var createVideoEncoder = ({
1250
1291
  waitForFinish: async () => {
1251
1292
  await encoder.flush();
1252
1293
  await outputQueue;
1253
- await ioSynchronizer.waitForFinish();
1294
+ await ioSynchronizer.waitForFinish(signal);
1254
1295
  },
1255
1296
  close,
1256
1297
  flush: async () => {
@@ -1269,25 +1310,34 @@ var makeVideoTrackHandler = ({
1269
1310
  defaultVideoCodec,
1270
1311
  onVideoTrack,
1271
1312
  logLevel,
1272
- container,
1313
+ outputContainer,
1273
1314
  rotate,
1274
1315
  progress
1275
- }) => async (track) => {
1316
+ }) => async ({ track, container: inputContainer }) => {
1276
1317
  if (controller.signal.aborted) {
1277
1318
  throw new error_cause_default("Aborted");
1278
1319
  }
1320
+ const canCopyTrack = canCopyVideoTrack({
1321
+ inputCodec: track.codecWithoutConfig,
1322
+ inputContainer,
1323
+ inputRotation: track.rotation,
1324
+ outputContainer,
1325
+ rotationToApply: rotate
1326
+ });
1279
1327
  const videoOperation = await (onVideoTrack ?? defaultOnVideoTrackHandler)({
1280
1328
  track,
1281
- defaultVideoCodec,
1329
+ defaultVideoCodec: defaultVideoCodec ?? getDefaultVideoCodec({ container: outputContainer }),
1282
1330
  logLevel,
1283
- container,
1284
- rotate
1331
+ outputContainer,
1332
+ rotate,
1333
+ inputContainer,
1334
+ canCopyTrack
1285
1335
  });
1286
1336
  if (videoOperation.type === "drop") {
1287
1337
  return null;
1288
1338
  }
1289
1339
  if (videoOperation.type === "fail") {
1290
- throw new error_cause_default(`Video track with ID ${track.trackId} could resolved with {"type": "fail"}. This could mean that this video track could neither be copied to the output container or re-encoded. You have the option to drop the track instead of failing it: https://remotion.dev/docs/webcodecs/track-transformation`);
1340
+ throw new error_cause_default(`Video track with ID ${track.trackId} could with {"type": "fail"}. This could mean that this video track could neither be copied to the output container or re-encoded. You have the option to drop the track instead of failing it: https://remotion.dev/docs/webcodecs/track-transformation`);
1291
1341
  }
1292
1342
  if (videoOperation.type === "copy") {
1293
1343
  Log.verbose(logLevel, `Copying video track with codec ${track.codec} and timescale ${track.timescale}`);
@@ -1305,7 +1355,6 @@ var makeVideoTrackHandler = ({
1305
1355
  chunk: sample,
1306
1356
  trackNumber: videoTrack.trackNumber,
1307
1357
  isVideo: true,
1308
- timescale: track.timescale,
1309
1358
  codecPrivate: track.codecPrivate
1310
1359
  });
1311
1360
  onMediaStateUpdate?.((prevState) => {
@@ -1356,7 +1405,6 @@ var makeVideoTrackHandler = ({
1356
1405
  chunk: convertEncodedChunk(chunk, trackNumber),
1357
1406
  trackNumber,
1358
1407
  isVideo: true,
1359
- timescale: track.timescale,
1360
1408
  codecPrivate: arrayBufferToUint8Array(metadata?.decoderConfig?.description ?? null)
1361
1409
  });
1362
1410
  onMediaStateUpdate?.((prevState) => {
@@ -1413,16 +1461,16 @@ var makeVideoTrackHandler = ({
1413
1461
  };
1414
1462
 
1415
1463
  // src/select-container-creator.ts
1416
- import { MediaParserInternals as MediaParserInternals7 } from "@remotion/media-parser";
1464
+ import { MediaParserInternals as MediaParserInternals8 } from "@remotion/media-parser";
1417
1465
  var selectContainerCreator = (container) => {
1418
1466
  if (container === "mp4") {
1419
- return MediaParserInternals7.createIsoBaseMedia;
1467
+ return MediaParserInternals8.createIsoBaseMedia;
1420
1468
  }
1421
1469
  if (container === "wav") {
1422
- return MediaParserInternals7.createWav;
1470
+ return MediaParserInternals8.createWav;
1423
1471
  }
1424
1472
  if (container === "webm") {
1425
- return MediaParserInternals7.createMatroskaMedia;
1473
+ return MediaParserInternals8.createMatroskaMedia;
1426
1474
  }
1427
1475
  throw new Error(`Unsupported container: ${container}`);
1428
1476
  };
@@ -1507,7 +1555,7 @@ var convertMedia = async function({
1507
1555
  if (videoCodec && videoCodec !== "vp8" && videoCodec !== "vp9") {
1508
1556
  return Promise.reject(new TypeError('Only `videoCodec: "vp8"` and `videoCodec: "vp9"` are supported currently'));
1509
1557
  }
1510
- const { resolve, reject, getPromiseToImmediatelyReturn } = MediaParserInternals8.withResolversAndWaitForReturn();
1558
+ const { resolve, reject, getPromiseToImmediatelyReturn } = MediaParserInternals9.withResolversAndWaitForReturn();
1511
1559
  const controller = new AbortController;
1512
1560
  const abortConversion = (errCause) => {
1513
1561
  reject(errCause);
@@ -1525,7 +1573,7 @@ var convertMedia = async function({
1525
1573
  everyMilliseconds: progressIntervalInMs ?? 100,
1526
1574
  signal: controller.signal
1527
1575
  });
1528
- const progressTracker = MediaParserInternals8.makeProgressTracker();
1576
+ const progressTracker = MediaParserInternals9.makeProgressTracker();
1529
1577
  const state = await creator({
1530
1578
  filename: generateOutputFilename(src, container),
1531
1579
  writer: await autoSelectWriter(writer, logLevel),
@@ -1564,7 +1612,7 @@ var convertMedia = async function({
1564
1612
  defaultVideoCodec: videoCodec ?? null,
1565
1613
  onVideoTrack: userVideoResolver ?? null,
1566
1614
  logLevel,
1567
- container,
1615
+ outputContainer: container,
1568
1616
  rotate: rotate ?? 0,
1569
1617
  progress: progressTracker
1570
1618
  });
@@ -1576,7 +1624,7 @@ var convertMedia = async function({
1576
1624
  state,
1577
1625
  onAudioTrack: userAudioResolver ?? null,
1578
1626
  logLevel,
1579
- container,
1627
+ outputContainer: container,
1580
1628
  progressTracker
1581
1629
  });
1582
1630
  parseMedia({
@@ -7,12 +7,13 @@ export declare const makeIoSynchronizer: ({ logLevel, label, progress, }: {
7
7
  }) => {
8
8
  inputItem: (timestamp: number, keyFrame: boolean) => void;
9
9
  onOutput: (timestamp: number) => void;
10
- waitFor: ({ _unprocessed, unemitted, minimumProgress, }: {
10
+ waitFor: ({ unprocessed, unemitted, minimumProgress, signal, }: {
11
11
  unemitted: number;
12
- _unprocessed: number;
12
+ unprocessed: number;
13
13
  minimumProgress: number | null;
14
+ signal: AbortSignal;
14
15
  }) => Promise<void>;
15
- waitForFinish: () => Promise<void>;
16
+ waitForFinish: (signal: AbortSignal) => Promise<void>;
16
17
  onProcessed: () => void;
17
18
  getUnprocessed: () => number;
18
19
  };