@movementinfra/expo-twostep-video 0.1.14 → 0.1.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +80 -7
- package/build/ExpoTwoStepVideo.types.d.ts +55 -0
- package/build/ExpoTwoStepVideo.types.d.ts.map +1 -1
- package/build/ExpoTwoStepVideo.types.js.map +1 -1
- package/build/ExpoTwoStepVideoModule.d.ts +17 -0
- package/build/ExpoTwoStepVideoModule.d.ts.map +1 -1
- package/build/ExpoTwoStepVideoModule.js.map +1 -1
- package/build/ExpoTwoStepVideoModule.web.d.ts +4 -0
- package/build/ExpoTwoStepVideoModule.web.d.ts.map +1 -1
- package/build/ExpoTwoStepVideoModule.web.js +15 -0
- package/build/ExpoTwoStepVideoModule.web.js.map +1 -1
- package/build/ExpoTwoStepVideoView.d.ts.map +1 -1
- package/build/ExpoTwoStepVideoView.js +112 -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 +139 -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 +156 -0
- package/build/components/PlayheadBar.js.map +1 -0
- package/build/index.d.ts +83 -2
- package/build/index.d.ts.map +1 -1
- package/build/index.js +91 -0
- package/build/index.js.map +1 -1
- package/ios/ExpoTwoStepVideoModule.swift +77 -1
- package/ios/ExpoTwoStepVideoView.swift +155 -36
- package/ios/TwoStepVideo/Core/MediaPicker.swift +355 -0
- package/ios/TwoStepVideo/TwoStepVideo.swift +4 -0
- package/package.json +1 -1
|
@@ -0,0 +1,139 @@
|
|
|
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
|
+
{/* Left side - positioned as a smaller hit zone to allow pinch gestures elsewhere */}
|
|
69
|
+
<View style={styles.half} pointerEvents="box-none">
|
|
70
|
+
<TouchableWithoutFeedback onPress={handleLeftTap}>
|
|
71
|
+
<View style={styles.hitZone}>
|
|
72
|
+
<Animated.View style={[styles.feedback, { opacity: leftOpacity }]}>
|
|
73
|
+
<View style={styles.feedbackCircle}>
|
|
74
|
+
<Text style={styles.arrowText}>{'<<'}</Text>
|
|
75
|
+
<Text style={styles.skipText}>{skipInterval}s</Text>
|
|
76
|
+
</View>
|
|
77
|
+
</Animated.View>
|
|
78
|
+
</View>
|
|
79
|
+
</TouchableWithoutFeedback>
|
|
80
|
+
</View>
|
|
81
|
+
{/* Right side - positioned as a smaller hit zone to allow pinch gestures elsewhere */}
|
|
82
|
+
<View style={styles.half} pointerEvents="box-none">
|
|
83
|
+
<TouchableWithoutFeedback onPress={handleRightTap}>
|
|
84
|
+
<View style={styles.hitZone}>
|
|
85
|
+
<Animated.View style={[styles.feedback, { opacity: rightOpacity }]}>
|
|
86
|
+
<View style={styles.feedbackCircle}>
|
|
87
|
+
<Text style={styles.arrowText}>{'>>'}</Text>
|
|
88
|
+
<Text style={styles.skipText}>{skipInterval}s</Text>
|
|
89
|
+
</View>
|
|
90
|
+
</Animated.View>
|
|
91
|
+
</View>
|
|
92
|
+
</TouchableWithoutFeedback>
|
|
93
|
+
</View>
|
|
94
|
+
</View>);
|
|
95
|
+
}
|
|
96
|
+
const HIT_ZONE_SIZE = 120;
|
|
97
|
+
const styles = StyleSheet.create({
|
|
98
|
+
container: {
|
|
99
|
+
...StyleSheet.absoluteFillObject,
|
|
100
|
+
flexDirection: 'row',
|
|
101
|
+
},
|
|
102
|
+
half: {
|
|
103
|
+
flex: 1,
|
|
104
|
+
justifyContent: 'center',
|
|
105
|
+
alignItems: 'center',
|
|
106
|
+
},
|
|
107
|
+
hitZone: {
|
|
108
|
+
// Smaller hit zone allows pinch gestures in other areas
|
|
109
|
+
width: HIT_ZONE_SIZE,
|
|
110
|
+
height: HIT_ZONE_SIZE,
|
|
111
|
+
borderRadius: HIT_ZONE_SIZE / 2,
|
|
112
|
+
justifyContent: 'center',
|
|
113
|
+
alignItems: 'center',
|
|
114
|
+
},
|
|
115
|
+
feedback: {
|
|
116
|
+
justifyContent: 'center',
|
|
117
|
+
alignItems: 'center',
|
|
118
|
+
},
|
|
119
|
+
feedbackCircle: {
|
|
120
|
+
width: 64,
|
|
121
|
+
height: 64,
|
|
122
|
+
borderRadius: 32,
|
|
123
|
+
backgroundColor: 'rgba(0,0,0,0.4)',
|
|
124
|
+
justifyContent: 'center',
|
|
125
|
+
alignItems: 'center',
|
|
126
|
+
},
|
|
127
|
+
arrowText: {
|
|
128
|
+
color: '#FFFFFF',
|
|
129
|
+
fontSize: 20,
|
|
130
|
+
fontWeight: '700',
|
|
131
|
+
},
|
|
132
|
+
skipText: {
|
|
133
|
+
color: '#FFFFFF',
|
|
134
|
+
fontSize: 12,
|
|
135
|
+
fontWeight: '600',
|
|
136
|
+
marginTop: 2,
|
|
137
|
+
},
|
|
138
|
+
});
|
|
139
|
+
//# 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 {/* Left side - positioned as a smaller hit zone to allow pinch gestures elsewhere */}\n <View style={styles.half} pointerEvents=\"box-none\">\n <TouchableWithoutFeedback onPress={handleLeftTap}>\n <View style={styles.hitZone}>\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 </View>\n {/* Right side - positioned as a smaller hit zone to allow pinch gestures elsewhere */}\n <View style={styles.half} pointerEvents=\"box-none\">\n <TouchableWithoutFeedback onPress={handleRightTap}>\n <View style={styles.hitZone}>\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 </View>\n );\n}\n\nconst HIT_ZONE_SIZE = 120;\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 hitZone: {\n // Smaller hit zone allows pinch gestures in other areas\n width: HIT_ZONE_SIZE,\n height: HIT_ZONE_SIZE,\n borderRadius: HIT_ZONE_SIZE / 2,\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,qBAuIlB"}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { useRef, useCallback, useEffect, useState } 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 [progressPercent, setProgressPercent] = useState(0);
|
|
10
|
+
const containerWidthRef = useRef(0);
|
|
11
|
+
const currentTimeRef = useRef(currentTime);
|
|
12
|
+
const durationRef = useRef(duration);
|
|
13
|
+
const isScrubbing = useRef(false);
|
|
14
|
+
// These animations use non-native driver (height) or native driver (scale/translate)
|
|
15
|
+
const barHeight = useRef(new Animated.Value(COLLAPSED_HEIGHT)).current;
|
|
16
|
+
const thumbScale = useRef(new Animated.Value(0)).current;
|
|
17
|
+
const thumbTranslateX = useRef(new Animated.Value(0)).current;
|
|
18
|
+
useEffect(() => {
|
|
19
|
+
currentTimeRef.current = currentTime;
|
|
20
|
+
if (!isScrubbing.current && duration > 0) {
|
|
21
|
+
const p = currentTime / duration;
|
|
22
|
+
setProgressPercent(p * 100);
|
|
23
|
+
thumbTranslateX.setValue(p * containerWidthRef.current);
|
|
24
|
+
}
|
|
25
|
+
}, [currentTime, duration, thumbTranslateX]);
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
durationRef.current = duration;
|
|
28
|
+
}, [duration]);
|
|
29
|
+
const expand = useCallback(() => {
|
|
30
|
+
Animated.parallel([
|
|
31
|
+
Animated.timing(barHeight, {
|
|
32
|
+
toValue: EXPANDED_HEIGHT,
|
|
33
|
+
duration: 150,
|
|
34
|
+
useNativeDriver: false,
|
|
35
|
+
}),
|
|
36
|
+
Animated.timing(thumbScale, {
|
|
37
|
+
toValue: 1,
|
|
38
|
+
duration: 150,
|
|
39
|
+
useNativeDriver: true,
|
|
40
|
+
}),
|
|
41
|
+
]).start();
|
|
42
|
+
}, [barHeight, thumbScale]);
|
|
43
|
+
const collapse = useCallback(() => {
|
|
44
|
+
Animated.parallel([
|
|
45
|
+
Animated.timing(barHeight, {
|
|
46
|
+
toValue: COLLAPSED_HEIGHT,
|
|
47
|
+
duration: 150,
|
|
48
|
+
useNativeDriver: false,
|
|
49
|
+
}),
|
|
50
|
+
Animated.timing(thumbScale, {
|
|
51
|
+
toValue: 0,
|
|
52
|
+
duration: 150,
|
|
53
|
+
useNativeDriver: true,
|
|
54
|
+
}),
|
|
55
|
+
]).start();
|
|
56
|
+
}, [barHeight, thumbScale]);
|
|
57
|
+
const updateProgress = useCallback((p) => {
|
|
58
|
+
setProgressPercent(p * 100);
|
|
59
|
+
thumbTranslateX.setValue(p * containerWidthRef.current);
|
|
60
|
+
}, [thumbTranslateX]);
|
|
61
|
+
const clampProgress = useCallback((pageX) => {
|
|
62
|
+
const width = containerWidthRef.current;
|
|
63
|
+
if (width <= 0 || durationRef.current <= 0)
|
|
64
|
+
return 0;
|
|
65
|
+
const clamped = Math.max(0, Math.min(1, pageX / width));
|
|
66
|
+
return clamped;
|
|
67
|
+
}, []);
|
|
68
|
+
const panResponder = useRef(PanResponder.create({
|
|
69
|
+
onStartShouldSetPanResponder: () => true,
|
|
70
|
+
onMoveShouldSetPanResponder: () => true,
|
|
71
|
+
onPanResponderGrant: (evt) => {
|
|
72
|
+
isScrubbing.current = true;
|
|
73
|
+
expand();
|
|
74
|
+
onSeekStart();
|
|
75
|
+
const p = clampProgress(evt.nativeEvent.locationX);
|
|
76
|
+
updateProgress(p);
|
|
77
|
+
onSeek(p * durationRef.current);
|
|
78
|
+
},
|
|
79
|
+
onPanResponderMove: (evt) => {
|
|
80
|
+
const width = containerWidthRef.current;
|
|
81
|
+
if (width <= 0)
|
|
82
|
+
return;
|
|
83
|
+
const p = Math.max(0, Math.min(1, evt.nativeEvent.locationX / width));
|
|
84
|
+
updateProgress(p);
|
|
85
|
+
onSeek(p * durationRef.current);
|
|
86
|
+
},
|
|
87
|
+
onPanResponderRelease: () => {
|
|
88
|
+
isScrubbing.current = false;
|
|
89
|
+
collapse();
|
|
90
|
+
onSeekEnd();
|
|
91
|
+
},
|
|
92
|
+
onPanResponderTerminate: () => {
|
|
93
|
+
isScrubbing.current = false;
|
|
94
|
+
collapse();
|
|
95
|
+
onSeekEnd();
|
|
96
|
+
},
|
|
97
|
+
})).current;
|
|
98
|
+
const onLayout = useCallback((e) => {
|
|
99
|
+
const width = e.nativeEvent.layout.width;
|
|
100
|
+
containerWidthRef.current = width;
|
|
101
|
+
// Update thumb position when container width changes
|
|
102
|
+
if (durationRef.current > 0) {
|
|
103
|
+
const p = currentTimeRef.current / durationRef.current;
|
|
104
|
+
thumbTranslateX.setValue(p * width);
|
|
105
|
+
}
|
|
106
|
+
}, [thumbTranslateX]);
|
|
107
|
+
return (<View style={styles.container} pointerEvents="box-none">
|
|
108
|
+
<View style={styles.hitArea} onLayout={onLayout} {...panResponder.panHandlers}>
|
|
109
|
+
<Animated.View style={[styles.track, { height: barHeight }]}>
|
|
110
|
+
{/* Use percentage-based width without Animated to avoid native driver issues */}
|
|
111
|
+
<View style={[styles.filled, { width: `${progressPercent}%` }]}/>
|
|
112
|
+
</Animated.View>
|
|
113
|
+
<Animated.View style={[
|
|
114
|
+
styles.thumb,
|
|
115
|
+
{
|
|
116
|
+
transform: [
|
|
117
|
+
{ translateX: thumbTranslateX },
|
|
118
|
+
{ scale: thumbScale },
|
|
119
|
+
],
|
|
120
|
+
},
|
|
121
|
+
]}/>
|
|
122
|
+
</View>
|
|
123
|
+
</View>);
|
|
124
|
+
}
|
|
125
|
+
const styles = StyleSheet.create({
|
|
126
|
+
container: {
|
|
127
|
+
position: 'absolute',
|
|
128
|
+
bottom: 0,
|
|
129
|
+
left: 0,
|
|
130
|
+
right: 0,
|
|
131
|
+
},
|
|
132
|
+
hitArea: {
|
|
133
|
+
height: HIT_SLOP_HEIGHT,
|
|
134
|
+
justifyContent: 'flex-end',
|
|
135
|
+
},
|
|
136
|
+
track: {
|
|
137
|
+
width: '100%',
|
|
138
|
+
backgroundColor: 'rgba(255,255,255,0.25)',
|
|
139
|
+
overflow: 'hidden',
|
|
140
|
+
},
|
|
141
|
+
filled: {
|
|
142
|
+
height: '100%',
|
|
143
|
+
backgroundColor: 'rgba(255,255,255,0.7)',
|
|
144
|
+
},
|
|
145
|
+
thumb: {
|
|
146
|
+
position: 'absolute',
|
|
147
|
+
bottom: -(THUMB_SIZE / 2) + COLLAPSED_HEIGHT / 2,
|
|
148
|
+
// Position at left edge minus half thumb width, translateX moves it
|
|
149
|
+
left: -(THUMB_SIZE / 2),
|
|
150
|
+
width: THUMB_SIZE,
|
|
151
|
+
height: THUMB_SIZE,
|
|
152
|
+
borderRadius: THUMB_SIZE / 2,
|
|
153
|
+
backgroundColor: '#FFFFFF',
|
|
154
|
+
},
|
|
155
|
+
});
|
|
156
|
+
//# 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, useState } 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 [progressPercent, setProgressPercent] = useState(0);\n const containerWidthRef = useRef(0);\n const currentTimeRef = useRef(currentTime);\n const durationRef = useRef(duration);\n const isScrubbing = useRef(false);\n\n // These animations use non-native driver (height) or native driver (scale/translate)\n const barHeight = useRef(new Animated.Value(COLLAPSED_HEIGHT)).current;\n const thumbScale = useRef(new Animated.Value(0)).current;\n const thumbTranslateX = useRef(new Animated.Value(0)).current;\n\n useEffect(() => {\n currentTimeRef.current = currentTime;\n if (!isScrubbing.current && duration > 0) {\n const p = currentTime / duration;\n setProgressPercent(p * 100);\n thumbTranslateX.setValue(p * containerWidthRef.current);\n }\n }, [currentTime, duration, thumbTranslateX]);\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 updateProgress = useCallback((p: number) => {\n setProgressPercent(p * 100);\n thumbTranslateX.setValue(p * containerWidthRef.current);\n }, [thumbTranslateX]);\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 updateProgress(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 updateProgress(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 const width = e.nativeEvent.layout.width;\n containerWidthRef.current = width;\n // Update thumb position when container width changes\n if (durationRef.current > 0) {\n const p = currentTimeRef.current / durationRef.current;\n thumbTranslateX.setValue(p * width);\n }\n }, [thumbTranslateX]);\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 {/* Use percentage-based width without Animated to avoid native driver issues */}\n <View style={[styles.filled, { width: `${progressPercent}%` }]} />\n </Animated.View>\n <Animated.View\n style={[\n styles.thumb,\n {\n transform: [\n { translateX: thumbTranslateX },\n { scale: thumbScale },\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 // Position at left edge minus half thumb width, translateX moves it\n left: -(THUMB_SIZE / 2),\n width: THUMB_SIZE,\n height: THUMB_SIZE,\n borderRadius: THUMB_SIZE / 2,\n backgroundColor: '#FFFFFF',\n },\n});\n"]}
|
package/build/index.d.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Built on native AVFoundation with Expo Modules
|
|
4
4
|
*/
|
|
5
5
|
import { type EventSubscription } from 'expo-modules-core';
|
|
6
|
-
import type { PanZoomVideoOptions } from './ExpoTwoStepVideo.types';
|
|
6
|
+
import type { PanZoomVideoOptions, PickVideoOptions, PickedVideo } from './ExpoTwoStepVideo.types';
|
|
7
7
|
/**
|
|
8
8
|
* Quality presets for video export
|
|
9
9
|
*/
|
|
@@ -412,7 +412,84 @@ export declare function transformVideo(options: TransformVideoOptions): Promise<
|
|
|
412
412
|
* ```
|
|
413
413
|
*/
|
|
414
414
|
export declare function loopSegment(options: LoopSegmentOptions): Promise<LoopResult>;
|
|
415
|
-
export type { PanZoomVideoOptions } from './ExpoTwoStepVideo.types';
|
|
415
|
+
export type { PanZoomVideoOptions, PickVideoOptions, PickedVideo, DoubleTapSkipEvent } from './ExpoTwoStepVideo.types';
|
|
416
|
+
/**
|
|
417
|
+
* Pick video(s) from the photo library using native PHPickerViewController
|
|
418
|
+
*
|
|
419
|
+
* This provides a native photo library picker that:
|
|
420
|
+
* - Supports albums and favorites
|
|
421
|
+
* - Handles iCloud video downloading automatically (with native progress UI)
|
|
422
|
+
* - Returns comprehensive metadata including creation date
|
|
423
|
+
* - No editing UI - just clean selection
|
|
424
|
+
*
|
|
425
|
+
* @param options - Picker options (optional)
|
|
426
|
+
* @returns Promise resolving to array of picked videos (empty if cancelled)
|
|
427
|
+
*
|
|
428
|
+
* @example
|
|
429
|
+
* ```typescript
|
|
430
|
+
* // Pick a single video
|
|
431
|
+
* const videos = await TwoStepVideo.pickVideo();
|
|
432
|
+
* if (videos.length > 0) {
|
|
433
|
+
* const video = videos[0];
|
|
434
|
+
* console.log(`Selected: ${video.fileName}`);
|
|
435
|
+
* console.log(`Duration: ${video.duration}s`);
|
|
436
|
+
* console.log(`Size: ${video.width}x${video.height}`);
|
|
437
|
+
* console.log(`File size: ${video.fileSize} bytes`);
|
|
438
|
+
* console.log(`Created: ${video.creationDate}`);
|
|
439
|
+
*
|
|
440
|
+
* // Load the video for editing
|
|
441
|
+
* const asset = await TwoStepVideo.loadAsset({ uri: video.uri });
|
|
442
|
+
*
|
|
443
|
+
* // Clean up the temp file when done
|
|
444
|
+
* TwoStepVideo.cleanupPickedVideo(video.path);
|
|
445
|
+
* }
|
|
446
|
+
*
|
|
447
|
+
* // Pick multiple videos
|
|
448
|
+
* const videos = await TwoStepVideo.pickVideo({ selectionLimit: 5 });
|
|
449
|
+
* ```
|
|
450
|
+
*/
|
|
451
|
+
export declare function pickVideo(options?: PickVideoOptions): Promise<PickedVideo[]>;
|
|
452
|
+
/**
|
|
453
|
+
* Clean up a specific picked video file from temp storage
|
|
454
|
+
* Call this when you're done with a video that was picked from the library
|
|
455
|
+
*
|
|
456
|
+
* @param path - File path to clean up (use the `path` property from PickedVideo)
|
|
457
|
+
*
|
|
458
|
+
* @example
|
|
459
|
+
* ```typescript
|
|
460
|
+
* const videos = await TwoStepVideo.pickVideo();
|
|
461
|
+
* const video = videos[0];
|
|
462
|
+
*
|
|
463
|
+
* // ... use the video ...
|
|
464
|
+
*
|
|
465
|
+
* // Clean up when done
|
|
466
|
+
* TwoStepVideo.cleanupPickedVideo(video.path);
|
|
467
|
+
* ```
|
|
468
|
+
*/
|
|
469
|
+
export declare function cleanupPickedVideo(path: string): void;
|
|
470
|
+
/**
|
|
471
|
+
* Clean up all picked video files from temp storage
|
|
472
|
+
* Useful for cleanup when unmounting screens or releasing all resources
|
|
473
|
+
*
|
|
474
|
+
* @example
|
|
475
|
+
* ```typescript
|
|
476
|
+
* // In a cleanup effect
|
|
477
|
+
* useEffect(() => {
|
|
478
|
+
* return () => {
|
|
479
|
+
* TwoStepVideo.cleanupAllPickedVideos();
|
|
480
|
+
* TwoStepVideo.releaseAll();
|
|
481
|
+
* };
|
|
482
|
+
* }, []);
|
|
483
|
+
* ```
|
|
484
|
+
*/
|
|
485
|
+
export declare function cleanupAllPickedVideos(): void;
|
|
486
|
+
/**
|
|
487
|
+
* Get list of currently tracked picked video paths
|
|
488
|
+
* Useful for debugging or manual cleanup
|
|
489
|
+
*
|
|
490
|
+
* @returns Array of file paths for picked videos in temp storage
|
|
491
|
+
*/
|
|
492
|
+
export declare function getPickedVideoPaths(): string[];
|
|
416
493
|
/**
|
|
417
494
|
* Apply pan and zoom transformation to a video
|
|
418
495
|
*
|
|
@@ -580,6 +657,10 @@ declare const _default: {
|
|
|
580
657
|
readonly VERTICAL: string;
|
|
581
658
|
readonly BOTH: string;
|
|
582
659
|
};
|
|
660
|
+
pickVideo: typeof pickVideo;
|
|
661
|
+
cleanupPickedVideo: typeof cleanupPickedVideo;
|
|
662
|
+
cleanupAllPickedVideos: typeof cleanupAllPickedVideos;
|
|
663
|
+
getPickedVideoPaths: typeof getPickedVideoPaths;
|
|
583
664
|
loadAsset: typeof loadAsset;
|
|
584
665
|
loadAssetFromPhotos: typeof loadAssetFromPhotos;
|
|
585
666
|
validateVideoUri: typeof validateVideoUri;
|
package/build/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,KAAK,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAG3D,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/build/index.js
CHANGED
|
@@ -239,6 +239,92 @@ export async function transformVideo(options) {
|
|
|
239
239
|
export async function loopSegment(options) {
|
|
240
240
|
return await ExpoTwoStepVideoModule.loopSegment(options.assetId, options.startTime, options.endTime, options.loopCount);
|
|
241
241
|
}
|
|
242
|
+
// MARK: - Media Picker Functions
|
|
243
|
+
/**
|
|
244
|
+
* Pick video(s) from the photo library using native PHPickerViewController
|
|
245
|
+
*
|
|
246
|
+
* This provides a native photo library picker that:
|
|
247
|
+
* - Supports albums and favorites
|
|
248
|
+
* - Handles iCloud video downloading automatically (with native progress UI)
|
|
249
|
+
* - Returns comprehensive metadata including creation date
|
|
250
|
+
* - No editing UI - just clean selection
|
|
251
|
+
*
|
|
252
|
+
* @param options - Picker options (optional)
|
|
253
|
+
* @returns Promise resolving to array of picked videos (empty if cancelled)
|
|
254
|
+
*
|
|
255
|
+
* @example
|
|
256
|
+
* ```typescript
|
|
257
|
+
* // Pick a single video
|
|
258
|
+
* const videos = await TwoStepVideo.pickVideo();
|
|
259
|
+
* if (videos.length > 0) {
|
|
260
|
+
* const video = videos[0];
|
|
261
|
+
* console.log(`Selected: ${video.fileName}`);
|
|
262
|
+
* console.log(`Duration: ${video.duration}s`);
|
|
263
|
+
* console.log(`Size: ${video.width}x${video.height}`);
|
|
264
|
+
* console.log(`File size: ${video.fileSize} bytes`);
|
|
265
|
+
* console.log(`Created: ${video.creationDate}`);
|
|
266
|
+
*
|
|
267
|
+
* // Load the video for editing
|
|
268
|
+
* const asset = await TwoStepVideo.loadAsset({ uri: video.uri });
|
|
269
|
+
*
|
|
270
|
+
* // Clean up the temp file when done
|
|
271
|
+
* TwoStepVideo.cleanupPickedVideo(video.path);
|
|
272
|
+
* }
|
|
273
|
+
*
|
|
274
|
+
* // Pick multiple videos
|
|
275
|
+
* const videos = await TwoStepVideo.pickVideo({ selectionLimit: 5 });
|
|
276
|
+
* ```
|
|
277
|
+
*/
|
|
278
|
+
export async function pickVideo(options) {
|
|
279
|
+
return await ExpoTwoStepVideoModule.pickVideo(options?.selectionLimit);
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* Clean up a specific picked video file from temp storage
|
|
283
|
+
* Call this when you're done with a video that was picked from the library
|
|
284
|
+
*
|
|
285
|
+
* @param path - File path to clean up (use the `path` property from PickedVideo)
|
|
286
|
+
*
|
|
287
|
+
* @example
|
|
288
|
+
* ```typescript
|
|
289
|
+
* const videos = await TwoStepVideo.pickVideo();
|
|
290
|
+
* const video = videos[0];
|
|
291
|
+
*
|
|
292
|
+
* // ... use the video ...
|
|
293
|
+
*
|
|
294
|
+
* // Clean up when done
|
|
295
|
+
* TwoStepVideo.cleanupPickedVideo(video.path);
|
|
296
|
+
* ```
|
|
297
|
+
*/
|
|
298
|
+
export function cleanupPickedVideo(path) {
|
|
299
|
+
ExpoTwoStepVideoModule.cleanupPickedVideo(path);
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Clean up all picked video files from temp storage
|
|
303
|
+
* Useful for cleanup when unmounting screens or releasing all resources
|
|
304
|
+
*
|
|
305
|
+
* @example
|
|
306
|
+
* ```typescript
|
|
307
|
+
* // In a cleanup effect
|
|
308
|
+
* useEffect(() => {
|
|
309
|
+
* return () => {
|
|
310
|
+
* TwoStepVideo.cleanupAllPickedVideos();
|
|
311
|
+
* TwoStepVideo.releaseAll();
|
|
312
|
+
* };
|
|
313
|
+
* }, []);
|
|
314
|
+
* ```
|
|
315
|
+
*/
|
|
316
|
+
export function cleanupAllPickedVideos() {
|
|
317
|
+
ExpoTwoStepVideoModule.cleanupAllPickedVideos();
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* Get list of currently tracked picked video paths
|
|
321
|
+
* Useful for debugging or manual cleanup
|
|
322
|
+
*
|
|
323
|
+
* @returns Array of file paths for picked videos in temp storage
|
|
324
|
+
*/
|
|
325
|
+
export function getPickedVideoPaths() {
|
|
326
|
+
return ExpoTwoStepVideoModule.getPickedVideoPaths();
|
|
327
|
+
}
|
|
242
328
|
/**
|
|
243
329
|
* Apply pan and zoom transformation to a video
|
|
244
330
|
*
|
|
@@ -428,6 +514,11 @@ export default {
|
|
|
428
514
|
// Constants
|
|
429
515
|
Quality,
|
|
430
516
|
Mirror,
|
|
517
|
+
// Media picker
|
|
518
|
+
pickVideo,
|
|
519
|
+
cleanupPickedVideo,
|
|
520
|
+
cleanupAllPickedVideos,
|
|
521
|
+
getPickedVideoPaths,
|
|
431
522
|
// Core functions
|
|
432
523
|
loadAsset,
|
|
433
524
|
loadAssetFromPhotos,
|