@movementinfra/expo-twostep-video 0.1.9 → 0.1.10
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.
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VideoScrubber.d.ts","sourceRoot":"","sources":["../src/VideoScrubber.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAqB/B,OAAO,EAAE,kBAAkB,EAAE,gBAAgB,EAAsB,MAAM,uBAAuB,CAAC;AAYjG;;;;;;;;;;;;;;;;GAgBG;AACH,QAAA,MAAM,aAAa,
|
|
1
|
+
{"version":3,"file":"VideoScrubber.d.ts","sourceRoot":"","sources":["../src/VideoScrubber.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAqB/B,OAAO,EAAE,kBAAkB,EAAE,gBAAgB,EAAsB,MAAM,uBAAuB,CAAC;AAYjG;;;;;;;;;;;;;;;;GAgBG;AACH,QAAA,MAAM,aAAa,6FAkdjB,CAAC;AAuGH,eAAe,aAAa,CAAC"}
|
package/build/VideoScrubber.js
CHANGED
|
@@ -40,6 +40,7 @@ const VideoScrubber = forwardRef((props, ref) => {
|
|
|
40
40
|
const currentTimeRef = useRef(currentTime);
|
|
41
41
|
const durationRef = useRef(duration);
|
|
42
42
|
const containerWidthRef = useRef(containerWidth);
|
|
43
|
+
const handleWidthRef = useRef(handleWidth);
|
|
43
44
|
// Sync refs with props when they change from outside (e.g., new video loaded)
|
|
44
45
|
useEffect(() => {
|
|
45
46
|
if (!dragging) {
|
|
@@ -57,6 +58,9 @@ const VideoScrubber = forwardRef((props, ref) => {
|
|
|
57
58
|
useEffect(() => {
|
|
58
59
|
containerWidthRef.current = containerWidth;
|
|
59
60
|
}, [containerWidth]);
|
|
61
|
+
useEffect(() => {
|
|
62
|
+
handleWidthRef.current = handleWidth;
|
|
63
|
+
}, [handleWidth]);
|
|
60
64
|
// Calculate thumbnail times
|
|
61
65
|
const thumbnailTimes = useMemo(() => {
|
|
62
66
|
if (duration <= 0)
|
|
@@ -91,12 +95,14 @@ const VideoScrubber = forwardRef((props, ref) => {
|
|
|
91
95
|
cancelled = true;
|
|
92
96
|
};
|
|
93
97
|
}, [assetId, thumbnailTimes, thumbnailHeight, duration]);
|
|
94
|
-
//
|
|
98
|
+
// The content area is between the handles
|
|
99
|
+
const contentWidth = containerWidth > 0 ? containerWidth - handleWidth * 2 : 0;
|
|
100
|
+
// Convert time to position within the content area (between handles)
|
|
95
101
|
const timeToPosition = useCallback((time) => {
|
|
96
|
-
if (duration <= 0 ||
|
|
97
|
-
return
|
|
98
|
-
return (time / duration) *
|
|
99
|
-
}, [duration,
|
|
102
|
+
if (duration <= 0 || contentWidth <= 0)
|
|
103
|
+
return handleWidth;
|
|
104
|
+
return handleWidth + (time / duration) * contentWidth;
|
|
105
|
+
}, [duration, contentWidth, handleWidth]);
|
|
100
106
|
// Calculate positions
|
|
101
107
|
const startPosition = timeToPosition(startTime);
|
|
102
108
|
const endPosition = timeToPosition(endTime);
|
|
@@ -149,12 +155,15 @@ const VideoScrubber = forwardRef((props, ref) => {
|
|
|
149
155
|
setDragging(type);
|
|
150
156
|
},
|
|
151
157
|
onPanResponderMove: (_e, gestureState) => {
|
|
152
|
-
const
|
|
158
|
+
const containerW = containerWidthRef.current;
|
|
159
|
+
const hw = handleWidthRef.current;
|
|
153
160
|
const dur = durationRef.current;
|
|
154
|
-
|
|
161
|
+
// Content width is where time is mapped (between handles)
|
|
162
|
+
const cw = containerW - hw * 2;
|
|
163
|
+
if (cw <= 0 || dur <= 0)
|
|
155
164
|
return;
|
|
156
165
|
const deltaX = gestureState.dx;
|
|
157
|
-
const deltaTime = (deltaX /
|
|
166
|
+
const deltaTime = (deltaX / cw) * dur;
|
|
158
167
|
let newTime = initialTime + deltaTime;
|
|
159
168
|
if (type === 'start') {
|
|
160
169
|
// Constrain start handle
|
|
@@ -186,10 +195,12 @@ const VideoScrubber = forwardRef((props, ref) => {
|
|
|
186
195
|
onScrubEndRef.current?.(dragEndTimeRef.current);
|
|
187
196
|
}
|
|
188
197
|
else {
|
|
189
|
-
const
|
|
198
|
+
const containerW = containerWidthRef.current;
|
|
199
|
+
const hw = handleWidthRef.current;
|
|
190
200
|
const dur = durationRef.current;
|
|
191
|
-
|
|
192
|
-
|
|
201
|
+
const cw = containerW - hw * 2;
|
|
202
|
+
if (cw > 0 && dur > 0) {
|
|
203
|
+
const deltaTime = (gestureState.dx / cw) * dur;
|
|
193
204
|
const newTime = Math.max(dragStartTimeRef.current, Math.min(initialTime + deltaTime, dragEndTimeRef.current));
|
|
194
205
|
onScrubEndRef.current?.(newTime);
|
|
195
206
|
}
|
|
@@ -204,20 +215,31 @@ const VideoScrubber = forwardRef((props, ref) => {
|
|
|
204
215
|
const startPanResponder = useMemo(() => createPanResponder('start'), [createPanResponder]);
|
|
205
216
|
const endPanResponder = useMemo(() => createPanResponder('end'), [createPanResponder]);
|
|
206
217
|
const playheadPanResponder = useMemo(() => createPanResponder('playhead'), [createPanResponder]);
|
|
207
|
-
// Tap to seek
|
|
218
|
+
// Tap to seek - locationX is relative to the tap area which spans from startPosition to endPosition
|
|
208
219
|
const handleTap = useCallback((e) => {
|
|
209
220
|
if (disabled)
|
|
210
221
|
return;
|
|
211
222
|
const { locationX } = e.nativeEvent;
|
|
223
|
+
const startT = dragStartTimeRef.current;
|
|
224
|
+
const endT = dragEndTimeRef.current;
|
|
225
|
+
const selectionDuration = endT - startT;
|
|
226
|
+
if (selectionDuration <= 0)
|
|
227
|
+
return;
|
|
228
|
+
// The tap area spans the selection, so locationX=0 is startTime and locationX=width is endTime
|
|
229
|
+
// Calculate time proportionally within the selection
|
|
212
230
|
const width = containerWidthRef.current;
|
|
213
231
|
const dur = durationRef.current;
|
|
214
232
|
if (width <= 0 || dur <= 0)
|
|
215
233
|
return;
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
const
|
|
219
|
-
|
|
220
|
-
|
|
234
|
+
// Calculate the width of the tap area (endPosition - startPosition in content coordinates)
|
|
235
|
+
const cw = width - handleWidth * 2; // contentWidth
|
|
236
|
+
const tapAreaWidth = ((endT - startT) / dur) * cw;
|
|
237
|
+
if (tapAreaWidth <= 0)
|
|
238
|
+
return;
|
|
239
|
+
const proportion = Math.max(0, Math.min(1, locationX / tapAreaWidth));
|
|
240
|
+
const time = startT + proportion * selectionDuration;
|
|
241
|
+
onScrubRef.current?.(time);
|
|
242
|
+
}, [disabled, handleWidth]);
|
|
221
243
|
// Expose ref methods
|
|
222
244
|
useImperativeHandle(ref, () => ({
|
|
223
245
|
regenerateThumbnails: async () => {
|
|
@@ -240,16 +262,16 @@ const VideoScrubber = forwardRef((props, ref) => {
|
|
|
240
262
|
},
|
|
241
263
|
getSelection: () => ({ startTime, endTime }),
|
|
242
264
|
}));
|
|
243
|
-
const thumbnailWidth =
|
|
244
|
-
?
|
|
265
|
+
const thumbnailWidth = contentWidth > 0 && thumbnails.length > 0
|
|
266
|
+
? contentWidth / thumbnails.length
|
|
245
267
|
: 0;
|
|
246
268
|
return (<View style={[
|
|
247
269
|
styles.container,
|
|
248
270
|
{ backgroundColor: theme.backgroundColor },
|
|
249
271
|
style,
|
|
250
272
|
]} onLayout={handleLayout}>
|
|
251
|
-
{/* Thumbnail Strip */}
|
|
252
|
-
<View style={[styles.thumbnailStrip, { height: thumbnailHeight }]}>
|
|
273
|
+
{/* Thumbnail Strip - positioned between handles */}
|
|
274
|
+
<View style={[styles.thumbnailStrip, { height: thumbnailHeight, marginLeft: handleWidth, width: contentWidth }]}>
|
|
253
275
|
{thumbnails.map((thumb, index) => (<Image key={index} source={{ uri: `data:image/png;base64,${thumb}` }} style={[
|
|
254
276
|
styles.thumbnail,
|
|
255
277
|
{
|
|
@@ -260,35 +282,35 @@ const VideoScrubber = forwardRef((props, ref) => {
|
|
|
260
282
|
{isLoading && thumbnails.length === 0 && (<View style={[styles.loadingPlaceholder, { height: thumbnailHeight }]}/>)}
|
|
261
283
|
</View>
|
|
262
284
|
|
|
263
|
-
{/* Dimmed region - left */}
|
|
285
|
+
{/* Dimmed region - left (covers trimmed content from left handle to start handle position) */}
|
|
264
286
|
<View style={[
|
|
265
287
|
styles.dimmedRegion,
|
|
266
288
|
{
|
|
267
|
-
left:
|
|
268
|
-
width: startPosition,
|
|
289
|
+
left: handleWidth,
|
|
290
|
+
width: Math.max(0, startPosition - handleWidth),
|
|
269
291
|
height: thumbnailHeight,
|
|
270
292
|
backgroundColor: theme.dimmedColor,
|
|
271
293
|
},
|
|
272
294
|
]} pointerEvents="none"/>
|
|
273
295
|
|
|
274
|
-
{/* Dimmed region - right */}
|
|
296
|
+
{/* Dimmed region - right (covers trimmed content from end handle position to right handle) */}
|
|
275
297
|
<View style={[
|
|
276
298
|
styles.dimmedRegion,
|
|
277
299
|
{
|
|
278
300
|
left: endPosition,
|
|
279
|
-
|
|
301
|
+
width: Math.max(0, containerWidth - handleWidth - endPosition),
|
|
280
302
|
height: thumbnailHeight,
|
|
281
303
|
backgroundColor: theme.dimmedColor,
|
|
282
304
|
},
|
|
283
305
|
]} pointerEvents="none"/>
|
|
284
306
|
|
|
285
|
-
{/* Selection frame - top and bottom borders */}
|
|
307
|
+
{/* Selection frame - top and bottom borders (span between handles) */}
|
|
286
308
|
<View style={[
|
|
287
309
|
styles.selectionBorder,
|
|
288
310
|
styles.selectionBorderTop,
|
|
289
311
|
{
|
|
290
|
-
left: startPosition
|
|
291
|
-
width: Math.max(0, endPosition - startPosition
|
|
312
|
+
left: startPosition,
|
|
313
|
+
width: Math.max(0, endPosition - startPosition),
|
|
292
314
|
backgroundColor: theme.borderColor,
|
|
293
315
|
},
|
|
294
316
|
]} pointerEvents="none"/>
|
|
@@ -296,19 +318,19 @@ const VideoScrubber = forwardRef((props, ref) => {
|
|
|
296
318
|
styles.selectionBorder,
|
|
297
319
|
styles.selectionBorderBottom,
|
|
298
320
|
{
|
|
299
|
-
left: startPosition
|
|
300
|
-
width: Math.max(0, endPosition - startPosition
|
|
321
|
+
left: startPosition,
|
|
322
|
+
width: Math.max(0, endPosition - startPosition),
|
|
301
323
|
top: thumbnailHeight - 2,
|
|
302
324
|
backgroundColor: theme.borderColor,
|
|
303
325
|
},
|
|
304
326
|
]} pointerEvents="none"/>
|
|
305
327
|
|
|
306
|
-
{/* Start Handle */}
|
|
328
|
+
{/* Start Handle - positioned to the LEFT of the trim point (outside content when startTime=0) */}
|
|
307
329
|
<Animated.View style={[
|
|
308
330
|
styles.handle,
|
|
309
331
|
styles.startHandle,
|
|
310
332
|
{
|
|
311
|
-
left: startPosition,
|
|
333
|
+
left: startPosition - handleWidth,
|
|
312
334
|
width: handleWidth,
|
|
313
335
|
height: thumbnailHeight,
|
|
314
336
|
backgroundColor: theme.handleColor,
|
|
@@ -321,12 +343,12 @@ const VideoScrubber = forwardRef((props, ref) => {
|
|
|
321
343
|
</View>
|
|
322
344
|
</Animated.View>
|
|
323
345
|
|
|
324
|
-
{/* End Handle */}
|
|
346
|
+
{/* End Handle - positioned to the RIGHT of the trim point (outside content when endTime=duration) */}
|
|
325
347
|
<Animated.View style={[
|
|
326
348
|
styles.handle,
|
|
327
349
|
styles.endHandle,
|
|
328
350
|
{
|
|
329
|
-
left: endPosition
|
|
351
|
+
left: endPosition,
|
|
330
352
|
width: handleWidth,
|
|
331
353
|
height: thumbnailHeight,
|
|
332
354
|
backgroundColor: theme.handleColor,
|
|
@@ -339,12 +361,12 @@ const VideoScrubber = forwardRef((props, ref) => {
|
|
|
339
361
|
</View>
|
|
340
362
|
</Animated.View>
|
|
341
363
|
|
|
342
|
-
{/* Tap area for seeking */}
|
|
364
|
+
{/* Tap area for seeking - covers the selection between start and end trim points */}
|
|
343
365
|
<View style={[
|
|
344
366
|
styles.tapArea,
|
|
345
367
|
{
|
|
346
|
-
left: startPosition
|
|
347
|
-
width: Math.max(0, endPosition - startPosition
|
|
368
|
+
left: startPosition,
|
|
369
|
+
width: Math.max(0, endPosition - startPosition),
|
|
348
370
|
height: thumbnailHeight,
|
|
349
371
|
},
|
|
350
372
|
]} onTouchEnd={handleTap}/>
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VideoScrubber.js","sourceRoot":"","sources":["../src/VideoScrubber.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,EACL,UAAU,EACV,WAAW,EACX,SAAS,EACT,mBAAmB,EACnB,OAAO,EACP,MAAM,EACN,QAAQ,GACT,MAAM,OAAO,CAAC;AACf,OAAO,EACL,IAAI,EACJ,KAAK,EACL,UAAU,EAEV,YAAY,EAGZ,QAAQ,GACT,MAAM,cAAc,CAAC;AAGtB,OAAO,sBAAsB,MAAM,0BAA0B,CAAC;AAE9D,MAAM,aAAa,GAAiC;IAClD,WAAW,EAAE,SAAS;IACtB,mBAAmB,EAAE,qBAAqB;IAC1C,aAAa,EAAE,SAAS;IACxB,eAAe,EAAE,SAAS;IAC1B,WAAW,EAAE,iBAAiB;IAC9B,WAAW,EAAE,SAAS;CACvB,CAAC;AAEF;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,aAAa,GAAG,UAAU,CAAuC,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE;IACpF,MAAM,EACJ,OAAO,EACP,QAAQ,EACR,WAAW,EACX,SAAS,EACT,OAAO,EACP,cAAc,GAAG,EAAE,EACnB,eAAe,GAAG,EAAE,EACpB,iBAAiB,EACjB,eAAe,EACf,OAAO,EACP,WAAW,EACX,UAAU,EACV,WAAW,GAAG,GAAG,EACjB,KAAK,EACL,QAAQ,GAAG,KAAK,EAChB,KAAK,EAAE,SAAS,EAChB,YAAY,GAAG,IAAI,EACnB,WAAW,GAAG,EAAE,GACjB,GAAG,KAAK,CAAC;IAEV,MAAM,KAAK,GAAG,OAAO,CACnB,GAAG,EAAE,CAAC,CAAC,EAAE,GAAG,aAAa,EAAE,GAAG,SAAS,EAAE,CAAC,EAC1C,CAAC,SAAS,CAAC,CACZ,CAAC;IAEF,MAAM,CAAC,UAAU,EAAE,aAAa,CAAC,GAAG,QAAQ,CAAW,EAAE,CAAC,CAAC;IAC3D,MAAM,CAAC,cAAc,EAAE,iBAAiB,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC;IACxD,MAAM,CAAC,SAAS,EAAE,YAAY,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC;IACjD,MAAM,CAAC,QAAQ,EAAE,WAAW,CAAC,GAAG,QAAQ,CAAsC,IAAI,CAAC,CAAC;IAEpF,yEAAyE;IACzE,MAAM,gBAAgB,GAAG,MAAM,CAAC,SAAS,CAAC,CAAC;IAC3C,MAAM,cAAc,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC;IACvC,MAAM,cAAc,GAAG,MAAM,CAAC,WAAW,CAAC,CAAC;IAC3C,MAAM,WAAW,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAC;IACrC,MAAM,iBAAiB,GAAG,MAAM,CAAC,cAAc,CAAC,CAAC;IAEjD,8EAA8E;IAC9E,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,CAAC,QAAQ,EAAE,CAAC;YACd,gBAAgB,CAAC,OAAO,GAAG,SAAS,CAAC;YACrC,cAAc,CAAC,OAAO,GAAG,OAAO,CAAC;QACnC,CAAC;IACH,CAAC,EAAE,CAAC,SAAS,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAC;IAEnC,0BAA0B;IAC1B,SAAS,CAAC,GAAG,EAAE;QACb,cAAc,CAAC,OAAO,GAAG,WAAW,CAAC;IACvC,CAAC,EAAE,CAAC,WAAW,CAAC,CAAC,CAAC;IAElB,SAAS,CAAC,GAAG,EAAE;QACb,WAAW,CAAC,OAAO,GAAG,QAAQ,CAAC;IACjC,CAAC,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC;IAEf,SAAS,CAAC,GAAG,EAAE;QACb,iBAAiB,CAAC,OAAO,GAAG,cAAc,CAAC;IAC7C,CAAC,EAAE,CAAC,cAAc,CAAC,CAAC,CAAC;IAErB,4BAA4B;IAC5B,MAAM,cAAc,GAAG,OAAO,CAAC,GAAG,EAAE;QAClC,IAAI,QAAQ,IAAI,CAAC;YAAE,OAAO,EAAE,CAAC;QAC7B,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,cAAc,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;QACzE,MAAM,QAAQ,GAAG,QAAQ,GAAG,KAAK,CAAC;QAClC,OAAO,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,QAAQ,GAAG,QAAQ,GAAG,CAAC,CAAC,CAAC;IAC9E,CAAC,EAAE,CAAC,QAAQ,EAAE,cAAc,CAAC,CAAC,CAAC;IAE/B,kBAAkB;IAClB,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,CAAC,OAAO,IAAI,cAAc,CAAC,MAAM,KAAK,CAAC,IAAI,QAAQ,IAAI,CAAC,EAAE,CAAC;YAC7D,aAAa,CAAC,EAAE,CAAC,CAAC;YAClB,YAAY,CAAC,KAAK,CAAC,CAAC;YACpB,OAAO;QACT,CAAC;QAED,IAAI,SAAS,GAAG,KAAK,CAAC;QACtB,YAAY,CAAC,IAAI,CAAC,CAAC;QAEnB,sBAAsB,CAAC,kBAAkB,CACvC,OAAO,EACP,cAAc,EACd,EAAE,KAAK,EAAE,eAAe,GAAG,GAAG,EAAE,MAAM,EAAE,eAAe,EAAE,CAC1D;aACE,IAAI,CAAC,CAAC,MAAgB,EAAE,EAAE;YACzB,IAAI,CAAC,SAAS,EAAE,CAAC;gBACf,aAAa,CAAC,MAAM,CAAC,CAAC;gBACtB,YAAY,CAAC,KAAK,CAAC,CAAC;YACtB,CAAC;QACH,CAAC,CAAC;aACD,KAAK,CAAC,CAAC,GAAU,EAAE,EAAE;YACpB,IAAI,CAAC,SAAS,EAAE,CAAC;gBACf,OAAO,CAAC,KAAK,CAAC,gCAAgC,EAAE,GAAG,CAAC,CAAC;gBACrD,YAAY,CAAC,KAAK,CAAC,CAAC;YACtB,CAAC;QACH,CAAC,CAAC,CAAC;QAEL,OAAO,GAAG,EAAE;YACV,SAAS,GAAG,IAAI,CAAC;QACnB,CAAC,CAAC;IACJ,CAAC,EAAE,CAAC,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,QAAQ,CAAC,CAAC,CAAC;IAEzD,0CAA0C;IAC1C,MAAM,cAAc,GAAG,WAAW,CAChC,CAAC,IAAY,EAAE,EAAE;QACf,IAAI,QAAQ,IAAI,CAAC,IAAI,cAAc,IAAI,CAAC;YAAE,OAAO,CAAC,CAAC;QACnD,OAAO,CAAC,IAAI,GAAG,QAAQ,CAAC,GAAG,cAAc,CAAC;IAC5C,CAAC,EACD,CAAC,QAAQ,EAAE,cAAc,CAAC,CAC3B,CAAC;IAEF,sBAAsB;IACtB,MAAM,aAAa,GAAG,cAAc,CAAC,SAAS,CAAC,CAAC;IAChD,MAAM,WAAW,GAAG,cAAc,CAAC,OAAO,CAAC,CAAC;IAC5C,MAAM,gBAAgB,GAAG,cAAc,CAAC,WAAW,CAAC,CAAC;IAErD,uBAAuB;IACvB,MAAM,YAAY,GAAG,WAAW,CAAC,CAAC,CAAoB,EAAE,EAAE;QACxD,iBAAiB,CAAC,CAAC,CAAC,WAAW,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAChD,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,2EAA2E;IAC3E,MAAM,oBAAoB,GAAG,MAAM,CAAC,iBAAiB,CAAC,CAAC;IACvD,MAAM,kBAAkB,GAAG,MAAM,CAAC,eAAe,CAAC,CAAC;IACnD,MAAM,UAAU,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC;IACnC,MAAM,cAAc,GAAG,MAAM,CAAC,WAAW,CAAC,CAAC;IAC3C,MAAM,aAAa,GAAG,MAAM,CAAC,UAAU,CAAC,CAAC;IAGzC,SAAS,CAAC,GAAG,EAAE;QACb,oBAAoB,CAAC,OAAO,GAAG,iBAAiB,CAAC;QACjD,kBAAkB,CAAC,OAAO,GAAG,eAAe,CAAC;QAC7C,UAAU,CAAC,OAAO,GAAG,OAAO,CAAC;QAC7B,cAAc,CAAC,OAAO,GAAG,WAAW,CAAC;QACrC,aAAa,CAAC,OAAO,GAAG,UAAU,CAAC;IACrC,CAAC,EAAE,CAAC,iBAAiB,EAAE,eAAe,EAAE,OAAO,EAAE,WAAW,EAAE,UAAU,CAAC,CAAC,CAAC;IAE3E,iDAAiD;IACjD,kFAAkF;IAClF,MAAM,kBAAkB,GAAG,WAAW,CACpC,CAAC,IAAkC,EAAE,EAAE;QACrC,IAAI,WAAW,GAAG,CAAC,CAAC;QAEpB,OAAO,YAAY,CAAC,MAAM,CAAC;YACzB,4BAA4B,EAAE,GAAG,EAAE,CAAC,CAAC,QAAQ;YAC7C,2BAA2B,EAAE,CAAC,CAAC,EAAE,YAAY,EAAE,EAAE;gBAC/C,uDAAuD;gBACvD,2DAA2D;gBAC3D,OAAO,CAAC,QAAQ,IAAI,IAAI,CAAC,GAAG,CAAC,YAAY,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC;YACpD,CAAC;YACD,0DAA0D;YAC1D,mCAAmC,EAAE,GAAG,EAAE,CAAC,KAAK,EAAE,wCAAwC;YAC1F,kCAAkC,EAAE,CAAC,CAAC,EAAE,YAAY,EAAE,EAAE;gBACtD,kDAAkD;gBAClD,OAAO,CAAC,QAAQ,IAAI,IAAI,CAAC,GAAG,CAAC,YAAY,CAAC,EAAE,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,GAAG,CAAC,YAAY,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,YAAY,CAAC,EAAE,CAAC,CAAC;YAC7G,CAAC;YACD,mBAAmB,EAAE,GAAG,EAAE;gBACxB,iEAAiE;gBACjE,IAAI,IAAI,KAAK,OAAO,EAAE,CAAC;oBACrB,WAAW,GAAG,gBAAgB,CAAC,OAAO,CAAC;gBACzC,CAAC;qBAAM,IAAI,IAAI,KAAK,KAAK,EAAE,CAAC;oBAC1B,WAAW,GAAG,cAAc,CAAC,OAAO,CAAC;gBACvC,CAAC;qBAAM,CAAC;oBACN,WAAW,GAAG,cAAc,CAAC,OAAO,CAAC;gBACvC,CAAC;gBACD,WAAW,CAAC,IAAI,CAAC,CAAC;YACpB,CAAC;YACD,kBAAkB,EAAE,CAAC,EAAyB,EAAE,YAAsC,EAAE,EAAE;gBACxF,MAAM,KAAK,GAAG,iBAAiB,CAAC,OAAO,CAAC;gBACxC,MAAM,GAAG,GAAG,WAAW,CAAC,OAAO,CAAC;gBAChC,IAAI,KAAK,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;oBAAE,OAAO;gBAEnC,MAAM,MAAM,GAAG,YAAY,CAAC,EAAE,CAAC;gBAC/B,MAAM,SAAS,GAAG,CAAC,MAAM,GAAG,KAAK,CAAC,GAAG,GAAG,CAAC;gBACzC,IAAI,OAAO,GAAG,WAAW,GAAG,SAAS,CAAC;gBAEtC,IAAI,IAAI,KAAK,OAAO,EAAE,CAAC;oBACrB,yBAAyB;oBACzB,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,cAAc,CAAC,OAAO,GAAG,WAAW,CAAC,CAAC,CAAC;oBAC/E,gBAAgB,CAAC,OAAO,GAAG,OAAO,CAAC;oBACnC,oBAAoB,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,CAAC;oBACxC,cAAc,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,CAAC;gBACpC,CAAC;qBAAM,IAAI,IAAI,KAAK,KAAK,EAAE,CAAC;oBAC1B,uBAAuB;oBACvB,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,gBAAgB,CAAC,OAAO,GAAG,WAAW,EAAE,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC,CAAC;oBACnF,cAAc,CAAC,OAAO,GAAG,OAAO,CAAC;oBACjC,kBAAkB,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,CAAC;oBACtC,cAAc,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,CAAC;gBACpC,CAAC;qBAAM,CAAC;oBACN,oCAAoC;oBACpC,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,gBAAgB,CAAC,OAAO,EAAE,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC;oBACxF,UAAU,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,CAAC;oBAC9B,cAAc,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,CAAC;gBACpC,CAAC;YACH,CAAC;YACD,qBAAqB,EAAE,CAAC,EAAyB,EAAE,YAAsC,EAAE,EAAE;gBAC3F,WAAW,CAAC,IAAI,CAAC,CAAC;gBAClB,IAAI,IAAI,KAAK,OAAO,EAAE,CAAC;oBACrB,aAAa,CAAC,OAAO,EAAE,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAC;gBACpD,CAAC;qBAAM,IAAI,IAAI,KAAK,KAAK,EAAE,CAAC;oBAC1B,aAAa,CAAC,OAAO,EAAE,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC;gBAClD,CAAC;qBAAM,CAAC;oBACN,MAAM,KAAK,GAAG,iBAAiB,CAAC,OAAO,CAAC;oBACxC,MAAM,GAAG,GAAG,WAAW,CAAC,OAAO,CAAC;oBAChC,IAAI,KAAK,GAAG,CAAC,IAAI,GAAG,GAAG,CAAC,EAAE,CAAC;wBACzB,MAAM,SAAS,GAAG,CAAC,YAAY,CAAC,EAAE,GAAG,KAAK,CAAC,GAAG,GAAG,CAAC;wBAClD,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CACtB,gBAAgB,CAAC,OAAO,EACxB,IAAI,CAAC,GAAG,CAAC,WAAW,GAAG,SAAS,EAAE,cAAc,CAAC,OAAO,CAAC,CAC1D,CAAC;wBACF,aAAa,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,CAAC;oBACnC,CAAC;gBACH,CAAC;YACH,CAAC;YACD,uBAAuB,EAAE,GAAG,EAAE;gBAC5B,WAAW,CAAC,IAAI,CAAC,CAAC;YACpB,CAAC;SACF,CAAC,CAAC;IACL,CAAC,EACD,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAC,mDAAmD;KAC5E,CAAC;IAEF,MAAM,iBAAiB,GAAG,OAAO,CAC/B,GAAG,EAAE,CAAC,kBAAkB,CAAC,OAAO,CAAC,EACjC,CAAC,kBAAkB,CAAC,CACrB,CAAC;IACF,MAAM,eAAe,GAAG,OAAO,CAC7B,GAAG,EAAE,CAAC,kBAAkB,CAAC,KAAK,CAAC,EAC/B,CAAC,kBAAkB,CAAC,CACrB,CAAC;IACF,MAAM,oBAAoB,GAAG,OAAO,CAClC,GAAG,EAAE,CAAC,kBAAkB,CAAC,UAAU,CAAC,EACpC,CAAC,kBAAkB,CAAC,CACrB,CAAC;IAEF,cAAc;IACd,MAAM,SAAS,GAAG,WAAW,CAC3B,CAAC,CAAwB,EAAE,EAAE;QAC3B,IAAI,QAAQ;YAAE,OAAO;QACrB,MAAM,EAAE,SAAS,EAAE,GAAG,CAAC,CAAC,WAAW,CAAC;QACpC,MAAM,KAAK,GAAG,iBAAiB,CAAC,OAAO,CAAC;QACxC,MAAM,GAAG,GAAG,WAAW,CAAC,OAAO,CAAC;QAChC,IAAI,KAAK,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;YAAE,OAAO;QAEnC,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,SAAS,GAAG,KAAK,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC;QACnE,6BAA6B;QAC7B,MAAM,WAAW,GAAG,IAAI,CAAC,GAAG,CAAC,gBAAgB,CAAC,OAAO,EAAE,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC;QAC/F,UAAU,CAAC,OAAO,EAAE,CAAC,WAAW,CAAC,CAAC;IACpC,CAAC,EACD,CAAC,QAAQ,CAAC,CACX,CAAC;IAEF,qBAAqB;IACrB,mBAAmB,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC,CAAC;QAC9B,oBAAoB,EAAE,KAAK,IAAI,EAAE;YAC/B,IAAI,CAAC,OAAO,IAAI,cAAc,CAAC,MAAM,KAAK,CAAC;gBAAE,OAAO;YACpD,YAAY,CAAC,IAAI,CAAC,CAAC;YACnB,IAAI,CAAC;gBACH,MAAM,MAAM,GAAG,MAAM,sBAAsB,CAAC,kBAAkB,CAC5D,OAAO,EACP,cAAc,EACd,EAAE,KAAK,EAAE,eAAe,GAAG,GAAG,EAAE,MAAM,EAAE,eAAe,EAAE,CAC1D,CAAC;gBACF,aAAa,CAAC,MAAM,CAAC,CAAC;YACxB,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,OAAO,CAAC,KAAK,CAAC,kCAAkC,EAAE,GAAG,CAAC,CAAC;YACzD,CAAC;oBAAS,CAAC;gBACT,YAAY,CAAC,KAAK,CAAC,CAAC;YACtB,CAAC;QACH,CAAC;QACD,mBAAmB,EAAE,CAAC,IAAY,EAAE,EAAE;YACpC,OAAO,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,SAAS,EAAE,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;QAC1D,CAAC;QACD,YAAY,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC;KAC7C,CAAC,CAAC,CAAC;IAEJ,MAAM,cAAc,GAAG,cAAc,GAAG,CAAC,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC;QAChE,CAAC,CAAC,cAAc,GAAG,UAAU,CAAC,MAAM;QACpC,CAAC,CAAC,CAAC,CAAC;IAEN,OAAO,CACL,CAAC,IAAI,CACH,KAAK,CAAC,CAAC;YACL,MAAM,CAAC,SAAS;YAChB,EAAE,eAAe,EAAE,KAAK,CAAC,eAAe,EAAE;YAC1C,KAAK;SACN,CAAC,CACF,QAAQ,CAAC,CAAC,YAAY,CAAC,CAEvB;MAAA,CAAC,qBAAqB,CACtB;MAAA,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,cAAc,EAAE,EAAE,MAAM,EAAE,eAAe,EAAE,CAAC,CAAC,CAChE;QAAA,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE,CAAC,CAChC,CAAC,KAAK,CACJ,GAAG,CAAC,CAAC,KAAK,CAAC,CACX,MAAM,CAAC,CAAC,EAAE,GAAG,EAAE,yBAAyB,KAAK,EAAE,EAAE,CAAC,CAClD,KAAK,CAAC,CAAC;gBACL,MAAM,CAAC,SAAS;gBAChB;oBACE,KAAK,EAAE,cAAc;oBACrB,MAAM,EAAE,eAAe;iBACxB;aACF,CAAC,CACF,UAAU,CAAC,OAAO,EAClB,CACH,CAAC,CACF;QAAA,CAAC,SAAS,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC,IAAI,CACvC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,kBAAkB,EAAE,EAAE,MAAM,EAAE,eAAe,EAAE,CAAC,CAAC,EAAG,CAC1E,CACH;MAAA,EAAE,IAAI,CAEN;;MAAA,CAAC,0BAA0B,CAC3B;MAAA,CAAC,IAAI,CACH,KAAK,CAAC,CAAC;YACL,MAAM,CAAC,YAAY;YACnB;gBACE,IAAI,EAAE,CAAC;gBACP,KAAK,EAAE,aAAa;gBACpB,MAAM,EAAE,eAAe;gBACvB,eAAe,EAAE,KAAK,CAAC,WAAW;aACnC;SACF,CAAC,CACF,aAAa,CAAC,MAAM,EAGtB;;MAAA,CAAC,2BAA2B,CAC5B;MAAA,CAAC,IAAI,CACH,KAAK,CAAC,CAAC;YACL,MAAM,CAAC,YAAY;YACnB;gBACE,IAAI,EAAE,WAAW;gBACjB,KAAK,EAAE,CAAC;gBACR,MAAM,EAAE,eAAe;gBACvB,eAAe,EAAE,KAAK,CAAC,WAAW;aACnC;SACF,CAAC,CACF,aAAa,CAAC,MAAM,EAGtB;;MAAA,CAAC,8CAA8C,CAC/C;MAAA,CAAC,IAAI,CACH,KAAK,CAAC,CAAC;YACL,MAAM,CAAC,eAAe;YACtB,MAAM,CAAC,kBAAkB;YACzB;gBACE,IAAI,EAAE,aAAa,GAAG,WAAW;gBACjC,KAAK,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,WAAW,GAAG,aAAa,GAAG,WAAW,GAAG,CAAC,CAAC;gBACjE,eAAe,EAAE,KAAK,CAAC,WAAW;aACnC;SACF,CAAC,CACF,aAAa,CAAC,MAAM,EAEtB;MAAA,CAAC,IAAI,CACH,KAAK,CAAC,CAAC;YACL,MAAM,CAAC,eAAe;YACtB,MAAM,CAAC,qBAAqB;YAC5B;gBACE,IAAI,EAAE,aAAa,GAAG,WAAW;gBACjC,KAAK,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,WAAW,GAAG,aAAa,GAAG,WAAW,GAAG,CAAC,CAAC;gBACjE,GAAG,EAAE,eAAe,GAAG,CAAC;gBACxB,eAAe,EAAE,KAAK,CAAC,WAAW;aACnC;SACF,CAAC,CACF,aAAa,CAAC,MAAM,EAGtB;;MAAA,CAAC,kBAAkB,CACnB;MAAA,CAAC,QAAQ,CAAC,IAAI,CACZ,KAAK,CAAC,CAAC;YACL,MAAM,CAAC,MAAM;YACb,MAAM,CAAC,WAAW;YAClB;gBACE,IAAI,EAAE,aAAa;gBACnB,KAAK,EAAE,WAAW;gBAClB,MAAM,EAAE,eAAe;gBACvB,eAAe,EAAE,KAAK,CAAC,WAAW;gBAClC,OAAO,EAAE,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;aACxC;SACF,CAAC,CACF,OAAO,CAAC,CAAC,EAAE,GAAG,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,CACtD,IAAI,iBAAiB,CAAC,WAAW,CAAC,CAElC;QAAA,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,aAAa,CAAC,CAChC;UAAA,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,WAAW,EAAE,MAAM,CAAC,kBAAkB,CAAC,CAAC,EAC7D;UAAA,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,WAAW,EAAE,MAAM,CAAC,qBAAqB,CAAC,CAAC,EAClE;QAAA,EAAE,IAAI,CACR;MAAA,EAAE,QAAQ,CAAC,IAAI,CAEf;;MAAA,CAAC,gBAAgB,CACjB;MAAA,CAAC,QAAQ,CAAC,IAAI,CACZ,KAAK,CAAC,CAAC;YACL,MAAM,CAAC,MAAM;YACb,MAAM,CAAC,SAAS;YAChB;gBACE,IAAI,EAAE,WAAW,GAAG,WAAW;gBAC/B,KAAK,EAAE,WAAW;gBAClB,MAAM,EAAE,eAAe;gBACvB,eAAe,EAAE,KAAK,CAAC,WAAW;gBAClC,OAAO,EAAE,QAAQ,KAAK,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;aACtC;SACF,CAAC,CACF,OAAO,CAAC,CAAC,EAAE,GAAG,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,CACtD,IAAI,eAAe,CAAC,WAAW,CAAC,CAEhC;QAAA,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,aAAa,CAAC,CAChC;UAAA,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,WAAW,EAAE,MAAM,CAAC,mBAAmB,CAAC,CAAC,EAC9D;UAAA,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,WAAW,EAAE,MAAM,CAAC,sBAAsB,CAAC,CAAC,EACnE;QAAA,EAAE,IAAI,CACR;MAAA,EAAE,QAAQ,CAAC,IAAI,CAEf;;MAAA,CAAC,0BAA0B,CAC3B;MAAA,CAAC,IAAI,CACH,KAAK,CAAC,CAAC;YACL,MAAM,CAAC,OAAO;YACd;gBACE,IAAI,EAAE,aAAa,GAAG,WAAW;gBACjC,KAAK,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,WAAW,GAAG,aAAa,GAAG,WAAW,GAAG,CAAC,CAAC;gBACjE,MAAM,EAAE,eAAe;aACxB;SACF,CAAC,CACF,UAAU,CAAC,CAAC,SAAS,CAAC,EAGxB;;MAAA,CAAC,cAAc,CACf;MAAA,CAAC,YAAY,IAAI,gBAAgB,IAAI,aAAa,IAAI,gBAAgB,IAAI,WAAW,IAAI,CACvF,CAAC,QAAQ,CAAC,IAAI,CACZ,KAAK,CAAC,CAAC;gBACL,MAAM,CAAC,QAAQ;gBACf;oBACE,IAAI,EAAE,gBAAgB,GAAG,CAAC;oBAC1B,MAAM,EAAE,eAAe,GAAG,CAAC;oBAC3B,GAAG,EAAE,CAAC,CAAC;oBACP,eAAe,EAAE,KAAK,CAAC,aAAa;oBACpC,OAAO,EAAE,QAAQ,KAAK,UAAU,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;iBAC3C;aACF,CAAC,CACF,IAAI,oBAAoB,CAAC,WAAW,CAAC,CAErC;UAAA,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,YAAY,EAAE,EAAE,eAAe,EAAE,KAAK,CAAC,aAAa,EAAE,CAAC,CAAC,EAC/E;QAAA,EAAE,QAAQ,CAAC,IAAI,CAAC,CACjB,CACH;IAAA,EAAE,IAAI,CAAC,CACR,CAAC;AACJ,CAAC,CAAC,CAAC;AAEH,aAAa,CAAC,WAAW,GAAG,eAAe,CAAC;AAE5C,MAAM,MAAM,GAAG,UAAU,CAAC,MAAM,CAAC;IAC/B,SAAS,EAAE;QACT,KAAK,EAAE,MAAM;QACb,YAAY,EAAE,CAAC;QACf,QAAQ,EAAE,QAAQ;QAClB,QAAQ,EAAE,UAAU;KACrB;IACD,cAAc,EAAE;QACd,aAAa,EAAE,KAAK;QACpB,KAAK,EAAE,MAAM;KACd;IACD,SAAS,EAAE;QACT,eAAe,EAAE,SAAS;KAC3B;IACD,kBAAkB,EAAE;QAClB,IAAI,EAAE,CAAC;QACP,eAAe,EAAE,SAAS;KAC3B;IACD,YAAY,EAAE;QACZ,QAAQ,EAAE,UAAU;QACpB,GAAG,EAAE,CAAC;KACP;IACD,eAAe,EAAE;QACf,QAAQ,EAAE,UAAU;QACpB,MAAM,EAAE,CAAC;KACV;IACD,kBAAkB,EAAE;QAClB,GAAG,EAAE,CAAC;KACP;IACD,qBAAqB,EAAE;QACrB,MAAM,EAAE,CAAC;KACV;IACD,MAAM,EAAE;QACN,QAAQ,EAAE,UAAU;QACpB,GAAG,EAAE,CAAC;QACN,cAAc,EAAE,QAAQ;QACxB,UAAU,EAAE,QAAQ;QACpB,YAAY,EAAE,CAAC;KAChB;IACD,WAAW,EAAE;QACX,mBAAmB,EAAE,CAAC;QACtB,sBAAsB,EAAE,CAAC;QACzB,oBAAoB,EAAE,CAAC;QACvB,uBAAuB,EAAE,CAAC;KAC3B;IACD,SAAS,EAAE;QACT,mBAAmB,EAAE,CAAC;QACtB,sBAAsB,EAAE,CAAC;QACzB,oBAAoB,EAAE,CAAC;QACvB,uBAAuB,EAAE,CAAC;KAC3B;IACD,aAAa,EAAE;QACb,KAAK,EAAE,CAAC;QACR,MAAM,EAAE,EAAE;QACV,cAAc,EAAE,QAAQ;QACxB,UAAU,EAAE,QAAQ;KACrB;IACD,WAAW,EAAE;QACX,KAAK,EAAE,CAAC;QACR,MAAM,EAAE,CAAC;QACT,eAAe,EAAE,MAAM;QACvB,YAAY,EAAE,CAAC;QACf,QAAQ,EAAE,UAAU;KACrB;IACD,kBAAkB,EAAE;QAClB,SAAS,EAAE,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC;QAChC,GAAG,EAAE,CAAC;KACP;IACD,qBAAqB,EAAE;QACrB,SAAS,EAAE,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC;QACjC,MAAM,EAAE,CAAC;KACV;IACD,mBAAmB,EAAE;QACnB,SAAS,EAAE,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC;QACjC,GAAG,EAAE,CAAC;KACP;IACD,sBAAsB,EAAE;QACtB,SAAS,EAAE,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC;QAChC,MAAM,EAAE,CAAC;KACV;IACD,OAAO,EAAE;QACP,QAAQ,EAAE,UAAU;QACpB,GAAG,EAAE,CAAC;KACP;IACD,QAAQ,EAAE;QACR,QAAQ,EAAE,UAAU;QACpB,KAAK,EAAE,CAAC;QACR,YAAY,EAAE,CAAC;KAChB;IACD,YAAY,EAAE;QACZ,QAAQ,EAAE,UAAU;QACpB,GAAG,EAAE,CAAC,CAAC;QACP,IAAI,EAAE,CAAC,CAAC;QACR,KAAK,EAAE,EAAE;QACT,MAAM,EAAE,EAAE;QACV,YAAY,EAAE,CAAC;KAChB;CACF,CAAC,CAAC;AAEH,eAAe,aAAa,CAAC","sourcesContent":["import * as React from 'react';\nimport {\n forwardRef,\n useCallback,\n useEffect,\n useImperativeHandle,\n useMemo,\n useRef,\n useState,\n} from 'react';\nimport {\n View,\n Image,\n StyleSheet,\n LayoutChangeEvent,\n PanResponder,\n PanResponderGestureState,\n GestureResponderEvent,\n Animated,\n} from 'react-native';\n\nimport { VideoScrubberProps, VideoScrubberRef, VideoScrubberTheme } from './VideoScrubber.types';\nimport ExpoTwoStepVideoModule from './ExpoTwoStepVideoModule';\n\nconst DEFAULT_THEME: Required<VideoScrubberTheme> = {\n handleColor: '#FFD700',\n selectedRegionColor: 'rgba(255,215,0,0.2)',\n playheadColor: '#FFFFFF',\n backgroundColor: '#1c1c1e',\n dimmedColor: 'rgba(0,0,0,0.6)',\n borderColor: '#FFD700',\n};\n\n/**\n * Video scrubber/trimmer component with thumbnail strip and draggable handles\n *\n * @example\n * ```tsx\n * <VideoScrubber\n * assetId={asset.id}\n * duration={asset.duration}\n * currentTime={currentTime}\n * startTime={trimStart}\n * endTime={trimEnd}\n * onStartTimeChange={setTrimStart}\n * onEndTimeChange={setTrimEnd}\n * onScrubbing={(time) => playerRef.current?.seek(time)}\n * />\n * ```\n */\nconst VideoScrubber = forwardRef<VideoScrubberRef, VideoScrubberProps>((props, ref) => {\n const {\n assetId,\n duration,\n currentTime,\n startTime,\n endTime,\n thumbnailCount = 10,\n thumbnailHeight = 50,\n onStartTimeChange,\n onEndTimeChange,\n onScrub,\n onScrubbing,\n onScrubEnd,\n minDuration = 0.5,\n style,\n disabled = false,\n theme: userTheme,\n showPlayhead = true,\n handleWidth = 20,\n } = props;\n\n const theme = useMemo(\n () => ({ ...DEFAULT_THEME, ...userTheme }),\n [userTheme]\n );\n\n const [thumbnails, setThumbnails] = useState<string[]>([]);\n const [containerWidth, setContainerWidth] = useState(0);\n const [isLoading, setIsLoading] = useState(true);\n const [dragging, setDragging] = useState<'start' | 'end' | 'playhead' | null>(null);\n\n // Track current values during drag - synced with props when not dragging\n const dragStartTimeRef = useRef(startTime);\n const dragEndTimeRef = useRef(endTime);\n const currentTimeRef = useRef(currentTime);\n const durationRef = useRef(duration);\n const containerWidthRef = useRef(containerWidth);\n\n // Sync refs with props when they change from outside (e.g., new video loaded)\n useEffect(() => {\n if (!dragging) {\n dragStartTimeRef.current = startTime;\n dragEndTimeRef.current = endTime;\n }\n }, [startTime, endTime, dragging]);\n\n // Keep other refs in sync\n useEffect(() => {\n currentTimeRef.current = currentTime;\n }, [currentTime]);\n\n useEffect(() => {\n durationRef.current = duration;\n }, [duration]);\n\n useEffect(() => {\n containerWidthRef.current = containerWidth;\n }, [containerWidth]);\n\n // Calculate thumbnail times\n const thumbnailTimes = useMemo(() => {\n if (duration <= 0) return [];\n const count = Math.min(thumbnailCount, Math.max(1, Math.ceil(duration)));\n const interval = duration / count;\n return Array.from({ length: count }, (_, i) => i * interval + interval / 2);\n }, [duration, thumbnailCount]);\n\n // Load thumbnails\n useEffect(() => {\n if (!assetId || thumbnailTimes.length === 0 || duration <= 0) {\n setThumbnails([]);\n setIsLoading(false);\n return;\n }\n\n let cancelled = false;\n setIsLoading(true);\n\n ExpoTwoStepVideoModule.generateThumbnails(\n assetId,\n thumbnailTimes,\n { width: thumbnailHeight * 1.5, height: thumbnailHeight }\n )\n .then((thumbs: string[]) => {\n if (!cancelled) {\n setThumbnails(thumbs);\n setIsLoading(false);\n }\n })\n .catch((err: Error) => {\n if (!cancelled) {\n console.error('Failed to generate thumbnails:', err);\n setIsLoading(false);\n }\n });\n\n return () => {\n cancelled = true;\n };\n }, [assetId, thumbnailTimes, thumbnailHeight, duration]);\n\n // Convert time to position and vice versa\n const timeToPosition = useCallback(\n (time: number) => {\n if (duration <= 0 || containerWidth <= 0) return 0;\n return (time / duration) * containerWidth;\n },\n [duration, containerWidth]\n );\n\n // Calculate positions\n const startPosition = timeToPosition(startTime);\n const endPosition = timeToPosition(endTime);\n const playheadPosition = timeToPosition(currentTime);\n\n // Handle layout change\n const handleLayout = useCallback((e: LayoutChangeEvent) => {\n setContainerWidth(e.nativeEvent.layout.width);\n }, []);\n\n // Store callbacks in refs to avoid recreating PanResponder on every render\n const onStartTimeChangeRef = useRef(onStartTimeChange);\n const onEndTimeChangeRef = useRef(onEndTimeChange);\n const onScrubRef = useRef(onScrub);\n const onScrubbingRef = useRef(onScrubbing);\n const onScrubEndRef = useRef(onScrubEnd);\n\n\n useEffect(() => {\n onStartTimeChangeRef.current = onStartTimeChange;\n onEndTimeChangeRef.current = onEndTimeChange;\n onScrubRef.current = onScrub;\n onScrubbingRef.current = onScrubbing;\n onScrubEndRef.current = onScrubEnd;\n }, [onStartTimeChange, onEndTimeChange, onScrub, onScrubbing, onScrubEnd]);\n\n // Create pan responders for handles and playhead\n // Using refs to avoid stale closures - PanResponder is only created once per type\n const createPanResponder = useCallback(\n (type: 'start' | 'end' | 'playhead') => {\n let initialTime = 0;\n\n return PanResponder.create({\n onStartShouldSetPanResponder: () => !disabled,\n onMoveShouldSetPanResponder: (_, gestureState) => {\n // Claim the gesture if there's any horizontal movement\n // This prevents ScrollView from capturing horizontal drags\n return !disabled && Math.abs(gestureState.dx) > 2;\n },\n // Capture phase handlers - these fire before parent views\n onStartShouldSetPanResponderCapture: () => false, // Don't capture on start, let it bubble\n onMoveShouldSetPanResponderCapture: (_, gestureState) => {\n // Only capture if horizontal movement is dominant\n return !disabled && Math.abs(gestureState.dx) > 5 && Math.abs(gestureState.dx) > Math.abs(gestureState.dy);\n },\n onPanResponderGrant: () => {\n // Read current values from refs at the moment the gesture starts\n if (type === 'start') {\n initialTime = dragStartTimeRef.current;\n } else if (type === 'end') {\n initialTime = dragEndTimeRef.current;\n } else {\n initialTime = currentTimeRef.current;\n }\n setDragging(type);\n },\n onPanResponderMove: (_e: GestureResponderEvent, gestureState: PanResponderGestureState) => {\n const width = containerWidthRef.current;\n const dur = durationRef.current;\n if (width <= 0 || dur <= 0) return;\n\n const deltaX = gestureState.dx;\n const deltaTime = (deltaX / width) * dur;\n let newTime = initialTime + deltaTime;\n\n if (type === 'start') {\n // Constrain start handle\n newTime = Math.max(0, Math.min(newTime, dragEndTimeRef.current - minDuration));\n dragStartTimeRef.current = newTime;\n onStartTimeChangeRef.current?.(newTime);\n onScrubbingRef.current?.(newTime);\n } else if (type === 'end') {\n // Constrain end handle\n newTime = Math.max(dragStartTimeRef.current + minDuration, Math.min(newTime, dur));\n dragEndTimeRef.current = newTime;\n onEndTimeChangeRef.current?.(newTime);\n onScrubbingRef.current?.(newTime);\n } else {\n // Playhead - constrain to selection\n newTime = Math.max(dragStartTimeRef.current, Math.min(newTime, dragEndTimeRef.current));\n onScrubRef.current?.(newTime);\n onScrubbingRef.current?.(newTime);\n }\n },\n onPanResponderRelease: (_e: GestureResponderEvent, gestureState: PanResponderGestureState) => {\n setDragging(null);\n if (type === 'start') {\n onScrubEndRef.current?.(dragStartTimeRef.current);\n } else if (type === 'end') {\n onScrubEndRef.current?.(dragEndTimeRef.current);\n } else {\n const width = containerWidthRef.current;\n const dur = durationRef.current;\n if (width > 0 && dur > 0) {\n const deltaTime = (gestureState.dx / width) * dur;\n const newTime = Math.max(\n dragStartTimeRef.current,\n Math.min(initialTime + deltaTime, dragEndTimeRef.current)\n );\n onScrubEndRef.current?.(newTime);\n }\n }\n },\n onPanResponderTerminate: () => {\n setDragging(null);\n },\n });\n },\n [disabled, minDuration] // Only recreate if disabled or minDuration changes\n );\n\n const startPanResponder = useMemo(\n () => createPanResponder('start'),\n [createPanResponder]\n );\n const endPanResponder = useMemo(\n () => createPanResponder('end'),\n [createPanResponder]\n );\n const playheadPanResponder = useMemo(\n () => createPanResponder('playhead'),\n [createPanResponder]\n );\n\n // Tap to seek\n const handleTap = useCallback(\n (e: GestureResponderEvent) => {\n if (disabled) return;\n const { locationX } = e.nativeEvent;\n const width = containerWidthRef.current;\n const dur = durationRef.current;\n if (width <= 0 || dur <= 0) return;\n\n const time = Math.max(0, Math.min(dur, (locationX / width) * dur));\n // Only seek within selection\n const clampedTime = Math.max(dragStartTimeRef.current, Math.min(time, dragEndTimeRef.current));\n onScrubRef.current?.(clampedTime);\n },\n [disabled]\n );\n\n // Expose ref methods\n useImperativeHandle(ref, () => ({\n regenerateThumbnails: async () => {\n if (!assetId || thumbnailTimes.length === 0) return;\n setIsLoading(true);\n try {\n const thumbs = await ExpoTwoStepVideoModule.generateThumbnails(\n assetId,\n thumbnailTimes,\n { width: thumbnailHeight * 1.5, height: thumbnailHeight }\n );\n setThumbnails(thumbs);\n } catch (err) {\n console.error('Failed to regenerate thumbnails:', err);\n } finally {\n setIsLoading(false);\n }\n },\n setPlayheadPosition: (time: number) => {\n onScrub?.(Math.max(startTime, Math.min(time, endTime)));\n },\n getSelection: () => ({ startTime, endTime }),\n }));\n\n const thumbnailWidth = containerWidth > 0 && thumbnails.length > 0\n ? containerWidth / thumbnails.length\n : 0;\n\n return (\n <View\n style={[\n styles.container,\n { backgroundColor: theme.backgroundColor },\n style,\n ]}\n onLayout={handleLayout}\n >\n {/* Thumbnail Strip */}\n <View style={[styles.thumbnailStrip, { height: thumbnailHeight }]}>\n {thumbnails.map((thumb, index) => (\n <Image\n key={index}\n source={{ uri: `data:image/png;base64,${thumb}` }}\n style={[\n styles.thumbnail,\n {\n width: thumbnailWidth,\n height: thumbnailHeight,\n },\n ]}\n resizeMode=\"cover\"\n />\n ))}\n {isLoading && thumbnails.length === 0 && (\n <View style={[styles.loadingPlaceholder, { height: thumbnailHeight }]} />\n )}\n </View>\n\n {/* Dimmed region - left */}\n <View\n style={[\n styles.dimmedRegion,\n {\n left: 0,\n width: startPosition,\n height: thumbnailHeight,\n backgroundColor: theme.dimmedColor,\n },\n ]}\n pointerEvents=\"none\"\n />\n\n {/* Dimmed region - right */}\n <View\n style={[\n styles.dimmedRegion,\n {\n left: endPosition,\n right: 0,\n height: thumbnailHeight,\n backgroundColor: theme.dimmedColor,\n },\n ]}\n pointerEvents=\"none\"\n />\n\n {/* Selection frame - top and bottom borders */}\n <View\n style={[\n styles.selectionBorder,\n styles.selectionBorderTop,\n {\n left: startPosition + handleWidth,\n width: Math.max(0, endPosition - startPosition - handleWidth * 2),\n backgroundColor: theme.borderColor,\n },\n ]}\n pointerEvents=\"none\"\n />\n <View\n style={[\n styles.selectionBorder,\n styles.selectionBorderBottom,\n {\n left: startPosition + handleWidth,\n width: Math.max(0, endPosition - startPosition - handleWidth * 2),\n top: thumbnailHeight - 2,\n backgroundColor: theme.borderColor,\n },\n ]}\n pointerEvents=\"none\"\n />\n\n {/* Start Handle */}\n <Animated.View\n style={[\n styles.handle,\n styles.startHandle,\n {\n left: startPosition,\n width: handleWidth,\n height: thumbnailHeight,\n backgroundColor: theme.handleColor,\n opacity: dragging === 'start' ? 0.8 : 1,\n },\n ]}\n hitSlop={{ top: 15, bottom: 15, left: 15, right: 10 }}\n {...startPanResponder.panHandlers}\n >\n <View style={styles.handleChevron}>\n <View style={[styles.chevronLine, styles.chevronLineTopLeft]} />\n <View style={[styles.chevronLine, styles.chevronLineBottomLeft]} />\n </View>\n </Animated.View>\n\n {/* End Handle */}\n <Animated.View\n style={[\n styles.handle,\n styles.endHandle,\n {\n left: endPosition - handleWidth,\n width: handleWidth,\n height: thumbnailHeight,\n backgroundColor: theme.handleColor,\n opacity: dragging === 'end' ? 0.8 : 1,\n },\n ]}\n hitSlop={{ top: 15, bottom: 15, left: 10, right: 15 }}\n {...endPanResponder.panHandlers}\n >\n <View style={styles.handleChevron}>\n <View style={[styles.chevronLine, styles.chevronLineTopRight]} />\n <View style={[styles.chevronLine, styles.chevronLineBottomRight]} />\n </View>\n </Animated.View>\n\n {/* Tap area for seeking */}\n <View\n style={[\n styles.tapArea,\n {\n left: startPosition + handleWidth,\n width: Math.max(0, endPosition - startPosition - handleWidth * 2),\n height: thumbnailHeight,\n },\n ]}\n onTouchEnd={handleTap}\n />\n\n {/* Playhead */}\n {showPlayhead && playheadPosition >= startPosition && playheadPosition <= endPosition && (\n <Animated.View\n style={[\n styles.playhead,\n {\n left: playheadPosition - 1,\n height: thumbnailHeight + 8,\n top: -4,\n backgroundColor: theme.playheadColor,\n opacity: dragging === 'playhead' ? 0.8 : 1,\n },\n ]}\n {...playheadPanResponder.panHandlers}\n >\n <View style={[styles.playheadKnob, { backgroundColor: theme.playheadColor }]} />\n </Animated.View>\n )}\n </View>\n );\n});\n\nVideoScrubber.displayName = 'VideoScrubber';\n\nconst styles = StyleSheet.create({\n container: {\n width: '100%',\n borderRadius: 8,\n overflow: 'hidden',\n position: 'relative',\n },\n thumbnailStrip: {\n flexDirection: 'row',\n width: '100%',\n },\n thumbnail: {\n backgroundColor: '#2c2c2e',\n },\n loadingPlaceholder: {\n flex: 1,\n backgroundColor: '#2c2c2e',\n },\n dimmedRegion: {\n position: 'absolute',\n top: 0,\n },\n selectionBorder: {\n position: 'absolute',\n height: 2,\n },\n selectionBorderTop: {\n top: 0,\n },\n selectionBorderBottom: {\n bottom: 0,\n },\n handle: {\n position: 'absolute',\n top: 0,\n justifyContent: 'center',\n alignItems: 'center',\n borderRadius: 4,\n },\n startHandle: {\n borderTopLeftRadius: 6,\n borderBottomLeftRadius: 6,\n borderTopRightRadius: 0,\n borderBottomRightRadius: 0,\n },\n endHandle: {\n borderTopLeftRadius: 0,\n borderBottomLeftRadius: 0,\n borderTopRightRadius: 6,\n borderBottomRightRadius: 6,\n },\n handleChevron: {\n width: 8,\n height: 16,\n justifyContent: 'center',\n alignItems: 'center',\n },\n chevronLine: {\n width: 2,\n height: 8,\n backgroundColor: '#000',\n borderRadius: 1,\n position: 'absolute',\n },\n chevronLineTopLeft: {\n transform: [{ rotate: '20deg' }],\n top: 0,\n },\n chevronLineBottomLeft: {\n transform: [{ rotate: '-20deg' }],\n bottom: 0,\n },\n chevronLineTopRight: {\n transform: [{ rotate: '-20deg' }],\n top: 0,\n },\n chevronLineBottomRight: {\n transform: [{ rotate: '20deg' }],\n bottom: 0,\n },\n tapArea: {\n position: 'absolute',\n top: 0,\n },\n playhead: {\n position: 'absolute',\n width: 2,\n borderRadius: 1,\n },\n playheadKnob: {\n position: 'absolute',\n top: -4,\n left: -4,\n width: 10,\n height: 10,\n borderRadius: 5,\n },\n});\n\nexport default VideoScrubber;\n"]}
|
|
1
|
+
{"version":3,"file":"VideoScrubber.js","sourceRoot":"","sources":["../src/VideoScrubber.tsx"],"names":[],"mappings":"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","sourcesContent":["import * as React from 'react';\nimport {\n forwardRef,\n useCallback,\n useEffect,\n useImperativeHandle,\n useMemo,\n useRef,\n useState,\n} from 'react';\nimport {\n View,\n Image,\n StyleSheet,\n LayoutChangeEvent,\n PanResponder,\n PanResponderGestureState,\n GestureResponderEvent,\n Animated,\n} from 'react-native';\n\nimport { VideoScrubberProps, VideoScrubberRef, VideoScrubberTheme } from './VideoScrubber.types';\nimport ExpoTwoStepVideoModule from './ExpoTwoStepVideoModule';\n\nconst DEFAULT_THEME: Required<VideoScrubberTheme> = {\n handleColor: '#FFD700',\n selectedRegionColor: 'rgba(255,215,0,0.2)',\n playheadColor: '#FFFFFF',\n backgroundColor: '#1c1c1e',\n dimmedColor: 'rgba(0,0,0,0.6)',\n borderColor: '#FFD700',\n};\n\n/**\n * Video scrubber/trimmer component with thumbnail strip and draggable handles\n *\n * @example\n * ```tsx\n * <VideoScrubber\n * assetId={asset.id}\n * duration={asset.duration}\n * currentTime={currentTime}\n * startTime={trimStart}\n * endTime={trimEnd}\n * onStartTimeChange={setTrimStart}\n * onEndTimeChange={setTrimEnd}\n * onScrubbing={(time) => playerRef.current?.seek(time)}\n * />\n * ```\n */\nconst VideoScrubber = forwardRef<VideoScrubberRef, VideoScrubberProps>((props, ref) => {\n const {\n assetId,\n duration,\n currentTime,\n startTime,\n endTime,\n thumbnailCount = 10,\n thumbnailHeight = 50,\n onStartTimeChange,\n onEndTimeChange,\n onScrub,\n onScrubbing,\n onScrubEnd,\n minDuration = 0.5,\n style,\n disabled = false,\n theme: userTheme,\n showPlayhead = true,\n handleWidth = 20,\n } = props;\n\n const theme = useMemo(\n () => ({ ...DEFAULT_THEME, ...userTheme }),\n [userTheme]\n );\n\n const [thumbnails, setThumbnails] = useState<string[]>([]);\n const [containerWidth, setContainerWidth] = useState(0);\n const [isLoading, setIsLoading] = useState(true);\n const [dragging, setDragging] = useState<'start' | 'end' | 'playhead' | null>(null);\n\n // Track current values during drag - synced with props when not dragging\n const dragStartTimeRef = useRef(startTime);\n const dragEndTimeRef = useRef(endTime);\n const currentTimeRef = useRef(currentTime);\n const durationRef = useRef(duration);\n const containerWidthRef = useRef(containerWidth);\n const handleWidthRef = useRef(handleWidth);\n\n // Sync refs with props when they change from outside (e.g., new video loaded)\n useEffect(() => {\n if (!dragging) {\n dragStartTimeRef.current = startTime;\n dragEndTimeRef.current = endTime;\n }\n }, [startTime, endTime, dragging]);\n\n // Keep other refs in sync\n useEffect(() => {\n currentTimeRef.current = currentTime;\n }, [currentTime]);\n\n useEffect(() => {\n durationRef.current = duration;\n }, [duration]);\n\n useEffect(() => {\n containerWidthRef.current = containerWidth;\n }, [containerWidth]);\n\n useEffect(() => {\n handleWidthRef.current = handleWidth;\n }, [handleWidth]);\n\n // Calculate thumbnail times\n const thumbnailTimes = useMemo(() => {\n if (duration <= 0) return [];\n const count = Math.min(thumbnailCount, Math.max(1, Math.ceil(duration)));\n const interval = duration / count;\n return Array.from({ length: count }, (_, i) => i * interval + interval / 2);\n }, [duration, thumbnailCount]);\n\n // Load thumbnails\n useEffect(() => {\n if (!assetId || thumbnailTimes.length === 0 || duration <= 0) {\n setThumbnails([]);\n setIsLoading(false);\n return;\n }\n\n let cancelled = false;\n setIsLoading(true);\n\n ExpoTwoStepVideoModule.generateThumbnails(\n assetId,\n thumbnailTimes,\n { width: thumbnailHeight * 1.5, height: thumbnailHeight }\n )\n .then((thumbs: string[]) => {\n if (!cancelled) {\n setThumbnails(thumbs);\n setIsLoading(false);\n }\n })\n .catch((err: Error) => {\n if (!cancelled) {\n console.error('Failed to generate thumbnails:', err);\n setIsLoading(false);\n }\n });\n\n return () => {\n cancelled = true;\n };\n }, [assetId, thumbnailTimes, thumbnailHeight, duration]);\n\n // The content area is between the handles\n const contentWidth = containerWidth > 0 ? containerWidth - handleWidth * 2 : 0;\n\n // Convert time to position within the content area (between handles)\n const timeToPosition = useCallback(\n (time: number) => {\n if (duration <= 0 || contentWidth <= 0) return handleWidth;\n return handleWidth + (time / duration) * contentWidth;\n },\n [duration, contentWidth, handleWidth]\n );\n\n // Calculate positions\n const startPosition = timeToPosition(startTime);\n const endPosition = timeToPosition(endTime);\n const playheadPosition = timeToPosition(currentTime);\n\n // Handle layout change\n const handleLayout = useCallback((e: LayoutChangeEvent) => {\n setContainerWidth(e.nativeEvent.layout.width);\n }, []);\n\n // Store callbacks in refs to avoid recreating PanResponder on every render\n const onStartTimeChangeRef = useRef(onStartTimeChange);\n const onEndTimeChangeRef = useRef(onEndTimeChange);\n const onScrubRef = useRef(onScrub);\n const onScrubbingRef = useRef(onScrubbing);\n const onScrubEndRef = useRef(onScrubEnd);\n\n\n useEffect(() => {\n onStartTimeChangeRef.current = onStartTimeChange;\n onEndTimeChangeRef.current = onEndTimeChange;\n onScrubRef.current = onScrub;\n onScrubbingRef.current = onScrubbing;\n onScrubEndRef.current = onScrubEnd;\n }, [onStartTimeChange, onEndTimeChange, onScrub, onScrubbing, onScrubEnd]);\n\n // Create pan responders for handles and playhead\n // Using refs to avoid stale closures - PanResponder is only created once per type\n const createPanResponder = useCallback(\n (type: 'start' | 'end' | 'playhead') => {\n let initialTime = 0;\n\n return PanResponder.create({\n onStartShouldSetPanResponder: () => !disabled,\n onMoveShouldSetPanResponder: (_, gestureState) => {\n // Claim the gesture if there's any horizontal movement\n // This prevents ScrollView from capturing horizontal drags\n return !disabled && Math.abs(gestureState.dx) > 2;\n },\n // Capture phase handlers - these fire before parent views\n onStartShouldSetPanResponderCapture: () => false, // Don't capture on start, let it bubble\n onMoveShouldSetPanResponderCapture: (_, gestureState) => {\n // Only capture if horizontal movement is dominant\n return !disabled && Math.abs(gestureState.dx) > 5 && Math.abs(gestureState.dx) > Math.abs(gestureState.dy);\n },\n onPanResponderGrant: () => {\n // Read current values from refs at the moment the gesture starts\n if (type === 'start') {\n initialTime = dragStartTimeRef.current;\n } else if (type === 'end') {\n initialTime = dragEndTimeRef.current;\n } else {\n initialTime = currentTimeRef.current;\n }\n setDragging(type);\n },\n onPanResponderMove: (_e: GestureResponderEvent, gestureState: PanResponderGestureState) => {\n const containerW = containerWidthRef.current;\n const hw = handleWidthRef.current;\n const dur = durationRef.current;\n // Content width is where time is mapped (between handles)\n const cw = containerW - hw * 2;\n if (cw <= 0 || dur <= 0) return;\n\n const deltaX = gestureState.dx;\n const deltaTime = (deltaX / cw) * dur;\n let newTime = initialTime + deltaTime;\n\n if (type === 'start') {\n // Constrain start handle\n newTime = Math.max(0, Math.min(newTime, dragEndTimeRef.current - minDuration));\n dragStartTimeRef.current = newTime;\n onStartTimeChangeRef.current?.(newTime);\n onScrubbingRef.current?.(newTime);\n } else if (type === 'end') {\n // Constrain end handle\n newTime = Math.max(dragStartTimeRef.current + minDuration, Math.min(newTime, dur));\n dragEndTimeRef.current = newTime;\n onEndTimeChangeRef.current?.(newTime);\n onScrubbingRef.current?.(newTime);\n } else {\n // Playhead - constrain to selection\n newTime = Math.max(dragStartTimeRef.current, Math.min(newTime, dragEndTimeRef.current));\n onScrubRef.current?.(newTime);\n onScrubbingRef.current?.(newTime);\n }\n },\n onPanResponderRelease: (_e: GestureResponderEvent, gestureState: PanResponderGestureState) => {\n setDragging(null);\n if (type === 'start') {\n onScrubEndRef.current?.(dragStartTimeRef.current);\n } else if (type === 'end') {\n onScrubEndRef.current?.(dragEndTimeRef.current);\n } else {\n const containerW = containerWidthRef.current;\n const hw = handleWidthRef.current;\n const dur = durationRef.current;\n const cw = containerW - hw * 2;\n if (cw > 0 && dur > 0) {\n const deltaTime = (gestureState.dx / cw) * dur;\n const newTime = Math.max(\n dragStartTimeRef.current,\n Math.min(initialTime + deltaTime, dragEndTimeRef.current)\n );\n onScrubEndRef.current?.(newTime);\n }\n }\n },\n onPanResponderTerminate: () => {\n setDragging(null);\n },\n });\n },\n [disabled, minDuration] // Only recreate if disabled or minDuration changes\n );\n\n const startPanResponder = useMemo(\n () => createPanResponder('start'),\n [createPanResponder]\n );\n const endPanResponder = useMemo(\n () => createPanResponder('end'),\n [createPanResponder]\n );\n const playheadPanResponder = useMemo(\n () => createPanResponder('playhead'),\n [createPanResponder]\n );\n\n // Tap to seek - locationX is relative to the tap area which spans from startPosition to endPosition\n const handleTap = useCallback(\n (e: GestureResponderEvent) => {\n if (disabled) return;\n const { locationX } = e.nativeEvent;\n const startT = dragStartTimeRef.current;\n const endT = dragEndTimeRef.current;\n const selectionDuration = endT - startT;\n if (selectionDuration <= 0) return;\n\n // The tap area spans the selection, so locationX=0 is startTime and locationX=width is endTime\n // Calculate time proportionally within the selection\n const width = containerWidthRef.current;\n const dur = durationRef.current;\n if (width <= 0 || dur <= 0) return;\n\n // Calculate the width of the tap area (endPosition - startPosition in content coordinates)\n const cw = width - handleWidth * 2; // contentWidth\n const tapAreaWidth = ((endT - startT) / dur) * cw;\n if (tapAreaWidth <= 0) return;\n\n const proportion = Math.max(0, Math.min(1, locationX / tapAreaWidth));\n const time = startT + proportion * selectionDuration;\n onScrubRef.current?.(time);\n },\n [disabled, handleWidth]\n );\n\n // Expose ref methods\n useImperativeHandle(ref, () => ({\n regenerateThumbnails: async () => {\n if (!assetId || thumbnailTimes.length === 0) return;\n setIsLoading(true);\n try {\n const thumbs = await ExpoTwoStepVideoModule.generateThumbnails(\n assetId,\n thumbnailTimes,\n { width: thumbnailHeight * 1.5, height: thumbnailHeight }\n );\n setThumbnails(thumbs);\n } catch (err) {\n console.error('Failed to regenerate thumbnails:', err);\n } finally {\n setIsLoading(false);\n }\n },\n setPlayheadPosition: (time: number) => {\n onScrub?.(Math.max(startTime, Math.min(time, endTime)));\n },\n getSelection: () => ({ startTime, endTime }),\n }));\n\n const thumbnailWidth = contentWidth > 0 && thumbnails.length > 0\n ? contentWidth / thumbnails.length\n : 0;\n\n return (\n <View\n style={[\n styles.container,\n { backgroundColor: theme.backgroundColor },\n style,\n ]}\n onLayout={handleLayout}\n >\n {/* Thumbnail Strip - positioned between handles */}\n <View style={[styles.thumbnailStrip, { height: thumbnailHeight, marginLeft: handleWidth, width: contentWidth }]}>\n {thumbnails.map((thumb, index) => (\n <Image\n key={index}\n source={{ uri: `data:image/png;base64,${thumb}` }}\n style={[\n styles.thumbnail,\n {\n width: thumbnailWidth,\n height: thumbnailHeight,\n },\n ]}\n resizeMode=\"cover\"\n />\n ))}\n {isLoading && thumbnails.length === 0 && (\n <View style={[styles.loadingPlaceholder, { height: thumbnailHeight }]} />\n )}\n </View>\n\n {/* Dimmed region - left (covers trimmed content from left handle to start handle position) */}\n <View\n style={[\n styles.dimmedRegion,\n {\n left: handleWidth,\n width: Math.max(0, startPosition - handleWidth),\n height: thumbnailHeight,\n backgroundColor: theme.dimmedColor,\n },\n ]}\n pointerEvents=\"none\"\n />\n\n {/* Dimmed region - right (covers trimmed content from end handle position to right handle) */}\n <View\n style={[\n styles.dimmedRegion,\n {\n left: endPosition,\n width: Math.max(0, containerWidth - handleWidth - endPosition),\n height: thumbnailHeight,\n backgroundColor: theme.dimmedColor,\n },\n ]}\n pointerEvents=\"none\"\n />\n\n {/* Selection frame - top and bottom borders (span between handles) */}\n <View\n style={[\n styles.selectionBorder,\n styles.selectionBorderTop,\n {\n left: startPosition,\n width: Math.max(0, endPosition - startPosition),\n backgroundColor: theme.borderColor,\n },\n ]}\n pointerEvents=\"none\"\n />\n <View\n style={[\n styles.selectionBorder,\n styles.selectionBorderBottom,\n {\n left: startPosition,\n width: Math.max(0, endPosition - startPosition),\n top: thumbnailHeight - 2,\n backgroundColor: theme.borderColor,\n },\n ]}\n pointerEvents=\"none\"\n />\n\n {/* Start Handle - positioned to the LEFT of the trim point (outside content when startTime=0) */}\n <Animated.View\n style={[\n styles.handle,\n styles.startHandle,\n {\n left: startPosition - handleWidth,\n width: handleWidth,\n height: thumbnailHeight,\n backgroundColor: theme.handleColor,\n opacity: dragging === 'start' ? 0.8 : 1,\n },\n ]}\n hitSlop={{ top: 15, bottom: 15, left: 15, right: 10 }}\n {...startPanResponder.panHandlers}\n >\n <View style={styles.handleChevron}>\n <View style={[styles.chevronLine, styles.chevronLineTopLeft]} />\n <View style={[styles.chevronLine, styles.chevronLineBottomLeft]} />\n </View>\n </Animated.View>\n\n {/* End Handle - positioned to the RIGHT of the trim point (outside content when endTime=duration) */}\n <Animated.View\n style={[\n styles.handle,\n styles.endHandle,\n {\n left: endPosition,\n width: handleWidth,\n height: thumbnailHeight,\n backgroundColor: theme.handleColor,\n opacity: dragging === 'end' ? 0.8 : 1,\n },\n ]}\n hitSlop={{ top: 15, bottom: 15, left: 10, right: 15 }}\n {...endPanResponder.panHandlers}\n >\n <View style={styles.handleChevron}>\n <View style={[styles.chevronLine, styles.chevronLineTopRight]} />\n <View style={[styles.chevronLine, styles.chevronLineBottomRight]} />\n </View>\n </Animated.View>\n\n {/* Tap area for seeking - covers the selection between start and end trim points */}\n <View\n style={[\n styles.tapArea,\n {\n left: startPosition,\n width: Math.max(0, endPosition - startPosition),\n height: thumbnailHeight,\n },\n ]}\n onTouchEnd={handleTap}\n />\n\n {/* Playhead */}\n {showPlayhead && playheadPosition >= startPosition && playheadPosition <= endPosition && (\n <Animated.View\n style={[\n styles.playhead,\n {\n left: playheadPosition - 1,\n height: thumbnailHeight + 8,\n top: -4,\n backgroundColor: theme.playheadColor,\n opacity: dragging === 'playhead' ? 0.8 : 1,\n },\n ]}\n {...playheadPanResponder.panHandlers}\n >\n <View style={[styles.playheadKnob, { backgroundColor: theme.playheadColor }]} />\n </Animated.View>\n )}\n </View>\n );\n});\n\nVideoScrubber.displayName = 'VideoScrubber';\n\nconst styles = StyleSheet.create({\n container: {\n width: '100%',\n borderRadius: 8,\n overflow: 'hidden',\n position: 'relative',\n },\n thumbnailStrip: {\n flexDirection: 'row',\n width: '100%',\n },\n thumbnail: {\n backgroundColor: '#2c2c2e',\n },\n loadingPlaceholder: {\n flex: 1,\n backgroundColor: '#2c2c2e',\n },\n dimmedRegion: {\n position: 'absolute',\n top: 0,\n },\n selectionBorder: {\n position: 'absolute',\n height: 2,\n },\n selectionBorderTop: {\n top: 0,\n },\n selectionBorderBottom: {\n bottom: 0,\n },\n handle: {\n position: 'absolute',\n top: 0,\n justifyContent: 'center',\n alignItems: 'center',\n borderRadius: 4,\n },\n startHandle: {\n borderTopLeftRadius: 6,\n borderBottomLeftRadius: 6,\n borderTopRightRadius: 0,\n borderBottomRightRadius: 0,\n },\n endHandle: {\n borderTopLeftRadius: 0,\n borderBottomLeftRadius: 0,\n borderTopRightRadius: 6,\n borderBottomRightRadius: 6,\n },\n handleChevron: {\n width: 8,\n height: 16,\n justifyContent: 'center',\n alignItems: 'center',\n },\n chevronLine: {\n width: 2,\n height: 8,\n backgroundColor: '#000',\n borderRadius: 1,\n position: 'absolute',\n },\n chevronLineTopLeft: {\n transform: [{ rotate: '20deg' }],\n top: 0,\n },\n chevronLineBottomLeft: {\n transform: [{ rotate: '-20deg' }],\n bottom: 0,\n },\n chevronLineTopRight: {\n transform: [{ rotate: '-20deg' }],\n top: 0,\n },\n chevronLineBottomRight: {\n transform: [{ rotate: '20deg' }],\n bottom: 0,\n },\n tapArea: {\n position: 'absolute',\n top: 0,\n },\n playhead: {\n position: 'absolute',\n width: 2,\n borderRadius: 1,\n },\n playheadKnob: {\n position: 'absolute',\n top: -4,\n left: -4,\n width: 10,\n height: 10,\n borderRadius: 5,\n },\n});\n\nexport default VideoScrubber;\n"]}
|
|
@@ -11,6 +11,7 @@ class ExpoTwoStepVideoView: ExpoView {
|
|
|
11
11
|
private var playerLayer: AVPlayerLayer?
|
|
12
12
|
private var timeObserver: Any?
|
|
13
13
|
private var isObservingStatus: Bool = false
|
|
14
|
+
private var isAudioSessionConfigured: Bool = false
|
|
14
15
|
|
|
15
16
|
/// Event dispatchers
|
|
16
17
|
let onPlaybackStatusChange = EventDispatcher()
|
|
@@ -32,6 +33,8 @@ class ExpoTwoStepVideoView: ExpoView {
|
|
|
32
33
|
clipsToBounds = true
|
|
33
34
|
backgroundColor = .black
|
|
34
35
|
setupPlayerLayer()
|
|
36
|
+
setupAudioSession()
|
|
37
|
+
setupAudioSessionObservers()
|
|
35
38
|
}
|
|
36
39
|
|
|
37
40
|
private func setupPlayerLayer() {
|
|
@@ -43,6 +46,87 @@ class ExpoTwoStepVideoView: ExpoView {
|
|
|
43
46
|
}
|
|
44
47
|
}
|
|
45
48
|
|
|
49
|
+
/// Configure audio session for video playback
|
|
50
|
+
/// Uses .playback category to ensure audio plays even when silent switch is on
|
|
51
|
+
private func setupAudioSession() {
|
|
52
|
+
do {
|
|
53
|
+
let audioSession = AVAudioSession.sharedInstance()
|
|
54
|
+
// .playback category: audio plays even with silent switch, stops other audio
|
|
55
|
+
// .defaultToSpeaker: routes audio to speaker by default (not earpiece)
|
|
56
|
+
try audioSession.setCategory(.playback, mode: .moviePlayback, options: [.defaultToSpeaker])
|
|
57
|
+
try audioSession.setActive(true, options: [.notifyOthersOnDeactivation])
|
|
58
|
+
isAudioSessionConfigured = true
|
|
59
|
+
} catch {
|
|
60
|
+
print("ExpoTwoStepVideoView: Failed to configure audio session: \(error)")
|
|
61
|
+
// Try a simpler configuration as fallback
|
|
62
|
+
do {
|
|
63
|
+
try AVAudioSession.sharedInstance().setCategory(.playback)
|
|
64
|
+
try AVAudioSession.sharedInstance().setActive(true)
|
|
65
|
+
isAudioSessionConfigured = true
|
|
66
|
+
} catch {
|
|
67
|
+
print("ExpoTwoStepVideoView: Fallback audio session also failed: \(error)")
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/// Listen for audio session interruptions (phone calls, other apps)
|
|
73
|
+
private func setupAudioSessionObservers() {
|
|
74
|
+
NotificationCenter.default.addObserver(
|
|
75
|
+
self,
|
|
76
|
+
selector: #selector(handleAudioSessionInterruption),
|
|
77
|
+
name: AVAudioSession.interruptionNotification,
|
|
78
|
+
object: AVAudioSession.sharedInstance()
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
NotificationCenter.default.addObserver(
|
|
82
|
+
self,
|
|
83
|
+
selector: #selector(handleAudioSessionRouteChange),
|
|
84
|
+
name: AVAudioSession.routeChangeNotification,
|
|
85
|
+
object: AVAudioSession.sharedInstance()
|
|
86
|
+
)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
@objc private func handleAudioSessionInterruption(notification: Notification) {
|
|
90
|
+
guard let userInfo = notification.userInfo,
|
|
91
|
+
let typeValue = userInfo[AVAudioSessionInterruptionTypeKey] as? UInt,
|
|
92
|
+
let type = AVAudioSession.InterruptionType(rawValue: typeValue) else {
|
|
93
|
+
return
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
switch type {
|
|
97
|
+
case .began:
|
|
98
|
+
// Interruption began - pause playback
|
|
99
|
+
player?.pause()
|
|
100
|
+
onPlaybackStatusChange(["status": "interrupted"])
|
|
101
|
+
case .ended:
|
|
102
|
+
// Interruption ended - check if we should resume
|
|
103
|
+
guard let optionsValue = userInfo[AVAudioSessionInterruptionOptionKey] as? UInt else { return }
|
|
104
|
+
let options = AVAudioSession.InterruptionOptions(rawValue: optionsValue)
|
|
105
|
+
if options.contains(.shouldResume) {
|
|
106
|
+
// Re-activate audio session and resume
|
|
107
|
+
setupAudioSession()
|
|
108
|
+
player?.play()
|
|
109
|
+
onPlaybackStatusChange(["status": "playing"])
|
|
110
|
+
}
|
|
111
|
+
@unknown default:
|
|
112
|
+
break
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
@objc private func handleAudioSessionRouteChange(notification: Notification) {
|
|
117
|
+
guard let userInfo = notification.userInfo,
|
|
118
|
+
let reasonValue = userInfo[AVAudioSessionRouteChangeReasonKey] as? UInt,
|
|
119
|
+
let reason = AVAudioSession.RouteChangeReason(rawValue: reasonValue) else {
|
|
120
|
+
return
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Pause when headphones are unplugged
|
|
124
|
+
if reason == .oldDeviceUnavailable {
|
|
125
|
+
player?.pause()
|
|
126
|
+
onPlaybackStatusChange(["status": "paused"])
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
46
130
|
override func layoutSubviews() {
|
|
47
131
|
super.layoutSubviews()
|
|
48
132
|
playerLayer?.frame = bounds
|
|
@@ -117,6 +201,21 @@ class ExpoTwoStepVideoView: ExpoView {
|
|
|
117
201
|
}
|
|
118
202
|
|
|
119
203
|
func play() {
|
|
204
|
+
// Ensure audio session is properly configured before playing
|
|
205
|
+
// This handles cases where another component may have changed the audio session
|
|
206
|
+
if !isAudioSessionConfigured {
|
|
207
|
+
setupAudioSession()
|
|
208
|
+
} else {
|
|
209
|
+
// Re-activate in case it was deactivated
|
|
210
|
+
do {
|
|
211
|
+
try AVAudioSession.sharedInstance().setActive(true, options: [.notifyOthersOnDeactivation])
|
|
212
|
+
} catch {
|
|
213
|
+
print("ExpoTwoStepVideoView: Failed to activate audio session: \(error)")
|
|
214
|
+
// Try full reconfiguration
|
|
215
|
+
setupAudioSession()
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
120
219
|
player?.play()
|
|
121
220
|
onPlaybackStatusChange(["status": "playing"])
|
|
122
221
|
}
|
|
@@ -216,6 +315,9 @@ class ExpoTwoStepVideoView: ExpoView {
|
|
|
216
315
|
|
|
217
316
|
deinit {
|
|
218
317
|
cleanup()
|
|
318
|
+
// Remove audio session observers
|
|
319
|
+
NotificationCenter.default.removeObserver(self, name: AVAudioSession.interruptionNotification, object: nil)
|
|
320
|
+
NotificationCenter.default.removeObserver(self, name: AVAudioSession.routeChangeNotification, object: nil)
|
|
219
321
|
// Also remove the layer to break any potential retain cycles
|
|
220
322
|
playerLayer?.removeFromSuperlayer()
|
|
221
323
|
playerLayer = nil
|