@movementinfra/expo-twostep-video 0.1.14 → 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/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/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
|
@@ -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":"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,YAAY,GAAG;IACzB,wDAAwD;IACxD,IAAI,EAAE,MAAM,CAAC;IACb,sDAAsD;IACtD,IAAI,EAAE,MAAM,CAAC;IACb,sDAAsD;IACtD,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,kBAAkB,GAAG,YAAY,CAAC;AAE9C;;GAEG;AACH,MAAM,MAAM,mBAAmB,GAAG;IAChC,6BAA6B;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,wDAAwD;IACxD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,sDAAsD;IACtD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,sDAAsD;IACtD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,qCAAqC;IACrC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,mCAAmC;IACnC,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,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,wCAAwC;IACxC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,wCAAwC;IACxC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,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,2CAA2C;IAC3C,eAAe,CAAC,EAAE,CAAC,KAAK,EAAE;QAAE,WAAW,EAAE,kBAAkB,CAAA;KAAE,KAAK,IAAI,CAAC;IACvE,mEAAmE;IACnE,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,iEAAiE;IACjE,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,iDAAiD;IACjD,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,iBAAiB;IACjB,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;CAC9B,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,gBAAgB,GAAG;IAC7B,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;CAC7B,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,mBAAmB,GAAG,gBAAgB,GAAG;IACnD,qCAAqC;IACrC,eAAe,EAAE,MAAM,OAAO,CAAC,YAAY,CAAC,CAAC;IAC7C,8CAA8C;IAC9C,eAAe,EAAE,CAAC,KAAK,EAAE,OAAO,CAAC,YAAY,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACjE,wDAAwD;IACxD,YAAY,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CACnC,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,gCAAgC,GAAG,qBAAqB,GAAG;IACrE,oDAAoD;IACpD,qBAAqB,CAAC,EAAE,OAAO,CAAC;CACjC,CAAC;AAEF;;;GAGG;AACH,MAAM,MAAM,8BAA8B,GAAG,gBAAgB,GAAG;IAC9D,4BAA4B;IAC5B,eAAe,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACrC,2BAA2B;IAC3B,cAAc,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CACrC,CAAC"}
|
|
@@ -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":"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","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"]}
|
|
@@ -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"]}
|