ugcinc 4.7.0 → 4.7.1

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.
@@ -12,6 +12,14 @@ const react_1 = require("react");
12
12
  const remotion_1 = require("remotion");
13
13
  const defaults_1 = require("../utils/defaults");
14
14
  const text_1 = require("../utils/text");
15
+ /**
16
+ * Preview sync threshold: remotion hard-seeks the media tag when it drifts
17
+ * further than this from the timeline (default ~0.65s). Fractional playback
18
+ * speeds accumulate drift, so the default produces large visible "rubberband"
19
+ * jumps; a tight threshold keeps corrections to a few frames. Preview-only —
20
+ * ignored during server rendering.
21
+ */
22
+ const ACCEPTABLE_TIME_SHIFT_SECONDS = 0.2;
15
23
  /**
16
24
  * Map FitMode to CSS object-fit
17
25
  */
@@ -102,7 +110,7 @@ function VideoElement({ segment, src, startFrame, durationInFrames, scale = 1 })
102
110
  const effectiveDurationMs = loopSourceDurationMs / speed;
103
111
  return Math.max(1, Math.round((effectiveDurationMs / 1000) * fps));
104
112
  }, [loop, loopSourceDurationMs, speed, fps]);
105
- const videoElement = ((0, jsx_runtime_1.jsx)(remotion_1.OffthreadVideo, { src: src, style: videoStyle, startFrom: startFromFrames, playbackRate: speed, volume: volume }));
113
+ const videoElement = ((0, jsx_runtime_1.jsx)(remotion_1.OffthreadVideo, { src: src, style: videoStyle, startFrom: startFromFrames, playbackRate: speed, volume: volume, acceptableTimeShiftInSeconds: ACCEPTABLE_TIME_SHIFT_SECONDS }));
106
114
  return ((0, jsx_runtime_1.jsx)("div", { style: containerStyle, children: loop && loopDurationInFrames ? ((0, jsx_runtime_1.jsx)(remotion_1.Loop, { durationInFrames: loopDurationInFrames, children: videoElement })) : (videoElement) }));
107
115
  }
108
116
  exports.default = VideoElement;
@@ -134,27 +134,28 @@ function calculateSegmentTimings(config, fps) {
134
134
  * ```
135
135
  */
136
136
  function VideoEditorComposition({ config, sources = {}, textContent = {}, }) {
137
- const frame = (0, remotion_1.useCurrentFrame)();
138
- const { fps, durationInFrames } = (0, remotion_1.useVideoConfig)();
137
+ const { fps } = (0, remotion_1.useVideoConfig)();
139
138
  // Calculate segment timings
140
139
  const segmentTimings = (0, react_1.useMemo)(() => calculateSegmentTimings(config, fps), [config, fps]);
141
- // Get active visual segments at current frame, sorted by zIndex
142
- const activeVisualSegments = (0, react_1.useMemo)(() => {
140
+ // All visual segments, statically sorted by zIndex (stable sort keeps the
141
+ // original channel order for ties). Every segment is always rendered inside
142
+ // a <Sequence> window, which handles per-frame visibility — mounting is no
143
+ // longer gated on the current frame, so upcoming media can premount.
144
+ const visualTimings = (0, react_1.useMemo)(() => {
143
145
  return segmentTimings
144
- .filter(({ segment, startFrame, endFrame }) => {
145
- // Only visual segments (including sequences)
146
- if (segment.type !== 'video' && segment.type !== 'image' && segment.type !== 'text' && segment.type !== 'image-sequence' && segment.type !== 'video-sequence') {
147
- return false;
148
- }
149
- // Check if active at current frame
150
- return frame >= startFrame && frame < endFrame;
146
+ .filter(({ segment }) => {
147
+ return segment.type === 'video' || segment.type === 'image' || segment.type === 'text' || segment.type === 'image-sequence' || segment.type === 'video-sequence';
151
148
  })
152
149
  .sort((a, b) => {
153
150
  const aZ = a.segment.zIndex ?? 0;
154
151
  const bZ = b.segment.zIndex ?? 0;
155
152
  return aZ - bZ;
156
153
  });
157
- }, [segmentTimings, frame]);
154
+ }, [segmentTimings]);
155
+ // Premount window for video sequences: in the Player, upcoming videos
156
+ // mount invisibly this many frames early so they buffer and pre-seek
157
+ // before becoming visible. No effect on server rendering.
158
+ const premountFrames = Math.round(fps * 2);
158
159
  // Get source URL for a segment
159
160
  const getSource = (segment) => {
160
161
  if (segment.source) {
@@ -170,25 +171,25 @@ function VideoEditorComposition({ config, sources = {}, textContent = {}, }) {
170
171
  const audioTimings = (0, react_1.useMemo)(() => {
171
172
  return segmentTimings.filter(({ segment }) => segment.type === 'audio');
172
173
  }, [segmentTimings]);
173
- return ((0, jsx_runtime_1.jsxs)(remotion_1.AbsoluteFill, { style: { backgroundColor: '#000000' }, children: [activeVisualSegments.map(({ segment, startFrame, durationInFrames }) => {
174
+ return ((0, jsx_runtime_1.jsxs)(remotion_1.AbsoluteFill, { style: { backgroundColor: '#000000' }, children: [visualTimings.map(({ segment, startFrame, durationInFrames }) => {
174
175
  if (segment.type === 'text') {
175
176
  const textSegment = segment;
176
177
  const text = getTextContent(textSegment);
177
- return ((0, jsx_runtime_1.jsx)(TextElement_1.TextElement, { segment: { ...textSegment, text }, scale: 1 }, segment.id));
178
+ return ((0, jsx_runtime_1.jsx)(remotion_1.Sequence, { from: startFrame, durationInFrames: durationInFrames, children: (0, jsx_runtime_1.jsx)(TextElement_1.TextElement, { segment: { ...textSegment, text }, scale: 1 }) }, segment.id));
178
179
  }
179
180
  if (segment.type === 'image') {
180
181
  const src = getSource(segment);
181
182
  if (!src) {
182
183
  return null;
183
184
  }
184
- return ((0, jsx_runtime_1.jsx)(ImageElement_1.ImageElement, { segment: segment, src: src, startFrame: startFrame, scale: 1 }, segment.id));
185
+ return ((0, jsx_runtime_1.jsx)(remotion_1.Sequence, { from: startFrame, durationInFrames: durationInFrames, children: (0, jsx_runtime_1.jsx)(ImageElement_1.ImageElement, { segment: segment, src: src, startFrame: 0, scale: 1 }) }, segment.id));
185
186
  }
186
187
  if (segment.type === 'video') {
187
188
  const src = getSource(segment);
188
189
  if (!src) {
189
190
  return null;
190
191
  }
191
- return ((0, jsx_runtime_1.jsx)(remotion_1.Sequence, { from: startFrame, durationInFrames: durationInFrames, children: (0, jsx_runtime_1.jsx)(VideoElement_1.VideoElement, { segment: segment, src: src, startFrame: 0, durationInFrames: durationInFrames, scale: 1 }) }, segment.id));
192
+ return ((0, jsx_runtime_1.jsx)(remotion_1.Sequence, { from: startFrame, durationInFrames: durationInFrames, premountFor: premountFrames, children: (0, jsx_runtime_1.jsx)(VideoElement_1.VideoElement, { segment: segment, src: src, startFrame: 0, durationInFrames: durationInFrames, scale: 1 }) }, segment.id));
192
193
  }
193
194
  if (segment.type === 'image-sequence') {
194
195
  const sequenceSegment = segment;
@@ -196,32 +197,29 @@ function VideoEditorComposition({ config, sources = {}, textContent = {}, }) {
196
197
  if (items.length === 0) {
197
198
  return null;
198
199
  }
199
- // Calculate which item should be shown at current frame
200
- const frameIntoSegment = frame - startFrame;
201
- const itemDurationFrames = Math.round((sequenceSegment.defaultDuration / 1000) * fps);
202
- const currentItemIndex = Math.floor(frameIntoSegment / itemDurationFrames);
203
- const currentItemUrl = items[currentItemIndex];
204
- if (currentItemIndex >= items.length || !currentItemUrl) {
205
- return null;
206
- }
207
- const itemStartFrame = startFrame + currentItemIndex * itemDurationFrames;
208
- const syntheticImageSegment = {
209
- id: `${segment.id}-item-${currentItemIndex}`,
210
- type: 'image',
211
- source: currentItemUrl,
212
- order: segment.order,
213
- offset: { type: 'absolute', value: 0 },
214
- xOffset: sequenceSegment.xOffset,
215
- yOffset: sequenceSegment.yOffset,
216
- width: sequenceSegment.width,
217
- height: sequenceSegment.height,
218
- fit: sequenceSegment.fit,
219
- rotation: sequenceSegment.rotation,
220
- opacity: sequenceSegment.opacity,
221
- zIndex: sequenceSegment.zIndex,
222
- fadeIn: sequenceSegment.fadeIn,
223
- };
224
- return ((0, jsx_runtime_1.jsx)(ImageElement_1.ImageElement, { segment: syntheticImageSegment, src: currentItemUrl, startFrame: itemStartFrame, scale: 1 }, `${segment.id}-${currentItemUrl}`));
200
+ const itemDurationFrames = Math.max(1, Math.round((sequenceSegment.defaultDuration / 1000) * fps));
201
+ return items.map((itemUrl, itemIndex) => {
202
+ if (!itemUrl) {
203
+ return null;
204
+ }
205
+ const syntheticImageSegment = {
206
+ id: `${segment.id}-item-${itemIndex}`,
207
+ type: 'image',
208
+ source: itemUrl,
209
+ order: segment.order,
210
+ offset: { type: 'absolute', value: 0 },
211
+ xOffset: sequenceSegment.xOffset,
212
+ yOffset: sequenceSegment.yOffset,
213
+ width: sequenceSegment.width,
214
+ height: sequenceSegment.height,
215
+ fit: sequenceSegment.fit,
216
+ rotation: sequenceSegment.rotation,
217
+ opacity: sequenceSegment.opacity,
218
+ zIndex: sequenceSegment.zIndex,
219
+ fadeIn: sequenceSegment.fadeIn,
220
+ };
221
+ return ((0, jsx_runtime_1.jsx)(remotion_1.Sequence, { from: startFrame + itemIndex * itemDurationFrames, durationInFrames: itemDurationFrames, children: (0, jsx_runtime_1.jsx)(ImageElement_1.ImageElement, { segment: syntheticImageSegment, src: itemUrl, startFrame: 0, scale: 1 }) }, `${segment.id}-item-${itemIndex}`));
222
+ });
225
223
  }
226
224
  if (segment.type === 'video-sequence') {
227
225
  const sequenceSegment = segment;
@@ -230,44 +228,33 @@ function VideoEditorComposition({ config, sources = {}, textContent = {}, }) {
230
228
  if (items.length === 0) {
231
229
  return null;
232
230
  }
233
- // Calculate which item should be shown at current frame
234
- const frameIntoSegment = frame - startFrame;
235
231
  let accumulatedFrames = 0;
236
- let currentItemIndex = -1;
237
- let itemStartFrame = startFrame;
238
- for (let i = 0; i < items.length; i++) {
239
- const itemDurationMs = durations[i] ?? 5000;
240
- const itemDurationFrames = Math.round((itemDurationMs / 1000) * fps);
241
- if (frameIntoSegment < accumulatedFrames + itemDurationFrames) {
242
- currentItemIndex = i;
243
- itemStartFrame = startFrame + accumulatedFrames;
244
- break;
245
- }
232
+ return items.map((itemUrl, itemIndex) => {
233
+ const itemDurationMs = durations[itemIndex] ?? 5000;
234
+ const itemDurationFrames = Math.max(1, Math.round((itemDurationMs / 1000) * fps));
235
+ const itemStartFrame = startFrame + accumulatedFrames;
246
236
  accumulatedFrames += itemDurationFrames;
247
- }
248
- const currentItemUrl = items[currentItemIndex];
249
- if (currentItemIndex < 0 || currentItemIndex >= items.length || !currentItemUrl) {
250
- return null;
251
- }
252
- const itemDurationMs = durations[currentItemIndex] ?? 5000;
253
- const itemDurationFrames = Math.round((itemDurationMs / 1000) * fps);
254
- const syntheticVideoSegment = {
255
- id: `${segment.id}-item-${currentItemIndex}`,
256
- type: 'video',
257
- source: currentItemUrl,
258
- order: segment.order,
259
- offset: { type: 'absolute', value: 0 },
260
- xOffset: sequenceSegment.xOffset,
261
- yOffset: sequenceSegment.yOffset,
262
- width: sequenceSegment.width,
263
- height: sequenceSegment.height,
264
- fit: sequenceSegment.fit,
265
- rotation: sequenceSegment.rotation,
266
- opacity: sequenceSegment.opacity,
267
- zIndex: sequenceSegment.zIndex,
268
- fadeIn: sequenceSegment.fadeIn,
269
- };
270
- return ((0, jsx_runtime_1.jsx)(remotion_1.Sequence, { from: itemStartFrame, durationInFrames: itemDurationFrames, children: (0, jsx_runtime_1.jsx)(VideoElement_1.VideoElement, { segment: syntheticVideoSegment, src: currentItemUrl, startFrame: 0, durationInFrames: itemDurationFrames, scale: 1 }) }, `${segment.id}-${currentItemUrl}`));
237
+ if (!itemUrl) {
238
+ return null;
239
+ }
240
+ const syntheticVideoSegment = {
241
+ id: `${segment.id}-item-${itemIndex}`,
242
+ type: 'video',
243
+ source: itemUrl,
244
+ order: segment.order,
245
+ offset: { type: 'absolute', value: 0 },
246
+ xOffset: sequenceSegment.xOffset,
247
+ yOffset: sequenceSegment.yOffset,
248
+ width: sequenceSegment.width,
249
+ height: sequenceSegment.height,
250
+ fit: sequenceSegment.fit,
251
+ rotation: sequenceSegment.rotation,
252
+ opacity: sequenceSegment.opacity,
253
+ zIndex: sequenceSegment.zIndex,
254
+ fadeIn: sequenceSegment.fadeIn,
255
+ };
256
+ return ((0, jsx_runtime_1.jsx)(remotion_1.Sequence, { from: itemStartFrame, durationInFrames: itemDurationFrames, premountFor: premountFrames, children: (0, jsx_runtime_1.jsx)(VideoElement_1.VideoElement, { segment: syntheticVideoSegment, src: itemUrl, startFrame: 0, durationInFrames: itemDurationFrames, scale: 1 }) }, `${segment.id}-item-${itemIndex}`));
257
+ });
271
258
  }
272
259
  return null;
273
260
  }), audioTimings.map(({ segment, startFrame, durationInFrames }) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ugcinc",
3
- "version": "4.7.0",
3
+ "version": "4.7.1",
4
4
  "description": "TypeScript/JavaScript client for the UGC Inc API",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",