@remotion/studio 4.0.496 → 4.0.498

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 (109) hide show
  1. package/dist/components/AudioWaveform.js +1 -11
  2. package/dist/components/Canvas.js +22 -203
  3. package/dist/components/Checkbox.js +1 -1
  4. package/dist/components/CompositionSelectorItem.js +2 -10
  5. package/dist/components/ConfigureLicenseModal.d.ts +4 -0
  6. package/dist/components/ConfigureLicenseModal.js +194 -0
  7. package/dist/components/Editor.d.ts +0 -1
  8. package/dist/components/Editor.js +2 -3
  9. package/dist/components/InspectorPanel/AlignmentControls.js +2 -0
  10. package/dist/components/InspectorPanel/CompositionInspector.js +2 -1
  11. package/dist/components/InspectorPanel/ConnectedCompositionsSection.js +27 -2
  12. package/dist/components/InspectorPanel/SequenceSelectionInspector.js +65 -8
  13. package/dist/components/InspectorPanel/use-composition-actions.js +7 -3
  14. package/dist/components/LicenseKeyValidation.d.ts +17 -0
  15. package/dist/components/LicenseKeyValidation.js +113 -0
  16. package/dist/components/Modals.js +2 -1
  17. package/dist/components/NewComposition/CodemodFooter.d.ts +1 -1
  18. package/dist/components/NewComposition/CodemodFooter.js +23 -4
  19. package/dist/components/NewComposition/InputDragger.d.ts +6 -0
  20. package/dist/components/NewComposition/InputDragger.js +12 -3
  21. package/dist/components/NewComposition/NewComposition.js +1 -1
  22. package/dist/components/NewComposition/use-rename-static-file.js +1 -1
  23. package/dist/components/RenderButton.js +0 -1
  24. package/dist/components/RenderModal/WebRenderModal.js +14 -8
  25. package/dist/components/SelectedOutlineElement.js +56 -88
  26. package/dist/components/SelectedOutlineOverlay.d.ts +1 -1
  27. package/dist/components/SelectedOutlineOverlay.js +10 -11
  28. package/dist/components/Timeline/MaxTimelineTracks.d.ts +0 -1
  29. package/dist/components/Timeline/MaxTimelineTracks.js +3 -3
  30. package/dist/components/Timeline/Timeline.js +19 -11
  31. package/dist/components/Timeline/TimelineAssetDropIndicator.d.ts +2 -0
  32. package/dist/components/Timeline/TimelineAssetDropIndicator.js +37 -0
  33. package/dist/components/Timeline/TimelineClipboardKeybindings.js +1 -7
  34. package/dist/components/Timeline/TimelineDragHandler.js +1 -1
  35. package/dist/components/Timeline/TimelineScrollable.js +3 -2
  36. package/dist/components/Timeline/TimelineSelection.d.ts +4 -0
  37. package/dist/components/Timeline/TimelineSelection.js +41 -6
  38. package/dist/components/Timeline/TimelineSequence.js +26 -14
  39. package/dist/components/Timeline/TimelineSequenceFrame.js +2 -4
  40. package/dist/components/Timeline/TimelineSequenceItem.js +7 -5
  41. package/dist/components/Timeline/TimelineSequencePropItem.js +4 -0
  42. package/dist/components/Timeline/TimelineSequenceRightEdgeDragHandle.d.ts +23 -3
  43. package/dist/components/Timeline/TimelineSequenceRightEdgeDragHandle.js +135 -19
  44. package/dist/components/Timeline/TimelineTimeIndicators.d.ts +16 -0
  45. package/dist/components/Timeline/TimelineTimeIndicators.js +154 -35
  46. package/dist/components/Timeline/TimelineTranslateField.js +2 -1
  47. package/dist/components/Timeline/TimelineVideoInfo.d.ts +1 -0
  48. package/dist/components/Timeline/TimelineVideoInfo.js +4 -1
  49. package/dist/components/Timeline/call-update-keyframe-settings.d.ts +17 -0
  50. package/dist/components/Timeline/call-update-keyframe-settings.js +43 -1
  51. package/dist/components/Timeline/delete-selected-timeline-item.d.ts +2 -0
  52. package/dist/components/Timeline/delete-selected-timeline-item.js +5 -4
  53. package/dist/components/Timeline/disable-sequence-interactivity.js +2 -0
  54. package/dist/components/Timeline/get-timeline-media-visualization-layout.d.ts +8 -0
  55. package/dist/components/Timeline/get-timeline-media-visualization-layout.js +10 -0
  56. package/dist/components/Timeline/get-timeline-video-info-widths.js +11 -3
  57. package/dist/components/Timeline/keyframe-clipboard.js +21 -5
  58. package/dist/components/Timeline/reset-selected-timeline-props.js +2 -0
  59. package/dist/components/Timeline/save-sequence-prop.d.ts +5 -3
  60. package/dist/components/Timeline/save-sequence-prop.js +43 -18
  61. package/dist/components/Timeline/should-clear-selection-on-pointer-down.d.ts +2 -0
  62. package/dist/components/Timeline/should-clear-selection-on-pointer-down.js +1 -1
  63. package/dist/components/Timeline/timeline-asset-drop-context.d.ts +1 -0
  64. package/dist/components/Timeline/timeline-asset-drop-context.js +5 -0
  65. package/dist/components/Timeline/timeline-scroll-logic.d.ts +7 -0
  66. package/dist/components/Timeline/timeline-scroll-logic.js +10 -1
  67. package/dist/components/Timeline/update-selected-easing.js +26 -23
  68. package/dist/components/Timeline/use-rename-sequence.js +2 -0
  69. package/dist/components/Timeline/use-sequence-freeze-frame-menu-item.d.ts +5 -0
  70. package/dist/components/Timeline/use-sequence-freeze-frame-menu-item.js +18 -9
  71. package/dist/components/Timeline/use-timeline-asset-drop.d.ts +1 -0
  72. package/dist/components/Timeline/use-timeline-asset-drop.js +168 -0
  73. package/dist/components/composition-menu-items.d.ts +15 -1
  74. package/dist/components/composition-menu-items.js +137 -107
  75. package/dist/components/drop-handler-data.d.ts +13 -0
  76. package/dist/components/drop-handler-data.js +117 -0
  77. package/dist/components/handle-drop.d.ts +13 -0
  78. package/dist/components/handle-drop.js +115 -0
  79. package/dist/components/import-assets.d.ts +26 -8
  80. package/dist/components/import-assets.js +161 -49
  81. package/dist/components/selected-outline-drag.d.ts +11 -1
  82. package/dist/components/selected-outline-drag.js +74 -1
  83. package/dist/esm/{chunk-0v4n7nkh.js → chunk-7a6q9hk2.js} +5271 -4259
  84. package/dist/esm/index.mjs +0 -19
  85. package/dist/esm/internals.mjs +5271 -4259
  86. package/dist/esm/previewEntry.mjs +5475 -4463
  87. package/dist/esm/renderEntry.mjs +1 -1
  88. package/dist/helpers/interactivity-enabled.d.ts +1 -1
  89. package/dist/helpers/interactivity-enabled.js +2 -2
  90. package/dist/helpers/is-video-with-last-frame-hold.d.ts +2 -0
  91. package/dist/helpers/is-video-with-last-frame-hold.js +9 -0
  92. package/dist/helpers/retry-payload.js +0 -1
  93. package/dist/helpers/url-state.d.ts +1 -0
  94. package/dist/helpers/url-state.js +5 -1
  95. package/dist/helpers/use-max-media-duration.js +5 -3
  96. package/dist/helpers/use-menu-structure.js +11 -3
  97. package/dist/icons/duplicate.d.ts +5 -0
  98. package/dist/icons/duplicate.js +10 -0
  99. package/dist/icons/snowflake.d.ts +5 -0
  100. package/dist/icons/snowflake.js +10 -0
  101. package/dist/icons/trash.d.ts +5 -0
  102. package/dist/icons/trash.js +8 -0
  103. package/dist/state/modals.d.ts +3 -1
  104. package/dist/visual-controls/VisualControls.js +1 -1
  105. package/package.json +11 -11
  106. package/dist/components/RenderModal/WebRenderModalLicense.d.ts +0 -9
  107. package/dist/components/RenderModal/WebRenderModalLicense.js +0 -189
  108. package/dist/components/RenderModal/WebRenderModalLicenseKeyDetails.d.ts +0 -13
  109. package/dist/components/RenderModal/WebRenderModalLicenseKeyDetails.js +0 -60
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.TimelineSequenceRightEdgeDragHandle = exports.useTimelineSequenceFromDrag = exports.TimelineSequenceLeftEdgeDragHandle = exports.getTimelineSequenceFromDragTargets = exports.getTimelineSequenceLeftEdgeDragTargets = exports.getTimelineSequenceDurationDragTargets = exports.getTimelineSequenceFromDragKeyframeMoves = exports.getTimelineSequenceFromDragChanges = exports.getTimelineSequenceFromDragValue = exports.getTimelineSequenceDurationDragChanges = exports.getTimelineSequenceLeftEdgeDragChanges = exports.getTimelineSequenceLeftEdgeDragValues = exports.getTimelineSequenceLeftEdgeDragDelta = exports.getTimelineSequenceDurationDragValue = exports.isTimelineSequenceDurationDraggable = void 0;
3
+ exports.TimelineSequenceRightEdgeDragHandle = exports.useTimelineSequenceFromDrag = exports.TimelineSequenceLeftEdgeDragHandle = exports.getTimelineSequenceFromDragTargets = exports.getTimelineSequenceLeftEdgeDragTargets = exports.getTimelineSequenceDurationDragTargets = exports.getTimelineSequenceFromDragKeyframeMoves = exports.getTimelineSequenceFromDragChanges = exports.getTimelineSequenceFromDragDelta = exports.getTimelineSequenceFromDragValue = exports.getTimelineSequenceDurationDragChanges = exports.getTimelineSequenceLeftEdgeDragChanges = exports.getTimelineSequenceLeftEdgeDragValues = exports.getTimelineSequenceLeftEdgeDragDelta = exports.getTimelineSequenceDurationDragValue = exports.isTimelineSequenceLeftEdgeDraggable = exports.isTimelineSequenceDurationDraggable = exports.isCascadingSequence = exports.isTransitionSeriesSequence = exports.timelineSequenceFromDragSnapThresholdPx = void 0;
4
4
  const jsx_runtime_1 = require("react/jsx-runtime");
5
5
  const studio_shared_1 = require("@remotion/studio-shared");
6
6
  const react_1 = require("react");
@@ -9,11 +9,14 @@ const no_react_1 = require("remotion/no-react");
9
9
  const calculate_timeline_1 = require("../../helpers/calculate-timeline");
10
10
  const client_id_1 = require("../../helpers/client-id");
11
11
  const colors_1 = require("../../helpers/colors");
12
+ const sort_by_nonce_history_1 = require("../../helpers/sort-by-nonce-history");
12
13
  const timeline_layout_1 = require("../../helpers/timeline-layout");
14
+ const editor_snapping_1 = require("../../state/editor-snapping");
13
15
  const ForceSpecificCursor_1 = require("../ForceSpecificCursor");
14
16
  const save_sequence_prop_1 = require("./save-sequence-prop");
15
17
  const TimelineSelection_1 = require("./TimelineSelection");
16
18
  const HANDLE_WIDTH = 6;
19
+ exports.timelineSequenceFromDragSnapThresholdPx = 10;
17
20
  const baseStyle = {
18
21
  position: 'absolute',
19
22
  top: 0,
@@ -81,50 +84,102 @@ const canUpdateTrimBefore = ({ propStatuses, nodePath, }) => {
81
84
  const status = (_b = (_a = remotion_1.Internals.getPropStatusesCtx(propStatuses, nodePath)) === null || _a === void 0 ? void 0 : _a.trimBefore) === null || _b === void 0 ? void 0 : _b.status;
82
85
  return status === 'static';
83
86
  };
84
- const isTimelineSequenceDurationDraggable = (sequence) => {
87
+ const isTransitionSeriesSequence = (sequence) => {
88
+ var _a;
89
+ return ((_a = sequence.controls) === null || _a === void 0 ? void 0 : _a.componentIdentity) ===
90
+ 'dev.remotion.transitions.TransitionSeries.Sequence';
91
+ };
92
+ exports.isTransitionSeriesSequence = isTransitionSeriesSequence;
93
+ const isSeriesSequence = (sequence) => {
85
94
  var _a;
86
- const isInteractiveSeriesSequence = ((_a = sequence.controls) === null || _a === void 0 ? void 0 : _a.componentIdentity) ===
95
+ return ((_a = sequence.controls) === null || _a === void 0 ? void 0 : _a.componentIdentity) ===
87
96
  'dev.remotion.remotion.Series.Sequence';
97
+ };
98
+ const isCascadingSequence = (sequence) => isSeriesSequence(sequence) || (0, exports.isTransitionSeriesSequence)(sequence);
99
+ exports.isCascadingSequence = isCascadingSequence;
100
+ const isTransitionSeriesTransition = (sequence) => {
101
+ var _a;
102
+ return ((_a = sequence === null || sequence === void 0 ? void 0 : sequence.controls) === null || _a === void 0 ? void 0 : _a.componentIdentity) ===
103
+ 'dev.remotion.transitions.TransitionSeries.Transition';
104
+ };
105
+ const getMinimumSequenceDuration = ({ sequence, sequences, }) => {
106
+ if (!(0, exports.isTransitionSeriesSequence)(sequence)) {
107
+ return 1;
108
+ }
109
+ const siblings = (0, sort_by_nonce_history_1.sortItemsByNonceHistory)(sequences.filter((candidate) => candidate.parent === sequence.parent &&
110
+ candidate.rootId === sequence.rootId &&
111
+ ((0, exports.isTransitionSeriesSequence)(candidate) ||
112
+ isTransitionSeriesTransition(candidate))));
113
+ const sequenceIndex = siblings.findIndex((candidate) => candidate.id === sequence.id);
114
+ if (sequenceIndex === -1) {
115
+ return 1;
116
+ }
117
+ const previous = siblings[sequenceIndex - 1];
118
+ const next = siblings[sequenceIndex + 1];
119
+ return Math.max(1, isTransitionSeriesTransition(previous) ? previous.duration : 1, isTransitionSeriesTransition(next) ? next.duration : 1);
120
+ };
121
+ const isTimelineSequenceDurationDraggable = (sequence) => {
122
+ const isInteractiveCascadingSequence = (0, exports.isCascadingSequence)(sequence);
88
123
  return ((sequence.type === 'sequence' ||
89
124
  sequence.type === 'image' ||
90
125
  sequence.type === 'audio' ||
91
126
  sequence.type === 'video') &&
92
127
  !sequence.loopDisplay &&
93
- (!sequence.isInsideSeries || isInteractiveSeriesSequence) &&
128
+ (!sequence.isInsideSeries || isInteractiveCascadingSequence) &&
94
129
  Boolean(sequence.controls));
95
130
  };
96
131
  exports.isTimelineSequenceDurationDraggable = isTimelineSequenceDurationDraggable;
97
- const isLeftEdgeDraggableSequence = (sequence) => {
98
- return (!sequence.isInsideSeries &&
132
+ const isTimelineSequenceLeftEdgeDraggable = (sequence) => {
133
+ return ((!sequence.isInsideSeries || (0, exports.isCascadingSequence)(sequence)) &&
99
134
  Boolean(sequence.controls) &&
100
135
  (sequence.type === 'sequence' ||
101
136
  sequence.type === 'image' ||
102
137
  sequence.type === 'audio' ||
103
138
  sequence.type === 'video'));
104
139
  };
140
+ exports.isTimelineSequenceLeftEdgeDraggable = isTimelineSequenceLeftEdgeDraggable;
141
+ const playbackRateComponentIdentities = new Set([
142
+ 'dev.remotion.gif.Gif',
143
+ 'dev.remotion.media.Audio',
144
+ 'dev.remotion.media.Video',
145
+ 'dev.remotion.remotion.AnimatedImage',
146
+ ]);
147
+ const getTrimBeforePlaybackRate = (sequence) => {
148
+ var _a, _b;
149
+ const componentIdentity = (_a = sequence.controls) === null || _a === void 0 ? void 0 : _a.componentIdentity;
150
+ if (componentIdentity === null ||
151
+ componentIdentity === undefined ||
152
+ !playbackRateComponentIdentities.has(componentIdentity)) {
153
+ return 1;
154
+ }
155
+ const runtimePlaybackRate = (_b = sequence.controls) === null || _b === void 0 ? void 0 : _b.currentRuntimeValueDotNotation.playbackRate;
156
+ return typeof runtimePlaybackRate === 'number' ? runtimePlaybackRate : 1;
157
+ };
105
158
  const isFromDraggableSequence = (sequence) => {
106
159
  return (!sequence.loopDisplay &&
107
160
  !sequence.isInsideSeries &&
108
161
  Boolean(sequence.controls));
109
162
  };
110
- const getTimelineSequenceDurationDragValue = ({ initialDuration, deltaFrames, }) => Math.max(1, initialDuration + deltaFrames);
163
+ const getTimelineSequenceDurationDragValue = ({ initialDuration, deltaFrames, minimumDuration = 1, }) => Math.max(minimumDuration, initialDuration + deltaFrames);
111
164
  exports.getTimelineSequenceDurationDragValue = getTimelineSequenceDurationDragValue;
112
- const getTimelineSequenceLeftEdgeDragDelta = ({ initialDuration, initialTrimBefore, deltaFrames, }) => {
113
- const minDeltaFrames = 0 - initialTrimBefore;
114
- const maxDeltaFrames = initialDuration - 1;
165
+ const getTimelineSequenceLeftEdgeDragDelta = ({ initialDuration, initialTrimBefore, deltaFrames, playbackRate, minimumDuration = 1, }) => {
166
+ const minDeltaFrames = 0 - initialTrimBefore / playbackRate;
167
+ const maxDeltaFrames = initialDuration - minimumDuration;
115
168
  return Math.max(minDeltaFrames, Math.min(deltaFrames, maxDeltaFrames));
116
169
  };
117
170
  exports.getTimelineSequenceLeftEdgeDragDelta = getTimelineSequenceLeftEdgeDragDelta;
118
- const getTimelineSequenceLeftEdgeDragValues = ({ initialDuration, initialFrom, initialTrimBefore, deltaFrames, }) => {
171
+ const getTimelineSequenceLeftEdgeDragValues = ({ initialDuration, initialFrom, initialTrimBefore, deltaFrames, playbackRate, minimumDuration = 1, }) => {
119
172
  const clampedDeltaFrames = (0, exports.getTimelineSequenceLeftEdgeDragDelta)({
120
173
  initialDuration,
121
174
  initialTrimBefore,
122
175
  deltaFrames,
176
+ playbackRate,
177
+ minimumDuration,
123
178
  });
124
179
  return {
125
180
  durationInFrames: initialDuration - clampedDeltaFrames,
126
181
  from: initialFrom + clampedDeltaFrames,
127
- trimBefore: initialTrimBefore + clampedDeltaFrames,
182
+ trimBefore: initialTrimBefore + clampedDeltaFrames * playbackRate,
128
183
  };
129
184
  };
130
185
  exports.getTimelineSequenceLeftEdgeDragValues = getTimelineSequenceLeftEdgeDragValues;
@@ -135,13 +190,16 @@ const getTimelineSequenceLeftEdgeDragChanges = ({ targets, deltaFrames, }) => {
135
190
  initialFrom: target.initialFrom,
136
191
  initialTrimBefore: target.initialTrimBefore,
137
192
  deltaFrames,
193
+ playbackRate: target.playbackRate,
194
+ minimumDuration: target.minimumDuration,
138
195
  });
139
196
  const changes = [];
140
- if (nextValues.from !== target.initialFrom) {
197
+ if (target.positionField !== null &&
198
+ nextValues.from !== target.initialFrom) {
141
199
  changes.push({
142
200
  fileName: target.fileName,
143
201
  nodePath: target.nodePath,
144
- fieldKey: 'from',
202
+ fieldKey: target.positionField,
145
203
  value: nextValues.from,
146
204
  defaultValue: '0',
147
205
  schema: target.schema,
@@ -176,6 +234,7 @@ const getTimelineSequenceDurationDragChanges = ({ targets, deltaFrames, }) => {
176
234
  const nextValue = (0, exports.getTimelineSequenceDurationDragValue)({
177
235
  initialDuration: target.initialDuration,
178
236
  deltaFrames,
237
+ minimumDuration: target.minimumDuration,
179
238
  });
180
239
  if (nextValue === target.initialDuration) {
181
240
  return [];
@@ -195,6 +254,33 @@ const getTimelineSequenceDurationDragChanges = ({ targets, deltaFrames, }) => {
195
254
  exports.getTimelineSequenceDurationDragChanges = getTimelineSequenceDurationDragChanges;
196
255
  const getTimelineSequenceFromDragValue = ({ initialFrom, deltaFrames, }) => initialFrom + deltaFrames;
197
256
  exports.getTimelineSequenceFromDragValue = getTimelineSequenceFromDragValue;
257
+ const getTimelineSequenceFromDragDelta = ({ deltaFrames, pxPerFrame, snappingEnabled, targets, }) => {
258
+ var _a;
259
+ if (!snappingEnabled) {
260
+ return deltaFrames;
261
+ }
262
+ let closestSnap;
263
+ for (const target of targets) {
264
+ if (!target.canSnapToTimelineStart) {
265
+ continue;
266
+ }
267
+ const nextFrom = (0, exports.getTimelineSequenceFromDragValue)({
268
+ initialFrom: target.initialFrom,
269
+ deltaFrames,
270
+ });
271
+ const distancePx = Math.abs(nextFrom * pxPerFrame);
272
+ if (distancePx > exports.timelineSequenceFromDragSnapThresholdPx ||
273
+ (closestSnap && closestSnap.distancePx <= distancePx)) {
274
+ continue;
275
+ }
276
+ closestSnap = {
277
+ deltaFrames: -target.initialFrom,
278
+ distancePx,
279
+ };
280
+ }
281
+ return (_a = closestSnap === null || closestSnap === void 0 ? void 0 : closestSnap.deltaFrames) !== null && _a !== void 0 ? _a : deltaFrames;
282
+ };
283
+ exports.getTimelineSequenceFromDragDelta = getTimelineSequenceFromDragDelta;
198
284
  const getTimelineSequenceFromDragChanges = ({ targets, deltaFrames, }) => {
199
285
  return targets.flatMap((target) => {
200
286
  const nextValue = (0, exports.getTimelineSequenceFromDragValue)({
@@ -289,6 +375,10 @@ const getTimelineSequenceDurationDragTargets = ({ draggedNodePathInfo, selectedI
289
375
  targets.set(key, {
290
376
  fileName: nodePath.absolutePath,
291
377
  initialDuration: originalSequence.duration,
378
+ minimumDuration: getMinimumSequenceDuration({
379
+ sequence: originalSequence,
380
+ sequences,
381
+ }),
292
382
  nodePath,
293
383
  schema: controls.schema,
294
384
  });
@@ -320,12 +410,13 @@ const getTimelineSequenceLeftEdgeDragTargets = ({ draggedNodePathInfo, selectedI
320
410
  if (!track ||
321
411
  !track.nodePathInfo ||
322
412
  !originalSequence ||
323
- !isLeftEdgeDraggableSequence(originalSequence)) {
413
+ !(0, exports.isTimelineSequenceLeftEdgeDraggable)(originalSequence)) {
324
414
  return null;
325
415
  }
326
416
  const nodePath = track.nodePathInfo.sequenceSubscriptionKey;
327
417
  const trimsMedia = originalSequence.type === 'audio' || originalSequence.type === 'video';
328
- if (!canUpdateFrom({ propStatuses, nodePath }) ||
418
+ const positionField = (0, exports.isCascadingSequence)(originalSequence) ? null : 'from';
419
+ if ((positionField === 'from' && !canUpdateFrom({ propStatuses, nodePath })) ||
329
420
  !canUpdateDurationInFrames({ propStatuses, nodePath }) ||
330
421
  !canUpdateTrimBefore({ propStatuses, nodePath })) {
331
422
  return null;
@@ -339,11 +430,17 @@ const getTimelineSequenceLeftEdgeDragTargets = ({ draggedNodePathInfo, selectedI
339
430
  targets.set(key, {
340
431
  fileName: nodePath.absolutePath,
341
432
  initialDuration: originalSequence.duration,
342
- initialFrom: originalSequence.from,
433
+ initialFrom: positionField === 'from' ? originalSequence.from : 0,
343
434
  initialTrimBefore: trimsMedia
344
435
  ? ((_a = originalSequence.trimBefore) !== null && _a !== void 0 ? _a : Math.max(0, originalSequence.startMediaFrom))
345
436
  : ((_b = originalSequence.trimBefore) !== null && _b !== void 0 ? _b : 0),
437
+ minimumDuration: getMinimumSequenceDuration({
438
+ sequence: originalSequence,
439
+ sequences,
440
+ }),
346
441
  nodePath,
442
+ playbackRate: getTrimBeforePlaybackRate(originalSequence),
443
+ positionField,
347
444
  schema: controls.schema,
348
445
  });
349
446
  }
@@ -388,6 +485,7 @@ const getTimelineSequenceFromDragTargets = ({ draggedNodePathInfo, selectedItems
388
485
  propStatuses,
389
486
  });
390
487
  targets.set(key, {
488
+ canSnapToTimelineStart: originalSequence.parent === null,
391
489
  effectKeyframes,
392
490
  fileName: nodePath.absolutePath,
393
491
  initialFrom: originalSequence.from,
@@ -468,6 +566,8 @@ const TimelineSequenceLeftEdgeDragHandle = ({ nodePathInfo, windowWidth, timelin
468
566
  return;
469
567
  }
470
568
  const savePromise = (0, save_sequence_prop_1.saveSequenceProps)({
569
+ addedKeyframes: null,
570
+ movedKeyframes: null,
471
571
  changes,
472
572
  setPropStatuses: latestSetPropStatuses,
473
573
  clientId: latestServerState.clientId,
@@ -549,8 +649,12 @@ const TimelineSequenceLeftEdgeDragHandle = ({ nodePathInfo, windowWidth, timelin
549
649
  initialFrom: target.initialFrom,
550
650
  initialTrimBefore: target.initialTrimBefore,
551
651
  deltaFrames,
652
+ playbackRate: target.playbackRate,
653
+ minimumDuration: target.minimumDuration,
552
654
  });
553
- latestRef.current.setDragOverrides(target.nodePath, 'from', remotion_1.Internals.makeStaticDragOverride(nextValues.from));
655
+ if (target.positionField !== null) {
656
+ latestRef.current.setDragOverrides(target.nodePath, target.positionField, remotion_1.Internals.makeStaticDragOverride(nextValues.from));
657
+ }
554
658
  latestRef.current.setDragOverrides(target.nodePath, 'durationInFrames', remotion_1.Internals.makeStaticDragOverride(nextValues.durationInFrames));
555
659
  latestRef.current.setDragOverrides(target.nodePath, 'trimBefore', remotion_1.Internals.makeStaticDragOverride(nextValues.trimBefore));
556
660
  }
@@ -598,6 +702,7 @@ const useTimelineSequenceFromDrag = ({ nodePathInfo, windowWidth, timelineDurati
598
702
  const { overrideIdToNodePathMappings } = (0, react_1.useContext)(remotion_1.Internals.OverrideIdsToNodePathsGettersContext);
599
703
  const { previewServerState } = (0, react_1.useContext)(client_id_1.StudioServerConnectionCtx);
600
704
  const currentSelection = (0, TimelineSelection_1.useCurrentTimelineSelectionStateAsRef)();
705
+ const { editorSnapping } = (0, react_1.useContext)(editor_snapping_1.EditorSnappingContext);
601
706
  const [dragging, setDragging] = (0, react_1.useState)(false);
602
707
  const dragStateRef = (0, react_1.useRef)(null);
603
708
  const latestRef = (0, react_1.useRef)({
@@ -609,6 +714,7 @@ const useTimelineSequenceFromDrag = ({ nodePathInfo, windowWidth, timelineDurati
609
714
  clearEffectDragOverrides,
610
715
  previewServerState,
611
716
  overrideIdToNodePathMappings,
717
+ editorSnapping,
612
718
  });
613
719
  latestRef.current = {
614
720
  nodePathInfo,
@@ -619,6 +725,7 @@ const useTimelineSequenceFromDrag = ({ nodePathInfo, windowWidth, timelineDurati
619
725
  clearEffectDragOverrides,
620
726
  previewServerState,
621
727
  overrideIdToNodePathMappings,
728
+ editorSnapping,
622
729
  };
623
730
  const finishDrag = (0, react_1.useCallback)((commit) => {
624
731
  const dragState = dragStateRef.current;
@@ -651,6 +758,7 @@ const useTimelineSequenceFromDrag = ({ nodePathInfo, windowWidth, timelineDurati
651
758
  return;
652
759
  }
653
760
  const savePromise = (0, save_sequence_prop_1.saveSequenceProps)({
761
+ addedKeyframes: null,
654
762
  changes,
655
763
  movedKeyframes: {
656
764
  sequenceKeyframes: keyframeMoves.sequenceKeyframes,
@@ -730,7 +838,12 @@ const useTimelineSequenceFromDrag = ({ nodePathInfo, windowWidth, timelineDurati
730
838
  return;
731
839
  }
732
840
  const dx = e.clientX - dragState.initialClientX;
733
- const deltaFrames = Math.round(dx / dragState.pxPerFrame);
841
+ const deltaFrames = (0, exports.getTimelineSequenceFromDragDelta)({
842
+ deltaFrames: Math.round(dx / dragState.pxPerFrame),
843
+ pxPerFrame: dragState.pxPerFrame,
844
+ snappingEnabled: latestRef.current.editorSnapping,
845
+ targets: dragState.targets,
846
+ });
734
847
  dragState.latestDeltaFrames = deltaFrames;
735
848
  for (const target of dragState.targets) {
736
849
  const nextFrom = (0, exports.getTimelineSequenceFromDragValue)({
@@ -843,6 +956,8 @@ const TimelineSequenceRightEdgeDragHandle = ({ nodePathInfo, windowWidth, timeli
843
956
  return;
844
957
  }
845
958
  const savePromise = (0, save_sequence_prop_1.saveSequenceProps)({
959
+ addedKeyframes: null,
960
+ movedKeyframes: null,
846
961
  changes,
847
962
  setPropStatuses: latestSetPropStatuses,
848
963
  clientId: latestServerState.clientId,
@@ -923,6 +1038,7 @@ const TimelineSequenceRightEdgeDragHandle = ({ nodePathInfo, windowWidth, timeli
923
1038
  latestRef.current.setDragOverrides(target.nodePath, 'durationInFrames', remotion_1.Internals.makeStaticDragOverride((0, exports.getTimelineSequenceDurationDragValue)({
924
1039
  initialDuration: target.initialDuration,
925
1040
  deltaFrames,
1041
+ minimumDuration: target.minimumDuration,
926
1042
  })));
927
1043
  }
928
1044
  };
@@ -2,4 +2,20 @@ import React from 'react';
2
2
  export declare const TIMELINE_TIME_INDICATOR_HEIGHT = 39;
3
3
  export declare const TimelineTimePlaceholders: React.FC;
4
4
  export declare const TimelineTimePadding: React.FC;
5
+ export declare const getNiceSecondInterval: (rawNthSecond: number) => number;
6
+ type TickInterval = {
7
+ readonly interval: number;
8
+ readonly unit: 'frames' | 'seconds';
9
+ };
10
+ export type TimelineTickScale = {
11
+ readonly labelEverySeconds: number;
12
+ readonly mediumTickEvery: TickInterval | null;
13
+ readonly minorTickEvery: TickInterval | null;
14
+ };
15
+ export declare const getTimelineTickScale: ({ fps, frameInterval, rawSecondMarkerEveryNth, }: {
16
+ readonly fps: number;
17
+ readonly frameInterval: number;
18
+ readonly rawSecondMarkerEveryNth: number;
19
+ }) => TimelineTickScale;
5
20
  export declare const TimelineTimeIndicators: React.FC;
21
+ export {};
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.TimelineTimeIndicators = exports.TimelineTimePadding = exports.TimelineTimePlaceholders = exports.TIMELINE_TIME_INDICATOR_HEIGHT = void 0;
3
+ exports.TimelineTimeIndicators = exports.getTimelineTickScale = exports.getNiceSecondInterval = exports.TimelineTimePadding = exports.TimelineTimePlaceholders = exports.TIMELINE_TIME_INDICATOR_HEIGHT = void 0;
4
4
  const jsx_runtime_1 = require("react/jsx-runtime");
5
5
  const react_1 = require("react");
6
6
  const remotion_1 = require("remotion");
@@ -33,6 +33,10 @@ const secondTick = {
33
33
  const TICK_LABEL_FONT_SIZE = 12;
34
34
  const TICK_LABEL_MARGIN_LEFT = 8;
35
35
  const TICK_LABEL_MIN_GAP = 16;
36
+ const MIN_SPACING_BETWEEN_FRAME_TICKS_PX = 5;
37
+ const MIN_SPACING_BETWEEN_TIME_TICKS_PX = 16;
38
+ const MIN_SPACING_BETWEEN_MEDIUM_FRAME_TICKS_PX = 24;
39
+ const MIN_SPACING_BETWEEN_MEDIUM_TIME_TICKS_PX = 48;
36
40
  const tickLabel = {
37
41
  fontSize: TICK_LABEL_FONT_SIZE,
38
42
  marginLeft: TICK_LABEL_MARGIN_LEFT,
@@ -60,6 +64,101 @@ const TimelineTimePadding = () => {
60
64
  } }));
61
65
  };
62
66
  exports.TimelineTimePadding = TimelineTimePadding;
67
+ const NICE_SECOND_INTERVALS = [
68
+ 1,
69
+ 2,
70
+ 5,
71
+ 10,
72
+ 15,
73
+ 30,
74
+ 60,
75
+ 2 * 60,
76
+ 3 * 60,
77
+ 5 * 60,
78
+ 10 * 60,
79
+ 15 * 60,
80
+ 20 * 60,
81
+ 30 * 60,
82
+ 60 * 60,
83
+ ];
84
+ const getNiceSecondInterval = (rawNthSecond) => {
85
+ for (const n of NICE_SECOND_INTERVALS) {
86
+ if (n >= rawNthSecond) {
87
+ return n;
88
+ }
89
+ }
90
+ return Math.ceil(rawNthSecond / 3600) * 3600;
91
+ };
92
+ exports.getNiceSecondInterval = getNiceSecondInterval;
93
+ const getIntegerDivisors = (value) => {
94
+ const lowerDivisors = [];
95
+ const upperDivisors = [];
96
+ for (let candidate = 1; candidate <= Math.sqrt(value); candidate++) {
97
+ if (value % candidate !== 0) {
98
+ continue;
99
+ }
100
+ lowerDivisors.push(candidate);
101
+ if (candidate !== value / candidate) {
102
+ upperDivisors.unshift(value / candidate);
103
+ }
104
+ }
105
+ return [...lowerDivisors, ...upperDivisors];
106
+ };
107
+ const getFrameIntervals = (fps) => {
108
+ if (Number.isInteger(fps)) {
109
+ return getIntegerDivisors(fps);
110
+ }
111
+ return [1, 2, 5, 10, 15, 20, 30, 60].filter((interval) => interval < fps);
112
+ };
113
+ const getSecondIntervals = (labelEverySeconds) => {
114
+ const hourlyIntervals = getIntegerDivisors(labelEverySeconds / 3600).map((hours) => hours * 3600);
115
+ return [...new Set([...NICE_SECOND_INTERVALS, ...hourlyIntervals])].sort((a, b) => a - b);
116
+ };
117
+ const getTimelineTickScale = ({ fps, frameInterval, rawSecondMarkerEveryNth, }) => {
118
+ const labelEverySeconds = (0, exports.getNiceSecondInterval)(rawSecondMarkerEveryNth);
119
+ const frameIntervals = getFrameIntervals(fps);
120
+ const rawFrameInterval = MIN_SPACING_BETWEEN_FRAME_TICKS_PX / frameInterval;
121
+ const minorFrameInterval = frameIntervals.find((interval) => interval >= rawFrameInterval && interval < fps);
122
+ if (minorFrameInterval !== undefined) {
123
+ const rawMediumFrameInterval = MIN_SPACING_BETWEEN_MEDIUM_FRAME_TICKS_PX / frameInterval;
124
+ const mediumFrameInterval = frameIntervals.find((interval) => interval >= rawMediumFrameInterval &&
125
+ interval < fps &&
126
+ interval % minorFrameInterval === 0);
127
+ return {
128
+ labelEverySeconds,
129
+ mediumTickEvery: mediumFrameInterval === undefined
130
+ ? null
131
+ : { interval: mediumFrameInterval, unit: 'frames' },
132
+ minorTickEvery: { interval: minorFrameInterval, unit: 'frames' },
133
+ };
134
+ }
135
+ const pixelsPerSecond = frameInterval * fps;
136
+ const secondIntervals = getSecondIntervals(labelEverySeconds);
137
+ const rawMinorSecondInterval = MIN_SPACING_BETWEEN_TIME_TICKS_PX / pixelsPerSecond;
138
+ const minorSecondInterval = secondIntervals.find((interval) => interval >= rawMinorSecondInterval &&
139
+ interval < labelEverySeconds &&
140
+ labelEverySeconds % interval === 0);
141
+ if (minorSecondInterval === undefined) {
142
+ return {
143
+ labelEverySeconds,
144
+ mediumTickEvery: null,
145
+ minorTickEvery: null,
146
+ };
147
+ }
148
+ const rawMediumSecondInterval = MIN_SPACING_BETWEEN_MEDIUM_TIME_TICKS_PX / pixelsPerSecond;
149
+ const mediumSecondInterval = secondIntervals.find((interval) => interval >= rawMediumSecondInterval &&
150
+ interval < labelEverySeconds &&
151
+ interval % minorSecondInterval === 0 &&
152
+ labelEverySeconds % interval === 0);
153
+ return {
154
+ labelEverySeconds,
155
+ mediumTickEvery: mediumSecondInterval === undefined
156
+ ? null
157
+ : { interval: mediumSecondInterval, unit: 'seconds' },
158
+ minorTickEvery: { interval: minorSecondInterval, unit: 'seconds' },
159
+ };
160
+ };
161
+ exports.getTimelineTickScale = getTimelineTickScale;
63
162
  const TimelineTimeIndicators = () => {
64
163
  const sliderTrack = (0, react_1.useContext)(TimelineWidthProvider_1.TimelineWidthContext);
65
164
  const video = remotion_1.Internals.useVideo();
@@ -101,53 +200,73 @@ const TimelineTimeIndicatorsInner = ({ windowWidth, durationInFrames, fps }) =>
101
200
  }, [windowWidth]);
102
201
  const ticks = (0, react_1.useMemo)(() => {
103
202
  const frameInterval = (0, timeline_scroll_logic_1.getFrameIncrementFromWidth)(durationInFrames, windowWidth);
104
- const MIN_SPACING_BETWEEN_TICKS_PX = 5;
105
203
  const maxTickLabelWidth = (0, render_frame_1.renderFrame)(durationInFrames - 1, fps).length *
106
204
  TICK_LABEL_FONT_SIZE *
107
205
  0.6;
108
206
  const minSpacingBetweenTickLabelsPx = TICK_LABEL_MARGIN_LEFT + maxTickLabelWidth + TICK_LABEL_MIN_GAP;
109
207
  const seconds = Math.floor(durationInFrames / fps);
110
- const secondMarkerEveryNth = Math.ceil(minSpacingBetweenTickLabelsPx / (frameInterval * fps));
111
- const frameMarkerEveryNth = Math.ceil(MIN_SPACING_BETWEEN_TICKS_PX / frameInterval);
112
- // Big ticks showing for every second
113
- const secondTicks = new Array(seconds)
114
- .fill(true)
115
- .map((_, index) => {
116
- return {
208
+ const rawSecondMarkerEveryNth = minSpacingBetweenTickLabelsPx / (frameInterval * fps);
209
+ const tickScale = (0, exports.getTimelineTickScale)({
210
+ fps,
211
+ frameInterval,
212
+ rawSecondMarkerEveryNth,
213
+ });
214
+ // Labeled ticks use human-friendly time intervals.
215
+ const secondTicks = [];
216
+ for (let index = 0; index < seconds; index += tickScale.labelEverySeconds) {
217
+ secondTicks.push({
117
218
  frame: index * fps,
118
219
  style: {
119
220
  ...secondTick,
120
221
  left: frameInterval * index * fps + timeline_layout_1.TIMELINE_PADDING,
121
222
  },
122
223
  showTime: index > 0,
123
- };
124
- })
125
- .filter((_, idx) => idx % secondMarkerEveryNth === 0);
126
- const frameTicks = new Array(durationInFrames)
127
- .fill(true)
128
- .map((_, index) => {
129
- return {
130
- frame: index,
131
- style: {
132
- ...tick,
133
- left: frameInterval * index + timeline_layout_1.TIMELINE_PADDING,
134
- height: index % fps === 0
135
- ? 10
136
- : (index / frameMarkerEveryNth) % 2 === 0
224
+ });
225
+ }
226
+ const hasSecondTick = new Set(secondTicks.map((t) => t.frame));
227
+ const subdivisionTicks = [];
228
+ const { minorTickEvery, mediumTickEvery } = tickScale;
229
+ if ((minorTickEvery === null || minorTickEvery === void 0 ? void 0 : minorTickEvery.unit) === 'frames') {
230
+ for (let frame = 0; frame < durationInFrames; frame += minorTickEvery.interval) {
231
+ if (hasSecondTick.has(frame)) {
232
+ continue;
233
+ }
234
+ subdivisionTicks.push({
235
+ frame,
236
+ style: {
237
+ ...tick,
238
+ left: frameInterval * frame + timeline_layout_1.TIMELINE_PADDING,
239
+ height: (Number.isInteger(fps) && frame % fps === 0) ||
240
+ ((mediumTickEvery === null || mediumTickEvery === void 0 ? void 0 : mediumTickEvery.unit) === 'frames' &&
241
+ frame % mediumTickEvery.interval === 0)
137
242
  ? 5
138
243
  : 2,
139
- },
140
- showTime: false,
141
- };
142
- })
143
- .filter((_, idx) => idx % frameMarkerEveryNth === 0);
144
- // Merge and deduplicate ticks
145
- const hasTicks = [];
146
- return [...secondTicks, ...frameTicks].filter((t) => {
147
- const alreadyUsed = hasTicks.find((ht) => ht === t.frame) !== undefined;
148
- hasTicks.push(t.frame);
149
- return !alreadyUsed;
150
- });
244
+ },
245
+ showTime: false,
246
+ });
247
+ }
248
+ }
249
+ if ((minorTickEvery === null || minorTickEvery === void 0 ? void 0 : minorTickEvery.unit) === 'seconds') {
250
+ for (let second = 0; second < seconds; second += minorTickEvery.interval) {
251
+ const frame = second * fps;
252
+ if (hasSecondTick.has(frame)) {
253
+ continue;
254
+ }
255
+ subdivisionTicks.push({
256
+ frame,
257
+ style: {
258
+ ...tick,
259
+ left: frameInterval * frame + timeline_layout_1.TIMELINE_PADDING,
260
+ height: (mediumTickEvery === null || mediumTickEvery === void 0 ? void 0 : mediumTickEvery.unit) === 'seconds' &&
261
+ second % mediumTickEvery.interval === 0
262
+ ? 5
263
+ : 2,
264
+ },
265
+ showTime: false,
266
+ });
267
+ }
268
+ }
269
+ return [...secondTicks, ...subdivisionTicks];
151
270
  }, [durationInFrames, fps, windowWidth]);
152
271
  return (jsx_runtime_1.jsx("div", { ref: ref, style: style, children: ticks.map((t) => {
153
272
  return (jsx_runtime_1.jsx("div", { style: t.style, children: t.showTime ? (jsx_runtime_1.jsx("div", { style: tickLabel, children: (0, render_frame_1.renderFrame)(t.frame, fps) })) : null }, t.frame));
@@ -17,6 +17,7 @@ const containerStyle = {
17
17
  display: 'flex',
18
18
  gap: 4,
19
19
  };
20
+ const translateDragSensitivity = 3;
20
21
  const TimelineTranslateField = ({ field, propStatus, effectiveValue, onSave, onDragValueChange, onDragEnd, }) => {
21
22
  const [dragX, setDragX] = (0, react_1.useState)(null);
22
23
  const [dragY, setDragY] = (0, react_1.useState)(null);
@@ -100,7 +101,7 @@ const TimelineTranslateField = ({ field, propStatus, effectiveValue, onSave, onD
100
101
  }
101
102
  }, [propStatus, onSave, dragX, codeX, decimalPlaces]);
102
103
  return (jsx_runtime_1.jsxs("span", { style: containerStyle, children: [
103
- jsx_runtime_1.jsx(InputDragger_1.InputDragger, { type: "number", value: dragX !== null && dragX !== void 0 ? dragX : codeX, style: leftDraggerStyle, status: "ok", small: true, onValueChange: onXChange, onValueChangeEnd: onXChangeEnd, onTextChange: onXTextChange, min: -Infinity, max: Infinity, step: step, formatter: formatter, rightAlign: false, snapToStep: false, dragDecimalPlaces: decimalPlaces }), jsx_runtime_1.jsx("div", { style: { marginLeft: -6, marginRight: -6 } }), jsx_runtime_1.jsx(InputDragger_1.InputDragger, { type: "number", value: dragY !== null && dragY !== void 0 ? dragY : codeY, style: rightDraggerStyle, status: "ok", small: true, onValueChange: onYChange, onValueChangeEnd: onYChangeEnd, onTextChange: onYTextChange, min: -Infinity, max: Infinity, step: step, formatter: formatter, rightAlign: false, snapToStep: false, dragDecimalPlaces: decimalPlaces })
104
+ jsx_runtime_1.jsx(InputDragger_1.InputDragger, { type: "number", value: dragX !== null && dragX !== void 0 ? dragX : codeX, style: leftDraggerStyle, status: "ok", small: true, onValueChange: onXChange, onValueChangeEnd: onXChangeEnd, onTextChange: onXTextChange, min: -Infinity, max: Infinity, step: step, formatter: formatter, rightAlign: false, snapToStep: false, dragDecimalPlaces: decimalPlaces, dragSensitivity: translateDragSensitivity }), jsx_runtime_1.jsx("div", { style: { marginLeft: -6, marginRight: -6 } }), jsx_runtime_1.jsx(InputDragger_1.InputDragger, { type: "number", value: dragY !== null && dragY !== void 0 ? dragY : codeY, style: rightDraggerStyle, status: "ok", small: true, onValueChange: onYChange, onValueChangeEnd: onYChangeEnd, onTextChange: onYTextChange, min: -Infinity, max: Infinity, step: step, formatter: formatter, rightAlign: false, snapToStep: false, dragDecimalPlaces: decimalPlaces, dragSensitivity: translateDragSensitivity })
104
105
  ] }));
105
106
  };
106
107
  exports.TimelineTranslateField = TimelineTranslateField;
@@ -15,4 +15,5 @@ export declare const TimelineVideoInfo: React.FC<{
15
15
  readonly postmountWidth: number;
16
16
  readonly loopDisplay: LoopDisplay | undefined;
17
17
  readonly frozenMediaFrame: number | null;
18
+ readonly extendLastFrame: boolean;
18
19
  }>;
@@ -26,7 +26,7 @@ const filmstripContainerStyle = {
26
26
  fontFamily: 'Arial, Helvetica',
27
27
  };
28
28
  const MAX_FROZEN_FRAME_CACHE_DEVIATION = timeline_utils_1.WEBCODECS_TIMESCALE * 0.05;
29
- const TimelineVideoInfo = ({ src, visualizationWidth, naturalWidth, startMediaFrom, mediaFrameAtSequenceZero, sequenceFrameOffset, durationInFrames, playbackRate, volume, doesVolumeChange, premountWidth, postmountWidth, loopDisplay, frozenMediaFrame, }) => {
29
+ const TimelineVideoInfo = ({ src, visualizationWidth, naturalWidth, startMediaFrom, mediaFrameAtSequenceZero, sequenceFrameOffset, durationInFrames, playbackRate, volume, doesVolumeChange, premountWidth, postmountWidth, loopDisplay, frozenMediaFrame, extendLastFrame, }) => {
30
30
  const { fps } = (0, remotion_1.useVideoConfig)();
31
31
  const ref = (0, react_1.useRef)(null);
32
32
  const [error, setError] = (0, react_1.useState)(null);
@@ -108,6 +108,7 @@ const TimelineVideoInfo = ({ src, visualizationWidth, naturalWidth, startMediaFr
108
108
  };
109
109
  }
110
110
  (0, timeline_utils_1.extractFrames)({
111
+ repeatLastFrame: extendLastFrame,
111
112
  timestampsInSeconds: ({ track, }) => {
112
113
  aspectRatio.current = track.width / track.height;
113
114
  timeline_utils_1.aspectRatioCache.set(src, aspectRatio.current);
@@ -216,6 +217,7 @@ const TimelineVideoInfo = ({ src, visualizationWidth, naturalWidth, startMediaFr
216
217
  }
217
218
  }
218
219
  (0, timeline_utils_1.extractFrames)({
220
+ repeatLastFrame: extendLastFrame,
219
221
  timestampsInSeconds: ({ track, }) => {
220
222
  aspectRatio.current = track.width / track.height;
221
223
  timeline_utils_1.aspectRatioCache.set(src, aspectRatio.current);
@@ -307,6 +309,7 @@ const TimelineVideoInfo = ({ src, visualizationWidth, naturalWidth, startMediaFr
307
309
  }, [
308
310
  durationInFrames,
309
311
  error,
312
+ extendLastFrame,
310
313
  fps,
311
314
  frozenMediaFrame,
312
315
  loopDisplay,