@remotion/studio 4.0.458 → 4.0.460

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 (66) hide show
  1. package/dist/audio-waveform-worker.js +4 -4
  2. package/dist/components/AudioWaveform.js +4 -4
  3. package/dist/components/Checkbox.d.ts +7 -0
  4. package/dist/components/Checkbox.js +38 -24
  5. package/dist/components/Editor.js +10 -9
  6. package/dist/components/ExpandedTracksProvider.d.ts +9 -4
  7. package/dist/components/ExpandedTracksProvider.js +45 -15
  8. package/dist/components/MenuBuildIndicator.js +0 -1
  9. package/dist/components/NewComposition/ComboBox.d.ts +1 -0
  10. package/dist/components/NewComposition/ComboBox.js +14 -5
  11. package/dist/components/SequencePropsSubscriptionProvider.d.ts +3 -0
  12. package/dist/components/SequencePropsSubscriptionProvider.js +26 -0
  13. package/dist/components/Timeline/SequencePropsObserver.d.ts +1 -0
  14. package/dist/components/Timeline/SequencePropsObserver.js +24 -0
  15. package/dist/components/Timeline/SubscribeToNodePaths.d.ts +7 -0
  16. package/dist/components/Timeline/SubscribeToNodePaths.js +15 -0
  17. package/dist/components/Timeline/Timeline.js +28 -48
  18. package/dist/components/Timeline/TimelineBooleanField.js +1 -1
  19. package/dist/components/Timeline/TimelineEnumField.js +18 -10
  20. package/dist/components/Timeline/TimelineExpandArrowButton.d.ts +1 -0
  21. package/dist/components/Timeline/TimelineExpandArrowButton.js +5 -3
  22. package/dist/components/Timeline/TimelineExpandedRow.d.ts +6 -6
  23. package/dist/components/Timeline/TimelineExpandedRow.js +4 -5
  24. package/dist/components/Timeline/TimelineExpandedSection.d.ts +2 -2
  25. package/dist/components/Timeline/TimelineExpandedSection.js +20 -9
  26. package/dist/components/Timeline/TimelineFieldRow.d.ts +2 -3
  27. package/dist/components/Timeline/TimelineFieldRow.js +23 -20
  28. package/dist/components/Timeline/TimelineHeightContainer.d.ts +7 -0
  29. package/dist/components/Timeline/TimelineHeightContainer.js +18 -0
  30. package/dist/components/Timeline/TimelineList.js +1 -1
  31. package/dist/components/Timeline/TimelineListItem.d.ts +2 -0
  32. package/dist/components/Timeline/TimelineListItem.js +27 -13
  33. package/dist/components/Timeline/TimelineSchemaField.js +4 -1
  34. package/dist/components/Timeline/TimelineTracks.js +18 -7
  35. package/dist/components/Timeline/TimelineVideoInfo.js +25 -140
  36. package/dist/components/Timeline/sequence-props-subscription-store.d.ts +10 -13
  37. package/dist/components/Timeline/sequence-props-subscription-store.js +56 -123
  38. package/dist/components/Timeline/use-sequence-props-subscription.d.ts +6 -6
  39. package/dist/components/Timeline/use-sequence-props-subscription.js +25 -55
  40. package/dist/components/Timeline/use-timeline-height.d.ts +5 -0
  41. package/dist/components/Timeline/use-timeline-height.js +48 -0
  42. package/dist/components/draw-peaks.js +7 -0
  43. package/dist/esm/audio-waveform-worker.mjs +9 -14
  44. package/dist/esm/{chunk-261b3aa0.js → chunk-nrgs0ad5.js} +2810 -2975
  45. package/dist/esm/internals.mjs +2810 -2975
  46. package/dist/esm/previewEntry.mjs +2809 -2974
  47. package/dist/esm/renderEntry.mjs +1 -1
  48. package/dist/helpers/calculate-timeline.d.ts +3 -2
  49. package/dist/helpers/calculate-timeline.js +21 -2
  50. package/dist/helpers/get-timeline-sequence-layout.js +3 -3
  51. package/dist/helpers/get-timeline-sequence-sort-key.d.ts +6 -1
  52. package/dist/helpers/timeline-layout.d.ts +18 -9
  53. package/dist/helpers/timeline-layout.js +24 -16
  54. package/dist/icons/Checkmark.d.ts +4 -1
  55. package/dist/icons/Checkmark.js +1 -5
  56. package/dist/icons/caret.d.ts +3 -1
  57. package/dist/icons/caret.js +5 -2
  58. package/package.json +10 -9
  59. package/dist/components/looped-media-timeline.d.ts +0 -6
  60. package/dist/components/looped-media-timeline.js +0 -14
  61. package/dist/helpers/extract-frames.d.ts +0 -18
  62. package/dist/helpers/extract-frames.js +0 -87
  63. package/dist/helpers/frame-database.d.ts +0 -16
  64. package/dist/helpers/frame-database.js +0 -65
  65. package/dist/helpers/resize-video-frame.d.ts +0 -4
  66. package/dist/helpers/resize-video-frame.js +0 -39
@@ -2,14 +2,11 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.TimelineVideoInfo = void 0;
4
4
  const jsx_runtime_1 = require("react/jsx-runtime");
5
+ const timeline_utils_1 = require("@remotion/timeline-utils");
5
6
  const react_1 = require("react");
6
7
  const remotion_1 = require("remotion");
7
- const extract_frames_1 = require("../../helpers/extract-frames");
8
- const frame_database_1 = require("../../helpers/frame-database");
9
- const resize_video_frame_1 = require("../../helpers/resize-video-frame");
10
8
  const timeline_layout_1 = require("../../helpers/timeline-layout");
11
9
  const AudioWaveform_1 = require("../AudioWaveform");
12
- const looped_media_timeline_1 = require("../looped-media-timeline");
13
10
  const FILMSTRIP_HEIGHT = timeline_layout_1.TIMELINE_LAYER_HEIGHT_IMAGE - 2;
14
11
  const outerStyle = {
15
12
  width: '100%',
@@ -26,132 +23,11 @@ const filmstripContainerStyle = {
26
23
  fontSize: 10,
27
24
  fontFamily: 'Arial, Helvetica',
28
25
  };
29
- const WEBCODECS_TIMESCALE = 1000000;
30
- const MAX_TIME_DEVIATION = WEBCODECS_TIMESCALE * 0.05;
31
- const getDurationOfOneFrame = ({ visualizationWidth, aspectRatio, segmentDuration, }) => {
32
- const framesFitInWidthUnrounded = visualizationWidth / (FILMSTRIP_HEIGHT * aspectRatio);
33
- return (segmentDuration / framesFitInWidthUnrounded) * WEBCODECS_TIMESCALE;
34
- };
35
- const fixRounding = (value) => {
36
- if (value % 1 >= 0.49999999) {
37
- return Math.ceil(value);
38
- }
39
- return Math.floor(value);
40
- };
41
- const calculateTimestampSlots = ({ visualizationWidth, fromSeconds, segmentDuration, aspectRatio, }) => {
42
- const framesFitInWidthUnrounded = visualizationWidth / (FILMSTRIP_HEIGHT * aspectRatio);
43
- const framesFitInWidth = Math.ceil(framesFitInWidthUnrounded);
44
- const durationOfOneFrame = getDurationOfOneFrame({
45
- visualizationWidth,
46
- aspectRatio,
47
- segmentDuration,
48
- });
49
- const timestampTargets = [];
50
- for (let i = 0; i < framesFitInWidth + 1; i++) {
51
- const target = fromSeconds * WEBCODECS_TIMESCALE + durationOfOneFrame * (i + 0.5);
52
- const snappedToDuration = (Math.round(fixRounding(target / durationOfOneFrame)) - 1) *
53
- durationOfOneFrame;
54
- timestampTargets.push(snappedToDuration);
55
- }
56
- return timestampTargets;
57
- };
58
- const ensureSlots = ({ filledSlots, naturalWidth, fromSeconds, toSeconds, aspectRatio, }) => {
59
- const segmentDuration = toSeconds - fromSeconds;
60
- const timestampTargets = calculateTimestampSlots({
61
- visualizationWidth: naturalWidth,
62
- fromSeconds,
63
- segmentDuration,
64
- aspectRatio,
65
- });
66
- for (const timestamp of timestampTargets) {
67
- if (!filledSlots.has(timestamp)) {
68
- filledSlots.set(timestamp, undefined);
69
- }
70
- }
71
- };
72
- const drawSlot = ({ frame, ctx, filledSlots, visualizationWidth, timestamp, segmentDuration, fromSeconds, }) => {
73
- const durationOfOneFrame = getDurationOfOneFrame({
74
- visualizationWidth,
75
- aspectRatio: frame.displayWidth / frame.displayHeight,
76
- segmentDuration,
77
- });
78
- const relativeTimestamp = timestamp - fromSeconds * WEBCODECS_TIMESCALE;
79
- const frameIndex = relativeTimestamp / durationOfOneFrame;
80
- const thumbnailWidth = frame.displayWidth / window.devicePixelRatio;
81
- const left = Math.floor(frameIndex * thumbnailWidth);
82
- const right = Math.ceil((frameIndex + 1) * thumbnailWidth);
83
- ctx.drawImage(frame, left, 0, right - left, frame.displayHeight / window.devicePixelRatio);
84
- filledSlots.set(timestamp, frame.timestamp);
85
- };
86
- const fillWithCachedFrames = ({ ctx, naturalWidth, filledSlots, src, segmentDuration, fromSeconds, }) => {
87
- const prefix = (0, frame_database_1.getFrameDatabaseKeyPrefix)(src);
88
- const keys = Array.from(frame_database_1.frameDatabase.keys()).filter((k) => k.startsWith(prefix));
89
- const targets = Array.from(filledSlots.keys());
90
- for (const timestamp of targets) {
91
- let bestKey;
92
- let bestDistance = Infinity;
93
- for (const key of keys) {
94
- const distance = Math.abs((0, frame_database_1.getTimestampFromFrameDatabaseKey)(key) - timestamp);
95
- if (distance < bestDistance) {
96
- bestDistance = distance;
97
- bestKey = key;
98
- }
99
- }
100
- if (!bestKey) {
101
- continue;
102
- }
103
- const frame = frame_database_1.frameDatabase.get(bestKey);
104
- if (!frame) {
105
- continue;
106
- }
107
- const alreadyFilled = filledSlots.get(timestamp);
108
- // Don't fill if a closer frame was already drawn
109
- if (alreadyFilled &&
110
- Math.abs(alreadyFilled - timestamp) <=
111
- Math.abs(frame.frame.timestamp - timestamp)) {
112
- continue;
113
- }
114
- frame.lastUsed = Date.now();
115
- drawSlot({
116
- ctx,
117
- frame: frame.frame,
118
- filledSlots,
119
- visualizationWidth: naturalWidth,
120
- timestamp,
121
- segmentDuration,
122
- fromSeconds,
123
- });
124
- }
125
- };
126
- const fillFrameWhereItFits = ({ frame, filledSlots, ctx, visualizationWidth, segmentDuration, fromSeconds, }) => {
127
- const slots = Array.from(filledSlots.keys());
128
- for (let i = 0; i < slots.length; i++) {
129
- const slot = slots[i];
130
- if (Math.abs(slot - frame.timestamp) > MAX_TIME_DEVIATION) {
131
- continue;
132
- }
133
- const filled = filledSlots.get(slot);
134
- // Don't fill if a better timestamp was already filled
135
- if (filled &&
136
- Math.abs(filled - slot) <= Math.abs(filled - frame.timestamp)) {
137
- continue;
138
- }
139
- drawSlot({
140
- ctx,
141
- frame,
142
- filledSlots,
143
- visualizationWidth,
144
- timestamp: slot,
145
- segmentDuration,
146
- fromSeconds,
147
- });
148
- }
149
- };
150
26
  const TimelineVideoInfo = ({ src, visualizationWidth, naturalWidth, trimBefore, durationInFrames, playbackRate, volume, doesVolumeChange, premountWidth, postmountWidth, loopDisplay, }) => {
151
27
  const { fps } = (0, remotion_1.useVideoConfig)();
152
28
  const ref = (0, react_1.useRef)(null);
153
29
  const [error, setError] = (0, react_1.useState)(null);
154
- const aspectRatio = (0, react_1.useRef)((0, frame_database_1.getAspectRatioFromCache)(src));
30
+ const aspectRatio = (0, react_1.useRef)((0, timeline_utils_1.getAspectRatioFromCache)(src));
155
31
  // for rendering frames
156
32
  (0, react_1.useEffect)(() => {
157
33
  if (error) {
@@ -170,11 +46,11 @@ const TimelineVideoInfo = ({ src, visualizationWidth, naturalWidth, trimBefore,
170
46
  return;
171
47
  }
172
48
  current.appendChild(canvas);
173
- const loopWidth = (0, looped_media_timeline_1.getLoopDisplayWidth)({
49
+ const loopWidth = (0, timeline_utils_1.getLoopDisplayWidth)({
174
50
  visualizationWidth: naturalWidth,
175
51
  loopDisplay,
176
52
  });
177
- const shouldRepeatVideo = (0, looped_media_timeline_1.shouldTileLoopDisplay)(loopDisplay);
53
+ const shouldRepeatVideo = (0, timeline_utils_1.shouldTileLoopDisplay)(loopDisplay);
178
54
  const targetCanvas = shouldRepeatVideo
179
55
  ? document.createElement('canvas')
180
56
  : canvas;
@@ -211,20 +87,23 @@ const TimelineVideoInfo = ({ src, visualizationWidth, naturalWidth, trimBefore,
211
87
  const toSeconds = fromSeconds + (visibleDurationInFrames * playbackRate) / fps;
212
88
  const targetWidth = shouldRepeatVideo ? targetCanvas.width : naturalWidth;
213
89
  if (aspectRatio.current !== null) {
214
- ensureSlots({
90
+ (0, timeline_utils_1.ensureSlots)({
215
91
  filledSlots,
216
92
  naturalWidth: targetWidth,
217
93
  fromSeconds,
218
94
  toSeconds,
219
95
  aspectRatio: aspectRatio.current,
96
+ frameHeight: FILMSTRIP_HEIGHT,
220
97
  });
221
- fillWithCachedFrames({
98
+ (0, timeline_utils_1.fillWithCachedFrames)({
222
99
  ctx: targetCtx,
223
100
  naturalWidth: targetWidth,
224
101
  filledSlots,
225
102
  src,
226
103
  segmentDuration: toSeconds - fromSeconds,
227
104
  fromSeconds,
105
+ devicePixelRatio: window.devicePixelRatio,
106
+ frameHeight: FILMSTRIP_HEIGHT,
228
107
  });
229
108
  repeatTarget();
230
109
  const unfilled = Array.from(filledSlots.keys()).filter((timestamp) => !filledSlots.get(timestamp));
@@ -235,18 +114,19 @@ const TimelineVideoInfo = ({ src, visualizationWidth, naturalWidth, trimBefore,
235
114
  };
236
115
  }
237
116
  }
238
- (0, extract_frames_1.extractFrames)({
117
+ (0, timeline_utils_1.extractFrames)({
239
118
  timestampsInSeconds: ({ track, }) => {
240
119
  aspectRatio.current = track.width / track.height;
241
- frame_database_1.aspectRatioCache.set(src, aspectRatio.current);
242
- ensureSlots({
120
+ timeline_utils_1.aspectRatioCache.set(src, aspectRatio.current);
121
+ (0, timeline_utils_1.ensureSlots)({
243
122
  filledSlots,
244
123
  fromSeconds,
245
124
  toSeconds,
246
125
  naturalWidth: targetWidth,
247
126
  aspectRatio: aspectRatio.current,
127
+ frameHeight: FILMSTRIP_HEIGHT,
248
128
  });
249
- return Array.from(filledSlots.keys()).map((timestamp) => timestamp / WEBCODECS_TIMESCALE);
129
+ return Array.from(filledSlots.keys()).map((timestamp) => timestamp / timeline_utils_1.WEBCODECS_TIMESCALE);
250
130
  },
251
131
  src,
252
132
  onVideoSample: (sample) => {
@@ -254,7 +134,7 @@ const TimelineVideoInfo = ({ src, visualizationWidth, naturalWidth, trimBefore,
254
134
  try {
255
135
  frame = sample.toVideoFrame();
256
136
  const scale = (FILMSTRIP_HEIGHT / frame.displayHeight) * window.devicePixelRatio;
257
- const transformed = (0, resize_video_frame_1.resizeVideoFrame)({
137
+ const transformed = (0, timeline_utils_1.resizeVideoFrame)({
258
138
  frame,
259
139
  scale,
260
140
  });
@@ -262,25 +142,28 @@ const TimelineVideoInfo = ({ src, visualizationWidth, naturalWidth, trimBefore,
262
142
  frame.close();
263
143
  }
264
144
  frame = undefined;
265
- const databaseKey = (0, frame_database_1.makeFrameDatabaseKey)(src, transformed.timestamp);
266
- (0, frame_database_1.addFrameToCache)(databaseKey, transformed);
145
+ const databaseKey = (0, timeline_utils_1.makeFrameDatabaseKey)(src, transformed.timestamp);
146
+ (0, timeline_utils_1.addFrameToCache)(databaseKey, transformed);
267
147
  if (aspectRatio.current === null) {
268
148
  throw new Error('Aspect ratio is not set');
269
149
  }
270
- ensureSlots({
150
+ (0, timeline_utils_1.ensureSlots)({
271
151
  filledSlots,
272
152
  fromSeconds,
273
153
  toSeconds,
274
154
  naturalWidth: targetWidth,
275
155
  aspectRatio: aspectRatio.current,
156
+ frameHeight: FILMSTRIP_HEIGHT,
276
157
  });
277
- fillFrameWhereItFits({
158
+ (0, timeline_utils_1.fillFrameWhereItFits)({
278
159
  ctx: targetCtx,
279
160
  filledSlots,
280
161
  visualizationWidth: targetWidth,
281
162
  frame: transformed,
282
163
  segmentDuration: toSeconds - fromSeconds,
283
164
  fromSeconds,
165
+ devicePixelRatio: window.devicePixelRatio,
166
+ frameHeight: FILMSTRIP_HEIGHT,
284
167
  });
285
168
  repeatTarget();
286
169
  }
@@ -300,13 +183,15 @@ const TimelineVideoInfo = ({ src, visualizationWidth, naturalWidth, trimBefore,
300
183
  if (controller.signal.aborted) {
301
184
  return;
302
185
  }
303
- fillWithCachedFrames({
186
+ (0, timeline_utils_1.fillWithCachedFrames)({
304
187
  ctx: targetCtx,
305
188
  naturalWidth: targetWidth,
306
189
  filledSlots,
307
190
  src,
308
191
  segmentDuration: toSeconds - fromSeconds,
309
192
  fromSeconds,
193
+ devicePixelRatio: window.devicePixelRatio,
194
+ frameHeight: FILMSTRIP_HEIGHT,
310
195
  });
311
196
  repeatTarget();
312
197
  })
@@ -1,19 +1,16 @@
1
- import type { EventSourceEvent, SequenceNodePath } from '@remotion/studio-shared';
2
- import { type CanUpdateSequencePropStatus } from 'remotion';
3
1
  import type { SequenceSchema } from 'remotion';
4
- export type SequencePropsSnapshot = {
5
- nodePath: SequenceNodePath | null;
6
- jsxInMapCallback: boolean;
7
- props: Record<string, CanUpdateSequencePropStatus> | null;
8
- };
9
- type SubscribeToEvent = (type: EventSourceEvent['type'], listener: (event: EventSourceEvent) => void) => () => void;
10
- export declare const acquireSequencePropsSubscription: ({ clientId, fileName, line, column, schema, subscribeToEvent, onChange, }: {
11
- clientId: string;
2
+ import { callApi } from '../call-api';
3
+ type SubscribeResult = Awaited<ReturnType<typeof callApi<'/api/subscribe-to-sequence-props'>>>;
4
+ type ApplyResult = (result: SubscribeResult) => void;
5
+ export declare const acquireSequencePropsSubscription: ({ fileName, line, column, schema, clientId, applyOnce, applyEach, }: {
12
6
  fileName: string;
13
7
  line: number;
14
8
  column: number;
15
9
  schema: SequenceSchema;
16
- subscribeToEvent: SubscribeToEvent;
17
- onChange: (snapshot: SequencePropsSnapshot) => void;
18
- }) => () => void;
10
+ clientId: string;
11
+ applyOnce: ApplyResult;
12
+ applyEach: ApplyResult;
13
+ }) => {
14
+ release: () => void;
15
+ };
19
16
  export {};
@@ -3,149 +3,82 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.acquireSequencePropsSubscription = void 0;
4
4
  const remotion_1 = require("remotion");
5
5
  const call_api_1 = require("../call-api");
6
- const INITIAL_SNAPSHOT = {
7
- nodePath: null,
8
- jsxInMapCallback: false,
9
- props: null,
10
- };
6
+ const makeKey = (fileName, line, column) => `${fileName}\0${line}\0${column}`;
11
7
  const entries = new Map();
12
- let globalUnsubscribe = null;
13
- const makeKey = (fileName, line, column) => `${fileName}|${line}|${column}`;
14
- const nodePathsEqual = (a, b) => {
15
- if (!a || !b) {
16
- return false;
17
- }
18
- return JSON.stringify(a) === JSON.stringify(b);
19
- };
20
- const notify = (entry) => {
21
- for (const listener of entry.listeners) {
22
- listener(entry.snapshot);
23
- }
24
- };
25
- const handleEvent = (event) => {
26
- if (event.type !== 'sequence-props-updated') {
27
- return;
28
- }
29
- for (const entry of entries.values()) {
30
- if (entry.fileName !== event.fileName) {
31
- continue;
32
- }
33
- if (!nodePathsEqual(entry.snapshot.nodePath, event.nodePath)) {
34
- continue;
35
- }
36
- if (event.result.canUpdate) {
37
- entry.snapshot = {
38
- nodePath: entry.snapshot.nodePath,
39
- jsxInMapCallback: event.result.jsxInMapCallback,
40
- props: event.result.props,
41
- };
42
- }
43
- else {
44
- entry.snapshot = {
45
- nodePath: entry.snapshot.nodePath,
46
- jsxInMapCallback: false,
47
- props: null,
48
- };
49
- }
50
- notify(entry);
51
- }
52
- };
53
- const ensureGlobalListener = (subscribeToEvent) => {
54
- if (globalUnsubscribe) {
55
- return;
56
- }
57
- globalUnsubscribe = subscribeToEvent('sequence-props-updated', handleEvent);
58
- };
59
- const teardownGlobalListenerIfEmpty = () => {
60
- if (entries.size > 0) {
61
- return;
62
- }
63
- if (globalUnsubscribe) {
64
- globalUnsubscribe();
65
- globalUnsubscribe = null;
66
- }
67
- };
68
- const fireUnsubscribe = (fileName, nodePath, clientId) => {
69
- (0, call_api_1.callApi)('/api/unsubscribe-from-sequence-props', {
70
- fileName,
71
- nodePath,
72
- clientId,
73
- }).catch(() => {
74
- // Ignore unsubscribe errors
75
- });
76
- };
77
- const acquireSequencePropsSubscription = ({ clientId, fileName, line, column, schema, subscribeToEvent, onChange, }) => {
8
+ const acquireSequencePropsSubscription = ({ fileName, line, column, schema, clientId, applyOnce, applyEach, }) => {
78
9
  const key = makeKey(fileName, line, column);
79
10
  let entry = entries.get(key);
80
11
  if (!entry) {
81
- const newEntry = {
82
- clientId,
83
- fileName,
84
- refCount: 0,
85
- snapshot: INITIAL_SNAPSHOT,
86
- listeners: new Set(),
87
- tornDown: false,
88
- };
89
- entries.set(key, newEntry);
90
- ensureGlobalListener(subscribeToEvent);
91
- (0, call_api_1.callApi)('/api/subscribe-to-sequence-props', {
12
+ const promise = (0, call_api_1.callApi)('/api/subscribe-to-sequence-props', {
92
13
  fileName,
93
14
  line,
94
15
  column,
95
16
  schema,
96
17
  clientId,
97
- })
18
+ });
19
+ const created = {
20
+ refCount: 0,
21
+ promise,
22
+ fileName,
23
+ clientId,
24
+ applyOnce,
25
+ };
26
+ entries.set(key, created);
27
+ entry = created;
28
+ promise
98
29
  .then((result) => {
99
- if (newEntry.tornDown) {
100
- if (result.canUpdate) {
101
- fireUnsubscribe(fileName, result.nodePath, clientId);
102
- }
30
+ const current = entries.get(key);
31
+ if (current !== created || !current.applyOnce) {
103
32
  return;
104
33
  }
105
- if (result.canUpdate) {
106
- newEntry.snapshot = {
107
- nodePath: result.nodePath,
108
- jsxInMapCallback: result.jsxInMapCallback,
109
- props: result.props,
110
- };
111
- }
112
- else {
113
- newEntry.snapshot = INITIAL_SNAPSHOT;
114
- }
115
- notify(newEntry);
34
+ const cb = current.applyOnce;
35
+ current.applyOnce = null;
36
+ cb(result);
116
37
  })
117
38
  .catch((err) => {
118
- if (newEntry.tornDown) {
39
+ const current = entries.get(key);
40
+ if (current !== created) {
119
41
  return;
120
42
  }
43
+ current.applyOnce = null;
121
44
  remotion_1.Internals.Log.error(err);
122
- newEntry.snapshot = INITIAL_SNAPSHOT;
123
- notify(newEntry);
124
45
  });
125
- entry = newEntry;
126
46
  }
127
- entry.refCount += 1;
128
- entry.listeners.add(onChange);
129
- onChange(entry.snapshot);
47
+ entry.refCount++;
48
+ const acquired = entry;
49
+ acquired.promise.then(applyEach).catch(() => {
50
+ // Error already logged by the first acquirer.
51
+ });
130
52
  let released = false;
131
- return () => {
132
- if (released) {
133
- return;
134
- }
135
- released = true;
136
- const e = entry;
137
- e.listeners.delete(onChange);
138
- e.refCount -= 1;
139
- if (e.refCount > 0) {
140
- return;
141
- }
142
- e.tornDown = true;
143
- const resolvedNodePath = e.snapshot.nodePath;
144
- entries.delete(key);
145
- if (resolvedNodePath) {
146
- fireUnsubscribe(e.fileName, resolvedNodePath, e.clientId);
147
- }
148
- teardownGlobalListenerIfEmpty();
53
+ return {
54
+ release: () => {
55
+ if (released) {
56
+ return;
57
+ }
58
+ released = true;
59
+ acquired.refCount--;
60
+ if (acquired.refCount > 0) {
61
+ return;
62
+ }
63
+ if (entries.get(key) === acquired) {
64
+ entries.delete(key);
65
+ }
66
+ acquired.promise
67
+ .then((result) => {
68
+ if (!result.canUpdate) {
69
+ return;
70
+ }
71
+ return (0, call_api_1.callApi)('/api/unsubscribe-from-sequence-props', {
72
+ fileName: acquired.fileName,
73
+ nodePath: result.nodePath,
74
+ clientId: acquired.clientId,
75
+ });
76
+ })
77
+ .catch(() => {
78
+ // Ignore — either the subscribe failed (nothing to clean up) or
79
+ // the unsubscribe failed (server-side TTL will handle it).
80
+ });
81
+ },
149
82
  };
150
83
  };
151
84
  exports.acquireSequencePropsSubscription = acquireSequencePropsSubscription;
@@ -1,7 +1,7 @@
1
- import type { SequenceNodePath } from '@remotion/studio-shared';
2
- import type { TSequence } from 'remotion';
1
+ import type { SequenceSchema } from 'remotion';
3
2
  import type { OriginalPosition } from '../../error-overlay/react-overlay/utils/get-source-map';
4
- export declare const useSequencePropsSubscription: (sequence: TSequence, originalLocation: OriginalPosition | null, visualModeEnabled: boolean) => {
5
- nodePath: SequenceNodePath | null;
6
- jsxInMapCallback: boolean;
7
- };
3
+ export declare const useSequencePropsSubscription: ({ originalLocation, overrideId, schema, }: {
4
+ overrideId: string;
5
+ schema: SequenceSchema;
6
+ originalLocation: OriginalPosition | null;
7
+ }) => void;
@@ -5,22 +5,11 @@ const react_1 = require("react");
5
5
  const remotion_1 = require("remotion");
6
6
  const client_id_1 = require("../../helpers/client-id");
7
7
  const sequence_props_subscription_store_1 = require("./sequence-props-subscription-store");
8
- const EMPTY_STATE = {
9
- nodePath: null,
10
- jsxInMapCallback: false,
11
- };
12
- const useSequencePropsSubscription = (sequence, originalLocation, visualModeEnabled) => {
13
- var _a, _b;
14
- var _c, _d, _e, _f;
15
- const { setCodeValues } = (0, react_1.useContext)(remotion_1.Internals.VisualModeOverridesContext);
16
- const overrideId = (_c = (_a = sequence.controls) === null || _a === void 0 ? void 0 : _a.overrideId) !== null && _c !== void 0 ? _c : null;
17
- const setPropStatusesForSequence = (0, react_1.useCallback)((statuses) => {
18
- if (!overrideId) {
19
- return;
20
- }
21
- setCodeValues(overrideId, statuses);
22
- }, [overrideId, setCodeValues]);
23
- const { previewServerState: state, subscribeToEvent } = (0, react_1.useContext)(client_id_1.StudioServerConnectionCtx);
8
+ const useSequencePropsSubscription = ({ originalLocation, overrideId, schema, }) => {
9
+ var _a, _b, _c;
10
+ const { setCodeValues } = (0, react_1.useContext)(remotion_1.Internals.VisualModeSettersContext);
11
+ const { setOverrideIdToNodePath } = (0, react_1.useContext)(remotion_1.Internals.OverrideIdsToNodePathsSettersContext);
12
+ const { previewServerState: state } = (0, react_1.useContext)(client_id_1.StudioServerConnectionCtx);
24
13
  const clientId = state.type === 'connected' ? state.clientId : undefined;
25
14
  const validatedLocation = (0, react_1.useMemo)(() => {
26
15
  var _a;
@@ -35,67 +24,48 @@ const useSequencePropsSubscription = (sequence, originalLocation, visualModeEnab
35
24
  column: (_a = originalLocation.column) !== null && _a !== void 0 ? _a : 0,
36
25
  };
37
26
  }, [originalLocation]);
38
- const [subscriptionState, setSubscriptionState] = (0, react_1.useState)(EMPTY_STATE);
39
- const isMountedRef = (0, react_1.useRef)(true);
40
- (0, react_1.useEffect)(() => {
41
- isMountedRef.current = true;
42
- return () => {
43
- isMountedRef.current = false;
44
- };
45
- }, []);
46
- const schema = (_b = sequence.controls) === null || _b === void 0 ? void 0 : _b.schema;
47
- const locationSource = (_d = validatedLocation === null || validatedLocation === void 0 ? void 0 : validatedLocation.source) !== null && _d !== void 0 ? _d : null;
48
- const locationLine = (_e = validatedLocation === null || validatedLocation === void 0 ? void 0 : validatedLocation.line) !== null && _e !== void 0 ? _e : null;
49
- const locationColumn = (_f = validatedLocation === null || validatedLocation === void 0 ? void 0 : validatedLocation.column) !== null && _f !== void 0 ? _f : null;
27
+ const locationSource = (_a = validatedLocation === null || validatedLocation === void 0 ? void 0 : validatedLocation.source) !== null && _a !== void 0 ? _a : null;
28
+ const locationLine = (_b = validatedLocation === null || validatedLocation === void 0 ? void 0 : validatedLocation.line) !== null && _b !== void 0 ? _b : null;
29
+ const locationColumn = (_c = validatedLocation === null || validatedLocation === void 0 ? void 0 : validatedLocation.column) !== null && _c !== void 0 ? _c : null;
50
30
  (0, react_1.useEffect)(() => {
51
- if (!visualModeEnabled) {
52
- setPropStatusesForSequence(null);
53
- setSubscriptionState(EMPTY_STATE);
54
- return;
55
- }
56
31
  if (!clientId ||
57
32
  !locationSource ||
58
33
  !locationLine ||
59
34
  locationColumn === null ||
60
35
  !schema) {
61
- setPropStatusesForSequence(null);
62
- setSubscriptionState(EMPTY_STATE);
63
36
  return;
64
37
  }
65
- const onChange = (snapshot) => {
66
- setSubscriptionState({
67
- nodePath: snapshot.nodePath,
68
- jsxInMapCallback: snapshot.jsxInMapCallback,
69
- });
70
- setPropStatusesForSequence(snapshot.props);
71
- };
72
- const release = (0, sequence_props_subscription_store_1.acquireSequencePropsSubscription)({
73
- clientId,
38
+ const { release } = (0, sequence_props_subscription_store_1.acquireSequencePropsSubscription)({
74
39
  fileName: locationSource,
75
40
  line: locationLine,
76
41
  column: locationColumn,
77
42
  schema,
78
- subscribeToEvent,
79
- onChange,
43
+ clientId,
44
+ applyOnce: (result) => {
45
+ if (!result.canUpdate) {
46
+ return;
47
+ }
48
+ setCodeValues(result.nodePath, result);
49
+ },
50
+ applyEach: (result) => {
51
+ if (!result.canUpdate) {
52
+ return;
53
+ }
54
+ setOverrideIdToNodePath(overrideId, result.nodePath);
55
+ },
80
56
  });
81
57
  return () => {
82
58
  release();
83
- // Only clear props on true unmount, not on re-subscribe due to
84
- // location changes — avoids flicker while re-subscribing.
85
- if (!isMountedRef.current) {
86
- setPropStatusesForSequence(null);
87
- }
88
59
  };
89
60
  }, [
90
61
  clientId,
91
62
  locationColumn,
92
63
  locationLine,
93
64
  locationSource,
65
+ overrideId,
94
66
  schema,
95
- setPropStatusesForSequence,
96
- subscribeToEvent,
97
- visualModeEnabled,
67
+ setCodeValues,
68
+ setOverrideIdToNodePath,
98
69
  ]);
99
- return subscriptionState;
100
70
  };
101
71
  exports.useSequencePropsSubscription = useSequencePropsSubscription;
@@ -0,0 +1,5 @@
1
+ import type { TrackWithHash } from '../../helpers/get-timeline-sequence-sort-key';
2
+ export declare const useTimelineHeight: ({ shown, hasBeenCut, }: {
3
+ shown: TrackWithHash[];
4
+ hasBeenCut: boolean;
5
+ }) => number;