@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
@@ -174,12 +174,16 @@ function EditorScreen({
174
174
  onBack,
175
175
  onSaved,
176
176
  onOpenCrop,
177
- musicList
177
+ musicList,
178
+ maxVideoDurationMs
178
179
  }) {
179
180
  const [activeIndex, setActiveIndex] = (0, _react.useState)(initialIndex);
180
181
  const currentItem = items[activeIndex] || items[0];
181
182
  const item = currentItem; // Aliasing to 'item' for ease of compatibility
182
183
 
184
+ (0, _react.useEffect)(() => {
185
+ setActiveIndex(initialIndex);
186
+ }, [initialIndex]);
183
187
  const [activeFilter, setActiveFilter] = (0, _react.useState)('none');
184
188
  const [imageOptions, setImageOptions] = (0, _react.useState)({
185
189
  rotateDegrees: 0,
@@ -190,13 +194,21 @@ function EditorScreen({
190
194
  saturation: 1,
191
195
  grayscale: false
192
196
  });
197
+ const [panel, setPanel] = (0, _react.useState)(item.type === 'video' ? 'trim' : 'filter');
193
198
  const [trimStart, setTrimStart] = (0, _react.useState)(0);
194
- const [trimEnd, setTrimEnd] = (0, _react.useState)(item.durationMs || 10000);
199
+ const [trimEnd, setTrimEnd] = (0, _react.useState)(() => {
200
+ const end = item.durationMs || 10000;
201
+ return maxVideoDurationMs ? Math.min(end, maxVideoDurationMs) : end;
202
+ });
195
203
  (0, _react.useEffect)(() => {
196
204
  setTrimStart(0);
197
- setTrimEnd(item.durationMs || 10000);
205
+ const end = item.durationMs || maxVideoDurationMs || 10000;
206
+ setTrimEnd(maxVideoDurationMs ? Math.min(end, maxVideoDurationMs) : end);
198
207
  setThumbnails([]);
199
- }, [item.id, item.durationMs]);
208
+ if (item.type === 'video' && maxVideoDurationMs && end > maxVideoDurationMs) {
209
+ setPanel('trim');
210
+ }
211
+ }, [item.id, item.durationMs, maxVideoDurationMs]);
200
212
  const [editsHistory, setEditsHistory] = (0, _react.useState)({});
201
213
  const editsHistoryRef = (0, _react.useRef)({});
202
214
  const [dimensionsMap, setDimensionsMap] = (0, _react.useState)({});
@@ -253,7 +265,7 @@ function EditorScreen({
253
265
  setCropOffset(saved.cropOffset);
254
266
  setZoomScale(saved.zoomScale);
255
267
  setStraightenAngle(saved.straightenAngle);
256
- setIsMuted(saved.isMuted);
268
+ setIsMuted(selectedMusic ? true : saved.isMuted);
257
269
  } else {
258
270
  setActiveFilter('none');
259
271
  setImageOptions({
@@ -266,7 +278,8 @@ function EditorScreen({
266
278
  grayscale: false
267
279
  });
268
280
  setTrimStart(0);
269
- setTrimEnd(targetItem.durationMs || 10000);
281
+ const end = targetItem.durationMs || 10000;
282
+ setTrimEnd(maxVideoDurationMs ? Math.min(end, maxVideoDurationMs) : end);
270
283
  setOverlays([]);
271
284
  setCropRatio(null);
272
285
  setCropOffset({
@@ -275,7 +288,14 @@ function EditorScreen({
275
288
  });
276
289
  setZoomScale(1);
277
290
  setStraightenAngle(0);
278
- setIsMuted(false);
291
+ setIsMuted(selectedMusic ? true : false);
292
+ }
293
+
294
+ // Force trim panel if video is too long
295
+ if (targetItem.type === 'video' && maxVideoDurationMs && targetItem.durationMs && targetItem.durationMs > maxVideoDurationMs) {
296
+ setPanel('trim');
297
+ } else if (!saved) {
298
+ setPanel(targetItem.type === 'video' ? 'trim' : 'filter');
279
299
  }
280
300
  };
281
301
  const handleScrollEnd = e => {
@@ -334,13 +354,13 @@ function EditorScreen({
334
354
  y: (o.y + 8) * renderScale,
335
355
  color: o.color,
336
356
  fontSize: o.fontSize * renderScale
337
- }))
357
+ })),
358
+ frameUri: edits.imageOptions.frame && FRAME_IMAGES[edits.imageOptions.frame] ? _reactNative.Image.resolveAssetSource(FRAME_IMAGES[edits.imageOptions.frame]).uri : undefined
338
359
  };
339
360
  };
340
361
  const [saving, setSaving] = (0, _react.useState)(false);
341
362
  const [videoPaused, setVideoPaused] = (0, _react.useState)(false);
342
- const [panel, setPanel] = (0, _react.useState)(item.type === 'video' ? 'trim' : 'filter');
343
- const resolvedMusicList = musicList || DUMMY_MUSIC_LIST;
363
+ const resolvedMusicList = musicList || [];
344
364
  const [selectedMusic, setSelectedMusic] = (0, _react.useState)(null);
345
365
  const [musicPaused, setMusicPaused] = (0, _react.useState)(false);
346
366
  const [showMusicModal, setShowMusicModal] = (0, _react.useState)(false);
@@ -1119,13 +1139,18 @@ function EditorScreen({
1119
1139
  y: (o.y + 8) * renderScale,
1120
1140
  color: o.color,
1121
1141
  fontSize: o.fontSize * renderScale
1122
- }))
1142
+ })),
1143
+ frameUri: imageOptions.frame && FRAME_IMAGES[imageOptions.frame] ? _reactNative.Image.resolveAssetSource(FRAME_IMAGES[imageOptions.frame]).uri : undefined
1123
1144
  };
1124
1145
  }, [imageOptions, cropOffset, maxPan, dimensions, cropRatio, straightenAngle, overlays]);
1125
1146
 
1126
1147
  // For visual trim
1127
1148
 
1128
1149
  const duration = item.durationMs ?? 10_000;
1150
+ const durationRef = (0, _react.useRef)(duration);
1151
+ (0, _react.useEffect)(() => {
1152
+ durationRef.current = duration;
1153
+ }, [duration]);
1129
1154
  const formatTime = ms => {
1130
1155
  const totalSec = Math.floor(ms / 1000);
1131
1156
  const mins = Math.floor(totalSec / 60);
@@ -1187,6 +1212,39 @@ function EditorScreen({
1187
1212
  generateThumbs();
1188
1213
  }
1189
1214
  }, [item.type, item.uri, duration, thumbnails.length]);
1215
+ const leftOverlayRef = (0, _react.useRef)(null);
1216
+ const rightOverlayRef = (0, _react.useRef)(null);
1217
+ const selectionRangeRef = (0, _react.useRef)(null);
1218
+ const leftHandleRef = (0, _react.useRef)(null);
1219
+ const rightHandleRef = (0, _react.useRef)(null);
1220
+ const updateNativeRefs = (newStartX, newEndX) => {
1221
+ leftOverlayRef.current?.setNativeProps({
1222
+ style: {
1223
+ width: newStartX
1224
+ }
1225
+ });
1226
+ rightOverlayRef.current?.setNativeProps({
1227
+ style: {
1228
+ left: newEndX
1229
+ }
1230
+ });
1231
+ selectionRangeRef.current?.setNativeProps({
1232
+ style: {
1233
+ left: newStartX,
1234
+ width: newEndX - newStartX
1235
+ }
1236
+ });
1237
+ leftHandleRef.current?.setNativeProps({
1238
+ style: {
1239
+ left: newStartX - 16
1240
+ }
1241
+ });
1242
+ rightHandleRef.current?.setNativeProps({
1243
+ style: {
1244
+ left: newEndX - 16
1245
+ }
1246
+ });
1247
+ };
1190
1248
  const startPanOffset = (0, _react.useRef)(0);
1191
1249
  const startPan = (0, _react.useRef)(_reactNative.PanResponder.create({
1192
1250
  onStartShouldSetPanResponder: () => true,
@@ -1200,20 +1258,76 @@ function EditorScreen({
1200
1258
  setScrollEnabled(false);
1201
1259
  },
1202
1260
  onPanResponderMove: (_, gesture) => {
1203
- const newX = Math.max(0, Math.min(endX.current - 32, startPanOffset.current + gesture.dx));
1261
+ let newX = Math.max(0, Math.min(endX.current - 32, startPanOffset.current + gesture.dx));
1262
+ let newTime = newX / TIMELINE_WIDTH * durationRef.current;
1263
+ const currentTrimEnd = endX.current / TIMELINE_WIDTH * durationRef.current;
1264
+ if (maxVideoDurationMs && currentTrimEnd - newTime > maxVideoDurationMs) {
1265
+ newTime = currentTrimEnd - maxVideoDurationMs;
1266
+ newX = newTime / durationRef.current * TIMELINE_WIDTH;
1267
+ }
1204
1268
  startX.current = newX;
1205
- const newTime = newX / TIMELINE_WIDTH * duration;
1206
- setTrimStart(newTime);
1269
+ updateNativeRefs(newX, endX.current);
1207
1270
  throttledSeek(newTime);
1208
1271
  },
1209
1272
  onPanResponderRelease: () => {
1210
1273
  isDraggingHandle.current = false;
1211
1274
  setScrollEnabled(true);
1275
+ setTrimStart(startX.current / TIMELINE_WIDTH * durationRef.current);
1276
+ setTrimEnd(endX.current / TIMELINE_WIDTH * durationRef.current);
1212
1277
  setSeekToMs(-1);
1213
1278
  },
1214
1279
  onPanResponderTerminate: () => {
1215
1280
  isDraggingHandle.current = false;
1216
1281
  setScrollEnabled(true);
1282
+ setTrimStart(startX.current / TIMELINE_WIDTH * durationRef.current);
1283
+ setTrimEnd(endX.current / TIMELINE_WIDTH * durationRef.current);
1284
+ setSeekToMs(-1);
1285
+ }
1286
+ })).current;
1287
+ const middlePanOffsetStart = (0, _react.useRef)(0);
1288
+ const middlePanOffsetEnd = (0, _react.useRef)(0);
1289
+ const middlePan = (0, _react.useRef)(_reactNative.PanResponder.create({
1290
+ onStartShouldSetPanResponder: () => true,
1291
+ onStartShouldSetPanResponderCapture: () => true,
1292
+ onMoveShouldSetPanResponder: () => true,
1293
+ onMoveShouldSetPanResponderCapture: () => true,
1294
+ onPanResponderGrant: () => {
1295
+ pushToHistory();
1296
+ middlePanOffsetStart.current = startX.current;
1297
+ middlePanOffsetEnd.current = endX.current;
1298
+ isDraggingHandle.current = true;
1299
+ setScrollEnabled(false);
1300
+ },
1301
+ onPanResponderMove: (_, gesture) => {
1302
+ const windowWidth = middlePanOffsetEnd.current - middlePanOffsetStart.current;
1303
+ let newStartX = middlePanOffsetStart.current + gesture.dx;
1304
+ let newEndX = middlePanOffsetEnd.current + gesture.dx;
1305
+ if (newStartX < 0) {
1306
+ newStartX = 0;
1307
+ newEndX = windowWidth;
1308
+ }
1309
+ if (newEndX > TIMELINE_WIDTH) {
1310
+ newEndX = TIMELINE_WIDTH;
1311
+ newStartX = TIMELINE_WIDTH - windowWidth;
1312
+ }
1313
+ startX.current = newStartX;
1314
+ endX.current = newEndX;
1315
+ const newStartTime = newStartX / TIMELINE_WIDTH * durationRef.current;
1316
+ updateNativeRefs(newStartX, newEndX);
1317
+ throttledSeek(newStartTime);
1318
+ },
1319
+ onPanResponderRelease: () => {
1320
+ isDraggingHandle.current = false;
1321
+ setScrollEnabled(true);
1322
+ setTrimStart(startX.current / TIMELINE_WIDTH * durationRef.current);
1323
+ setTrimEnd(endX.current / TIMELINE_WIDTH * durationRef.current);
1324
+ setSeekToMs(-1);
1325
+ },
1326
+ onPanResponderTerminate: () => {
1327
+ isDraggingHandle.current = false;
1328
+ setScrollEnabled(true);
1329
+ setTrimStart(startX.current / TIMELINE_WIDTH * durationRef.current);
1330
+ setTrimEnd(endX.current / TIMELINE_WIDTH * durationRef.current);
1217
1331
  setSeekToMs(-1);
1218
1332
  }
1219
1333
  })).current;
@@ -1230,20 +1344,29 @@ function EditorScreen({
1230
1344
  setScrollEnabled(false);
1231
1345
  },
1232
1346
  onPanResponderMove: (_, gesture) => {
1233
- const newX = Math.min(TIMELINE_WIDTH, Math.max(startX.current + 32, endPanOffset.current + gesture.dx));
1347
+ let newX = Math.min(TIMELINE_WIDTH, Math.max(startX.current + 32, endPanOffset.current + gesture.dx));
1348
+ let newTime = newX / TIMELINE_WIDTH * durationRef.current;
1349
+ const currentTrimStart = startX.current / TIMELINE_WIDTH * durationRef.current;
1350
+ if (maxVideoDurationMs && newTime - currentTrimStart > maxVideoDurationMs) {
1351
+ newTime = currentTrimStart + maxVideoDurationMs;
1352
+ newX = newTime / durationRef.current * TIMELINE_WIDTH;
1353
+ }
1234
1354
  endX.current = newX;
1235
- const newTime = newX / TIMELINE_WIDTH * duration;
1236
- setTrimEnd(newTime);
1355
+ updateNativeRefs(startX.current, newX);
1237
1356
  throttledSeek(newTime);
1238
1357
  },
1239
1358
  onPanResponderRelease: () => {
1240
1359
  isDraggingHandle.current = false;
1241
1360
  setScrollEnabled(true);
1361
+ setTrimStart(startX.current / TIMELINE_WIDTH * durationRef.current);
1362
+ setTrimEnd(endX.current / TIMELINE_WIDTH * durationRef.current);
1242
1363
  setSeekToMs(-1);
1243
1364
  },
1244
1365
  onPanResponderTerminate: () => {
1245
1366
  isDraggingHandle.current = false;
1246
1367
  setScrollEnabled(true);
1368
+ setTrimStart(startX.current / TIMELINE_WIDTH * durationRef.current);
1369
+ setTrimEnd(endX.current / TIMELINE_WIDTH * durationRef.current);
1247
1370
  setSeekToMs(-1);
1248
1371
  }
1249
1372
  })).current;
@@ -2018,11 +2141,16 @@ function EditorScreen({
2018
2141
  });
2019
2142
  }
2020
2143
  } else {
2144
+ const safeEndMs = Math.min(trimEnd, item.durationMs || 10000);
2145
+ const safeStartMs = Math.min(trimStart, Math.max(0, safeEndMs - 100));
2146
+ const isFullTrim = trimStart === 0 && trimEnd >= (item.durationMs || 10000);
2021
2147
  exportUri = await (0, _MediaEditor.trimVideo)(item.uri, {
2022
- startMs: trimStart,
2023
- endMs: trimEnd,
2148
+ startMs: safeStartMs,
2149
+ endMs: safeEndMs,
2024
2150
  mute: isMuted,
2025
- musicUri: selectedMusic?.url || undefined,
2151
+ ...(selectedMusic?.url ? {
2152
+ musicUri: selectedMusic.url
2153
+ } : {}),
2026
2154
  ...activeOptions
2027
2155
  });
2028
2156
  }
@@ -2039,6 +2167,7 @@ function EditorScreen({
2039
2167
  setSaving(true);
2040
2168
  saveEditsForIndex(activeIndex);
2041
2169
  const updatedItems = [...items];
2170
+ let cumulativeMusicOffsetMs = 0;
2042
2171
  for (let i = 0; i < items.length; i++) {
2043
2172
  const targetItem = items[i];
2044
2173
  let edits = editsHistoryRef.current[targetItem.id];
@@ -2064,6 +2193,7 @@ function EditorScreen({
2064
2193
  outUri = await (0, _MediaEditor.trimVideo)(outUri, {
2065
2194
  isImage: true,
2066
2195
  musicUri: selectedMusic.url,
2196
+ musicOffsetMs: cumulativeMusicOffsetMs,
2067
2197
  rotateDegrees: 0,
2068
2198
  flipX: false,
2069
2199
  flipY: false,
@@ -2078,12 +2208,20 @@ function EditorScreen({
2078
2208
  uri: outUri,
2079
2209
  thumbnailUri: outUri
2080
2210
  };
2211
+ cumulativeMusicOffsetMs += 10000; // Images are 10s by default
2081
2212
  } else {
2213
+ const originalDuration = targetItem.durationMs || maxVideoDurationMs || 10000;
2214
+ const safeEndMs = Math.min(edits.trimEnd, originalDuration);
2215
+ const safeStartMs = Math.min(edits.trimStart, Math.max(0, safeEndMs - 100));
2216
+ const isFullTrim = edits.trimStart === 0 && edits.trimEnd >= originalDuration;
2082
2217
  const outUri = await (0, _MediaEditor.trimVideo)(targetItem.uri, {
2083
- startMs: edits.trimStart,
2084
- endMs: edits.trimEnd,
2218
+ startMs: safeStartMs,
2219
+ endMs: safeEndMs,
2085
2220
  mute: edits.isMuted,
2086
- musicUri: selectedMusic?.url || undefined,
2221
+ ...(selectedMusic?.url ? {
2222
+ musicUri: selectedMusic.url,
2223
+ musicOffsetMs: cumulativeMusicOffsetMs
2224
+ } : {}),
2087
2225
  ...opts
2088
2226
  });
2089
2227
  let newThumb = undefined;
@@ -2101,6 +2239,7 @@ function EditorScreen({
2101
2239
  thumbnailUri: newThumb ? newThumb : targetItem.thumbnailUri,
2102
2240
  durationMs: newDuration
2103
2241
  };
2242
+ cumulativeMusicOffsetMs += newDuration;
2104
2243
  }
2105
2244
  } else {
2106
2245
  if (selectedMusic) {
@@ -2109,6 +2248,7 @@ function EditorScreen({
2109
2248
  outUri = await (0, _MediaEditor.trimVideo)(targetItem.uri, {
2110
2249
  isImage: true,
2111
2250
  musicUri: selectedMusic.url,
2251
+ musicOffsetMs: cumulativeMusicOffsetMs,
2112
2252
  rotateDegrees: 0,
2113
2253
  flipX: false,
2114
2254
  flipY: false,
@@ -2117,12 +2257,17 @@ function EditorScreen({
2117
2257
  saturation: 1,
2118
2258
  grayscale: false
2119
2259
  });
2260
+ cumulativeMusicOffsetMs += 10000;
2120
2261
  } else {
2262
+ const safeEndMs = targetItem.durationMs || 10000;
2121
2263
  outUri = await (0, _MediaEditor.trimVideo)(targetItem.uri, {
2122
2264
  startMs: 0,
2123
- endMs: targetItem.durationMs || 10000,
2265
+ endMs: safeEndMs,
2124
2266
  mute: isMuted,
2125
- musicUri: selectedMusic.url,
2267
+ ...(selectedMusic?.url ? {
2268
+ musicUri: selectedMusic.url,
2269
+ musicOffsetMs: cumulativeMusicOffsetMs
2270
+ } : {}),
2126
2271
  rotateDegrees: 0,
2127
2272
  flipX: false,
2128
2273
  flipY: false,
@@ -2131,6 +2276,7 @@ function EditorScreen({
2131
2276
  saturation: 1,
2132
2277
  grayscale: false
2133
2278
  });
2279
+ cumulativeMusicOffsetMs += safeEndMs;
2134
2280
  }
2135
2281
  updatedItems[i] = {
2136
2282
  ...targetItem,
@@ -2672,7 +2818,15 @@ function EditorScreen({
2672
2818
  },
2673
2819
  style: styles.filmstripImage
2674
2820
  }, idx)), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
2675
- style: styles.timelineOverlay
2821
+ style: [styles.timelineOverlay, {
2822
+ left: 0,
2823
+ width: trimStart / duration * TIMELINE_WIDTH
2824
+ }]
2825
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
2826
+ style: [styles.timelineOverlay, {
2827
+ left: trimEnd / duration * TIMELINE_WIDTH,
2828
+ right: 0
2829
+ }]
2676
2830
  }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
2677
2831
  style: [styles.selectionRange, {
2678
2832
  left: trimStart / duration * TIMELINE_WIDTH,
@@ -2683,20 +2837,18 @@ function EditorScreen({
2683
2837
  style: [styles.customHandle, styles.customHandleLeft, {
2684
2838
  left: trimStart / duration * TIMELINE_WIDTH - 16
2685
2839
  }],
2686
- ...startPan.panHandlers,
2687
2840
  children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
2688
2841
  style: styles.handleBarLine
2689
2842
  })
2690
2843
  }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
2691
2844
  style: [styles.customHandle, styles.customHandleRight, {
2692
- left: trimEnd / duration * TIMELINE_WIDTH
2845
+ left: trimEnd / duration * TIMELINE_WIDTH - 16
2693
2846
  }],
2694
- ...endPan.panHandlers,
2695
2847
  children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
2696
2848
  style: styles.handleBarLine
2697
2849
  })
2698
2850
  })]
2699
- }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Pressable, {
2851
+ }), resolvedMusicList.length > 0 && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Pressable, {
2700
2852
  style: styles.subTrackRow,
2701
2853
  onPress: () => setShowMusicModal(true),
2702
2854
  children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
@@ -2977,7 +3129,7 @@ function EditorScreen({
2977
3129
  style: styles.toolLabel,
2978
3130
  children: "Text"
2979
3131
  })]
2980
- }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Pressable, {
3132
+ }), resolvedMusicList.length > 0 && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Pressable, {
2981
3133
  style: [styles.toolButton, showMusicModal && styles.toolButtonActive],
2982
3134
  onPress: () => setShowMusicModal(true),
2983
3135
  children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
@@ -3139,7 +3291,7 @@ function EditorScreen({
3139
3291
  contentContainerStyle: [styles.toolButtonsRow, {
3140
3292
  flexGrow: 1
3141
3293
  }],
3142
- children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Pressable, {
3294
+ children: [resolvedMusicList.length > 0 && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Pressable, {
3143
3295
  style: [styles.toolButton, showMusicModal && styles.toolButtonActive],
3144
3296
  onPress: () => setShowMusicModal(true),
3145
3297
  children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
@@ -3308,6 +3460,12 @@ function EditorScreen({
3308
3460
  data: items,
3309
3461
  extraData: [overlays, editingTextId, editsHistory, activeFilter, imageOptions, trimStart, trimEnd, isMuted, activeIndex],
3310
3462
  keyExtractor: it => it.id,
3463
+ initialScrollIndex: activeIndex,
3464
+ getItemLayout: (_, index) => ({
3465
+ length: SCREEN_WIDTH,
3466
+ offset: SCREEN_WIDTH * index,
3467
+ index
3468
+ }),
3311
3469
  horizontal: true,
3312
3470
  pagingEnabled: false,
3313
3471
  showsHorizontalScrollIndicator: false,
@@ -3418,7 +3576,7 @@ function EditorScreen({
3418
3576
  fontWeight: '700'
3419
3577
  },
3420
3578
  children: (() => {
3421
- const selMs = trimEnd - trimStart;
3579
+ const selMs = (endX.current - startX.current) / TIMELINE_WIDTH * duration;
3422
3580
  const totalSec = Math.floor(selMs / 1000);
3423
3581
  return totalSec >= 60 ? `${Math.floor(totalSec / 60)}:${(totalSec % 60).toString().padStart(2, '0')} selected` : `${(selMs / 1000).toFixed(1)}s selected`;
3424
3582
  })()
@@ -3429,30 +3587,54 @@ function EditorScreen({
3429
3587
  }],
3430
3588
  children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
3431
3589
  style: styles.filmstrip,
3432
- children: [thumbnails.map((uri, idx) => /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Image, {
3590
+ children: [thumbnails.length === 0 ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
3591
+ style: {
3592
+ flex: 1,
3593
+ justifyContent: 'center',
3594
+ alignItems: 'center'
3595
+ },
3596
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.ActivityIndicator, {
3597
+ color: "#ffffff",
3598
+ size: "small"
3599
+ })
3600
+ }) : thumbnails.map((uri, idx) => /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Image, {
3433
3601
  source: {
3434
3602
  uri
3435
3603
  },
3436
3604
  style: styles.filmstripImage
3437
3605
  }, idx)), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
3438
- style: styles.timelineOverlay
3606
+ ref: leftOverlayRef,
3607
+ style: [styles.timelineOverlay, {
3608
+ left: 0,
3609
+ width: startX.current
3610
+ }]
3439
3611
  }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
3440
- style: [styles.selectionRange, {
3441
- left: trimStart / duration * TIMELINE_WIDTH,
3442
- width: (trimEnd - trimStart) / duration * TIMELINE_WIDTH
3612
+ ref: rightOverlayRef,
3613
+ style: [styles.timelineOverlay, {
3614
+ left: endX.current,
3615
+ right: 0
3443
3616
  }]
3617
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
3618
+ ref: selectionRangeRef,
3619
+ style: [styles.selectionRange, {
3620
+ left: startX.current,
3621
+ width: endX.current - startX.current
3622
+ }],
3623
+ ...middlePan.panHandlers
3444
3624
  })]
3445
3625
  }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
3626
+ ref: leftHandleRef,
3446
3627
  style: [styles.customHandle, styles.customHandleLeft, {
3447
- left: trimStart / duration * TIMELINE_WIDTH - 16
3628
+ left: startX.current - 16
3448
3629
  }],
3449
3630
  ...startPan.panHandlers,
3450
3631
  children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
3451
3632
  style: styles.handleBarLine
3452
3633
  })
3453
3634
  }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
3635
+ ref: rightHandleRef,
3454
3636
  style: [styles.customHandle, styles.customHandleRight, {
3455
- left: trimEnd / duration * TIMELINE_WIDTH
3637
+ left: endX.current - 16
3456
3638
  }],
3457
3639
  ...endPan.panHandlers,
3458
3640
  children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
@@ -3730,7 +3912,7 @@ function EditorScreen({
3730
3912
  contentContainerStyle: [styles.toolButtonsRow, {
3731
3913
  flexGrow: 1
3732
3914
  }],
3733
- children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Pressable, {
3915
+ children: [resolvedMusicList.length > 0 && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Pressable, {
3734
3916
  style: [styles.toolButton, showMusicModal && styles.toolButtonActive],
3735
3917
  onPress: () => setShowMusicModal(true),
3736
3918
  children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
@@ -4038,6 +4220,7 @@ function EditorScreen({
4038
4220
  onPress: () => {
4039
4221
  setSelectedMusic(null);
4040
4222
  setMusicPaused(true);
4223
+ setIsMuted(false);
4041
4224
  },
4042
4225
  style: styles.musicFooterRemoveBtn,
4043
4226
  children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
@@ -4805,7 +4988,9 @@ const styles = _reactNative.StyleSheet.create({
4805
4988
  height: 60
4806
4989
  },
4807
4990
  timelineOverlay: {
4808
- ..._reactNative.StyleSheet.absoluteFillObject,
4991
+ position: 'absolute',
4992
+ top: 0,
4993
+ bottom: 0,
4809
4994
  backgroundColor: 'rgba(0,0,0,0.6)'
4810
4995
  },
4811
4996
  selectionRange: {
@@ -4815,7 +5000,7 @@ const styles = _reactNative.StyleSheet.create({
4815
5000
  backgroundColor: 'transparent',
4816
5001
  borderTopWidth: 2,
4817
5002
  borderBottomWidth: 2,
4818
- borderColor: '#fff'
5003
+ borderColor: '#FFD60A'
4819
5004
  },
4820
5005
  handle: {
4821
5006
  position: 'absolute',
@@ -4841,7 +5026,7 @@ const styles = _reactNative.StyleSheet.create({
4841
5026
  top: 0,
4842
5027
  width: 16,
4843
5028
  height: 60,
4844
- backgroundColor: '#FFFFFF',
5029
+ backgroundColor: '#FFD60A',
4845
5030
  justifyContent: 'center',
4846
5031
  alignItems: 'center',
4847
5032
  zIndex: 20