@movementinfra/expo-twostep-video 0.1.13 → 0.1.15
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.
- package/README.md +69 -0
- package/build/ExpoTwoStepVideo.types.d.ts +6 -0
- package/build/ExpoTwoStepVideo.types.d.ts.map +1 -1
- package/build/ExpoTwoStepVideo.types.js.map +1 -1
- package/build/ExpoTwoStepVideoView.d.ts.map +1 -1
- package/build/ExpoTwoStepVideoView.js +51 -2
- package/build/ExpoTwoStepVideoView.js.map +1 -1
- package/build/VideoScrubber.d.ts.map +1 -1
- package/build/VideoScrubber.js +4 -2
- package/build/VideoScrubber.js.map +1 -1
- package/build/components/DoubleTapSkip.d.ts +9 -0
- package/build/components/DoubleTapSkip.d.ts.map +1 -0
- package/build/components/DoubleTapSkip.js +124 -0
- package/build/components/DoubleTapSkip.js.map +1 -0
- package/build/components/PlayheadBar.d.ts +10 -0
- package/build/components/PlayheadBar.d.ts.map +1 -0
- package/build/components/PlayheadBar.js +144 -0
- package/build/components/PlayheadBar.js.map +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -224,6 +224,72 @@ TwoStepVideo.releaseAll(); // Release everything
|
|
|
224
224
|
TwoStepVideo.cleanupFile(result.uri); // Delete temp file
|
|
225
225
|
```
|
|
226
226
|
|
|
227
|
+
## Video Scrubber Component
|
|
228
|
+
|
|
229
|
+
### VideoScrubber
|
|
230
|
+
|
|
231
|
+
A native-style video scrubber/trimmer with thumbnail strip, draggable trim handles, and a draggable playhead for frame-accurate seeking:
|
|
232
|
+
|
|
233
|
+
```tsx
|
|
234
|
+
import { VideoScrubber, VideoScrubberRef } from 'expo-twostep-video';
|
|
235
|
+
|
|
236
|
+
function VideoEditor({ asset }: { asset: VideoAsset }) {
|
|
237
|
+
const playerRef = useRef<TwoStepVideoViewRef>(null);
|
|
238
|
+
const [trimStart, setTrimStart] = useState(0);
|
|
239
|
+
const [trimEnd, setTrimEnd] = useState(asset.duration);
|
|
240
|
+
const [currentTime, setCurrentTime] = useState(0);
|
|
241
|
+
|
|
242
|
+
return (
|
|
243
|
+
<View>
|
|
244
|
+
<TwoStepVideoView
|
|
245
|
+
ref={playerRef}
|
|
246
|
+
assetId={asset.id}
|
|
247
|
+
onProgress={(e) => setCurrentTime(e.nativeEvent.currentTime)}
|
|
248
|
+
/>
|
|
249
|
+
|
|
250
|
+
<VideoScrubber
|
|
251
|
+
assetId={asset.id}
|
|
252
|
+
duration={asset.duration}
|
|
253
|
+
currentTime={currentTime}
|
|
254
|
+
startTime={trimStart}
|
|
255
|
+
endTime={trimEnd}
|
|
256
|
+
onStartTimeChange={setTrimStart}
|
|
257
|
+
onEndTimeChange={setTrimEnd}
|
|
258
|
+
onScrub={(time) => playerRef.current?.seek(time)}
|
|
259
|
+
onScrubbing={(time) => playerRef.current?.seek(time)}
|
|
260
|
+
/>
|
|
261
|
+
</View>
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
**Props:**
|
|
267
|
+
|
|
268
|
+
| Prop | Type | Description |
|
|
269
|
+
|------|------|-------------|
|
|
270
|
+
| `assetId` | `string` | Asset ID for thumbnail generation |
|
|
271
|
+
| `duration` | `number` | Total video duration in seconds |
|
|
272
|
+
| `currentTime` | `number` | Current playback position (for playhead) |
|
|
273
|
+
| `startTime` | `number` | Selected start time in seconds |
|
|
274
|
+
| `endTime` | `number` | Selected end time in seconds |
|
|
275
|
+
| `thumbnailCount` | `number` | Number of thumbnails (default: 10) |
|
|
276
|
+
| `thumbnailHeight` | `number` | Height of thumbnail strip (default: 50) |
|
|
277
|
+
| `onStartTimeChange` | `function` | Called when start handle moves |
|
|
278
|
+
| `onEndTimeChange` | `function` | Called when end handle moves |
|
|
279
|
+
| `onScrub` | `function` | Called when playhead is tapped or dragged |
|
|
280
|
+
| `onScrubbing` | `function` | Called continuously during any drag |
|
|
281
|
+
| `onScrubEnd` | `function` | Called when drag ends |
|
|
282
|
+
| `minDuration` | `number` | Minimum selection duration (default: 0.5) |
|
|
283
|
+
| `showPlayhead` | `boolean` | Show playhead indicator (default: true) |
|
|
284
|
+
| `handleWidth` | `number` | Handle width in pixels (default: 20) |
|
|
285
|
+
| `theme` | `object` | Custom colors for handles, playhead, etc. |
|
|
286
|
+
|
|
287
|
+
**Features:**
|
|
288
|
+
- Drag the **yellow handles** to adjust trim start/end times
|
|
289
|
+
- Drag the **white playhead** to scrub through the video
|
|
290
|
+
- Tap anywhere in the selection to seek to that position
|
|
291
|
+
- All drag operations call `onScrubbing` for smooth video preview
|
|
292
|
+
|
|
227
293
|
## Video Player Component
|
|
228
294
|
|
|
229
295
|
### TwoStepVideoView
|
|
@@ -331,6 +397,9 @@ import type {
|
|
|
331
397
|
PanZoomState,
|
|
332
398
|
MirrorAxis,
|
|
333
399
|
VideoQuality,
|
|
400
|
+
VideoScrubberProps,
|
|
401
|
+
VideoScrubberRef,
|
|
402
|
+
VideoScrubberTheme,
|
|
334
403
|
} from 'expo-twostep-video';
|
|
335
404
|
```
|
|
336
405
|
|
|
@@ -86,6 +86,12 @@ export type TwoStepVideoViewProps = {
|
|
|
86
86
|
onPanZoomChange?: (event: {
|
|
87
87
|
nativeEvent: PanZoomChangeEvent;
|
|
88
88
|
}) => void;
|
|
89
|
+
/** Show playhead bar overlay at bottom of video (default: true) */
|
|
90
|
+
showPlayheadBar?: boolean;
|
|
91
|
+
/** Enable double-tap to skip forward/backward (default: true) */
|
|
92
|
+
enableDoubleTapSkip?: boolean;
|
|
93
|
+
/** Seconds to skip on double-tap (default: 5) */
|
|
94
|
+
doubleTapSkipInterval?: number;
|
|
89
95
|
/** View style */
|
|
90
96
|
style?: StyleProp<ViewStyle>;
|
|
91
97
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ExpoTwoStepVideo.types.d.ts","sourceRoot":"","sources":["../src/ExpoTwoStepVideo.types.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ExpoTwoStepVideo.types.d.ts","sourceRoot":"","sources":["../src/ExpoTwoStepVideo.types.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ExpoTwoStepVideo.types.js","sourceRoot":"","sources":["../src/ExpoTwoStepVideo.types.ts"],"names":[],"mappings":"","sourcesContent":["import type { StyleProp, ViewStyle } from 'react-native';\n\n/**\n * Playback status event payload\n */\nexport type PlaybackStatusEvent = {\n status: 'ready' | 'playing' | 'paused' | 'ended' | 'seeked';\n time?: number;\n};\n\n/**\n * Progress event payload\n */\nexport type ProgressEvent = {\n currentTime: number;\n duration: number;\n progress: number;\n};\n\n/**\n * Error event payload\n */\nexport type ErrorEvent = {\n error: string;\n};\n\n/**\n * Pan/Zoom state representing the current view transform\n */\nexport type PanZoomState = {\n /** Horizontal pan position (-1.0 to 1.0, 0 = center) */\n panX: number;\n /** Vertical pan position (-1.0 to 1.0, 0 = center) */\n panY: number;\n /** Zoom level (1.0 = no zoom, 2.0 = 2x zoom, etc.) */\n zoomLevel: number;\n};\n\n/**\n * Pan/Zoom change event payload\n */\nexport type PanZoomChangeEvent = PanZoomState;\n\n/**\n * Options for creating a pan/zoom composition\n */\nexport type PanZoomVideoOptions = {\n /** ID of the loaded asset */\n assetId: string;\n /** Horizontal pan position (-1.0 to 1.0, 0 = center) */\n panX?: number;\n /** Vertical pan position (-1.0 to 1.0, 0 = center) */\n panY?: number;\n /** Zoom level (1.0 = no zoom, 2.0 = 2x zoom, etc.) */\n zoomLevel?: number;\n /** Optional start time in seconds */\n startTime?: number;\n /** Optional end time in seconds */\n endTime?: number;\n};\n\n/**\n * Video player view props\n */\nexport type TwoStepVideoViewProps = {\n /** ID of a composition to play (from trimVideo, mirrorVideo, etc.) */\n compositionId?: string;\n /** ID of an asset to play (from loadAsset) */\n assetId?: string;\n /** Enable continuous looping - video will restart automatically when it ends */\n loop?: boolean;\n /** Minimum zoom level (default: 1.0) */\n minZoom?: number;\n /** Maximum zoom level (default: 5.0) */\n maxZoom?: number;\n /** Called when playback status changes */\n onPlaybackStatusChange?: (event: { nativeEvent: PlaybackStatusEvent }) => void;\n /** Called periodically with playback progress */\n onProgress?: (event: { nativeEvent: ProgressEvent }) => void;\n /** Called when playback reaches the end (not called if loop is true) */\n onEnd?: (event: { nativeEvent: Record<string, never> }) => void;\n /** Called when an error occurs */\n onError?: (event: { nativeEvent: ErrorEvent }) => void;\n /** Called when pan/zoom gesture changes */\n onPanZoomChange?: (event: { nativeEvent: PanZoomChangeEvent }) => void;\n /** View style */\n style?: StyleProp<ViewStyle>;\n};\n\n/**\n * Base ref handle for video player views (common playback controls)\n */\nexport type BaseVideoViewRef = {\n /** Start playback */\n play: () => Promise<void>;\n /** Pause playback */\n pause: () => Promise<void>;\n /** Seek to a specific time in seconds */\n seek: (time: number) => Promise<void>;\n /** Restart playback from the beginning */\n replay: () => Promise<void>;\n};\n\n/**\n * Ref handle for video player view (with pan/zoom support)\n */\nexport type TwoStepVideoViewRef = BaseVideoViewRef & {\n /** Get the current pan/zoom state */\n getPanZoomState: () => Promise<PanZoomState>;\n /** Set the pan/zoom state programmatically */\n setPanZoomState: (state: Partial<PanZoomState>) => Promise<void>;\n /** Reset pan/zoom to default state (zoom=1, pan=0,0) */\n resetPanZoom: () => Promise<void>;\n};\n\n/**\n * Video player controller view props (with native controls)\n */\nexport type TwoStepPlayerControllerViewProps = TwoStepVideoViewProps & {\n /** Show native playback controls (default: true) */\n showsPlaybackControls?: boolean;\n};\n\n/**\n * Ref handle for video player controller view (with fullscreen support)\n * Note: Pan/zoom gestures are not available on this view as it uses AVPlayerViewController\n */\nexport type TwoStepPlayerControllerViewRef = BaseVideoViewRef & {\n /** Enter fullscreen mode */\n enterFullscreen: () => Promise<void>;\n /** Exit fullscreen mode */\n exitFullscreen: () => Promise<void>;\n};\n"]}
|
|
1
|
+
{"version":3,"file":"ExpoTwoStepVideo.types.js","sourceRoot":"","sources":["../src/ExpoTwoStepVideo.types.ts"],"names":[],"mappings":"","sourcesContent":["import type { StyleProp, ViewStyle } from 'react-native';\n\n/**\n * Playback status event payload\n */\nexport type PlaybackStatusEvent = {\n status: 'ready' | 'playing' | 'paused' | 'ended' | 'seeked';\n time?: number;\n};\n\n/**\n * Progress event payload\n */\nexport type ProgressEvent = {\n currentTime: number;\n duration: number;\n progress: number;\n};\n\n/**\n * Error event payload\n */\nexport type ErrorEvent = {\n error: string;\n};\n\n/**\n * Pan/Zoom state representing the current view transform\n */\nexport type PanZoomState = {\n /** Horizontal pan position (-1.0 to 1.0, 0 = center) */\n panX: number;\n /** Vertical pan position (-1.0 to 1.0, 0 = center) */\n panY: number;\n /** Zoom level (1.0 = no zoom, 2.0 = 2x zoom, etc.) */\n zoomLevel: number;\n};\n\n/**\n * Pan/Zoom change event payload\n */\nexport type PanZoomChangeEvent = PanZoomState;\n\n/**\n * Options for creating a pan/zoom composition\n */\nexport type PanZoomVideoOptions = {\n /** ID of the loaded asset */\n assetId: string;\n /** Horizontal pan position (-1.0 to 1.0, 0 = center) */\n panX?: number;\n /** Vertical pan position (-1.0 to 1.0, 0 = center) */\n panY?: number;\n /** Zoom level (1.0 = no zoom, 2.0 = 2x zoom, etc.) */\n zoomLevel?: number;\n /** Optional start time in seconds */\n startTime?: number;\n /** Optional end time in seconds */\n endTime?: number;\n};\n\n/**\n * Video player view props\n */\nexport type TwoStepVideoViewProps = {\n /** ID of a composition to play (from trimVideo, mirrorVideo, etc.) */\n compositionId?: string;\n /** ID of an asset to play (from loadAsset) */\n assetId?: string;\n /** Enable continuous looping - video will restart automatically when it ends */\n loop?: boolean;\n /** Minimum zoom level (default: 1.0) */\n minZoom?: number;\n /** Maximum zoom level (default: 5.0) */\n maxZoom?: number;\n /** Called when playback status changes */\n onPlaybackStatusChange?: (event: { nativeEvent: PlaybackStatusEvent }) => void;\n /** Called periodically with playback progress */\n onProgress?: (event: { nativeEvent: ProgressEvent }) => void;\n /** Called when playback reaches the end (not called if loop is true) */\n onEnd?: (event: { nativeEvent: Record<string, never> }) => void;\n /** Called when an error occurs */\n onError?: (event: { nativeEvent: ErrorEvent }) => void;\n /** Called when pan/zoom gesture changes */\n onPanZoomChange?: (event: { nativeEvent: PanZoomChangeEvent }) => void;\n /** Show playhead bar overlay at bottom of video (default: true) */\n showPlayheadBar?: boolean;\n /** Enable double-tap to skip forward/backward (default: true) */\n enableDoubleTapSkip?: boolean;\n /** Seconds to skip on double-tap (default: 5) */\n doubleTapSkipInterval?: number;\n /** View style */\n style?: StyleProp<ViewStyle>;\n};\n\n/**\n * Base ref handle for video player views (common playback controls)\n */\nexport type BaseVideoViewRef = {\n /** Start playback */\n play: () => Promise<void>;\n /** Pause playback */\n pause: () => Promise<void>;\n /** Seek to a specific time in seconds */\n seek: (time: number) => Promise<void>;\n /** Restart playback from the beginning */\n replay: () => Promise<void>;\n};\n\n/**\n * Ref handle for video player view (with pan/zoom support)\n */\nexport type TwoStepVideoViewRef = BaseVideoViewRef & {\n /** Get the current pan/zoom state */\n getPanZoomState: () => Promise<PanZoomState>;\n /** Set the pan/zoom state programmatically */\n setPanZoomState: (state: Partial<PanZoomState>) => Promise<void>;\n /** Reset pan/zoom to default state (zoom=1, pan=0,0) */\n resetPanZoom: () => Promise<void>;\n};\n\n/**\n * Video player controller view props (with native controls)\n */\nexport type TwoStepPlayerControllerViewProps = TwoStepVideoViewProps & {\n /** Show native playback controls (default: true) */\n showsPlaybackControls?: boolean;\n};\n\n/**\n * Ref handle for video player controller view (with fullscreen support)\n * Note: Pan/zoom gestures are not available on this view as it uses AVPlayerViewController\n */\nexport type TwoStepPlayerControllerViewRef = BaseVideoViewRef & {\n /** Enter fullscreen mode */\n enterFullscreen: () => Promise<void>;\n /** Exit fullscreen mode */\n exitFullscreen: () => Promise<void>;\n};\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ExpoTwoStepVideoView.d.ts","sourceRoot":"","sources":["../src/ExpoTwoStepVideoView.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"ExpoTwoStepVideoView.d.ts","sourceRoot":"","sources":["../src/ExpoTwoStepVideoView.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAI/B,OAAO,EAAgB,qBAAqB,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAsBpG;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,QAAA,MAAM,gBAAgB,mGA0HrB,CAAC;AAIF,eAAe,gBAAgB,CAAC"}
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { requireNativeView } from 'expo';
|
|
2
2
|
import * as React from 'react';
|
|
3
|
-
import { forwardRef, useImperativeHandle, useRef } from 'react';
|
|
3
|
+
import { forwardRef, useCallback, useImperativeHandle, useRef } from 'react';
|
|
4
|
+
import { View } from 'react-native';
|
|
5
|
+
import PlayheadBar from './components/PlayheadBar';
|
|
6
|
+
import DoubleTapSkip from './components/DoubleTapSkip';
|
|
4
7
|
const NativeView = requireNativeView('ExpoTwoStepVideo');
|
|
5
8
|
/**
|
|
6
9
|
* Video player view that can play compositions and assets directly
|
|
@@ -25,7 +28,15 @@ const NativeView = requireNativeView('ExpoTwoStepVideo');
|
|
|
25
28
|
* ```
|
|
26
29
|
*/
|
|
27
30
|
const TwoStepVideoView = forwardRef((props, ref) => {
|
|
31
|
+
const { showPlayheadBar = true, enableDoubleTapSkip = true, doubleTapSkipInterval = 5, onProgress, onPlaybackStatusChange, style, ...nativeProps } = props;
|
|
28
32
|
const nativeRef = useRef(null);
|
|
33
|
+
const currentTimeRef = useRef(0);
|
|
34
|
+
const durationRef = useRef(0);
|
|
35
|
+
const isPlayingRef = useRef(false);
|
|
36
|
+
const wasPlayingRef = useRef(false);
|
|
37
|
+
// Force re-render for overlay state
|
|
38
|
+
const [currentTime, setCurrentTime] = React.useState(0);
|
|
39
|
+
const [duration, setDuration] = React.useState(0);
|
|
29
40
|
useImperativeHandle(ref, () => ({
|
|
30
41
|
play: async () => {
|
|
31
42
|
await nativeRef.current?.play();
|
|
@@ -50,7 +61,45 @@ const TwoStepVideoView = forwardRef((props, ref) => {
|
|
|
50
61
|
await nativeRef.current?.resetPanZoom();
|
|
51
62
|
},
|
|
52
63
|
}));
|
|
53
|
-
|
|
64
|
+
const handleProgress = useCallback((event) => {
|
|
65
|
+
currentTimeRef.current = event.nativeEvent.currentTime;
|
|
66
|
+
durationRef.current = event.nativeEvent.duration;
|
|
67
|
+
setCurrentTime(event.nativeEvent.currentTime);
|
|
68
|
+
setDuration(event.nativeEvent.duration);
|
|
69
|
+
onProgress?.(event);
|
|
70
|
+
}, [onProgress]);
|
|
71
|
+
const handlePlaybackStatusChange = useCallback((event) => {
|
|
72
|
+
isPlayingRef.current = event.nativeEvent.status === 'playing';
|
|
73
|
+
onPlaybackStatusChange?.(event);
|
|
74
|
+
}, [onPlaybackStatusChange]);
|
|
75
|
+
// PlayheadBar callbacks
|
|
76
|
+
const handleSeekStart = useCallback(() => {
|
|
77
|
+
wasPlayingRef.current = isPlayingRef.current;
|
|
78
|
+
if (isPlayingRef.current) {
|
|
79
|
+
nativeRef.current?.pause();
|
|
80
|
+
}
|
|
81
|
+
}, []);
|
|
82
|
+
const handlePlayheadSeek = useCallback((time) => {
|
|
83
|
+
nativeRef.current?.seek(time);
|
|
84
|
+
currentTimeRef.current = time;
|
|
85
|
+
setCurrentTime(time);
|
|
86
|
+
}, []);
|
|
87
|
+
const handleSeekEnd = useCallback(() => {
|
|
88
|
+
if (wasPlayingRef.current) {
|
|
89
|
+
nativeRef.current?.play();
|
|
90
|
+
}
|
|
91
|
+
}, []);
|
|
92
|
+
// DoubleTapSkip callback
|
|
93
|
+
const handleDoubleTapSeek = useCallback((time) => {
|
|
94
|
+
nativeRef.current?.seek(time);
|
|
95
|
+
currentTimeRef.current = time;
|
|
96
|
+
setCurrentTime(time);
|
|
97
|
+
}, []);
|
|
98
|
+
return (<View style={[style, { overflow: 'hidden' }]}>
|
|
99
|
+
<NativeView ref={nativeRef} {...nativeProps} onProgress={handleProgress} onPlaybackStatusChange={handlePlaybackStatusChange} style={{ flex: 1 }}/>
|
|
100
|
+
{enableDoubleTapSkip && (<DoubleTapSkip currentTime={currentTime} duration={duration} skipInterval={doubleTapSkipInterval} onSeek={handleDoubleTapSeek}/>)}
|
|
101
|
+
{showPlayheadBar && (<PlayheadBar currentTime={currentTime} duration={duration} onSeekStart={handleSeekStart} onSeek={handlePlayheadSeek} onSeekEnd={handleSeekEnd}/>)}
|
|
102
|
+
</View>);
|
|
54
103
|
});
|
|
55
104
|
TwoStepVideoView.displayName = 'TwoStepVideoView';
|
|
56
105
|
export default TwoStepVideoView;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ExpoTwoStepVideoView.js","sourceRoot":"","sources":["../src/ExpoTwoStepVideoView.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAC;AACzC,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,EAAE,UAAU,EAAE,mBAAmB,EAAE,MAAM,EAAE,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"ExpoTwoStepVideoView.js","sourceRoot":"","sources":["../src/ExpoTwoStepVideoView.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAC;AACzC,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,mBAAmB,EAAE,MAAM,EAAE,MAAM,OAAO,CAAC;AAC7E,OAAO,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AAGpC,OAAO,WAAW,MAAM,0BAA0B,CAAC;AACnD,OAAO,aAAa,MAAM,4BAA4B,CAAC;AAkBvD,MAAM,UAAU,GAAyC,iBAAiB,CAAC,kBAAkB,CAAC,CAAC;AAE/F;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,gBAAgB,GAAG,UAAU,CACjC,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE;IACb,MAAM,EACJ,eAAe,GAAG,IAAI,EACtB,mBAAmB,GAAG,IAAI,EAC1B,qBAAqB,GAAG,CAAC,EACzB,UAAU,EACV,sBAAsB,EACtB,KAAK,EACL,GAAG,WAAW,EACf,GAAG,KAAK,CAAC;IAEV,MAAM,SAAS,GAAG,MAAM,CAAgB,IAAI,CAAC,CAAC;IAC9C,MAAM,cAAc,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;IACjC,MAAM,WAAW,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;IAC9B,MAAM,YAAY,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;IACnC,MAAM,aAAa,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;IAEpC,oCAAoC;IACpC,MAAM,CAAC,WAAW,EAAE,cAAc,CAAC,GAAG,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;IACxD,MAAM,CAAC,QAAQ,EAAE,WAAW,CAAC,GAAG,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;IAElD,mBAAmB,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC,CAAC;QAC9B,IAAI,EAAE,KAAK,IAAI,EAAE;YACf,MAAM,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC;QAClC,CAAC;QACD,KAAK,EAAE,KAAK,IAAI,EAAE;YAChB,MAAM,SAAS,CAAC,OAAO,EAAE,KAAK,EAAE,CAAC;QACnC,CAAC;QACD,IAAI,EAAE,KAAK,EAAE,IAAY,EAAE,EAAE;YAC3B,MAAM,SAAS,CAAC,OAAO,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;QACtC,CAAC;QACD,MAAM,EAAE,KAAK,IAAI,EAAE;YACjB,MAAM,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,CAAC;QACpC,CAAC;QACD,eAAe,EAAE,KAAK,IAAI,EAAE;YAC1B,MAAM,KAAK,GAAG,MAAM,SAAS,CAAC,OAAO,EAAE,eAAe,EAAE,CAAC;YACzD,OAAO,KAAK,IAAI,EAAE,IAAI,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,CAAC;QACrD,CAAC;QACD,eAAe,EAAE,KAAK,EAAE,KAA4B,EAAE,EAAE;YACtD,MAAM,SAAS,CAAC,OAAO,EAAE,eAAe,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC;QACpF,CAAC;QACD,YAAY,EAAE,KAAK,IAAI,EAAE;YACvB,MAAM,SAAS,CAAC,OAAO,EAAE,YAAY,EAAE,CAAC;QAC1C,CAAC;KACF,CAAC,CAAC,CAAC;IAEJ,MAAM,cAAc,GAAG,WAAW,CAChC,CAAC,KAAmF,EAAE,EAAE;QACtF,cAAc,CAAC,OAAO,GAAG,KAAK,CAAC,WAAW,CAAC,WAAW,CAAC;QACvD,WAAW,CAAC,OAAO,GAAG,KAAK,CAAC,WAAW,CAAC,QAAQ,CAAC;QACjD,cAAc,CAAC,KAAK,CAAC,WAAW,CAAC,WAAW,CAAC,CAAC;QAC9C,WAAW,CAAC,KAAK,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC;QACxC,UAAU,EAAE,CAAC,KAAK,CAAC,CAAC;IACtB,CAAC,EACD,CAAC,UAAU,CAAC,CACb,CAAC;IAEF,MAAM,0BAA0B,GAAG,WAAW,CAC5C,CAAC,KAAyD,EAAE,EAAE;QAC5D,YAAY,CAAC,OAAO,GAAG,KAAK,CAAC,WAAW,CAAC,MAAM,KAAK,SAAS,CAAC;QAC9D,sBAAsB,EAAE,CAAC,KAAY,CAAC,CAAC;IACzC,CAAC,EACD,CAAC,sBAAsB,CAAC,CACzB,CAAC;IAEF,wBAAwB;IACxB,MAAM,eAAe,GAAG,WAAW,CAAC,GAAG,EAAE;QACvC,aAAa,CAAC,OAAO,GAAG,YAAY,CAAC,OAAO,CAAC;QAC7C,IAAI,YAAY,CAAC,OAAO,EAAE,CAAC;YACzB,SAAS,CAAC,OAAO,EAAE,KAAK,EAAE,CAAC;QAC7B,CAAC;IACH,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,MAAM,kBAAkB,GAAG,WAAW,CAAC,CAAC,IAAY,EAAE,EAAE;QACtD,SAAS,CAAC,OAAO,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;QAC9B,cAAc,CAAC,OAAO,GAAG,IAAI,CAAC;QAC9B,cAAc,CAAC,IAAI,CAAC,CAAC;IACvB,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,MAAM,aAAa,GAAG,WAAW,CAAC,GAAG,EAAE;QACrC,IAAI,aAAa,CAAC,OAAO,EAAE,CAAC;YAC1B,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC;QAC5B,CAAC;IACH,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,yBAAyB;IACzB,MAAM,mBAAmB,GAAG,WAAW,CAAC,CAAC,IAAY,EAAE,EAAE;QACvD,SAAS,CAAC,OAAO,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;QAC9B,cAAc,CAAC,OAAO,GAAG,IAAI,CAAC;QAC9B,cAAc,CAAC,IAAI,CAAC,CAAC;IACvB,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,OAAO,CACL,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,EAAE,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC,CAAC,CAC3C;QAAA,CAAC,UAAU,CACT,GAAG,CAAC,CAAC,SAAS,CAAC,CACf,IAAI,WAAW,CAAC,CAChB,UAAU,CAAC,CAAC,cAAc,CAAC,CAC3B,sBAAsB,CAAC,CAAC,0BAA0B,CAAC,CACnD,KAAK,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC,EAErB;QAAA,CAAC,mBAAmB,IAAI,CACtB,CAAC,aAAa,CACZ,WAAW,CAAC,CAAC,WAAW,CAAC,CACzB,QAAQ,CAAC,CAAC,QAAQ,CAAC,CACnB,YAAY,CAAC,CAAC,qBAAqB,CAAC,CACpC,MAAM,CAAC,CAAC,mBAAmB,CAAC,EAC5B,CACH,CACD;QAAA,CAAC,eAAe,IAAI,CAClB,CAAC,WAAW,CACV,WAAW,CAAC,CAAC,WAAW,CAAC,CACzB,QAAQ,CAAC,CAAC,QAAQ,CAAC,CACnB,WAAW,CAAC,CAAC,eAAe,CAAC,CAC7B,MAAM,CAAC,CAAC,kBAAkB,CAAC,CAC3B,SAAS,CAAC,CAAC,aAAa,CAAC,EACzB,CACH,CACH;MAAA,EAAE,IAAI,CAAC,CACR,CAAC;AACJ,CAAC,CACF,CAAC;AAEF,gBAAgB,CAAC,WAAW,GAAG,kBAAkB,CAAC;AAElD,eAAe,gBAAgB,CAAC","sourcesContent":["import { requireNativeView } from 'expo';\nimport * as React from 'react';\nimport { forwardRef, useCallback, useImperativeHandle, useRef } from 'react';\nimport { View } from 'react-native';\n\nimport { PanZoomState, TwoStepVideoViewProps, TwoStepVideoViewRef } from './ExpoTwoStepVideo.types';\nimport PlayheadBar from './components/PlayheadBar';\nimport DoubleTapSkip from './components/DoubleTapSkip';\n\n// Internal props type that includes the native ref\ntype NativeViewProps = TwoStepVideoViewProps & {\n ref?: React.Ref<NativeViewRef>;\n};\n\n// Native view ref with native methods\ntype NativeViewRef = {\n play: () => Promise<void>;\n pause: () => Promise<void>;\n seek: (time: number) => Promise<void>;\n replay: () => Promise<void>;\n getPanZoomState: () => Promise<PanZoomState>;\n setPanZoomState: (panX?: number, panY?: number, zoomLevel?: number) => Promise<void>;\n resetPanZoom: () => Promise<void>;\n};\n\nconst NativeView: React.ComponentType<NativeViewProps> = requireNativeView('ExpoTwoStepVideo');\n\n/**\n * Video player view that can play compositions and assets directly\n *\n * @example\n * ```tsx\n * const playerRef = useRef<TwoStepVideoViewRef>(null);\n *\n * // Play a composition (from mirrorVideo, trimVideo, etc.)\n * <TwoStepVideoView\n * ref={playerRef}\n * compositionId={composition.id}\n * onPlaybackStatusChange={(e) => console.log(e.nativeEvent.status)}\n * onProgress={(e) => setProgress(e.nativeEvent.progress)}\n * style={{ width: '100%', height: 300 }}\n * />\n *\n * // Control playback\n * playerRef.current?.play();\n * playerRef.current?.pause();\n * playerRef.current?.seek(5.0);\n * ```\n */\nconst TwoStepVideoView = forwardRef<TwoStepVideoViewRef, TwoStepVideoViewProps>(\n (props, ref) => {\n const {\n showPlayheadBar = true,\n enableDoubleTapSkip = true,\n doubleTapSkipInterval = 5,\n onProgress,\n onPlaybackStatusChange,\n style,\n ...nativeProps\n } = props;\n\n const nativeRef = useRef<NativeViewRef>(null);\n const currentTimeRef = useRef(0);\n const durationRef = useRef(0);\n const isPlayingRef = useRef(false);\n const wasPlayingRef = useRef(false);\n\n // Force re-render for overlay state\n const [currentTime, setCurrentTime] = React.useState(0);\n const [duration, setDuration] = React.useState(0);\n\n useImperativeHandle(ref, () => ({\n play: async () => {\n await nativeRef.current?.play();\n },\n pause: async () => {\n await nativeRef.current?.pause();\n },\n seek: async (time: number) => {\n await nativeRef.current?.seek(time);\n },\n replay: async () => {\n await nativeRef.current?.replay();\n },\n getPanZoomState: async () => {\n const state = await nativeRef.current?.getPanZoomState();\n return state ?? { panX: 0, panY: 0, zoomLevel: 1 };\n },\n setPanZoomState: async (state: Partial<PanZoomState>) => {\n await nativeRef.current?.setPanZoomState(state.panX, state.panY, state.zoomLevel);\n },\n resetPanZoom: async () => {\n await nativeRef.current?.resetPanZoom();\n },\n }));\n\n const handleProgress = useCallback(\n (event: { nativeEvent: { currentTime: number; duration: number; progress: number } }) => {\n currentTimeRef.current = event.nativeEvent.currentTime;\n durationRef.current = event.nativeEvent.duration;\n setCurrentTime(event.nativeEvent.currentTime);\n setDuration(event.nativeEvent.duration);\n onProgress?.(event);\n },\n [onProgress]\n );\n\n const handlePlaybackStatusChange = useCallback(\n (event: { nativeEvent: { status: string; time?: number } }) => {\n isPlayingRef.current = event.nativeEvent.status === 'playing';\n onPlaybackStatusChange?.(event as any);\n },\n [onPlaybackStatusChange]\n );\n\n // PlayheadBar callbacks\n const handleSeekStart = useCallback(() => {\n wasPlayingRef.current = isPlayingRef.current;\n if (isPlayingRef.current) {\n nativeRef.current?.pause();\n }\n }, []);\n\n const handlePlayheadSeek = useCallback((time: number) => {\n nativeRef.current?.seek(time);\n currentTimeRef.current = time;\n setCurrentTime(time);\n }, []);\n\n const handleSeekEnd = useCallback(() => {\n if (wasPlayingRef.current) {\n nativeRef.current?.play();\n }\n }, []);\n\n // DoubleTapSkip callback\n const handleDoubleTapSeek = useCallback((time: number) => {\n nativeRef.current?.seek(time);\n currentTimeRef.current = time;\n setCurrentTime(time);\n }, []);\n\n return (\n <View style={[style, { overflow: 'hidden' }]}>\n <NativeView\n ref={nativeRef}\n {...nativeProps}\n onProgress={handleProgress}\n onPlaybackStatusChange={handlePlaybackStatusChange}\n style={{ flex: 1 }}\n />\n {enableDoubleTapSkip && (\n <DoubleTapSkip\n currentTime={currentTime}\n duration={duration}\n skipInterval={doubleTapSkipInterval}\n onSeek={handleDoubleTapSeek}\n />\n )}\n {showPlayheadBar && (\n <PlayheadBar\n currentTime={currentTime}\n duration={duration}\n onSeekStart={handleSeekStart}\n onSeek={handlePlayheadSeek}\n onSeekEnd={handleSeekEnd}\n />\n )}\n </View>\n );\n }\n);\n\nTwoStepVideoView.displayName = 'TwoStepVideoView';\n\nexport default TwoStepVideoView;\n"]}
|
|
@@ -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,6FAqdjB,CAAC;AAuGH,eAAe,aAAa,CAAC"}
|
package/build/VideoScrubber.js
CHANGED
|
@@ -368,10 +368,11 @@ const VideoScrubber = forwardRef((props, ref) => {
|
|
|
368
368
|
left: startPosition,
|
|
369
369
|
width: Math.max(0, endPosition - startPosition),
|
|
370
370
|
height: thumbnailHeight,
|
|
371
|
+
zIndex: 1,
|
|
371
372
|
},
|
|
372
373
|
]} onTouchEnd={handleTap}/>
|
|
373
374
|
|
|
374
|
-
{/* Playhead */}
|
|
375
|
+
{/* Playhead - rendered last for highest z-index */}
|
|
375
376
|
{showPlayhead && playheadPosition >= startPosition && playheadPosition <= endPosition && (<Animated.View style={[
|
|
376
377
|
styles.playhead,
|
|
377
378
|
{
|
|
@@ -380,8 +381,9 @@ const VideoScrubber = forwardRef((props, ref) => {
|
|
|
380
381
|
top: -4,
|
|
381
382
|
backgroundColor: theme.playheadColor,
|
|
382
383
|
opacity: dragging === 'playhead' ? 0.8 : 1,
|
|
384
|
+
zIndex: 10,
|
|
383
385
|
},
|
|
384
|
-
]} {...playheadPanResponder.panHandlers}>
|
|
386
|
+
]} hitSlop={{ top: 20, bottom: 20, left: 20, right: 20 }} {...playheadPanResponder.panHandlers}>
|
|
385
387
|
<View style={[styles.playheadKnob, { backgroundColor: theme.playheadColor }]}/>
|
|
386
388
|
</Animated.View>)}
|
|
387
389
|
</View>);
|
|
@@ -1 +1 @@
|
|
|
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"]}
|
|
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 zIndex: 1,\n },\n ]}\n onTouchEnd={handleTap}\n />\n\n {/* Playhead - rendered last for highest z-index */}\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 zIndex: 10,\n },\n ]}\n hitSlop={{ top: 20, bottom: 20, left: 20, right: 20 }}\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"]}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
export type DoubleTapSkipProps = {
|
|
3
|
+
currentTime: number;
|
|
4
|
+
duration: number;
|
|
5
|
+
skipInterval: number;
|
|
6
|
+
onSeek: (time: number) => void;
|
|
7
|
+
};
|
|
8
|
+
export default function DoubleTapSkip({ currentTime, duration, skipInterval, onSeek, }: DoubleTapSkipProps): React.JSX.Element;
|
|
9
|
+
//# sourceMappingURL=DoubleTapSkip.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DoubleTapSkip.d.ts","sourceRoot":"","sources":["../../src/components/DoubleTapSkip.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAU/B,MAAM,MAAM,kBAAkB,GAAG;IAC/B,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CAChC,CAAC;AAIF,MAAM,CAAC,OAAO,UAAU,aAAa,CAAC,EACpC,WAAW,EACX,QAAQ,EACR,YAAY,EACZ,MAAM,GACP,EAAE,kBAAkB,qBA2FpB"}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { useRef, useCallback } from 'react';
|
|
3
|
+
import { View, StyleSheet, TouchableWithoutFeedback, Animated, Text, } from 'react-native';
|
|
4
|
+
const DOUBLE_TAP_DELAY = 300;
|
|
5
|
+
export default function DoubleTapSkip({ currentTime, duration, skipInterval, onSeek, }) {
|
|
6
|
+
const currentTimeRef = useRef(currentTime);
|
|
7
|
+
currentTimeRef.current = currentTime;
|
|
8
|
+
const durationRef = useRef(duration);
|
|
9
|
+
durationRef.current = duration;
|
|
10
|
+
const lastTapLeft = useRef(0);
|
|
11
|
+
const lastTapRight = useRef(0);
|
|
12
|
+
const leftTimeoutRef = useRef(null);
|
|
13
|
+
const rightTimeoutRef = useRef(null);
|
|
14
|
+
const leftOpacity = useRef(new Animated.Value(0)).current;
|
|
15
|
+
const rightOpacity = useRef(new Animated.Value(0)).current;
|
|
16
|
+
const showFeedback = useCallback((opacity) => {
|
|
17
|
+
opacity.setValue(1);
|
|
18
|
+
Animated.timing(opacity, {
|
|
19
|
+
toValue: 0,
|
|
20
|
+
duration: 600,
|
|
21
|
+
delay: 200,
|
|
22
|
+
useNativeDriver: true,
|
|
23
|
+
}).start();
|
|
24
|
+
}, []);
|
|
25
|
+
const handleLeftTap = useCallback(() => {
|
|
26
|
+
const now = Date.now();
|
|
27
|
+
if (now - lastTapLeft.current < DOUBLE_TAP_DELAY) {
|
|
28
|
+
// Double tap detected
|
|
29
|
+
if (leftTimeoutRef.current) {
|
|
30
|
+
clearTimeout(leftTimeoutRef.current);
|
|
31
|
+
leftTimeoutRef.current = null;
|
|
32
|
+
}
|
|
33
|
+
const newTime = Math.max(0, currentTimeRef.current - skipInterval);
|
|
34
|
+
onSeek(newTime);
|
|
35
|
+
showFeedback(leftOpacity);
|
|
36
|
+
lastTapLeft.current = 0;
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
lastTapLeft.current = now;
|
|
40
|
+
leftTimeoutRef.current = setTimeout(() => {
|
|
41
|
+
lastTapLeft.current = 0;
|
|
42
|
+
leftTimeoutRef.current = null;
|
|
43
|
+
}, DOUBLE_TAP_DELAY);
|
|
44
|
+
}
|
|
45
|
+
}, [skipInterval, onSeek, showFeedback, leftOpacity]);
|
|
46
|
+
const handleRightTap = useCallback(() => {
|
|
47
|
+
const now = Date.now();
|
|
48
|
+
if (now - lastTapRight.current < DOUBLE_TAP_DELAY) {
|
|
49
|
+
// Double tap detected
|
|
50
|
+
if (rightTimeoutRef.current) {
|
|
51
|
+
clearTimeout(rightTimeoutRef.current);
|
|
52
|
+
rightTimeoutRef.current = null;
|
|
53
|
+
}
|
|
54
|
+
const newTime = Math.min(durationRef.current, currentTimeRef.current + skipInterval);
|
|
55
|
+
onSeek(newTime);
|
|
56
|
+
showFeedback(rightOpacity);
|
|
57
|
+
lastTapRight.current = 0;
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
lastTapRight.current = now;
|
|
61
|
+
rightTimeoutRef.current = setTimeout(() => {
|
|
62
|
+
lastTapRight.current = 0;
|
|
63
|
+
rightTimeoutRef.current = null;
|
|
64
|
+
}, DOUBLE_TAP_DELAY);
|
|
65
|
+
}
|
|
66
|
+
}, [skipInterval, onSeek, showFeedback, rightOpacity]);
|
|
67
|
+
return (<View style={styles.container} pointerEvents="box-none">
|
|
68
|
+
<TouchableWithoutFeedback onPress={handleLeftTap}>
|
|
69
|
+
<View style={styles.half}>
|
|
70
|
+
<Animated.View style={[styles.feedback, { opacity: leftOpacity }]}>
|
|
71
|
+
<View style={styles.feedbackCircle}>
|
|
72
|
+
<Text style={styles.arrowText}>{'<<'}</Text>
|
|
73
|
+
<Text style={styles.skipText}>{skipInterval}s</Text>
|
|
74
|
+
</View>
|
|
75
|
+
</Animated.View>
|
|
76
|
+
</View>
|
|
77
|
+
</TouchableWithoutFeedback>
|
|
78
|
+
<TouchableWithoutFeedback onPress={handleRightTap}>
|
|
79
|
+
<View style={styles.half}>
|
|
80
|
+
<Animated.View style={[styles.feedback, { opacity: rightOpacity }]}>
|
|
81
|
+
<View style={styles.feedbackCircle}>
|
|
82
|
+
<Text style={styles.arrowText}>{'>>'}</Text>
|
|
83
|
+
<Text style={styles.skipText}>{skipInterval}s</Text>
|
|
84
|
+
</View>
|
|
85
|
+
</Animated.View>
|
|
86
|
+
</View>
|
|
87
|
+
</TouchableWithoutFeedback>
|
|
88
|
+
</View>);
|
|
89
|
+
}
|
|
90
|
+
const styles = StyleSheet.create({
|
|
91
|
+
container: {
|
|
92
|
+
...StyleSheet.absoluteFillObject,
|
|
93
|
+
flexDirection: 'row',
|
|
94
|
+
},
|
|
95
|
+
half: {
|
|
96
|
+
flex: 1,
|
|
97
|
+
justifyContent: 'center',
|
|
98
|
+
alignItems: 'center',
|
|
99
|
+
},
|
|
100
|
+
feedback: {
|
|
101
|
+
justifyContent: 'center',
|
|
102
|
+
alignItems: 'center',
|
|
103
|
+
},
|
|
104
|
+
feedbackCircle: {
|
|
105
|
+
width: 64,
|
|
106
|
+
height: 64,
|
|
107
|
+
borderRadius: 32,
|
|
108
|
+
backgroundColor: 'rgba(0,0,0,0.4)',
|
|
109
|
+
justifyContent: 'center',
|
|
110
|
+
alignItems: 'center',
|
|
111
|
+
},
|
|
112
|
+
arrowText: {
|
|
113
|
+
color: '#FFFFFF',
|
|
114
|
+
fontSize: 20,
|
|
115
|
+
fontWeight: '700',
|
|
116
|
+
},
|
|
117
|
+
skipText: {
|
|
118
|
+
color: '#FFFFFF',
|
|
119
|
+
fontSize: 12,
|
|
120
|
+
fontWeight: '600',
|
|
121
|
+
marginTop: 2,
|
|
122
|
+
},
|
|
123
|
+
});
|
|
124
|
+
//# sourceMappingURL=DoubleTapSkip.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DoubleTapSkip.js","sourceRoot":"","sources":["../../src/components/DoubleTapSkip.tsx"],"names":[],"mappings":"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","sourcesContent":["import * as React from 'react';\nimport { useRef, useCallback } from 'react';\nimport {\n View,\n StyleSheet,\n TouchableWithoutFeedback,\n Animated,\n Text,\n} from 'react-native';\n\nexport type DoubleTapSkipProps = {\n currentTime: number;\n duration: number;\n skipInterval: number;\n onSeek: (time: number) => void;\n};\n\nconst DOUBLE_TAP_DELAY = 300;\n\nexport default function DoubleTapSkip({\n currentTime,\n duration,\n skipInterval,\n onSeek,\n}: DoubleTapSkipProps) {\n const currentTimeRef = useRef(currentTime);\n currentTimeRef.current = currentTime;\n\n const durationRef = useRef(duration);\n durationRef.current = duration;\n\n const lastTapLeft = useRef(0);\n const lastTapRight = useRef(0);\n const leftTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const rightTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n const leftOpacity = useRef(new Animated.Value(0)).current;\n const rightOpacity = useRef(new Animated.Value(0)).current;\n\n const showFeedback = useCallback((opacity: Animated.Value) => {\n opacity.setValue(1);\n Animated.timing(opacity, {\n toValue: 0,\n duration: 600,\n delay: 200,\n useNativeDriver: true,\n }).start();\n }, []);\n\n const handleLeftTap = useCallback(() => {\n const now = Date.now();\n if (now - lastTapLeft.current < DOUBLE_TAP_DELAY) {\n // Double tap detected\n if (leftTimeoutRef.current) {\n clearTimeout(leftTimeoutRef.current);\n leftTimeoutRef.current = null;\n }\n const newTime = Math.max(0, currentTimeRef.current - skipInterval);\n onSeek(newTime);\n showFeedback(leftOpacity);\n lastTapLeft.current = 0;\n } else {\n lastTapLeft.current = now;\n leftTimeoutRef.current = setTimeout(() => {\n lastTapLeft.current = 0;\n leftTimeoutRef.current = null;\n }, DOUBLE_TAP_DELAY);\n }\n }, [skipInterval, onSeek, showFeedback, leftOpacity]);\n\n const handleRightTap = useCallback(() => {\n const now = Date.now();\n if (now - lastTapRight.current < DOUBLE_TAP_DELAY) {\n // Double tap detected\n if (rightTimeoutRef.current) {\n clearTimeout(rightTimeoutRef.current);\n rightTimeoutRef.current = null;\n }\n const newTime = Math.min(durationRef.current, currentTimeRef.current + skipInterval);\n onSeek(newTime);\n showFeedback(rightOpacity);\n lastTapRight.current = 0;\n } else {\n lastTapRight.current = now;\n rightTimeoutRef.current = setTimeout(() => {\n lastTapRight.current = 0;\n rightTimeoutRef.current = null;\n }, DOUBLE_TAP_DELAY);\n }\n }, [skipInterval, onSeek, showFeedback, rightOpacity]);\n\n return (\n <View style={styles.container} pointerEvents=\"box-none\">\n <TouchableWithoutFeedback onPress={handleLeftTap}>\n <View style={styles.half}>\n <Animated.View style={[styles.feedback, { opacity: leftOpacity }]}>\n <View style={styles.feedbackCircle}>\n <Text style={styles.arrowText}>{'<<'}</Text>\n <Text style={styles.skipText}>{skipInterval}s</Text>\n </View>\n </Animated.View>\n </View>\n </TouchableWithoutFeedback>\n <TouchableWithoutFeedback onPress={handleRightTap}>\n <View style={styles.half}>\n <Animated.View style={[styles.feedback, { opacity: rightOpacity }]}>\n <View style={styles.feedbackCircle}>\n <Text style={styles.arrowText}>{'>>'}</Text>\n <Text style={styles.skipText}>{skipInterval}s</Text>\n </View>\n </Animated.View>\n </View>\n </TouchableWithoutFeedback>\n </View>\n );\n}\n\nconst styles = StyleSheet.create({\n container: {\n ...StyleSheet.absoluteFillObject,\n flexDirection: 'row',\n },\n half: {\n flex: 1,\n justifyContent: 'center',\n alignItems: 'center',\n },\n feedback: {\n justifyContent: 'center',\n alignItems: 'center',\n },\n feedbackCircle: {\n width: 64,\n height: 64,\n borderRadius: 32,\n backgroundColor: 'rgba(0,0,0,0.4)',\n justifyContent: 'center',\n alignItems: 'center',\n },\n arrowText: {\n color: '#FFFFFF',\n fontSize: 20,\n fontWeight: '700',\n },\n skipText: {\n color: '#FFFFFF',\n fontSize: 12,\n fontWeight: '600',\n marginTop: 2,\n },\n});\n"]}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
export type PlayheadBarProps = {
|
|
3
|
+
currentTime: number;
|
|
4
|
+
duration: number;
|
|
5
|
+
onSeekStart: () => void;
|
|
6
|
+
onSeek: (time: number) => void;
|
|
7
|
+
onSeekEnd: () => void;
|
|
8
|
+
};
|
|
9
|
+
export default function PlayheadBar({ currentTime, duration, onSeekStart, onSeek, onSeekEnd, }: PlayheadBarProps): React.JSX.Element;
|
|
10
|
+
//# sourceMappingURL=PlayheadBar.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PlayheadBar.d.ts","sourceRoot":"","sources":["../../src/components/PlayheadBar.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAU/B,MAAM,MAAM,gBAAgB,GAAG;IAC7B,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,IAAI,CAAC;IACxB,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B,SAAS,EAAE,MAAM,IAAI,CAAC;CACvB,CAAC;AAOF,MAAM,CAAC,OAAO,UAAU,WAAW,CAAC,EAClC,WAAW,EACX,QAAQ,EACR,WAAW,EACX,MAAM,EACN,SAAS,GACV,EAAE,gBAAgB,qBA6HlB"}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { useRef, useCallback, useEffect } from 'react';
|
|
3
|
+
import { View, StyleSheet, PanResponder, Animated, } from 'react-native';
|
|
4
|
+
const COLLAPSED_HEIGHT = 2;
|
|
5
|
+
const EXPANDED_HEIGHT = 4;
|
|
6
|
+
const THUMB_SIZE = 14;
|
|
7
|
+
const HIT_SLOP_HEIGHT = 30;
|
|
8
|
+
export default function PlayheadBar({ currentTime, duration, onSeekStart, onSeek, onSeekEnd, }) {
|
|
9
|
+
const containerWidthRef = useRef(0);
|
|
10
|
+
const currentTimeRef = useRef(currentTime);
|
|
11
|
+
const durationRef = useRef(duration);
|
|
12
|
+
const isScrubbing = useRef(false);
|
|
13
|
+
const barHeight = useRef(new Animated.Value(COLLAPSED_HEIGHT)).current;
|
|
14
|
+
const thumbScale = useRef(new Animated.Value(0)).current;
|
|
15
|
+
const progress = useRef(new Animated.Value(0)).current;
|
|
16
|
+
useEffect(() => {
|
|
17
|
+
currentTimeRef.current = currentTime;
|
|
18
|
+
if (!isScrubbing.current && duration > 0) {
|
|
19
|
+
progress.setValue(currentTime / duration);
|
|
20
|
+
}
|
|
21
|
+
}, [currentTime, duration, progress]);
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
durationRef.current = duration;
|
|
24
|
+
}, [duration]);
|
|
25
|
+
const expand = useCallback(() => {
|
|
26
|
+
Animated.parallel([
|
|
27
|
+
Animated.timing(barHeight, {
|
|
28
|
+
toValue: EXPANDED_HEIGHT,
|
|
29
|
+
duration: 150,
|
|
30
|
+
useNativeDriver: false,
|
|
31
|
+
}),
|
|
32
|
+
Animated.timing(thumbScale, {
|
|
33
|
+
toValue: 1,
|
|
34
|
+
duration: 150,
|
|
35
|
+
useNativeDriver: true,
|
|
36
|
+
}),
|
|
37
|
+
]).start();
|
|
38
|
+
}, [barHeight, thumbScale]);
|
|
39
|
+
const collapse = useCallback(() => {
|
|
40
|
+
Animated.parallel([
|
|
41
|
+
Animated.timing(barHeight, {
|
|
42
|
+
toValue: COLLAPSED_HEIGHT,
|
|
43
|
+
duration: 150,
|
|
44
|
+
useNativeDriver: false,
|
|
45
|
+
}),
|
|
46
|
+
Animated.timing(thumbScale, {
|
|
47
|
+
toValue: 0,
|
|
48
|
+
duration: 150,
|
|
49
|
+
useNativeDriver: true,
|
|
50
|
+
}),
|
|
51
|
+
]).start();
|
|
52
|
+
}, [barHeight, thumbScale]);
|
|
53
|
+
const clampProgress = useCallback((pageX) => {
|
|
54
|
+
const width = containerWidthRef.current;
|
|
55
|
+
if (width <= 0 || durationRef.current <= 0)
|
|
56
|
+
return 0;
|
|
57
|
+
const clamped = Math.max(0, Math.min(1, pageX / width));
|
|
58
|
+
return clamped;
|
|
59
|
+
}, []);
|
|
60
|
+
const panResponder = useRef(PanResponder.create({
|
|
61
|
+
onStartShouldSetPanResponder: () => true,
|
|
62
|
+
onMoveShouldSetPanResponder: () => true,
|
|
63
|
+
onPanResponderGrant: (evt) => {
|
|
64
|
+
isScrubbing.current = true;
|
|
65
|
+
expand();
|
|
66
|
+
onSeekStart();
|
|
67
|
+
const p = clampProgress(evt.nativeEvent.locationX);
|
|
68
|
+
progress.setValue(p);
|
|
69
|
+
onSeek(p * durationRef.current);
|
|
70
|
+
},
|
|
71
|
+
onPanResponderMove: (evt) => {
|
|
72
|
+
const width = containerWidthRef.current;
|
|
73
|
+
if (width <= 0)
|
|
74
|
+
return;
|
|
75
|
+
const p = Math.max(0, Math.min(1, evt.nativeEvent.locationX / width));
|
|
76
|
+
progress.setValue(p);
|
|
77
|
+
onSeek(p * durationRef.current);
|
|
78
|
+
},
|
|
79
|
+
onPanResponderRelease: () => {
|
|
80
|
+
isScrubbing.current = false;
|
|
81
|
+
collapse();
|
|
82
|
+
onSeekEnd();
|
|
83
|
+
},
|
|
84
|
+
onPanResponderTerminate: () => {
|
|
85
|
+
isScrubbing.current = false;
|
|
86
|
+
collapse();
|
|
87
|
+
onSeekEnd();
|
|
88
|
+
},
|
|
89
|
+
})).current;
|
|
90
|
+
const onLayout = useCallback((e) => {
|
|
91
|
+
containerWidthRef.current = e.nativeEvent.layout.width;
|
|
92
|
+
}, []);
|
|
93
|
+
const filledWidth = progress.interpolate({
|
|
94
|
+
inputRange: [0, 1],
|
|
95
|
+
outputRange: ['0%', '100%'],
|
|
96
|
+
});
|
|
97
|
+
return (<View style={styles.container} pointerEvents="box-none">
|
|
98
|
+
<View style={styles.hitArea} onLayout={onLayout} {...panResponder.panHandlers}>
|
|
99
|
+
<Animated.View style={[styles.track, { height: barHeight }]}>
|
|
100
|
+
<Animated.View style={[styles.filled, { width: filledWidth }]}/>
|
|
101
|
+
</Animated.View>
|
|
102
|
+
<Animated.View style={[
|
|
103
|
+
styles.thumb,
|
|
104
|
+
{
|
|
105
|
+
transform: [{ scale: thumbScale }],
|
|
106
|
+
left: progress.interpolate({
|
|
107
|
+
inputRange: [0, 1],
|
|
108
|
+
outputRange: [-(THUMB_SIZE / 2), containerWidthRef.current > 0 ? containerWidthRef.current - THUMB_SIZE / 2 : 0],
|
|
109
|
+
}),
|
|
110
|
+
},
|
|
111
|
+
]}/>
|
|
112
|
+
</View>
|
|
113
|
+
</View>);
|
|
114
|
+
}
|
|
115
|
+
const styles = StyleSheet.create({
|
|
116
|
+
container: {
|
|
117
|
+
position: 'absolute',
|
|
118
|
+
bottom: 0,
|
|
119
|
+
left: 0,
|
|
120
|
+
right: 0,
|
|
121
|
+
},
|
|
122
|
+
hitArea: {
|
|
123
|
+
height: HIT_SLOP_HEIGHT,
|
|
124
|
+
justifyContent: 'flex-end',
|
|
125
|
+
},
|
|
126
|
+
track: {
|
|
127
|
+
width: '100%',
|
|
128
|
+
backgroundColor: 'rgba(255,255,255,0.25)',
|
|
129
|
+
overflow: 'hidden',
|
|
130
|
+
},
|
|
131
|
+
filled: {
|
|
132
|
+
height: '100%',
|
|
133
|
+
backgroundColor: 'rgba(255,255,255,0.7)',
|
|
134
|
+
},
|
|
135
|
+
thumb: {
|
|
136
|
+
position: 'absolute',
|
|
137
|
+
bottom: -(THUMB_SIZE / 2) + COLLAPSED_HEIGHT / 2,
|
|
138
|
+
width: THUMB_SIZE,
|
|
139
|
+
height: THUMB_SIZE,
|
|
140
|
+
borderRadius: THUMB_SIZE / 2,
|
|
141
|
+
backgroundColor: '#FFFFFF',
|
|
142
|
+
},
|
|
143
|
+
});
|
|
144
|
+
//# sourceMappingURL=PlayheadBar.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PlayheadBar.js","sourceRoot":"","sources":["../../src/components/PlayheadBar.tsx"],"names":[],"mappings":"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","sourcesContent":["import * as React from 'react';\nimport { useRef, useCallback, useEffect } from 'react';\nimport {\n View,\n StyleSheet,\n PanResponder,\n Animated,\n LayoutChangeEvent,\n} from 'react-native';\n\nexport type PlayheadBarProps = {\n currentTime: number;\n duration: number;\n onSeekStart: () => void;\n onSeek: (time: number) => void;\n onSeekEnd: () => void;\n};\n\nconst COLLAPSED_HEIGHT = 2;\nconst EXPANDED_HEIGHT = 4;\nconst THUMB_SIZE = 14;\nconst HIT_SLOP_HEIGHT = 30;\n\nexport default function PlayheadBar({\n currentTime,\n duration,\n onSeekStart,\n onSeek,\n onSeekEnd,\n}: PlayheadBarProps) {\n const containerWidthRef = useRef(0);\n const currentTimeRef = useRef(currentTime);\n const durationRef = useRef(duration);\n const isScrubbing = useRef(false);\n\n const barHeight = useRef(new Animated.Value(COLLAPSED_HEIGHT)).current;\n const thumbScale = useRef(new Animated.Value(0)).current;\n const progress = useRef(new Animated.Value(0)).current;\n\n useEffect(() => {\n currentTimeRef.current = currentTime;\n if (!isScrubbing.current && duration > 0) {\n progress.setValue(currentTime / duration);\n }\n }, [currentTime, duration, progress]);\n\n useEffect(() => {\n durationRef.current = duration;\n }, [duration]);\n\n const expand = useCallback(() => {\n Animated.parallel([\n Animated.timing(barHeight, {\n toValue: EXPANDED_HEIGHT,\n duration: 150,\n useNativeDriver: false,\n }),\n Animated.timing(thumbScale, {\n toValue: 1,\n duration: 150,\n useNativeDriver: true,\n }),\n ]).start();\n }, [barHeight, thumbScale]);\n\n const collapse = useCallback(() => {\n Animated.parallel([\n Animated.timing(barHeight, {\n toValue: COLLAPSED_HEIGHT,\n duration: 150,\n useNativeDriver: false,\n }),\n Animated.timing(thumbScale, {\n toValue: 0,\n duration: 150,\n useNativeDriver: true,\n }),\n ]).start();\n }, [barHeight, thumbScale]);\n\n const clampProgress = useCallback((pageX: number): number => {\n const width = containerWidthRef.current;\n if (width <= 0 || durationRef.current <= 0) return 0;\n const clamped = Math.max(0, Math.min(1, pageX / width));\n return clamped;\n }, []);\n\n const panResponder = useRef(\n PanResponder.create({\n onStartShouldSetPanResponder: () => true,\n onMoveShouldSetPanResponder: () => true,\n onPanResponderGrant: (evt) => {\n isScrubbing.current = true;\n expand();\n onSeekStart();\n\n const p = clampProgress(evt.nativeEvent.locationX);\n progress.setValue(p);\n onSeek(p * durationRef.current);\n },\n onPanResponderMove: (evt) => {\n const width = containerWidthRef.current;\n if (width <= 0) return;\n const p = Math.max(0, Math.min(1, evt.nativeEvent.locationX / width));\n progress.setValue(p);\n onSeek(p * durationRef.current);\n },\n onPanResponderRelease: () => {\n isScrubbing.current = false;\n collapse();\n onSeekEnd();\n },\n onPanResponderTerminate: () => {\n isScrubbing.current = false;\n collapse();\n onSeekEnd();\n },\n })\n ).current;\n\n const onLayout = useCallback((e: LayoutChangeEvent) => {\n containerWidthRef.current = e.nativeEvent.layout.width;\n }, []);\n\n const filledWidth = progress.interpolate({\n inputRange: [0, 1],\n outputRange: ['0%', '100%'],\n });\n\n return (\n <View style={styles.container} pointerEvents=\"box-none\">\n <View\n style={styles.hitArea}\n onLayout={onLayout}\n {...panResponder.panHandlers}\n >\n <Animated.View style={[styles.track, { height: barHeight }]}>\n <Animated.View style={[styles.filled, { width: filledWidth }]} />\n </Animated.View>\n <Animated.View\n style={[\n styles.thumb,\n {\n transform: [{ scale: thumbScale }],\n left: progress.interpolate({\n inputRange: [0, 1],\n outputRange: [-(THUMB_SIZE / 2), containerWidthRef.current > 0 ? containerWidthRef.current - THUMB_SIZE / 2 : 0],\n }),\n },\n ]}\n />\n </View>\n </View>\n );\n}\n\nconst styles = StyleSheet.create({\n container: {\n position: 'absolute',\n bottom: 0,\n left: 0,\n right: 0,\n },\n hitArea: {\n height: HIT_SLOP_HEIGHT,\n justifyContent: 'flex-end',\n },\n track: {\n width: '100%',\n backgroundColor: 'rgba(255,255,255,0.25)',\n overflow: 'hidden',\n },\n filled: {\n height: '100%',\n backgroundColor: 'rgba(255,255,255,0.7)',\n },\n thumb: {\n position: 'absolute',\n bottom: -(THUMB_SIZE / 2) + COLLAPSED_HEIGHT / 2,\n width: THUMB_SIZE,\n height: THUMB_SIZE,\n borderRadius: THUMB_SIZE / 2,\n backgroundColor: '#FFFFFF',\n },\n});\n"]}
|