@technotoil/image-video-editor 0.1.1 → 0.1.2

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 (35) hide show
  1. package/README.md +17 -3
  2. package/android/src/main/java/com/technotoil/image_videoeditor/FrameGrabberModule.kt +2 -6
  3. package/android/src/main/java/com/technotoil/image_videoeditor/MediaEditorModule.kt +75 -35
  4. package/android/src/main/java/com/technotoil/image_videoeditor/MediaLibraryModule.kt +51 -35
  5. package/android/src/main/java/com/technotoil/image_videoeditor/RNVideoPreviewManager.kt +98 -117
  6. package/ios/RNMediaEditor.m +38 -7
  7. package/ios/RNMediaLibrary.m +19 -15
  8. package/ios/RNVideoPreviewManager.m +2 -0
  9. package/lib/commonjs/components/VideoEditor.js +100 -32
  10. package/lib/commonjs/components/VideoEditor.js.map +1 -1
  11. package/lib/commonjs/screens/CropScreen.js +5 -3
  12. package/lib/commonjs/screens/CropScreen.js.map +1 -1
  13. package/lib/commonjs/screens/EditorScreen.js +229 -44
  14. package/lib/commonjs/screens/EditorScreen.js.map +1 -1
  15. package/lib/commonjs/screens/PickScreen.js +214 -122
  16. package/lib/commonjs/screens/PickScreen.js.map +1 -1
  17. package/lib/module/components/VideoEditor.js +100 -33
  18. package/lib/module/components/VideoEditor.js.map +1 -1
  19. package/lib/module/screens/CropScreen.js +5 -3
  20. package/lib/module/screens/CropScreen.js.map +1 -1
  21. package/lib/module/screens/EditorScreen.js +229 -44
  22. package/lib/module/screens/EditorScreen.js.map +1 -1
  23. package/lib/module/screens/PickScreen.js +215 -123
  24. package/lib/module/screens/PickScreen.js.map +1 -1
  25. package/lib/typescript/src/components/VideoEditor.d.ts +10 -2
  26. package/lib/typescript/src/screens/CropScreen.d.ts +2 -1
  27. package/lib/typescript/src/screens/EditorScreen.d.ts +2 -1
  28. package/lib/typescript/src/screens/PickScreen.d.ts +4 -1
  29. package/lib/typescript/src/types.d.ts +1 -0
  30. package/package.json +4 -1
  31. package/src/components/VideoEditor.tsx +68 -11
  32. package/src/screens/CropScreen.tsx +8 -3
  33. package/src/screens/EditorScreen.tsx +227 -61
  34. package/src/screens/PickScreen.tsx +197 -119
  35. package/src/types.ts +1 -0
@@ -168,12 +168,16 @@ export function EditorScreen({
168
168
  onBack,
169
169
  onSaved,
170
170
  onOpenCrop,
171
- musicList
171
+ musicList,
172
+ maxVideoDurationMs
172
173
  }) {
173
174
  const [activeIndex, setActiveIndex] = useState(initialIndex);
174
175
  const currentItem = items[activeIndex] || items[0];
175
176
  const item = currentItem; // Aliasing to 'item' for ease of compatibility
176
177
 
178
+ useEffect(() => {
179
+ setActiveIndex(initialIndex);
180
+ }, [initialIndex]);
177
181
  const [activeFilter, setActiveFilter] = useState('none');
178
182
  const [imageOptions, setImageOptions] = useState({
179
183
  rotateDegrees: 0,
@@ -184,13 +188,21 @@ export function EditorScreen({
184
188
  saturation: 1,
185
189
  grayscale: false
186
190
  });
191
+ const [panel, setPanel] = useState(item.type === 'video' ? 'trim' : 'filter');
187
192
  const [trimStart, setTrimStart] = useState(0);
188
- const [trimEnd, setTrimEnd] = useState(item.durationMs || 10000);
193
+ const [trimEnd, setTrimEnd] = useState(() => {
194
+ const end = item.durationMs || 10000;
195
+ return maxVideoDurationMs ? Math.min(end, maxVideoDurationMs) : end;
196
+ });
189
197
  useEffect(() => {
190
198
  setTrimStart(0);
191
- setTrimEnd(item.durationMs || 10000);
199
+ const end = item.durationMs || maxVideoDurationMs || 10000;
200
+ setTrimEnd(maxVideoDurationMs ? Math.min(end, maxVideoDurationMs) : end);
192
201
  setThumbnails([]);
193
- }, [item.id, item.durationMs]);
202
+ if (item.type === 'video' && maxVideoDurationMs && end > maxVideoDurationMs) {
203
+ setPanel('trim');
204
+ }
205
+ }, [item.id, item.durationMs, maxVideoDurationMs]);
194
206
  const [editsHistory, setEditsHistory] = useState({});
195
207
  const editsHistoryRef = useRef({});
196
208
  const [dimensionsMap, setDimensionsMap] = useState({});
@@ -247,7 +259,7 @@ export function EditorScreen({
247
259
  setCropOffset(saved.cropOffset);
248
260
  setZoomScale(saved.zoomScale);
249
261
  setStraightenAngle(saved.straightenAngle);
250
- setIsMuted(saved.isMuted);
262
+ setIsMuted(selectedMusic ? true : saved.isMuted);
251
263
  } else {
252
264
  setActiveFilter('none');
253
265
  setImageOptions({
@@ -260,7 +272,8 @@ export function EditorScreen({
260
272
  grayscale: false
261
273
  });
262
274
  setTrimStart(0);
263
- setTrimEnd(targetItem.durationMs || 10000);
275
+ const end = targetItem.durationMs || 10000;
276
+ setTrimEnd(maxVideoDurationMs ? Math.min(end, maxVideoDurationMs) : end);
264
277
  setOverlays([]);
265
278
  setCropRatio(null);
266
279
  setCropOffset({
@@ -269,7 +282,14 @@ export function EditorScreen({
269
282
  });
270
283
  setZoomScale(1);
271
284
  setStraightenAngle(0);
272
- setIsMuted(false);
285
+ setIsMuted(selectedMusic ? true : false);
286
+ }
287
+
288
+ // Force trim panel if video is too long
289
+ if (targetItem.type === 'video' && maxVideoDurationMs && targetItem.durationMs && targetItem.durationMs > maxVideoDurationMs) {
290
+ setPanel('trim');
291
+ } else if (!saved) {
292
+ setPanel(targetItem.type === 'video' ? 'trim' : 'filter');
273
293
  }
274
294
  };
275
295
  const handleScrollEnd = e => {
@@ -328,13 +348,13 @@ export function EditorScreen({
328
348
  y: (o.y + 8) * renderScale,
329
349
  color: o.color,
330
350
  fontSize: o.fontSize * renderScale
331
- }))
351
+ })),
352
+ frameUri: edits.imageOptions.frame && FRAME_IMAGES[edits.imageOptions.frame] ? Image.resolveAssetSource(FRAME_IMAGES[edits.imageOptions.frame]).uri : undefined
332
353
  };
333
354
  };
334
355
  const [saving, setSaving] = useState(false);
335
356
  const [videoPaused, setVideoPaused] = useState(false);
336
- const [panel, setPanel] = useState(item.type === 'video' ? 'trim' : 'filter');
337
- const resolvedMusicList = musicList || DUMMY_MUSIC_LIST;
357
+ const resolvedMusicList = musicList || [];
338
358
  const [selectedMusic, setSelectedMusic] = useState(null);
339
359
  const [musicPaused, setMusicPaused] = useState(false);
340
360
  const [showMusicModal, setShowMusicModal] = useState(false);
@@ -1113,13 +1133,18 @@ export function EditorScreen({
1113
1133
  y: (o.y + 8) * renderScale,
1114
1134
  color: o.color,
1115
1135
  fontSize: o.fontSize * renderScale
1116
- }))
1136
+ })),
1137
+ frameUri: imageOptions.frame && FRAME_IMAGES[imageOptions.frame] ? Image.resolveAssetSource(FRAME_IMAGES[imageOptions.frame]).uri : undefined
1117
1138
  };
1118
1139
  }, [imageOptions, cropOffset, maxPan, dimensions, cropRatio, straightenAngle, overlays]);
1119
1140
 
1120
1141
  // For visual trim
1121
1142
 
1122
1143
  const duration = item.durationMs ?? 10_000;
1144
+ const durationRef = useRef(duration);
1145
+ useEffect(() => {
1146
+ durationRef.current = duration;
1147
+ }, [duration]);
1123
1148
  const formatTime = ms => {
1124
1149
  const totalSec = Math.floor(ms / 1000);
1125
1150
  const mins = Math.floor(totalSec / 60);
@@ -1181,6 +1206,39 @@ export function EditorScreen({
1181
1206
  generateThumbs();
1182
1207
  }
1183
1208
  }, [item.type, item.uri, duration, thumbnails.length]);
1209
+ const leftOverlayRef = useRef(null);
1210
+ const rightOverlayRef = useRef(null);
1211
+ const selectionRangeRef = useRef(null);
1212
+ const leftHandleRef = useRef(null);
1213
+ const rightHandleRef = useRef(null);
1214
+ const updateNativeRefs = (newStartX, newEndX) => {
1215
+ leftOverlayRef.current?.setNativeProps({
1216
+ style: {
1217
+ width: newStartX
1218
+ }
1219
+ });
1220
+ rightOverlayRef.current?.setNativeProps({
1221
+ style: {
1222
+ left: newEndX
1223
+ }
1224
+ });
1225
+ selectionRangeRef.current?.setNativeProps({
1226
+ style: {
1227
+ left: newStartX,
1228
+ width: newEndX - newStartX
1229
+ }
1230
+ });
1231
+ leftHandleRef.current?.setNativeProps({
1232
+ style: {
1233
+ left: newStartX - 16
1234
+ }
1235
+ });
1236
+ rightHandleRef.current?.setNativeProps({
1237
+ style: {
1238
+ left: newEndX - 16
1239
+ }
1240
+ });
1241
+ };
1184
1242
  const startPanOffset = useRef(0);
1185
1243
  const startPan = useRef(PanResponder.create({
1186
1244
  onStartShouldSetPanResponder: () => true,
@@ -1194,20 +1252,76 @@ export function EditorScreen({
1194
1252
  setScrollEnabled(false);
1195
1253
  },
1196
1254
  onPanResponderMove: (_, gesture) => {
1197
- const newX = Math.max(0, Math.min(endX.current - 32, startPanOffset.current + gesture.dx));
1255
+ let newX = Math.max(0, Math.min(endX.current - 32, startPanOffset.current + gesture.dx));
1256
+ let newTime = newX / TIMELINE_WIDTH * durationRef.current;
1257
+ const currentTrimEnd = endX.current / TIMELINE_WIDTH * durationRef.current;
1258
+ if (maxVideoDurationMs && currentTrimEnd - newTime > maxVideoDurationMs) {
1259
+ newTime = currentTrimEnd - maxVideoDurationMs;
1260
+ newX = newTime / durationRef.current * TIMELINE_WIDTH;
1261
+ }
1198
1262
  startX.current = newX;
1199
- const newTime = newX / TIMELINE_WIDTH * duration;
1200
- setTrimStart(newTime);
1263
+ updateNativeRefs(newX, endX.current);
1201
1264
  throttledSeek(newTime);
1202
1265
  },
1203
1266
  onPanResponderRelease: () => {
1204
1267
  isDraggingHandle.current = false;
1205
1268
  setScrollEnabled(true);
1269
+ setTrimStart(startX.current / TIMELINE_WIDTH * durationRef.current);
1270
+ setTrimEnd(endX.current / TIMELINE_WIDTH * durationRef.current);
1206
1271
  setSeekToMs(-1);
1207
1272
  },
1208
1273
  onPanResponderTerminate: () => {
1209
1274
  isDraggingHandle.current = false;
1210
1275
  setScrollEnabled(true);
1276
+ setTrimStart(startX.current / TIMELINE_WIDTH * durationRef.current);
1277
+ setTrimEnd(endX.current / TIMELINE_WIDTH * durationRef.current);
1278
+ setSeekToMs(-1);
1279
+ }
1280
+ })).current;
1281
+ const middlePanOffsetStart = useRef(0);
1282
+ const middlePanOffsetEnd = useRef(0);
1283
+ const middlePan = useRef(PanResponder.create({
1284
+ onStartShouldSetPanResponder: () => true,
1285
+ onStartShouldSetPanResponderCapture: () => true,
1286
+ onMoveShouldSetPanResponder: () => true,
1287
+ onMoveShouldSetPanResponderCapture: () => true,
1288
+ onPanResponderGrant: () => {
1289
+ pushToHistory();
1290
+ middlePanOffsetStart.current = startX.current;
1291
+ middlePanOffsetEnd.current = endX.current;
1292
+ isDraggingHandle.current = true;
1293
+ setScrollEnabled(false);
1294
+ },
1295
+ onPanResponderMove: (_, gesture) => {
1296
+ const windowWidth = middlePanOffsetEnd.current - middlePanOffsetStart.current;
1297
+ let newStartX = middlePanOffsetStart.current + gesture.dx;
1298
+ let newEndX = middlePanOffsetEnd.current + gesture.dx;
1299
+ if (newStartX < 0) {
1300
+ newStartX = 0;
1301
+ newEndX = windowWidth;
1302
+ }
1303
+ if (newEndX > TIMELINE_WIDTH) {
1304
+ newEndX = TIMELINE_WIDTH;
1305
+ newStartX = TIMELINE_WIDTH - windowWidth;
1306
+ }
1307
+ startX.current = newStartX;
1308
+ endX.current = newEndX;
1309
+ const newStartTime = newStartX / TIMELINE_WIDTH * durationRef.current;
1310
+ updateNativeRefs(newStartX, newEndX);
1311
+ throttledSeek(newStartTime);
1312
+ },
1313
+ onPanResponderRelease: () => {
1314
+ isDraggingHandle.current = false;
1315
+ setScrollEnabled(true);
1316
+ setTrimStart(startX.current / TIMELINE_WIDTH * durationRef.current);
1317
+ setTrimEnd(endX.current / TIMELINE_WIDTH * durationRef.current);
1318
+ setSeekToMs(-1);
1319
+ },
1320
+ onPanResponderTerminate: () => {
1321
+ isDraggingHandle.current = false;
1322
+ setScrollEnabled(true);
1323
+ setTrimStart(startX.current / TIMELINE_WIDTH * durationRef.current);
1324
+ setTrimEnd(endX.current / TIMELINE_WIDTH * durationRef.current);
1211
1325
  setSeekToMs(-1);
1212
1326
  }
1213
1327
  })).current;
@@ -1224,20 +1338,29 @@ export function EditorScreen({
1224
1338
  setScrollEnabled(false);
1225
1339
  },
1226
1340
  onPanResponderMove: (_, gesture) => {
1227
- const newX = Math.min(TIMELINE_WIDTH, Math.max(startX.current + 32, endPanOffset.current + gesture.dx));
1341
+ let newX = Math.min(TIMELINE_WIDTH, Math.max(startX.current + 32, endPanOffset.current + gesture.dx));
1342
+ let newTime = newX / TIMELINE_WIDTH * durationRef.current;
1343
+ const currentTrimStart = startX.current / TIMELINE_WIDTH * durationRef.current;
1344
+ if (maxVideoDurationMs && newTime - currentTrimStart > maxVideoDurationMs) {
1345
+ newTime = currentTrimStart + maxVideoDurationMs;
1346
+ newX = newTime / durationRef.current * TIMELINE_WIDTH;
1347
+ }
1228
1348
  endX.current = newX;
1229
- const newTime = newX / TIMELINE_WIDTH * duration;
1230
- setTrimEnd(newTime);
1349
+ updateNativeRefs(startX.current, newX);
1231
1350
  throttledSeek(newTime);
1232
1351
  },
1233
1352
  onPanResponderRelease: () => {
1234
1353
  isDraggingHandle.current = false;
1235
1354
  setScrollEnabled(true);
1355
+ setTrimStart(startX.current / TIMELINE_WIDTH * durationRef.current);
1356
+ setTrimEnd(endX.current / TIMELINE_WIDTH * durationRef.current);
1236
1357
  setSeekToMs(-1);
1237
1358
  },
1238
1359
  onPanResponderTerminate: () => {
1239
1360
  isDraggingHandle.current = false;
1240
1361
  setScrollEnabled(true);
1362
+ setTrimStart(startX.current / TIMELINE_WIDTH * durationRef.current);
1363
+ setTrimEnd(endX.current / TIMELINE_WIDTH * durationRef.current);
1241
1364
  setSeekToMs(-1);
1242
1365
  }
1243
1366
  })).current;
@@ -2012,11 +2135,16 @@ export function EditorScreen({
2012
2135
  });
2013
2136
  }
2014
2137
  } else {
2138
+ const safeEndMs = Math.min(trimEnd, item.durationMs || 10000);
2139
+ const safeStartMs = Math.min(trimStart, Math.max(0, safeEndMs - 100));
2140
+ const isFullTrim = trimStart === 0 && trimEnd >= (item.durationMs || 10000);
2015
2141
  exportUri = await trimVideo(item.uri, {
2016
- startMs: trimStart,
2017
- endMs: trimEnd,
2142
+ startMs: safeStartMs,
2143
+ endMs: safeEndMs,
2018
2144
  mute: isMuted,
2019
- musicUri: selectedMusic?.url || undefined,
2145
+ ...(selectedMusic?.url ? {
2146
+ musicUri: selectedMusic.url
2147
+ } : {}),
2020
2148
  ...activeOptions
2021
2149
  });
2022
2150
  }
@@ -2033,6 +2161,7 @@ export function EditorScreen({
2033
2161
  setSaving(true);
2034
2162
  saveEditsForIndex(activeIndex);
2035
2163
  const updatedItems = [...items];
2164
+ let cumulativeMusicOffsetMs = 0;
2036
2165
  for (let i = 0; i < items.length; i++) {
2037
2166
  const targetItem = items[i];
2038
2167
  let edits = editsHistoryRef.current[targetItem.id];
@@ -2058,6 +2187,7 @@ export function EditorScreen({
2058
2187
  outUri = await trimVideo(outUri, {
2059
2188
  isImage: true,
2060
2189
  musicUri: selectedMusic.url,
2190
+ musicOffsetMs: cumulativeMusicOffsetMs,
2061
2191
  rotateDegrees: 0,
2062
2192
  flipX: false,
2063
2193
  flipY: false,
@@ -2072,12 +2202,20 @@ export function EditorScreen({
2072
2202
  uri: outUri,
2073
2203
  thumbnailUri: outUri
2074
2204
  };
2205
+ cumulativeMusicOffsetMs += 10000; // Images are 10s by default
2075
2206
  } else {
2207
+ const originalDuration = targetItem.durationMs || maxVideoDurationMs || 10000;
2208
+ const safeEndMs = Math.min(edits.trimEnd, originalDuration);
2209
+ const safeStartMs = Math.min(edits.trimStart, Math.max(0, safeEndMs - 100));
2210
+ const isFullTrim = edits.trimStart === 0 && edits.trimEnd >= originalDuration;
2076
2211
  const outUri = await trimVideo(targetItem.uri, {
2077
- startMs: edits.trimStart,
2078
- endMs: edits.trimEnd,
2212
+ startMs: safeStartMs,
2213
+ endMs: safeEndMs,
2079
2214
  mute: edits.isMuted,
2080
- musicUri: selectedMusic?.url || undefined,
2215
+ ...(selectedMusic?.url ? {
2216
+ musicUri: selectedMusic.url,
2217
+ musicOffsetMs: cumulativeMusicOffsetMs
2218
+ } : {}),
2081
2219
  ...opts
2082
2220
  });
2083
2221
  let newThumb = undefined;
@@ -2095,6 +2233,7 @@ export function EditorScreen({
2095
2233
  thumbnailUri: newThumb ? newThumb : targetItem.thumbnailUri,
2096
2234
  durationMs: newDuration
2097
2235
  };
2236
+ cumulativeMusicOffsetMs += newDuration;
2098
2237
  }
2099
2238
  } else {
2100
2239
  if (selectedMusic) {
@@ -2103,6 +2242,7 @@ export function EditorScreen({
2103
2242
  outUri = await trimVideo(targetItem.uri, {
2104
2243
  isImage: true,
2105
2244
  musicUri: selectedMusic.url,
2245
+ musicOffsetMs: cumulativeMusicOffsetMs,
2106
2246
  rotateDegrees: 0,
2107
2247
  flipX: false,
2108
2248
  flipY: false,
@@ -2111,12 +2251,17 @@ export function EditorScreen({
2111
2251
  saturation: 1,
2112
2252
  grayscale: false
2113
2253
  });
2254
+ cumulativeMusicOffsetMs += 10000;
2114
2255
  } else {
2256
+ const safeEndMs = targetItem.durationMs || 10000;
2115
2257
  outUri = await trimVideo(targetItem.uri, {
2116
2258
  startMs: 0,
2117
- endMs: targetItem.durationMs || 10000,
2259
+ endMs: safeEndMs,
2118
2260
  mute: isMuted,
2119
- musicUri: selectedMusic.url,
2261
+ ...(selectedMusic?.url ? {
2262
+ musicUri: selectedMusic.url,
2263
+ musicOffsetMs: cumulativeMusicOffsetMs
2264
+ } : {}),
2120
2265
  rotateDegrees: 0,
2121
2266
  flipX: false,
2122
2267
  flipY: false,
@@ -2125,6 +2270,7 @@ export function EditorScreen({
2125
2270
  saturation: 1,
2126
2271
  grayscale: false
2127
2272
  });
2273
+ cumulativeMusicOffsetMs += safeEndMs;
2128
2274
  }
2129
2275
  updatedItems[i] = {
2130
2276
  ...targetItem,
@@ -2666,7 +2812,15 @@ export function EditorScreen({
2666
2812
  },
2667
2813
  style: styles.filmstripImage
2668
2814
  }, idx)), /*#__PURE__*/_jsx(View, {
2669
- style: styles.timelineOverlay
2815
+ style: [styles.timelineOverlay, {
2816
+ left: 0,
2817
+ width: trimStart / duration * TIMELINE_WIDTH
2818
+ }]
2819
+ }), /*#__PURE__*/_jsx(View, {
2820
+ style: [styles.timelineOverlay, {
2821
+ left: trimEnd / duration * TIMELINE_WIDTH,
2822
+ right: 0
2823
+ }]
2670
2824
  }), /*#__PURE__*/_jsx(View, {
2671
2825
  style: [styles.selectionRange, {
2672
2826
  left: trimStart / duration * TIMELINE_WIDTH,
@@ -2677,20 +2831,18 @@ export function EditorScreen({
2677
2831
  style: [styles.customHandle, styles.customHandleLeft, {
2678
2832
  left: trimStart / duration * TIMELINE_WIDTH - 16
2679
2833
  }],
2680
- ...startPan.panHandlers,
2681
2834
  children: /*#__PURE__*/_jsx(View, {
2682
2835
  style: styles.handleBarLine
2683
2836
  })
2684
2837
  }), /*#__PURE__*/_jsx(View, {
2685
2838
  style: [styles.customHandle, styles.customHandleRight, {
2686
- left: trimEnd / duration * TIMELINE_WIDTH
2839
+ left: trimEnd / duration * TIMELINE_WIDTH - 16
2687
2840
  }],
2688
- ...endPan.panHandlers,
2689
2841
  children: /*#__PURE__*/_jsx(View, {
2690
2842
  style: styles.handleBarLine
2691
2843
  })
2692
2844
  })]
2693
- }), /*#__PURE__*/_jsxs(Pressable, {
2845
+ }), resolvedMusicList.length > 0 && /*#__PURE__*/_jsxs(Pressable, {
2694
2846
  style: styles.subTrackRow,
2695
2847
  onPress: () => setShowMusicModal(true),
2696
2848
  children: [/*#__PURE__*/_jsx(Text, {
@@ -2971,7 +3123,7 @@ export function EditorScreen({
2971
3123
  style: styles.toolLabel,
2972
3124
  children: "Text"
2973
3125
  })]
2974
- }), /*#__PURE__*/_jsxs(Pressable, {
3126
+ }), resolvedMusicList.length > 0 && /*#__PURE__*/_jsxs(Pressable, {
2975
3127
  style: [styles.toolButton, showMusicModal && styles.toolButtonActive],
2976
3128
  onPress: () => setShowMusicModal(true),
2977
3129
  children: [/*#__PURE__*/_jsx(View, {
@@ -3133,7 +3285,7 @@ export function EditorScreen({
3133
3285
  contentContainerStyle: [styles.toolButtonsRow, {
3134
3286
  flexGrow: 1
3135
3287
  }],
3136
- children: [/*#__PURE__*/_jsxs(Pressable, {
3288
+ children: [resolvedMusicList.length > 0 && /*#__PURE__*/_jsxs(Pressable, {
3137
3289
  style: [styles.toolButton, showMusicModal && styles.toolButtonActive],
3138
3290
  onPress: () => setShowMusicModal(true),
3139
3291
  children: [/*#__PURE__*/_jsx(View, {
@@ -3302,6 +3454,12 @@ export function EditorScreen({
3302
3454
  data: items,
3303
3455
  extraData: [overlays, editingTextId, editsHistory, activeFilter, imageOptions, trimStart, trimEnd, isMuted, activeIndex],
3304
3456
  keyExtractor: it => it.id,
3457
+ initialScrollIndex: activeIndex,
3458
+ getItemLayout: (_, index) => ({
3459
+ length: SCREEN_WIDTH,
3460
+ offset: SCREEN_WIDTH * index,
3461
+ index
3462
+ }),
3305
3463
  horizontal: true,
3306
3464
  pagingEnabled: false,
3307
3465
  showsHorizontalScrollIndicator: false,
@@ -3412,7 +3570,7 @@ export function EditorScreen({
3412
3570
  fontWeight: '700'
3413
3571
  },
3414
3572
  children: (() => {
3415
- const selMs = trimEnd - trimStart;
3573
+ const selMs = (endX.current - startX.current) / TIMELINE_WIDTH * duration;
3416
3574
  const totalSec = Math.floor(selMs / 1000);
3417
3575
  return totalSec >= 60 ? `${Math.floor(totalSec / 60)}:${(totalSec % 60).toString().padStart(2, '0')} selected` : `${(selMs / 1000).toFixed(1)}s selected`;
3418
3576
  })()
@@ -3423,30 +3581,54 @@ export function EditorScreen({
3423
3581
  }],
3424
3582
  children: [/*#__PURE__*/_jsxs(View, {
3425
3583
  style: styles.filmstrip,
3426
- children: [thumbnails.map((uri, idx) => /*#__PURE__*/_jsx(Image, {
3584
+ children: [thumbnails.length === 0 ? /*#__PURE__*/_jsx(View, {
3585
+ style: {
3586
+ flex: 1,
3587
+ justifyContent: 'center',
3588
+ alignItems: 'center'
3589
+ },
3590
+ children: /*#__PURE__*/_jsx(ActivityIndicator, {
3591
+ color: "#ffffff",
3592
+ size: "small"
3593
+ })
3594
+ }) : thumbnails.map((uri, idx) => /*#__PURE__*/_jsx(Image, {
3427
3595
  source: {
3428
3596
  uri
3429
3597
  },
3430
3598
  style: styles.filmstripImage
3431
3599
  }, idx)), /*#__PURE__*/_jsx(View, {
3432
- style: styles.timelineOverlay
3600
+ ref: leftOverlayRef,
3601
+ style: [styles.timelineOverlay, {
3602
+ left: 0,
3603
+ width: startX.current
3604
+ }]
3433
3605
  }), /*#__PURE__*/_jsx(View, {
3434
- style: [styles.selectionRange, {
3435
- left: trimStart / duration * TIMELINE_WIDTH,
3436
- width: (trimEnd - trimStart) / duration * TIMELINE_WIDTH
3606
+ ref: rightOverlayRef,
3607
+ style: [styles.timelineOverlay, {
3608
+ left: endX.current,
3609
+ right: 0
3437
3610
  }]
3611
+ }), /*#__PURE__*/_jsx(View, {
3612
+ ref: selectionRangeRef,
3613
+ style: [styles.selectionRange, {
3614
+ left: startX.current,
3615
+ width: endX.current - startX.current
3616
+ }],
3617
+ ...middlePan.panHandlers
3438
3618
  })]
3439
3619
  }), /*#__PURE__*/_jsx(View, {
3620
+ ref: leftHandleRef,
3440
3621
  style: [styles.customHandle, styles.customHandleLeft, {
3441
- left: trimStart / duration * TIMELINE_WIDTH - 16
3622
+ left: startX.current - 16
3442
3623
  }],
3443
3624
  ...startPan.panHandlers,
3444
3625
  children: /*#__PURE__*/_jsx(View, {
3445
3626
  style: styles.handleBarLine
3446
3627
  })
3447
3628
  }), /*#__PURE__*/_jsx(View, {
3629
+ ref: rightHandleRef,
3448
3630
  style: [styles.customHandle, styles.customHandleRight, {
3449
- left: trimEnd / duration * TIMELINE_WIDTH
3631
+ left: endX.current - 16
3450
3632
  }],
3451
3633
  ...endPan.panHandlers,
3452
3634
  children: /*#__PURE__*/_jsx(View, {
@@ -3724,7 +3906,7 @@ export function EditorScreen({
3724
3906
  contentContainerStyle: [styles.toolButtonsRow, {
3725
3907
  flexGrow: 1
3726
3908
  }],
3727
- children: [/*#__PURE__*/_jsxs(Pressable, {
3909
+ children: [resolvedMusicList.length > 0 && /*#__PURE__*/_jsxs(Pressable, {
3728
3910
  style: [styles.toolButton, showMusicModal && styles.toolButtonActive],
3729
3911
  onPress: () => setShowMusicModal(true),
3730
3912
  children: [/*#__PURE__*/_jsx(View, {
@@ -4032,6 +4214,7 @@ export function EditorScreen({
4032
4214
  onPress: () => {
4033
4215
  setSelectedMusic(null);
4034
4216
  setMusicPaused(true);
4217
+ setIsMuted(false);
4035
4218
  },
4036
4219
  style: styles.musicFooterRemoveBtn,
4037
4220
  children: /*#__PURE__*/_jsx(Text, {
@@ -4799,7 +4982,9 @@ const styles = StyleSheet.create({
4799
4982
  height: 60
4800
4983
  },
4801
4984
  timelineOverlay: {
4802
- ...StyleSheet.absoluteFillObject,
4985
+ position: 'absolute',
4986
+ top: 0,
4987
+ bottom: 0,
4803
4988
  backgroundColor: 'rgba(0,0,0,0.6)'
4804
4989
  },
4805
4990
  selectionRange: {
@@ -4809,7 +4994,7 @@ const styles = StyleSheet.create({
4809
4994
  backgroundColor: 'transparent',
4810
4995
  borderTopWidth: 2,
4811
4996
  borderBottomWidth: 2,
4812
- borderColor: '#fff'
4997
+ borderColor: '#FFD60A'
4813
4998
  },
4814
4999
  handle: {
4815
5000
  position: 'absolute',
@@ -4835,7 +5020,7 @@ const styles = StyleSheet.create({
4835
5020
  top: 0,
4836
5021
  width: 16,
4837
5022
  height: 60,
4838
- backgroundColor: '#FFFFFF',
5023
+ backgroundColor: '#FFD60A',
4839
5024
  justifyContent: 'center',
4840
5025
  alignItems: 'center',
4841
5026
  zIndex: 20