@remotion/studio 4.0.452 → 4.0.454

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 (34) hide show
  1. package/dist/audio-waveform-worker.d.ts +1 -0
  2. package/dist/audio-waveform-worker.js +102 -0
  3. package/dist/components/AudioWaveform.d.ts +2 -0
  4. package/dist/components/AudioWaveform.js +168 -18
  5. package/dist/components/CurrentAsset.js +13 -5
  6. package/dist/components/Timeline/LoopedIndicator.js +5 -19
  7. package/dist/components/Timeline/TimelineSequence.js +18 -10
  8. package/dist/components/Timeline/TimelineVideoInfo.d.ts +2 -0
  9. package/dist/components/Timeline/TimelineVideoInfo.js +51 -12
  10. package/dist/components/audio-waveform-worker-types.d.ts +28 -0
  11. package/dist/components/audio-waveform-worker-types.js +2 -0
  12. package/dist/components/draw-peaks.d.ts +1 -1
  13. package/dist/components/load-waveform-peaks.d.ts +11 -1
  14. package/dist/components/load-waveform-peaks.js +33 -36
  15. package/dist/components/looped-media-timeline.d.ts +6 -0
  16. package/dist/components/looped-media-timeline.js +14 -0
  17. package/dist/components/slice-waveform-peaks.d.ts +7 -0
  18. package/dist/components/slice-waveform-peaks.js +15 -0
  19. package/dist/components/waveform-peak-processor.d.ts +23 -0
  20. package/dist/components/waveform-peak-processor.js +77 -0
  21. package/dist/esm/audio-waveform-worker.mjs +351 -0
  22. package/dist/esm/{chunk-hxr6txpe.js → chunk-g39hwn0a.js} +434 -108
  23. package/dist/esm/internals.mjs +434 -108
  24. package/dist/esm/previewEntry.mjs +434 -108
  25. package/dist/esm/renderEntry.mjs +1 -1
  26. package/dist/helpers/calculate-timeline.js +16 -0
  27. package/dist/helpers/extract-frames.js +12 -3
  28. package/dist/helpers/get-duration-or-compute.d.ts +2 -0
  29. package/dist/helpers/get-duration-or-compute.js +10 -0
  30. package/dist/helpers/get-timeline-nestedness.js +2 -1
  31. package/dist/helpers/use-max-media-duration.js +2 -2
  32. package/dist/make-audio-waveform-worker.d.ts +1 -0
  33. package/dist/make-audio-waveform-worker.js +10 -0
  34. package/package.json +19 -10
@@ -6615,6 +6615,13 @@ import { ALL_FORMATS, Input, UrlSource } from "mediabunny";
6615
6615
  import { useContext as useContext12, useEffect as useEffect22, useMemo as useMemo29, useState as useState24 } from "react";
6616
6616
  import { Internals as Internals11, staticFile } from "remotion";
6617
6617
 
6618
+ // src/helpers/get-duration-or-compute.ts
6619
+ var getDurationOrCompute = async (input) => {
6620
+ return await input.getDurationFromMetadata(undefined, {
6621
+ skipLiveWait: true
6622
+ }) ?? input.computeDuration(undefined, { skipLiveWait: true });
6623
+ };
6624
+
6618
6625
  // src/components/use-static-files.ts
6619
6626
  import React40, { createContext as createContext10, useContext as useContext11, useEffect as useEffect21, useState as useState23 } from "react";
6620
6627
  import { useRemotionEnvironment } from "remotion";
@@ -6758,15 +6765,21 @@ var CurrentAsset = () => {
6758
6765
  source: new UrlSource(url)
6759
6766
  });
6760
6767
  Promise.all([
6761
- input.computeDuration(),
6768
+ getDurationOrCompute(input),
6762
6769
  input.getFormat(),
6763
6770
  input.getPrimaryVideoTrack()
6764
- ]).then(([duration, format, videoTrack]) => {
6771
+ ]).then(async ([duration, format, videoTrack]) => {
6772
+ if (videoTrack && await videoTrack.isLive()) {
6773
+ throw new Error("Live streams are not currently supported by Remotion. Sorry! Source: " + url);
6774
+ }
6775
+ if (videoTrack && await videoTrack.isRelativeToUnixEpoch()) {
6776
+ throw new Error("Streams with UNIX timestamps are not currently supported by Remotion. Sorry! Source: " + url);
6777
+ }
6765
6778
  setMediaMetadata({
6766
6779
  duration,
6767
6780
  format: format.name,
6768
- width: videoTrack?.displayWidth ?? null,
6769
- height: videoTrack?.displayHeight ?? null
6781
+ width: videoTrack ? await videoTrack.getDisplayWidth() : null,
6782
+ height: videoTrack ? await videoTrack.getDisplayHeight() : null
6770
6783
  });
6771
6784
  }).catch(() => {});
6772
6785
  return () => {
@@ -21050,7 +21063,8 @@ var getTimelineNestedLevel = (sequence, allSequences, depth) => {
21050
21063
  if (!parentSequence) {
21051
21064
  throw new Error("has parentId but no parent");
21052
21065
  }
21053
- return getTimelineNestedLevel(parentSequence, allSequences, depth + 1);
21066
+ const parentContributes = parentSequence.showInTimeline;
21067
+ return getTimelineNestedLevel(parentSequence, allSequences, parentContributes ? depth + 1 : depth);
21054
21068
  };
21055
21069
 
21056
21070
  // src/helpers/get-timeline-sequence-hash.ts
@@ -21099,6 +21113,19 @@ var getTimelineSequenceSequenceSortKey = (track, tracks, sameHashes = {}, nonceR
21099
21113
  };
21100
21114
 
21101
21115
  // src/helpers/calculate-timeline.ts
21116
+ var getInheritedLoopDisplay = (sequence, sequences) => {
21117
+ if (sequence.loopDisplay) {
21118
+ return sequence.loopDisplay;
21119
+ }
21120
+ if (!sequence.parent) {
21121
+ return;
21122
+ }
21123
+ const parent = sequences.find((s) => s.id === sequence.parent);
21124
+ if (!parent) {
21125
+ return;
21126
+ }
21127
+ return getInheritedLoopDisplay(parent, sequences);
21128
+ };
21102
21129
  var calculateTimeline = ({
21103
21130
  sequences
21104
21131
  }) => {
@@ -21127,7 +21154,8 @@ var calculateTimeline = ({
21127
21154
  sequence: {
21128
21155
  ...sequence,
21129
21156
  from: visibleStart,
21130
- duration: visibleDuration
21157
+ duration: visibleDuration,
21158
+ loopDisplay: sequence.type === "audio" || sequence.type === "video" ? getInheritedLoopDisplay(sequence, sortedSequences) : sequence.loopDisplay
21131
21159
  },
21132
21160
  depth: getTimelineNestedLevel(sequence, sortedSequences, 0),
21133
21161
  hash: actualHash,
@@ -23736,7 +23764,7 @@ var useMaxMediaDuration = (s, fps) => {
23736
23764
  formats: ALL_FORMATS2,
23737
23765
  source: new UrlSource2(src)
23738
23766
  });
23739
- input2.computeDuration().then((duration) => {
23767
+ getDurationOrCompute(input2).then((duration) => {
23740
23768
  cache.set(src, Math.floor(duration * fps));
23741
23769
  setMaxMediaDuration(Math.floor(duration * fps));
23742
23770
  }).catch((e) => {
@@ -23763,6 +23791,13 @@ var useMaxMediaDuration = (s, fps) => {
23763
23791
  import { useEffect as useEffect73, useMemo as useMemo119, useRef as useRef43, useState as useState79 } from "react";
23764
23792
  import { Internals as Internals55 } from "remotion";
23765
23793
 
23794
+ // src/make-audio-waveform-worker.ts
23795
+ var makeAudioWaveformWorker = () => {
23796
+ return new Worker(new URL("./audio-waveform-worker.mjs", import.meta.url), {
23797
+ type: "module"
23798
+ });
23799
+ };
23800
+
23766
23801
  // src/components/parse-color.ts
23767
23802
  var colorCache = new Map;
23768
23803
  var parseColor = (color) => {
@@ -23838,12 +23873,107 @@ var drawBars = (canvas, peaks, color, volume, width) => {
23838
23873
 
23839
23874
  // src/components/load-waveform-peaks.ts
23840
23875
  import { ALL_FORMATS as ALL_FORMATS3, AudioSampleSink, Input as Input3, UrlSource as UrlSource3 } from "mediabunny";
23876
+
23877
+ // src/components/waveform-peak-processor.ts
23878
+ var emitWaveformProgress = ({
23879
+ completedPeaks,
23880
+ final,
23881
+ onProgress,
23882
+ peaks,
23883
+ totalPeaks
23884
+ }) => {
23885
+ onProgress?.({
23886
+ peaks,
23887
+ completedPeaks,
23888
+ totalPeaks,
23889
+ final
23890
+ });
23891
+ };
23892
+ var createWaveformPeakProcessor = ({
23893
+ totalPeaks,
23894
+ samplesPerPeak,
23895
+ onProgress,
23896
+ progressIntervalInMs,
23897
+ now
23898
+ }) => {
23899
+ const peaks = new Float32Array(totalPeaks);
23900
+ let peakIndex = 0;
23901
+ let peakMax = 0;
23902
+ let sampleInPeak = 0;
23903
+ let lastProgressAt = 0;
23904
+ let lastProgressPeak = 0;
23905
+ const emitProgress = (force) => {
23906
+ const timestamp = now();
23907
+ if (!force && peakIndex === lastProgressPeak && sampleInPeak === 0) {
23908
+ return;
23909
+ }
23910
+ if (!force && timestamp - lastProgressAt < progressIntervalInMs) {
23911
+ return;
23912
+ }
23913
+ lastProgressAt = timestamp;
23914
+ lastProgressPeak = peakIndex;
23915
+ emitWaveformProgress({
23916
+ peaks,
23917
+ completedPeaks: peakIndex,
23918
+ totalPeaks,
23919
+ final: force,
23920
+ onProgress
23921
+ });
23922
+ };
23923
+ return {
23924
+ peaks,
23925
+ processSampleChunk: (floats, channels) => {
23926
+ const frameCount = Math.floor(floats.length / Math.max(1, channels));
23927
+ for (let frame2 = 0;frame2 < frameCount; frame2++) {
23928
+ let framePeak = 0;
23929
+ for (let channel = 0;channel < channels; channel++) {
23930
+ const sampleIndex = frame2 * channels + channel;
23931
+ const abs = Math.abs(floats[sampleIndex] ?? 0);
23932
+ if (abs > framePeak) {
23933
+ framePeak = abs;
23934
+ }
23935
+ }
23936
+ if (framePeak > peakMax) {
23937
+ peakMax = framePeak;
23938
+ }
23939
+ sampleInPeak++;
23940
+ if (sampleInPeak >= samplesPerPeak) {
23941
+ if (peakIndex < totalPeaks) {
23942
+ peaks[peakIndex] = peakMax;
23943
+ }
23944
+ peakIndex++;
23945
+ peakMax = 0;
23946
+ sampleInPeak = 0;
23947
+ }
23948
+ }
23949
+ emitProgress(false);
23950
+ },
23951
+ finalize: () => {
23952
+ if (sampleInPeak > 0 && peakIndex < totalPeaks) {
23953
+ peaks[peakIndex] = peakMax;
23954
+ peakIndex++;
23955
+ }
23956
+ emitProgress(true);
23957
+ }
23958
+ };
23959
+ };
23960
+
23961
+ // src/components/load-waveform-peaks.ts
23841
23962
  var TARGET_SAMPLE_RATE = 100;
23963
+ var DEFAULT_PROGRESS_INTERVAL_IN_MS = 50;
23842
23964
  var peaksCache = new Map;
23843
- async function loadWaveformPeaks(url, signal) {
23965
+ async function loadWaveformPeaks(url, signal, options) {
23844
23966
  const cached = peaksCache.get(url);
23845
- if (cached)
23967
+ if (cached) {
23968
+ emitWaveformProgress({
23969
+ peaks: cached,
23970
+ completedPeaks: cached.length,
23971
+ totalPeaks: cached.length,
23972
+ final: true,
23973
+ onProgress: options?.onProgress
23974
+ });
23846
23975
  return cached;
23976
+ }
23847
23977
  const input2 = new Input3({
23848
23978
  formats: ALL_FORMATS3,
23849
23979
  source: new UrlSource3(url)
@@ -23853,15 +23983,24 @@ async function loadWaveformPeaks(url, signal) {
23853
23983
  if (!audioTrack) {
23854
23984
  return new Float32Array(0);
23855
23985
  }
23856
- const { sampleRate } = audioTrack;
23857
- const durationInSeconds = await audioTrack.computeDuration();
23986
+ if (await audioTrack.isLive()) {
23987
+ throw new Error("Live streams are not currently supported by Remotion. Sorry! Source: " + url);
23988
+ }
23989
+ if (await audioTrack.isRelativeToUnixEpoch()) {
23990
+ throw new Error("Streams with UNIX timestamps are not currently supported by Remotion. Sorry! Source: " + url);
23991
+ }
23992
+ const sampleRate = await audioTrack.getSampleRate();
23993
+ const durationInSeconds = await audioTrack.getDurationFromMetadata({ skipLiveWait: true }) ?? await audioTrack.computeDuration({ skipLiveWait: true });
23858
23994
  const totalPeaks = Math.ceil(durationInSeconds * TARGET_SAMPLE_RATE);
23859
23995
  const samplesPerPeak = Math.max(1, Math.floor(sampleRate / TARGET_SAMPLE_RATE));
23860
- const peaks = new Float32Array(totalPeaks);
23861
- let peakIndex = 0;
23862
- let peakMax = 0;
23863
- let sampleInPeak = 0;
23864
23996
  const sink = new AudioSampleSink(audioTrack);
23997
+ const processor = createWaveformPeakProcessor({
23998
+ totalPeaks,
23999
+ samplesPerPeak,
24000
+ onProgress: options?.onProgress,
24001
+ progressIntervalInMs: options?.progressIntervalInMs ?? DEFAULT_PROGRESS_INTERVAL_IN_MS,
24002
+ now: () => Date.now()
24003
+ });
23865
24004
  for await (const sample of sink.samples()) {
23866
24005
  if (signal.aborted) {
23867
24006
  sample.close();
@@ -23874,34 +24013,11 @@ async function loadWaveformPeaks(url, signal) {
23874
24013
  const floats = new Float32Array(bytesNeeded / 4);
23875
24014
  sample.copyTo(floats, { format: "f32", planeIndex: 0 });
23876
24015
  const channels = Math.max(1, sample.numberOfChannels);
23877
- const frames = sample.numberOfFrames;
23878
24016
  sample.close();
23879
- for (let frame2 = 0;frame2 < frames; frame2++) {
23880
- let framePeak = 0;
23881
- for (let channel = 0;channel < channels; channel++) {
23882
- const sampleIndex = frame2 * channels + channel;
23883
- const abs = Math.abs(floats[sampleIndex] ?? 0);
23884
- if (abs > framePeak) {
23885
- framePeak = abs;
23886
- }
23887
- }
23888
- if (framePeak > peakMax) {
23889
- peakMax = framePeak;
23890
- }
23891
- sampleInPeak++;
23892
- if (sampleInPeak >= samplesPerPeak) {
23893
- if (peakIndex < totalPeaks) {
23894
- peaks[peakIndex] = peakMax;
23895
- }
23896
- peakIndex++;
23897
- peakMax = 0;
23898
- sampleInPeak = 0;
23899
- }
23900
- }
23901
- }
23902
- if (sampleInPeak > 0 && peakIndex < totalPeaks) {
23903
- peaks[peakIndex] = peakMax;
24017
+ processor.processSampleChunk(floats, channels);
23904
24018
  }
24019
+ processor.finalize();
24020
+ const { peaks } = processor;
23905
24021
  peaksCache.set(url, peaks);
23906
24022
  return peaks;
23907
24023
  } finally {
@@ -23909,8 +24025,50 @@ async function loadWaveformPeaks(url, signal) {
23909
24025
  }
23910
24026
  }
23911
24027
 
24028
+ // src/components/looped-media-timeline.ts
24029
+ var shouldTileLoopDisplay = (loopDisplay) => {
24030
+ return loopDisplay !== undefined && loopDisplay.numberOfTimes > 1;
24031
+ };
24032
+ var getLoopDisplayWidth = ({
24033
+ visualizationWidth,
24034
+ loopDisplay
24035
+ }) => {
24036
+ if (!shouldTileLoopDisplay(loopDisplay)) {
24037
+ return visualizationWidth;
24038
+ }
24039
+ return visualizationWidth / loopDisplay.numberOfTimes;
24040
+ };
24041
+
24042
+ // src/components/slice-waveform-peaks.ts
24043
+ var sliceWaveformPeaks = ({
24044
+ durationInFrames,
24045
+ fps,
24046
+ peaks,
24047
+ playbackRate,
24048
+ startFrom
24049
+ }) => {
24050
+ if (peaks.length === 0) {
24051
+ return peaks;
24052
+ }
24053
+ const startTimeInSeconds = startFrom / fps;
24054
+ const durationInSeconds = durationInFrames / fps * playbackRate;
24055
+ const startPeakIndex = Math.floor(startTimeInSeconds * TARGET_SAMPLE_RATE);
24056
+ const endPeakIndex = Math.ceil((startTimeInSeconds + durationInSeconds) * TARGET_SAMPLE_RATE);
24057
+ return peaks.subarray(Math.max(0, startPeakIndex), Math.min(peaks.length, endPeakIndex));
24058
+ };
24059
+
23912
24060
  // src/components/AudioWaveform.tsx
23913
24061
  import { jsx as jsx212, jsxs as jsxs102 } from "react/jsx-runtime";
24062
+ var EMPTY_PEAKS = new Float32Array(0);
24063
+ var canRetryCanvasTransfer = (err) => {
24064
+ return err instanceof DOMException && err.name === "InvalidStateError";
24065
+ };
24066
+ var canUseAudioWaveformWorker = () => {
24067
+ if (typeof Worker === "undefined" || typeof OffscreenCanvas === "undefined" || typeof HTMLCanvasElement === "undefined") {
24068
+ return false;
24069
+ }
24070
+ return "transferControlToOffscreen" in HTMLCanvasElement.prototype;
24071
+ };
23914
24072
  var container43 = {
23915
24073
  display: "flex",
23916
24074
  flexDirection: "row",
@@ -23929,11 +24087,41 @@ var errorMessage = {
23929
24087
  opacity: 0.75
23930
24088
  };
23931
24089
  var waveformCanvasStyle = {
23932
- pointerEvents: "none"
24090
+ pointerEvents: "none",
24091
+ width: "100%",
24092
+ height: "100%"
23933
24093
  };
23934
24094
  var volumeCanvasStyle = {
23935
24095
  position: "absolute"
23936
24096
  };
24097
+ var drawLoopedWaveform = ({
24098
+ canvas,
24099
+ peaks,
24100
+ volume,
24101
+ visualizationWidth,
24102
+ loopWidth
24103
+ }) => {
24104
+ const h = canvas.height;
24105
+ const w = Math.ceil(visualizationWidth);
24106
+ const targetCanvas = document.createElement("canvas");
24107
+ targetCanvas.width = Math.max(1, Math.ceil(loopWidth));
24108
+ targetCanvas.height = h;
24109
+ drawBars(targetCanvas, peaks, "rgba(255, 255, 255, 0.6)", volume, targetCanvas.width);
24110
+ canvas.width = w;
24111
+ canvas.height = h;
24112
+ const ctx = canvas.getContext("2d");
24113
+ if (!ctx) {
24114
+ throw new Error("Failed to get canvas context");
24115
+ }
24116
+ const pattern = ctx.createPattern(targetCanvas, "repeat-x");
24117
+ if (!pattern) {
24118
+ return;
24119
+ }
24120
+ pattern.setTransform(new DOMMatrix().scaleSelf(loopWidth / targetCanvas.width, 1));
24121
+ ctx.clearRect(0, 0, w, h);
24122
+ ctx.fillStyle = pattern;
24123
+ ctx.fillRect(0, 0, w, h);
24124
+ };
23937
24125
  var AudioWaveform = ({
23938
24126
  src,
23939
24127
  startFrom,
@@ -23941,10 +24129,13 @@ var AudioWaveform = ({
23941
24129
  visualizationWidth,
23942
24130
  volume,
23943
24131
  doesVolumeChange,
23944
- playbackRate
24132
+ playbackRate,
24133
+ loopDisplay
23945
24134
  }) => {
23946
24135
  const [peaks, setPeaks] = useState79(null);
23947
24136
  const [error, setError] = useState79(null);
24137
+ const [waveformCanvasKey, setWaveformCanvasKey] = useState79(0);
24138
+ const canUseWorkerPath = useMemo119(() => canUseAudioWaveformWorker(), []);
23948
24139
  const vidConf = Internals55.useUnsafeVideoConfig();
23949
24140
  if (vidConf === null) {
23950
24141
  throw new Error("Expected video config");
@@ -23952,8 +24143,15 @@ var AudioWaveform = ({
23952
24143
  const containerRef = useRef43(null);
23953
24144
  const waveformCanvas = useRef43(null);
23954
24145
  const volumeCanvas = useRef43(null);
24146
+ const waveformWorker = useRef43(null);
24147
+ const hasTransferredCanvas = useRef43(false);
24148
+ const latestRequestId = useRef43(0);
23955
24149
  useEffect73(() => {
24150
+ if (canUseWorkerPath) {
24151
+ return;
24152
+ }
23956
24153
  const controller = new AbortController;
24154
+ setPeaks(null);
23957
24155
  setError(null);
23958
24156
  loadWaveformPeaks(src, controller.signal).then((p) => {
23959
24157
  if (!controller.signal.aborted) {
@@ -23965,30 +24163,127 @@ var AudioWaveform = ({
23965
24163
  }
23966
24164
  });
23967
24165
  return () => controller.abort();
23968
- }, [src]);
24166
+ }, [canUseWorkerPath, src]);
24167
+ useEffect73(() => {
24168
+ if (!canUseWorkerPath) {
24169
+ return;
24170
+ }
24171
+ const canvasElement = waveformCanvas.current;
24172
+ if (!canvasElement || hasTransferredCanvas.current) {
24173
+ return;
24174
+ }
24175
+ const worker = makeAudioWaveformWorker();
24176
+ waveformWorker.current = worker;
24177
+ worker.addEventListener("message", (event) => {
24178
+ if (event.data.type === "error") {
24179
+ if (event.data.requestId !== latestRequestId.current) {
24180
+ return;
24181
+ }
24182
+ setError(new Error(event.data.message));
24183
+ }
24184
+ });
24185
+ let offscreen;
24186
+ try {
24187
+ offscreen = canvasElement.transferControlToOffscreen();
24188
+ } catch (err) {
24189
+ worker.terminate();
24190
+ waveformWorker.current = null;
24191
+ if (canRetryCanvasTransfer(err)) {
24192
+ setWaveformCanvasKey((key4) => key4 + 1);
24193
+ return;
24194
+ }
24195
+ throw err;
24196
+ }
24197
+ hasTransferredCanvas.current = true;
24198
+ worker.postMessage({ type: "init", canvas: offscreen }, [offscreen]);
24199
+ return () => {
24200
+ worker.postMessage({ type: "dispose" });
24201
+ worker.terminate();
24202
+ waveformWorker.current = null;
24203
+ hasTransferredCanvas.current = false;
24204
+ };
24205
+ }, [canUseWorkerPath, waveformCanvasKey]);
23969
24206
  const portionPeaks = useMemo119(() => {
23970
- if (!peaks || peaks.length === 0) {
24207
+ if (canUseWorkerPath || !peaks) {
23971
24208
  return null;
23972
24209
  }
23973
- const startTimeInSeconds = startFrom / vidConf.fps;
23974
- const durationInSeconds = durationInFrames / vidConf.fps * playbackRate;
23975
- const startPeakIndex = Math.floor(startTimeInSeconds * TARGET_SAMPLE_RATE);
23976
- const endPeakIndex = Math.ceil((startTimeInSeconds + durationInSeconds) * TARGET_SAMPLE_RATE);
23977
- return peaks.slice(Math.max(0, startPeakIndex), Math.min(peaks.length, endPeakIndex));
23978
- }, [peaks, startFrom, durationInFrames, vidConf.fps, playbackRate]);
24210
+ return sliceWaveformPeaks({
24211
+ durationInFrames: shouldTileLoopDisplay(loopDisplay) ? loopDisplay.durationInFrames : durationInFrames,
24212
+ fps: vidConf.fps,
24213
+ peaks,
24214
+ playbackRate,
24215
+ startFrom
24216
+ });
24217
+ }, [
24218
+ canUseWorkerPath,
24219
+ durationInFrames,
24220
+ loopDisplay,
24221
+ peaks,
24222
+ playbackRate,
24223
+ startFrom,
24224
+ vidConf.fps
24225
+ ]);
23979
24226
  useEffect73(() => {
23980
24227
  const { current: canvasElement } = waveformCanvas;
23981
24228
  const { current: containerElement } = containerRef;
23982
- if (!canvasElement || !containerElement || !portionPeaks || portionPeaks.length === 0) {
24229
+ if (!canvasElement || !containerElement) {
23983
24230
  return;
23984
24231
  }
23985
24232
  const h = containerElement.clientHeight;
23986
24233
  const w = Math.ceil(visualizationWidth);
24234
+ const vol = typeof volume === "number" ? volume : 1;
24235
+ if (canUseWorkerPath) {
24236
+ const worker = waveformWorker.current;
24237
+ if (!worker || !hasTransferredCanvas.current) {
24238
+ return;
24239
+ }
24240
+ latestRequestId.current += 1;
24241
+ setError(null);
24242
+ const message = {
24243
+ type: "render",
24244
+ requestId: latestRequestId.current,
24245
+ src,
24246
+ width: w,
24247
+ height: h,
24248
+ volume: vol,
24249
+ startFrom,
24250
+ durationInFrames,
24251
+ fps: vidConf.fps,
24252
+ playbackRate,
24253
+ loopDisplay
24254
+ };
24255
+ worker.postMessage(message);
24256
+ return;
24257
+ }
23987
24258
  canvasElement.width = w;
23988
24259
  canvasElement.height = h;
23989
- const vol = typeof volume === "number" ? volume : 1;
23990
- drawBars(canvasElement, portionPeaks, "rgba(255, 255, 255, 0.6)", vol, w);
23991
- }, [portionPeaks, visualizationWidth, volume]);
24260
+ if (shouldTileLoopDisplay(loopDisplay)) {
24261
+ drawLoopedWaveform({
24262
+ canvas: canvasElement,
24263
+ peaks: portionPeaks ?? EMPTY_PEAKS,
24264
+ volume: vol,
24265
+ visualizationWidth,
24266
+ loopWidth: getLoopDisplayWidth({
24267
+ visualizationWidth,
24268
+ loopDisplay
24269
+ })
24270
+ });
24271
+ } else {
24272
+ drawBars(canvasElement, portionPeaks ?? EMPTY_PEAKS, "rgba(255, 255, 255, 0.6)", vol, w);
24273
+ }
24274
+ }, [
24275
+ canUseWorkerPath,
24276
+ durationInFrames,
24277
+ loopDisplay,
24278
+ playbackRate,
24279
+ portionPeaks,
24280
+ src,
24281
+ startFrom,
24282
+ vidConf.fps,
24283
+ visualizationWidth,
24284
+ volume,
24285
+ waveformCanvasKey
24286
+ ]);
23992
24287
  useEffect73(() => {
23993
24288
  const { current: volumeCanvasElement } = volumeCanvas;
23994
24289
  const { current: containerElement } = containerRef;
@@ -24022,6 +24317,7 @@ var AudioWaveform = ({
24022
24317
  context.stroke();
24023
24318
  }, [visualizationWidth, volume, doesVolumeChange]);
24024
24319
  if (error) {
24320
+ console.error(error);
24025
24321
  return /* @__PURE__ */ jsx212("div", {
24026
24322
  style: container43,
24027
24323
  children: /* @__PURE__ */ jsx212("div", {
@@ -24030,7 +24326,7 @@ var AudioWaveform = ({
24030
24326
  })
24031
24327
  });
24032
24328
  }
24033
- if (!peaks) {
24329
+ if (!canUseWorkerPath && !peaks) {
24034
24330
  return null;
24035
24331
  }
24036
24332
  return /* @__PURE__ */ jsxs102("div", {
@@ -24040,7 +24336,7 @@ var AudioWaveform = ({
24040
24336
  /* @__PURE__ */ jsx212("canvas", {
24041
24337
  ref: waveformCanvas,
24042
24338
  style: waveformCanvasStyle
24043
- }),
24339
+ }, waveformCanvasKey),
24044
24340
  /* @__PURE__ */ jsx212("canvas", {
24045
24341
  ref: volumeCanvas,
24046
24342
  style: volumeCanvasStyle
@@ -24063,7 +24359,8 @@ var width = {
24063
24359
  position: "relative"
24064
24360
  };
24065
24361
  var icon4 = {
24066
- height: 12
24362
+ height: 12,
24363
+ filter: "drop-shadow(0 0 2px rgba(0, 0, 0, 0.9)) drop-shadow(0 1px 2px rgba(0, 0, 0, 0.8))"
24067
24364
  };
24068
24365
  var Icon = () => /* @__PURE__ */ jsx213("svg", {
24069
24366
  viewBox: "0 0 512 512",
@@ -24073,44 +24370,23 @@ var Icon = () => /* @__PURE__ */ jsx213("svg", {
24073
24370
  d: "M512 256c0 88.224-71.775 160-160 160H170.067l34.512 32.419c9.875 9.276 10.119 24.883.539 34.464l-10.775 10.775c-9.373 9.372-24.568 9.372-33.941 0l-92.686-92.686c-9.373-9.373-9.373-24.568 0-33.941l92.686-92.686c9.373-9.373 24.568-9.373 33.941 0l10.775 10.775c9.581 9.581 9.337 25.187-.539 34.464L170.067 352H352c52.935 0 96-43.065 96-96 0-13.958-2.996-27.228-8.376-39.204-4.061-9.039-2.284-19.626 4.723-26.633l12.183-12.183c11.499-11.499 30.965-8.526 38.312 5.982C505.814 205.624 512 230.103 512 256zM72.376 295.204C66.996 283.228 64 269.958 64 256c0-52.935 43.065-96 96-96h181.933l-34.512 32.419c-9.875 9.276-10.119 24.883-.539 34.464l10.775 10.775c9.373 9.372 24.568 9.372 33.941 0l92.686-92.686c9.373-9.373 9.373-24.568 0-33.941l-92.686-92.686c-9.373-9.373-24.568-9.373-33.941 0L306.882 29.12c-9.581 9.581-9.337 25.187.539 34.464L341.933 96H160C71.775 96 0 167.776 0 256c0 25.897 6.186 50.376 17.157 72.039 7.347 14.508 26.813 17.481 38.312 5.982l12.183-12.183c7.008-7.008 8.786-17.595 4.724-26.634z"
24074
24371
  })
24075
24372
  });
24076
- var topLine = {
24077
- top: 0,
24078
- height: 2,
24079
- width: 1,
24080
- background: LIGHT_COLOR
24081
- };
24082
- var bottomLine = {
24083
- top: 0,
24084
- height: 2,
24373
+ var verticalLine = {
24374
+ height: "100%",
24085
24375
  width: 1,
24086
- background: LIGHT_COLOR
24087
- };
24088
- var topContainer = {
24089
- justifyContent: "flex-start",
24090
- alignItems: "center"
24376
+ background: "rgb(255,255,255, 0.5)"
24091
24377
  };
24092
24378
  var centerContainer = {
24093
24379
  justifyContent: "center",
24094
24380
  alignItems: "center"
24095
24381
  };
24096
- var bottomContainer = {
24097
- justifyContent: "flex-end",
24098
- alignItems: "center"
24099
- };
24100
24382
  var LoopedIndicator = () => {
24101
24383
  return /* @__PURE__ */ jsxs103("div", {
24102
24384
  style: width,
24103
24385
  children: [
24104
24386
  /* @__PURE__ */ jsx213(AbsoluteFill5, {
24105
- style: topContainer,
24106
- children: /* @__PURE__ */ jsx213("div", {
24107
- style: topLine
24108
- })
24109
- }),
24110
- /* @__PURE__ */ jsx213(AbsoluteFill5, {
24111
- style: bottomContainer,
24387
+ style: centerContainer,
24112
24388
  children: /* @__PURE__ */ jsx213("div", {
24113
- style: bottomLine
24389
+ style: verticalLine
24114
24390
  })
24115
24391
  }),
24116
24392
  /* @__PURE__ */ jsx213(AbsoluteFill5, {
@@ -24253,17 +24529,23 @@ async function extractFrames({
24253
24529
  }
24254
24530
  try {
24255
24531
  const [durationInSeconds, format, videoTrack] = await Promise.all([
24256
- input2.computeDuration(),
24532
+ getDurationOrCompute(input2),
24257
24533
  input2.getFormat(),
24258
24534
  input2.getPrimaryVideoTrack()
24259
24535
  ]);
24260
24536
  if (!videoTrack) {
24261
24537
  throw new Error("No video track found in the input");
24262
24538
  }
24539
+ if (await videoTrack.isLive()) {
24540
+ throw new Error("Live streams are not currently supported by Remotion. Sorry! Source: " + src);
24541
+ }
24542
+ if (await videoTrack.isRelativeToUnixEpoch()) {
24543
+ throw new Error("Streams with UNIX timestamps are not currently supported by Remotion. Sorry! Source: " + src);
24544
+ }
24263
24545
  const timestamps = typeof timestampsInSeconds === "function" ? await timestampsInSeconds({
24264
24546
  track: {
24265
- width: videoTrack.displayWidth,
24266
- height: videoTrack.displayHeight
24547
+ width: await videoTrack.getDisplayWidth(),
24548
+ height: await videoTrack.getDisplayHeight()
24267
24549
  },
24268
24550
  container: format.name,
24269
24551
  durationInSeconds
@@ -24582,7 +24864,8 @@ var TimelineVideoInfo = ({
24582
24864
  volume,
24583
24865
  doesVolumeChange,
24584
24866
  premountWidth,
24585
- postmountWidth
24867
+ postmountWidth,
24868
+ loopDisplay
24586
24869
  }) => {
24587
24870
  const { fps } = useVideoConfig5();
24588
24871
  const ref2 = useRef45(null);
@@ -24605,25 +24888,54 @@ var TimelineVideoInfo = ({
24605
24888
  return;
24606
24889
  }
24607
24890
  current.appendChild(canvas);
24891
+ const loopWidth = getLoopDisplayWidth({
24892
+ visualizationWidth: naturalWidth,
24893
+ loopDisplay
24894
+ });
24895
+ const shouldRepeatVideo = shouldTileLoopDisplay(loopDisplay);
24896
+ const targetCanvas = shouldRepeatVideo ? document.createElement("canvas") : canvas;
24897
+ targetCanvas.width = shouldRepeatVideo ? Math.max(1, Math.ceil(loopWidth)) : canvas.width;
24898
+ targetCanvas.height = canvas.height;
24899
+ const targetCtx = shouldRepeatVideo ? targetCanvas.getContext("2d") : ctx;
24900
+ if (!targetCtx) {
24901
+ current.removeChild(canvas);
24902
+ return;
24903
+ }
24904
+ const repeatTarget = () => {
24905
+ if (!shouldRepeatVideo) {
24906
+ return;
24907
+ }
24908
+ const pattern = ctx.createPattern(targetCanvas, "repeat-x");
24909
+ if (!pattern) {
24910
+ return;
24911
+ }
24912
+ pattern.setTransform(new DOMMatrix().scaleSelf(loopWidth / targetCanvas.width, 1));
24913
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
24914
+ ctx.fillStyle = pattern;
24915
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
24916
+ };
24608
24917
  const filledSlots = new Map;
24609
24918
  const fromSeconds = trimBefore / fps;
24610
- const toSeconds = fromSeconds + durationInFrames * playbackRate / fps;
24919
+ const visibleDurationInFrames = shouldRepeatVideo && loopDisplay ? loopDisplay.durationInFrames : durationInFrames;
24920
+ const toSeconds = fromSeconds + visibleDurationInFrames * playbackRate / fps;
24921
+ const targetWidth = shouldRepeatVideo ? targetCanvas.width : naturalWidth;
24611
24922
  if (aspectRatio.current !== null) {
24612
24923
  ensureSlots({
24613
24924
  filledSlots,
24614
- naturalWidth,
24925
+ naturalWidth: targetWidth,
24615
24926
  fromSeconds,
24616
24927
  toSeconds,
24617
24928
  aspectRatio: aspectRatio.current
24618
24929
  });
24619
24930
  fillWithCachedFrames({
24620
- ctx,
24621
- naturalWidth,
24931
+ ctx: targetCtx,
24932
+ naturalWidth: targetWidth,
24622
24933
  filledSlots,
24623
24934
  src,
24624
24935
  segmentDuration: toSeconds - fromSeconds,
24625
24936
  fromSeconds
24626
24937
  });
24938
+ repeatTarget();
24627
24939
  const unfilled = Array.from(filledSlots.keys()).filter((timestamp) => !filledSlots.get(timestamp));
24628
24940
  if (unfilled.length === 0) {
24629
24941
  return () => {
@@ -24641,7 +24953,7 @@ var TimelineVideoInfo = ({
24641
24953
  filledSlots,
24642
24954
  fromSeconds,
24643
24955
  toSeconds,
24644
- naturalWidth,
24956
+ naturalWidth: targetWidth,
24645
24957
  aspectRatio: aspectRatio.current
24646
24958
  });
24647
24959
  return Array.from(filledSlots.keys()).map((timestamp) => timestamp / WEBCODECS_TIMESCALE);
@@ -24669,17 +24981,18 @@ var TimelineVideoInfo = ({
24669
24981
  filledSlots,
24670
24982
  fromSeconds,
24671
24983
  toSeconds,
24672
- naturalWidth,
24984
+ naturalWidth: targetWidth,
24673
24985
  aspectRatio: aspectRatio.current
24674
24986
  });
24675
24987
  fillFrameWhereItFits({
24676
- ctx,
24988
+ ctx: targetCtx,
24677
24989
  filledSlots,
24678
- visualizationWidth: naturalWidth,
24990
+ visualizationWidth: targetWidth,
24679
24991
  frame: transformed,
24680
24992
  segmentDuration: toSeconds - fromSeconds,
24681
24993
  fromSeconds
24682
24994
  });
24995
+ repeatTarget();
24683
24996
  } catch (e) {
24684
24997
  if (frame2) {
24685
24998
  frame2.close();
@@ -24695,13 +25008,14 @@ var TimelineVideoInfo = ({
24695
25008
  return;
24696
25009
  }
24697
25010
  fillWithCachedFrames({
24698
- ctx,
24699
- naturalWidth,
25011
+ ctx: targetCtx,
25012
+ naturalWidth: targetWidth,
24700
25013
  filledSlots,
24701
25014
  src,
24702
25015
  segmentDuration: toSeconds - fromSeconds,
24703
25016
  fromSeconds
24704
25017
  });
25018
+ repeatTarget();
24705
25019
  }).catch((e) => {
24706
25020
  setError(e);
24707
25021
  });
@@ -24713,6 +25027,7 @@ var TimelineVideoInfo = ({
24713
25027
  durationInFrames,
24714
25028
  error,
24715
25029
  fps,
25030
+ loopDisplay,
24716
25031
  naturalWidth,
24717
25032
  playbackRate,
24718
25033
  src,
@@ -24744,7 +25059,8 @@ var TimelineVideoInfo = ({
24744
25059
  durationInFrames,
24745
25060
  volume,
24746
25061
  doesVolumeChange,
24747
- playbackRate
25062
+ playbackRate,
25063
+ loopDisplay
24748
25064
  })
24749
25065
  })
24750
25066
  ]
@@ -24769,29 +25085,37 @@ var TimelineSequence = ({ s }) => {
24769
25085
  var Inner4 = ({ s, windowWidth }) => {
24770
25086
  const video = Internals56.useVideo();
24771
25087
  const maxMediaDuration = useMaxMediaDuration(s, video?.fps ?? 30);
25088
+ const effectiveMaxMediaDuration = s.loopDisplay ? null : maxMediaDuration;
24772
25089
  if (!video) {
24773
25090
  throw new TypeError("Expected video config");
24774
25091
  }
24775
25092
  const frame2 = useCurrentFrame2();
24776
25093
  const relativeFrame = frame2 - s.from;
25094
+ const displayDurationInFrames = s.loopDisplay ? s.loopDisplay.durationInFrames * s.loopDisplay.numberOfTimes : s.duration;
24777
25095
  const relativeFrameWithPremount = relativeFrame + (s.premountDisplay ?? 0);
24778
- const relativeFrameWithPostmount = relativeFrame - s.duration;
25096
+ const relativeFrameWithPostmount = relativeFrame - displayDurationInFrames;
24779
25097
  const roundedFrame = Math.round(relativeFrame * 100) / 100;
24780
- const isInRange = relativeFrame >= 0 && relativeFrame < s.duration;
24781
- const isPremounting = relativeFrameWithPremount >= 0 && relativeFrameWithPremount < s.duration && !isInRange;
25098
+ const isInRange = relativeFrame >= 0 && relativeFrame < displayDurationInFrames;
25099
+ const isPremounting = relativeFrameWithPremount >= 0 && relativeFrameWithPremount < displayDurationInFrames && !isInRange;
24782
25100
  const isPostmounting = relativeFrameWithPostmount >= 0 && relativeFrameWithPostmount < (s.postmountDisplay ?? 0) && !isInRange;
24783
25101
  const { marginLeft, width: width2, naturalWidth, premountWidth, postmountWidth } = useMemo121(() => {
24784
25102
  return getTimelineSequenceLayout({
24785
- durationInFrames: s.loopDisplay ? s.loopDisplay.durationInFrames * s.loopDisplay.numberOfTimes : s.duration,
25103
+ durationInFrames: displayDurationInFrames,
24786
25104
  startFrom: s.loopDisplay ? s.from + s.loopDisplay.startOffset : s.from,
24787
25105
  startFromMedia: s.type === "sequence" || s.type === "image" ? 0 : s.startMediaFrom,
24788
- maxMediaDuration,
25106
+ maxMediaDuration: effectiveMaxMediaDuration,
24789
25107
  video,
24790
25108
  windowWidth,
24791
25109
  premountDisplay: s.premountDisplay,
24792
25110
  postmountDisplay: s.postmountDisplay
24793
25111
  });
24794
- }, [maxMediaDuration, s, video, windowWidth]);
25112
+ }, [
25113
+ displayDurationInFrames,
25114
+ effectiveMaxMediaDuration,
25115
+ s,
25116
+ video,
25117
+ windowWidth
25118
+ ]);
24795
25119
  const style11 = useMemo121(() => {
24796
25120
  return {
24797
25121
  background: s.type === "audio" ? AUDIO_GRADIENT : s.type === "video" ? VIDEO_GRADIENT : s.type === "image" ? IMAGE_GRADIENT : BLUE,
@@ -24806,7 +25130,7 @@ var Inner4 = ({ s, windowWidth }) => {
24806
25130
  opacity: isInRange ? 1 : 0.5
24807
25131
  };
24808
25132
  }, [isInRange, marginLeft, s.type, width2]);
24809
- if (maxMediaDuration === null) {
25133
+ if (maxMediaDuration === null && !s.loopDisplay) {
24810
25134
  return null;
24811
25135
  }
24812
25136
  return /* @__PURE__ */ jsxs106("div", {
@@ -24849,7 +25173,8 @@ var Inner4 = ({ s, windowWidth }) => {
24849
25173
  startFrom: s.startMediaFrom,
24850
25174
  durationInFrames: s.duration,
24851
25175
  volume: s.volume,
24852
- playbackRate: s.playbackRate
25176
+ playbackRate: s.playbackRate,
25177
+ loopDisplay: s.loopDisplay
24853
25178
  }) : null,
24854
25179
  s.type === "video" ? /* @__PURE__ */ jsx218(TimelineVideoInfo, {
24855
25180
  src: s.src,
@@ -24861,7 +25186,8 @@ var Inner4 = ({ s, windowWidth }) => {
24861
25186
  volume: s.volume,
24862
25187
  doesVolumeChange: s.doesVolumeChange,
24863
25188
  premountWidth: premountWidth ?? 0,
24864
- postmountWidth: postmountWidth ?? 0
25189
+ postmountWidth: postmountWidth ?? 0,
25190
+ loopDisplay: s.loopDisplay
24865
25191
  }) : null,
24866
25192
  s.type === "image" ? /* @__PURE__ */ jsx218(TimelineImageInfo, {
24867
25193
  src: s.src,