@8man/react-native-media-console
Version:
A media player for the react-native-video component
433 lines (424 loc) • 13.3 kB
JavaScript
import { View, Text, Pressable, Dimensions } from 'react-native';
import React, { useState, useRef, useEffect } from 'react';
import Animated, { useAnimatedStyle, withTiming, withSequence, useSharedValue, withDelay, runOnJS } from 'react-native-reanimated';
//@ts-ignore
import Icon from '@expo/vector-icons/MaterialIcons';
import SystemSetting from 'react-native-system-setting';
import { GestureDetector, Gesture, GestureHandlerRootView } from 'react-native-gesture-handler';
const SWIPE_RANGE = 370;
const Ripple = ({
visible,
isLeft,
totalTime,
showControls
}) => {
const {
width: SCREEN_WIDTH,
height: SCREEN_HEIGHT
} = Dimensions.get('window');
const scale = useSharedValue(0);
const opacity = useSharedValue(0);
React.useEffect(() => {
if (visible) {
scale.value = withSequence(withTiming(1.5, {
duration: 400
}), withDelay(400, withTiming(0, {
duration: 400
})));
opacity.value = withSequence(withTiming(0.4, {
duration: 400
}), withDelay(400, withTiming(0, {
duration: 400
})));
}
}, [visible]);
const rippleStyle = useAnimatedStyle(() => ({
opacity: opacity.value,
//@ts-ignore
transform: [{
scale: scale.value
}]
}));
return visible ? /*#__PURE__*/React.createElement(View, {
style: {
position: 'absolute',
top: showControls ? -70 : -45,
left: isLeft ? '-10%' : undefined,
right: isLeft ? undefined : '-10%',
width: SCREEN_WIDTH / 2.5,
height: SCREEN_HEIGHT,
zIndex: 999
}
}, /*#__PURE__*/React.createElement(Animated.View, {
style: [{
position: 'absolute',
width: '100%',
height: '100%',
backgroundColor: 'rgba(0,0,0,0.9)',
justifyContent: 'center',
alignItems: 'center',
borderRadius: SCREEN_HEIGHT / 2
}, rippleStyle]
}, /*#__PURE__*/React.createElement(Icon, {
name: isLeft ? 'fast-rewind' : 'fast-forward',
size: 28,
color: "white"
}), !isNaN(totalTime) && totalTime > 0 && /*#__PURE__*/React.createElement(Text, {
style: {
color: 'white',
marginTop: 8,
fontSize: 12
}
}, Math.floor(totalTime), "s"))) : null;
};
const Gestures = ({
forward,
rewind,
togglePlayPause,
toggleControls,
doubleTapTime,
tapActionTimeout,
tapAnywhereToPause,
rewindTime = 10,
showControls,
disableGesture
}) => {
const [rippleVisible, setRippleVisible] = useState(false);
// const [ripplePosition,setRipplePosition] = useState({x: 0, y: 0});
const [isLeftRipple, setIsLeftRipple] = useState(false);
const [totalSkipTime, setTotalSkipTime] = useState(0);
const initialTapPosition = useRef({
x: 0,
y: 0
});
const isDoubleTapRef = useRef(false);
const currentSideRef = useRef(null);
const tapCountRef = useRef(0);
const skipTimeoutRef = useRef(null);
const lastTapTimeRef = useRef(0);
const {
width: SCREEN_WIDTH
} = Dimensions.get('window');
const resetState = () => {
isDoubleTapRef.current = false;
currentSideRef.current = null;
tapCountRef.current = 0;
lastTapTimeRef.current = 0;
setTotalSkipTime(0);
setRippleVisible(false);
if (skipTimeoutRef.current) {
clearTimeout(skipTimeoutRef.current);
skipTimeoutRef.current = null;
}
};
const handleSkip = async () => {
try {
const count = Number(tapCountRef.current) - 1;
const baseTime = Number(rewindTime);
const skipTime = baseTime * count;
console.log('Skip calculation:', {
count,
baseTime,
skipTime,
side: currentSideRef.current,
isValidNumber: !isNaN(skipTime) && skipTime > 0
});
if (!isNaN(skipTime) && skipTime > 0) {
if (currentSideRef.current === 'left') {
rewind(skipTime);
} else if (currentSideRef.current === 'right') {
forward(skipTime);
}
}
} catch (error) {
console.error('Error while skipping:', error);
} finally {
resetState();
}
};
const handleTap = (e, side) => {
const now = Date.now();
const touchX = e.nativeEvent.locationX;
const touchY = e.nativeEvent.locationY;
console.log('Tap details:', {
touchX,
side,
isDoubleTap: isDoubleTapRef.current,
timeSinceLastTap: now - lastTapTimeRef.current
});
if (now - lastTapTimeRef.current > 500) {
resetState();
}
if (!isDoubleTapRef.current) {
isDoubleTapRef.current = true;
initialTapPosition.current = {
x: touchX,
y: touchY
};
currentSideRef.current = side;
tapCountRef.current = 1;
lastTapTimeRef.current = now;
tapActionTimeout.current = setTimeout(() => {
if (tapAnywhereToPause) {
togglePlayPause();
} else {
toggleControls();
}
resetState();
}, doubleTapTime);
} else {
if (tapActionTimeout.current) {
clearTimeout(tapActionTimeout.current);
tapActionTimeout.current = null;
}
if (currentSideRef.current === side) {
tapCountRef.current += 1;
lastTapTimeRef.current = now;
const count = Number(tapCountRef.current) - 1;
const baseTime = Number(rewindTime);
const newSkipTime = baseTime * count;
console.log('Multiple tap calculation:', {
count,
baseTime,
newSkipTime,
tapCount: tapCountRef.current,
side
});
setTotalSkipTime(newSkipTime);
setRippleVisible(true);
// setRipplePosition(initialTapPosition.current);
setIsLeftRipple(side === 'left');
if (skipTimeoutRef.current) {
clearTimeout(skipTimeoutRef.current);
}
skipTimeoutRef.current = setTimeout(handleSkip, 500);
} else {
resetState();
isDoubleTapRef.current = true;
initialTapPosition.current = {
x: touchX,
y: touchY
};
currentSideRef.current = side;
tapCountRef.current = 1;
lastTapTimeRef.current = now;
tapActionTimeout.current = setTimeout(() => {
resetState();
}, doubleTapTime);
}
}
};
React.useEffect(() => {
return () => {
if (skipTimeoutRef.current) {
clearTimeout(skipTimeoutRef.current);
}
if (tapActionTimeout.current) {
clearTimeout(tapActionTimeout.current);
}
};
}, []);
const volumeValue = useSharedValue(0);
const brightnessValue = useSharedValue(0);
const startVolume = useSharedValue(0);
const startBrightness = useSharedValue(0);
const [displayVolume, setDisplayVolume] = useState(0);
const [displayBrightness, setDisplayBrightness] = useState(0);
const [isVolumeVisible, setIsVolumeVisible] = useState(false);
const [isBrightnessVisible, setIsBrightnessVisible] = useState(false);
// Store original values in refs to maintain latest values for cleanup
const originalSettings = useRef({
volume: 0,
brightness: 0
});
React.useEffect(() => {
const initializeSettings = async () => {
try {
const currentVolume = await SystemSetting.getVolume();
const currentBrightness = await SystemSetting.getBrightness();
volumeValue.value = currentVolume;
brightnessValue.value = currentBrightness;
setDisplayVolume(currentVolume);
setDisplayBrightness(currentBrightness);
} catch (error) {
console.error('Error initializing settings:', error);
}
};
initializeSettings();
}, []);
const updateSystemVolume = React.useCallback(newVolume => {
const clampedVolume = Math.max(0, Math.min(1, newVolume));
SystemSetting.setVolume(clampedVolume);
setDisplayVolume(clampedVolume);
}, []);
const updateSystemBrightness = React.useCallback(newBrightness => {
const clampedBrightness = Math.max(0, Math.min(1, newBrightness));
SystemSetting.setAppBrightness(clampedBrightness);
setDisplayBrightness(clampedBrightness);
}, []);
const panGesture = Gesture.Pan().minDistance(10) // Minimum distance before gesture starts
.onStart(event => {
'worklet';
const isLeftSide = event.x < SCREEN_WIDTH / 2;
if (isLeftSide) {
startBrightness.value = brightnessValue.value;
runOnJS(setIsBrightnessVisible)(true);
} else {
startVolume.value = volumeValue.value;
runOnJS(setIsVolumeVisible)(true);
}
}).onUpdate(event => {
'worklet';
const isLeftSide = event.x < SCREEN_WIDTH / 2;
const change = -event.translationY / SWIPE_RANGE;
if (isLeftSide) {
// Brightness control
const newBrightness = Math.max(0, Math.min(1, startBrightness.value + change));
brightnessValue.value = newBrightness;
runOnJS(updateSystemBrightness)(newBrightness);
} else {
// Volume control
const newVolume = Math.max(0, Math.min(1, startVolume.value + change));
volumeValue.value = newVolume;
runOnJS(updateSystemVolume)(newVolume);
}
}).onFinalize(() => {
'worklet';
runOnJS(setIsVolumeVisible)(false);
runOnJS(setIsBrightnessVisible)(false);
});
const ControlOverlay = /*#__PURE__*/React.memo(({
value,
isVisible,
isVolume
}) => {
if (!isVisible) return null;
return /*#__PURE__*/React.createElement(Animated.View, {
// @ts-ignore
style: {
position: 'absolute',
top: '50%',
left: !isVolume ? undefined : '15%',
right: !isVolume ? '15%' : undefined,
transform: [{
translateX: 0
}, {
translateY: showControls ? -20 : 0
}],
backgroundColor: 'rgba(0, 0, 0, 0.6)',
borderRadius: 10,
minWidth: 50,
padding: 10,
alignItems: 'center',
zIndex: 1000
}
}, /*#__PURE__*/React.createElement(Icon, {
name: isVolume ? value === 0 ? 'volume-mute' : value < 0.3 ? 'volume-down' : 'volume-up' : 'brightness-6',
size: 24,
color: "white"
}), /*#__PURE__*/React.createElement(Text, {
style: {
color: 'white',
marginTop: 5
}
}, Math.round(value * 100)));
});
// Initialize and store original settings
useEffect(() => {
let mounted = true;
const initializeSettings = async () => {
try {
const [currentVolume, currentBrightness] = await Promise.all([SystemSetting.getVolume(), SystemSetting.getBrightness()]);
if (mounted) {
// Store original values
originalSettings.current = {
volume: currentVolume,
brightness: currentBrightness
};
// Set initial values
volumeValue.value = currentVolume;
brightnessValue.value = currentBrightness;
setDisplayVolume(currentVolume);
setDisplayBrightness(currentBrightness);
console.log('Original settings stored:🔥', {
volume: currentVolume,
brightness: currentBrightness
});
}
} catch (error) {
console.error('Error initializing settings:', error);
}
};
initializeSettings();
// Cleanup function
return () => {
mounted = false;
const resetSettings = async () => {
try {
// console.log('Resetting to original settings:🔥', originalSettings.current);
await Promise.all([
// SystemSetting.setVolume(originalSettings.current.volume),
SystemSetting.setAppBrightness(originalSettings.current.brightness)]);
// console.log('Settings reset successfully');
} catch (error) {
console.error('Error resetting settings:', error);
}
};
resetSettings();
};
}, []);
if (disableGesture) {
return null;
}
return /*#__PURE__*/React.createElement(GestureHandlerRootView, {
style: {
width: '100%',
height: '70%'
}
}, /*#__PURE__*/React.createElement(GestureDetector, {
gesture: panGesture
}, /*#__PURE__*/React.createElement(View, {
style: {
position: 'relative',
width: '100%',
height: '100%',
flexDirection: 'row'
}
}, /*#__PURE__*/React.createElement(Pressable, {
onPress: e => handleTap(e, 'left'),
style: {
flex: 1,
top: 40,
height: '100%',
position: 'relative'
}
}, /*#__PURE__*/React.createElement(Ripple, {
visible: rippleVisible && isLeftRipple,
showControls: showControls,
isLeft: true,
totalTime: totalSkipTime
})), /*#__PURE__*/React.createElement(Pressable, {
onPress: e => handleTap(e, 'right'),
style: {
top: 40,
flex: 1,
height: '100%',
position: 'relative'
}
}, /*#__PURE__*/React.createElement(Ripple, {
visible: rippleVisible && !isLeftRipple,
showControls: showControls,
isLeft: false,
totalTime: totalSkipTime
})))), /*#__PURE__*/React.createElement(ControlOverlay, {
value: displayVolume,
isVisible: isVolumeVisible,
isVolume: true
}), /*#__PURE__*/React.createElement(ControlOverlay, {
value: displayBrightness,
isVisible: isBrightnessVisible,
isVolume: false
}));
};
export default Gestures;
//# sourceMappingURL=Gestures.js.map