@movementinfra/expo-twostep-video 0.1.9 → 0.1.11
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/build/ExpoTwoStepVideo.types.d.ts +46 -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 +10 -0
- package/build/ExpoTwoStepVideoView.js.map +1 -1
- package/build/TwoStepPlayerControllerView.d.ts.map +1 -1
- package/build/TwoStepPlayerControllerView.js +10 -0
- package/build/TwoStepPlayerControllerView.js.map +1 -1
- package/build/VideoScrubber.d.ts.map +1 -1
- package/build/VideoScrubber.js +60 -38
- package/build/VideoScrubber.js.map +1 -1
- package/build/index.d.ts +53 -1
- package/build/index.d.ts.map +1 -1
- package/build/index.js +37 -0
- package/build/index.js.map +1 -1
- package/ios/ExpoTwoStepVideoModule.swift +75 -1
- package/ios/ExpoTwoStepVideoView.swift +315 -0
- package/ios/TwoStepVideo/Core/VideoTransformer.swift +167 -0
- package/package.json +1 -1
|
@@ -20,6 +20,38 @@ export type ProgressEvent = {
|
|
|
20
20
|
export type ErrorEvent = {
|
|
21
21
|
error: string;
|
|
22
22
|
};
|
|
23
|
+
/**
|
|
24
|
+
* Pan/Zoom state representing the current view transform
|
|
25
|
+
*/
|
|
26
|
+
export type PanZoomState = {
|
|
27
|
+
/** Horizontal pan position (-1.0 to 1.0, 0 = center) */
|
|
28
|
+
panX: number;
|
|
29
|
+
/** Vertical pan position (-1.0 to 1.0, 0 = center) */
|
|
30
|
+
panY: number;
|
|
31
|
+
/** Zoom level (1.0 = no zoom, 2.0 = 2x zoom, etc.) */
|
|
32
|
+
zoomLevel: number;
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* Pan/Zoom change event payload
|
|
36
|
+
*/
|
|
37
|
+
export type PanZoomChangeEvent = PanZoomState;
|
|
38
|
+
/**
|
|
39
|
+
* Options for creating a pan/zoom composition
|
|
40
|
+
*/
|
|
41
|
+
export type PanZoomVideoOptions = {
|
|
42
|
+
/** ID of the loaded asset */
|
|
43
|
+
assetId: string;
|
|
44
|
+
/** Horizontal pan position (-1.0 to 1.0, 0 = center) */
|
|
45
|
+
panX?: number;
|
|
46
|
+
/** Vertical pan position (-1.0 to 1.0, 0 = center) */
|
|
47
|
+
panY?: number;
|
|
48
|
+
/** Zoom level (1.0 = no zoom, 2.0 = 2x zoom, etc.) */
|
|
49
|
+
zoomLevel?: number;
|
|
50
|
+
/** Optional start time in seconds */
|
|
51
|
+
startTime?: number;
|
|
52
|
+
/** Optional end time in seconds */
|
|
53
|
+
endTime?: number;
|
|
54
|
+
};
|
|
23
55
|
/**
|
|
24
56
|
* Video player view props
|
|
25
57
|
*/
|
|
@@ -30,6 +62,10 @@ export type TwoStepVideoViewProps = {
|
|
|
30
62
|
assetId?: string;
|
|
31
63
|
/** Enable continuous looping - video will restart automatically when it ends */
|
|
32
64
|
loop?: boolean;
|
|
65
|
+
/** Minimum zoom level (default: 1.0) */
|
|
66
|
+
minZoom?: number;
|
|
67
|
+
/** Maximum zoom level (default: 5.0) */
|
|
68
|
+
maxZoom?: number;
|
|
33
69
|
/** Called when playback status changes */
|
|
34
70
|
onPlaybackStatusChange?: (event: {
|
|
35
71
|
nativeEvent: PlaybackStatusEvent;
|
|
@@ -46,6 +82,10 @@ export type TwoStepVideoViewProps = {
|
|
|
46
82
|
onError?: (event: {
|
|
47
83
|
nativeEvent: ErrorEvent;
|
|
48
84
|
}) => void;
|
|
85
|
+
/** Called when pan/zoom gesture changes */
|
|
86
|
+
onPanZoomChange?: (event: {
|
|
87
|
+
nativeEvent: PanZoomChangeEvent;
|
|
88
|
+
}) => void;
|
|
49
89
|
/** View style */
|
|
50
90
|
style?: StyleProp<ViewStyle>;
|
|
51
91
|
};
|
|
@@ -61,6 +101,12 @@ export type TwoStepVideoViewRef = {
|
|
|
61
101
|
seek: (time: number) => Promise<void>;
|
|
62
102
|
/** Restart playback from the beginning */
|
|
63
103
|
replay: () => Promise<void>;
|
|
104
|
+
/** Get the current pan/zoom state */
|
|
105
|
+
getPanZoomState: () => Promise<PanZoomState>;
|
|
106
|
+
/** Set the pan/zoom state programmatically */
|
|
107
|
+
setPanZoomState: (state: Partial<PanZoomState>) => Promise<void>;
|
|
108
|
+
/** Reset pan/zoom to default state (zoom=1, pan=0,0) */
|
|
109
|
+
resetPanZoom: () => Promise<void>;
|
|
64
110
|
};
|
|
65
111
|
/**
|
|
66
112
|
* Video player controller view props (with native controls)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ExpoTwoStepVideo.types.d.ts","sourceRoot":"","sources":["../src/ExpoTwoStepVideo.types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAEzD;;GAEG;AACH,MAAM,MAAM,mBAAmB,GAAG;IAChC,MAAM,EAAE,OAAO,GAAG,SAAS,GAAG,QAAQ,GAAG,OAAO,GAAG,QAAQ,CAAC;IAC5D,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,aAAa,GAAG;IAC1B,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,UAAU,GAAG;IACvB,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,qBAAqB,GAAG;IAClC,sEAAsE;IACtE,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,8CAA8C;IAC9C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,gFAAgF;IAChF,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,0CAA0C;IAC1C,sBAAsB,CAAC,EAAE,CAAC,KAAK,EAAE;QAAE,WAAW,EAAE,mBAAmB,CAAA;KAAE,KAAK,IAAI,CAAC;IAC/E,iDAAiD;IACjD,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE;QAAE,WAAW,EAAE,aAAa,CAAA;KAAE,KAAK,IAAI,CAAC;IAC7D,wEAAwE;IACxE,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE;QAAE,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAA;KAAE,KAAK,IAAI,CAAC;IAChE,kCAAkC;IAClC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE;QAAE,WAAW,EAAE,UAAU,CAAA;KAAE,KAAK,IAAI,CAAC;IACvD,iBAAiB;IACjB,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;CAC9B,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,mBAAmB,GAAG;IAChC,qBAAqB;IACrB,IAAI,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1B,qBAAqB;IACrB,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3B,yCAAyC;IACzC,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACtC,0CAA0C;IAC1C,MAAM,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;
|
|
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 * 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 /** 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 /** View style */\n style?: StyleProp<ViewStyle>;\n};\n\n/**\n * Ref handle for video player view\n */\nexport type TwoStepVideoViewRef = {\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 * 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 */\nexport type TwoStepPlayerControllerViewRef = TwoStepVideoViewRef & {\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 /** View style */\n style?: StyleProp<ViewStyle>;\n};\n\n/**\n * Ref handle for video player view\n */\nexport type TwoStepVideoViewRef = {\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 /** 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 */\nexport type TwoStepPlayerControllerViewRef = TwoStepVideoViewRef & {\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;AAG/B,OAAO,
|
|
1
|
+
{"version":3,"file":"ExpoTwoStepVideoView.d.ts","sourceRoot":"","sources":["../src/ExpoTwoStepVideoView.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAG/B,OAAO,EAAgB,qBAAqB,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAoBpG;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,QAAA,MAAM,gBAAgB,mGA+BrB,CAAC;AAIF,eAAe,gBAAgB,CAAC"}
|
|
@@ -39,6 +39,16 @@ const TwoStepVideoView = forwardRef((props, ref) => {
|
|
|
39
39
|
replay: async () => {
|
|
40
40
|
await nativeRef.current?.replay();
|
|
41
41
|
},
|
|
42
|
+
getPanZoomState: async () => {
|
|
43
|
+
const state = await nativeRef.current?.getPanZoomState();
|
|
44
|
+
return state ?? { panX: 0, panY: 0, zoomLevel: 1 };
|
|
45
|
+
},
|
|
46
|
+
setPanZoomState: async (state) => {
|
|
47
|
+
await nativeRef.current?.setPanZoomState(state.panX, state.panY, state.zoomLevel);
|
|
48
|
+
},
|
|
49
|
+
resetPanZoom: async () => {
|
|
50
|
+
await nativeRef.current?.resetPanZoom();
|
|
51
|
+
},
|
|
42
52
|
}));
|
|
43
53
|
return <NativeView ref={nativeRef} {...props}/>;
|
|
44
54
|
});
|
|
@@ -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,mBAAmB,EAAE,MAAM,EAAE,MAAM,OAAO,CAAC;AAoBhE,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,SAAS,GAAG,MAAM,CAAgB,IAAI,CAAC,CAAC;IAE9C,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,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,SAAS,CAAC,CAAC,IAAI,KAAK,CAAC,EAAG,CAAC;AACnD,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, useImperativeHandle, useRef } from 'react';\n\nimport { PanZoomState, TwoStepVideoViewProps, TwoStepVideoViewRef } from './ExpoTwoStepVideo.types';\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 nativeRef = useRef<NativeViewRef>(null);\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 return <NativeView ref={nativeRef} {...props} />;\n }\n);\n\nTwoStepVideoView.displayName = 'TwoStepVideoView';\n\nexport default TwoStepVideoView;\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TwoStepPlayerControllerView.d.ts","sourceRoot":"","sources":["../src/TwoStepPlayerControllerView.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAG/B,OAAO,
|
|
1
|
+
{"version":3,"file":"TwoStepPlayerControllerView.d.ts","sourceRoot":"","sources":["../src/TwoStepPlayerControllerView.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAG/B,OAAO,EAAkD,8BAA8B,EAAE,MAAM,0BAA0B,CAAC;AAmB1H;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,QAAA,MAAM,2BAA2B;;wDAqChC,CAAC;AAIF,eAAe,2BAA2B,CAAC"}
|
|
@@ -46,6 +46,16 @@ const TwoStepPlayerControllerView = forwardRef((props, ref) => {
|
|
|
46
46
|
exitFullscreen: async () => {
|
|
47
47
|
await nativeRef.current?.exitFullscreen();
|
|
48
48
|
},
|
|
49
|
+
// Pan/zoom not supported on controller view (uses AVPlayerViewController)
|
|
50
|
+
getPanZoomState: async () => {
|
|
51
|
+
return { panX: 0, panY: 0, zoomLevel: 1 };
|
|
52
|
+
},
|
|
53
|
+
setPanZoomState: async (_state) => {
|
|
54
|
+
// Not supported on controller view
|
|
55
|
+
},
|
|
56
|
+
resetPanZoom: async () => {
|
|
57
|
+
// Not supported on controller view
|
|
58
|
+
},
|
|
49
59
|
}));
|
|
50
60
|
return <NativeView ref={nativeRef} {...props}/>;
|
|
51
61
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TwoStepPlayerControllerView.js","sourceRoot":"","sources":["../src/TwoStepPlayerControllerView.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;AAmBhE,MAAM,UAAU,GAAyC,iBAAiB,CAAC,kDAAkD,CAAC,CAAC;AAE/H;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,2BAA2B,GAAG,UAAU,CAC5C,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE;IACb,MAAM,SAAS,GAAG,MAAM,CAAgB,IAAI,CAAC,CAAC;IAE9C,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,SAAS,CAAC,OAAO,EAAE,eAAe,EAAE,CAAC;QAC7C,CAAC;QACD,cAAc,EAAE,KAAK,IAAI,EAAE;YACzB,MAAM,SAAS,CAAC,OAAO,EAAE,cAAc,EAAE,CAAC;QAC5C,CAAC;KACF,CAAC,CAAC,CAAC;IAEJ,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,SAAS,CAAC,CAAC,IAAI,KAAK,CAAC,EAAG,CAAC;AACnD,CAAC,CACF,CAAC;AAEF,2BAA2B,CAAC,WAAW,GAAG,6BAA6B,CAAC;AAExE,eAAe,2BAA2B,CAAC","sourcesContent":["import { requireNativeView } from 'expo';\nimport * as React from 'react';\nimport { forwardRef, useImperativeHandle, useRef } from 'react';\n\nimport { TwoStepPlayerControllerViewProps, TwoStepPlayerControllerViewRef } from './ExpoTwoStepVideo.types';\n\n// Internal props type that includes the native ref\ntype NativeViewProps = TwoStepPlayerControllerViewProps & {\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 enterFullscreen: () => Promise<void>;\n exitFullscreen: () => Promise<void>;\n};\n\nconst NativeView: React.ComponentType<NativeViewProps> = requireNativeView('ExpoTwoStepVideo_ExpoTwoStepPlayerControllerView');\n\n/**\n * Video player view with native iOS controls and fullscreen support\n *\n * Uses AVPlayerViewController under the hood for native playback controls,\n * AirPlay support, picture-in-picture, and fullscreen mode.\n *\n * @example\n * ```tsx\n * const playerRef = useRef<TwoStepPlayerControllerViewRef>(null);\n *\n * // Play a video with native controls\n * <TwoStepPlayerControllerView\n * ref={playerRef}\n * assetId={asset.id}\n * showsPlaybackControls={true}\n * onProgress={(e) => setProgress(e.nativeEvent.progress)}\n * style={{ width: '100%', height: 300 }}\n * />\n *\n * // Enter fullscreen mode\n * playerRef.current?.enterFullscreen();\n * ```\n */\nconst TwoStepPlayerControllerView = forwardRef<TwoStepPlayerControllerViewRef, TwoStepPlayerControllerViewProps>(\n (props, ref) => {\n const nativeRef = useRef<NativeViewRef>(null);\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 enterFullscreen: async () => {\n await nativeRef.current?.enterFullscreen();\n },\n exitFullscreen: async () => {\n await nativeRef.current?.exitFullscreen();\n },\n }));\n\n return <NativeView ref={nativeRef} {...props} />;\n }\n);\n\nTwoStepPlayerControllerView.displayName = 'TwoStepPlayerControllerView';\n\nexport default TwoStepPlayerControllerView;\n"]}
|
|
1
|
+
{"version":3,"file":"TwoStepPlayerControllerView.js","sourceRoot":"","sources":["../src/TwoStepPlayerControllerView.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;AAmBhE,MAAM,UAAU,GAAyC,iBAAiB,CAAC,kDAAkD,CAAC,CAAC;AAE/H;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,2BAA2B,GAAG,UAAU,CAC5C,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE;IACb,MAAM,SAAS,GAAG,MAAM,CAAgB,IAAI,CAAC,CAAC;IAE9C,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,SAAS,CAAC,OAAO,EAAE,eAAe,EAAE,CAAC;QAC7C,CAAC;QACD,cAAc,EAAE,KAAK,IAAI,EAAE;YACzB,MAAM,SAAS,CAAC,OAAO,EAAE,cAAc,EAAE,CAAC;QAC5C,CAAC;QACD,0EAA0E;QAC1E,eAAe,EAAE,KAAK,IAA2B,EAAE;YACjD,OAAO,EAAE,IAAI,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,CAAC;QAC5C,CAAC;QACD,eAAe,EAAE,KAAK,EAAE,MAA6B,EAAE,EAAE;YACvD,mCAAmC;QACrC,CAAC;QACD,YAAY,EAAE,KAAK,IAAI,EAAE;YACvB,mCAAmC;QACrC,CAAC;KACF,CAAC,CAAC,CAAC;IAEJ,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,SAAS,CAAC,CAAC,IAAI,KAAK,CAAC,EAAG,CAAC;AACnD,CAAC,CACF,CAAC;AAEF,2BAA2B,CAAC,WAAW,GAAG,6BAA6B,CAAC;AAExE,eAAe,2BAA2B,CAAC","sourcesContent":["import { requireNativeView } from 'expo';\nimport * as React from 'react';\nimport { forwardRef, useImperativeHandle, useRef } from 'react';\n\nimport { PanZoomState, TwoStepPlayerControllerViewProps, TwoStepPlayerControllerViewRef } from './ExpoTwoStepVideo.types';\n\n// Internal props type that includes the native ref\ntype NativeViewProps = TwoStepPlayerControllerViewProps & {\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 enterFullscreen: () => Promise<void>;\n exitFullscreen: () => Promise<void>;\n};\n\nconst NativeView: React.ComponentType<NativeViewProps> = requireNativeView('ExpoTwoStepVideo_ExpoTwoStepPlayerControllerView');\n\n/**\n * Video player view with native iOS controls and fullscreen support\n *\n * Uses AVPlayerViewController under the hood for native playback controls,\n * AirPlay support, picture-in-picture, and fullscreen mode.\n *\n * @example\n * ```tsx\n * const playerRef = useRef<TwoStepPlayerControllerViewRef>(null);\n *\n * // Play a video with native controls\n * <TwoStepPlayerControllerView\n * ref={playerRef}\n * assetId={asset.id}\n * showsPlaybackControls={true}\n * onProgress={(e) => setProgress(e.nativeEvent.progress)}\n * style={{ width: '100%', height: 300 }}\n * />\n *\n * // Enter fullscreen mode\n * playerRef.current?.enterFullscreen();\n * ```\n */\nconst TwoStepPlayerControllerView = forwardRef<TwoStepPlayerControllerViewRef, TwoStepPlayerControllerViewProps>(\n (props, ref) => {\n const nativeRef = useRef<NativeViewRef>(null);\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 enterFullscreen: async () => {\n await nativeRef.current?.enterFullscreen();\n },\n exitFullscreen: async () => {\n await nativeRef.current?.exitFullscreen();\n },\n // Pan/zoom not supported on controller view (uses AVPlayerViewController)\n getPanZoomState: async (): Promise<PanZoomState> => {\n return { panX: 0, panY: 0, zoomLevel: 1 };\n },\n setPanZoomState: async (_state: Partial<PanZoomState>) => {\n // Not supported on controller view\n },\n resetPanZoom: async () => {\n // Not supported on controller view\n },\n }));\n\n return <NativeView ref={nativeRef} {...props} />;\n }\n);\n\nTwoStepPlayerControllerView.displayName = 'TwoStepPlayerControllerView';\n\nexport default TwoStepPlayerControllerView;\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,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":"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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\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"]}
|