@8man/react-native-media-console
Version:
Controls for react-native-video
529 lines (509 loc) • 18.6 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _reactNative = require("react-native");
var _react = _interopRequireWildcard(require("react"));
var _reactNativeReanimated = _interopRequireWildcard(require("react-native-reanimated"));
var _MaterialIcons = _interopRequireDefault(require("@expo/vector-icons/MaterialIcons"));
var Brightness = _interopRequireWildcard(require("expo-brightness"));
var _reactNativeVolumeManager = require("react-native-volume-manager");
var _reactNativeGestureHandler = require("react-native-gesture-handler");
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
const SWIPE_RANGE = 370;
const Ripple = /*#__PURE__*/_react.default.memo(({
visible,
isLeft,
totalTime,
showControls
}) => {
const screenDimensions = (0, _react.useMemo)(() => _reactNative.Dimensions.get('window'), []);
const {
width: SCREEN_WIDTH,
height: SCREEN_HEIGHT
} = screenDimensions;
const scale = (0, _reactNativeReanimated.useSharedValue)(0);
const opacity = (0, _reactNativeReanimated.useSharedValue)(0);
_react.default.useEffect(() => {
if (visible) {
scale.value = (0, _reactNativeReanimated.withSequence)((0, _reactNativeReanimated.withTiming)(1.5, {
duration: 400
}), (0, _reactNativeReanimated.withDelay)(400, (0, _reactNativeReanimated.withTiming)(0, {
duration: 400
})));
opacity.value = (0, _reactNativeReanimated.withSequence)((0, _reactNativeReanimated.withTiming)(0.4, {
duration: 400
}), (0, _reactNativeReanimated.withDelay)(400, (0, _reactNativeReanimated.withTiming)(0, {
duration: 400
})));
}
}, [visible, scale, opacity]);
const rippleStyle = (0, _reactNativeReanimated.useAnimatedStyle)(() => ({
opacity: opacity.value,
//@ts-ignore
transform: [{
scale: scale.value
}]
}), []);
const containerStyle = (0, _react.useMemo)(() => ({
position: 'absolute',
top: showControls ? -70 : -45,
left: isLeft ? '-10%' : undefined,
right: isLeft ? undefined : '-10%',
width: SCREEN_WIDTH / 2.5,
height: SCREEN_HEIGHT,
zIndex: 999
}), [showControls, isLeft, SCREEN_WIDTH, SCREEN_HEIGHT]);
const innerStyle = (0, _react.useMemo)(() => ({
position: 'absolute',
width: '100%',
height: '100%',
backgroundColor: 'rgba(0,0,0,0.9)',
justifyContent: 'center',
alignItems: 'center',
borderRadius: SCREEN_HEIGHT / 2
}), [SCREEN_HEIGHT]);
const textStyle = (0, _react.useMemo)(() => ({
color: 'white',
marginTop: 8,
fontSize: 12
}), []);
return visible ? /*#__PURE__*/_react.default.createElement(_reactNative.View, {
style: containerStyle
}, /*#__PURE__*/_react.default.createElement(_reactNativeReanimated.default.View, {
style: [innerStyle, rippleStyle]
}, /*#__PURE__*/_react.default.createElement(_MaterialIcons.default, {
name: isLeft ? 'fast-rewind' : 'fast-forward',
size: 28,
color: "white"
}), !isNaN(totalTime) && totalTime > 0 && /*#__PURE__*/_react.default.createElement(_reactNative.Text, {
style: textStyle
}, Math.floor(totalTime), "s"))) : null;
});
const Gestures = ({
forward,
rewind,
togglePlayPause,
toggleControls,
doubleTapTime,
tapActionTimeout,
tapAnywhereToPause,
rewindTime = 10,
showControls,
disableGesture,
setPlayback
}) => {
const [rippleVisible, setRippleVisible] = (0, _react.useState)(false);
const [isLeftRipple, setIsLeftRipple] = (0, _react.useState)(false);
const [totalSkipTime, setTotalSkipTime] = (0, _react.useState)(0);
const [displayVolume, setDisplayVolume] = (0, _react.useState)(0);
const [displayBrightness, setDisplayBrightness] = (0, _react.useState)(0);
const [isVolumeVisible, setIsVolumeVisible] = (0, _react.useState)(false);
const [isBrightnessVisible, setIsBrightnessVisible] = (0, _react.useState)(false);
const [toastMessage, setToastMessage] = (0, _react.useState)(null);
// Memoize screen dimensions
const screenDimensions = (0, _react.useMemo)(() => _reactNative.Dimensions.get('window'), []);
const {
width: SCREEN_WIDTH
} = screenDimensions;
// Refs
const initialTapPosition = (0, _react.useRef)({
x: 0,
y: 0
});
const isDoubleTapRef = (0, _react.useRef)(false);
const currentSideRef = (0, _react.useRef)(null);
const tapCountRef = (0, _react.useRef)(0);
const skipTimeoutRef = (0, _react.useRef)(null);
const lastTapTimeRef = (0, _react.useRef)(0);
const originalSettings = (0, _react.useRef)({
volume: 0,
brightness: 0
});
// Shared values
const volumeValue = (0, _reactNativeReanimated.useSharedValue)(0);
const brightnessValue = (0, _reactNativeReanimated.useSharedValue)(0);
const startVolume = (0, _reactNativeReanimated.useSharedValue)(0);
const startBrightness = (0, _reactNativeReanimated.useSharedValue)(0);
const toastOpacity = (0, _reactNativeReanimated.useSharedValue)(0);
// Toast styles (inline, no Tailwind)
const toastAnimatedStyle = (0, _reactNativeReanimated.useAnimatedStyle)(() => ({
opacity: toastOpacity.value,
transform: [{
translateY: (1 - toastOpacity.value) * -4 // subtle lift-in
}]
}), []);
const toastContainerStyle = (0, _react.useMemo)(() => ({
position: 'absolute',
width: '100%',
top: 48,
// ~ top-12
justifyContent: 'center',
alignItems: 'center',
paddingHorizontal: 8,
// px-2
zIndex: 1200
}), []);
const toastTextStyle = (0, _react.useMemo)(() => ({
color: 'white',
// text-white
backgroundColor: 'rgba(0,0,0,0.5)',
// bg-black/50
padding: 8,
// p-2
borderRadius: 9999,
// rounded-full
fontSize: 16 // text-base
}), []);
const show2xToast = (0, _react.useCallback)(() => {
setToastMessage('2× speed');
toastOpacity.value = (0, _reactNativeReanimated.withTiming)(1, {
duration: 150
});
}, [toastOpacity]);
const hideToast = (0, _react.useCallback)(() => {
toastOpacity.value = (0, _reactNativeReanimated.withTiming)(0, {
duration: 150
}, finished => {
if (finished) {
(0, _reactNativeReanimated.runOnJS)(setToastMessage)(null);
}
});
}, [toastOpacity]);
const resetState = (0, _react.useCallback)(() => {
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 = (0, _react.useCallback)(async () => {
try {
const count = Number(tapCountRef.current) - 1;
const baseTime = Number(rewindTime);
const skipTime = baseTime * count;
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();
}
}, [rewindTime, rewind, forward, resetState]);
const handleTap = (0, _react.useCallback)((e, side) => {
const now = Date.now();
const touchX = e.nativeEvent.locationX;
const touchY = e.nativeEvent.locationY;
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;
setTotalSkipTime(newSkipTime);
setRippleVisible(true);
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);
}
}
}, [resetState, tapAnywhereToPause, togglePlayPause, toggleControls, doubleTapTime, rewindTime, handleSkip]);
const updateSystemVolume = (0, _react.useCallback)(newVolume => {
const clampedVolume = Math.max(0, Math.min(1, newVolume));
_reactNativeVolumeManager.VolumeManager.setVolume(clampedVolume);
setDisplayVolume(clampedVolume);
}, []);
const updateSystemBrightness = (0, _react.useCallback)(newBrightness => {
const clampedBrightness = Math.max(0, Math.min(1, newBrightness));
Brightness.setBrightnessAsync(clampedBrightness);
setDisplayBrightness(clampedBrightness);
}, []);
const panGesture = (0, _react.useMemo)(() => _reactNativeGestureHandler.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;
(0, _reactNativeReanimated.runOnJS)(setIsBrightnessVisible)(true);
} else {
startVolume.value = volumeValue.value;
(0, _reactNativeReanimated.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;
(0, _reactNativeReanimated.runOnJS)(updateSystemBrightness)(newBrightness);
} else {
// Volume control
const newVolume = Math.max(0, Math.min(1, startVolume.value + change));
volumeValue.value = newVolume;
(0, _reactNativeReanimated.runOnJS)(updateSystemVolume)(newVolume);
}
}).onFinalize(() => {
'worklet';
(0, _reactNativeReanimated.runOnJS)(setIsVolumeVisible)(false);
(0, _reactNativeReanimated.runOnJS)(setIsBrightnessVisible)(false);
}), [SCREEN_WIDTH, updateSystemBrightness, updateSystemVolume]);
const ControlOverlay = /*#__PURE__*/_react.default.memo(({
value,
isVisible,
isVolume
}) => {
const containerStyle = (0, _react.useMemo)(() => ({
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
}), [isVolume, showControls]);
const textStyle = (0, _react.useMemo)(() => ({
color: 'white',
marginTop: 5
}), []);
const iconName = (0, _react.useMemo)(() => {
if (isVolume) {
return value === 0 ? 'volume-mute' : value < 0.3 ? 'volume-down' : 'volume-up';
}
return 'brightness-6';
}, [isVolume, value]);
if (!isVisible) return null;
return /*#__PURE__*/_react.default.createElement(_reactNativeReanimated.default.View, {
style: containerStyle
}, /*#__PURE__*/_react.default.createElement(_MaterialIcons.default, {
name: iconName,
size: 24,
color: "white"
}), /*#__PURE__*/_react.default.createElement(_reactNative.Text, {
style: textStyle
}, Math.round(value * 100)));
});
// Initialize and store original settings
(0, _react.useEffect)(() => {
let mounted = true;
const initializeSettings = async () => {
try {
const [currentVolume, currentBrightness] = await Promise.all([_reactNativeVolumeManager.VolumeManager.getVolume(), Brightness.getBrightnessAsync()]);
if (mounted) {
// Store original values
originalSettings.current = {
volume: currentVolume.volume,
brightness: currentBrightness
};
// Set initial values
volumeValue.value = currentVolume.volume;
brightnessValue.value = currentBrightness;
setDisplayVolume(currentVolume.volume);
setDisplayBrightness(currentBrightness);
console.log('Original settings stored:🔥', {
volume: currentVolume,
brightness: currentBrightness
});
}
} catch (error) {
console.error('Error initializing settings:', error);
}
};
initializeSettings();
return () => {
mounted = false;
};
}, []);
// Cleanup useEffect
(0, _react.useEffect)(() => {
return () => {
if (skipTimeoutRef.current) {
clearTimeout(skipTimeoutRef.current);
}
if (tapActionTimeout.current) {
clearTimeout(tapActionTimeout.current);
}
};
}, []);
// Initialize and store original settings
(0, _react.useEffect)(() => {
let mounted = true;
const initializeSettings = async () => {
try {
const [currentVolume, currentBrightness] = await Promise.all([_reactNativeVolumeManager.VolumeManager.getVolume(), Brightness.getBrightnessAsync()]);
if (mounted) {
// Store original values
originalSettings.current = {
volume: currentVolume.volume,
brightness: currentBrightness
};
// Set initial values
volumeValue.value = currentVolume.volume;
brightnessValue.value = currentBrightness;
setDisplayVolume(currentVolume.volume);
setDisplayBrightness(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();
// };
}, []);
// Memoize container styles
const containerStyle = (0, _react.useMemo)(() => ({
width: '100%',
height: '70%'
}), []);
const gestureContainerStyle = (0, _react.useMemo)(() => ({
position: 'relative',
width: '100%',
height: '100%',
flexDirection: 'row'
}), []);
const leftPressableStyle = (0, _react.useMemo)(() => ({
flex: 1,
top: 40,
height: '100%',
position: 'relative'
}), []);
const rightPressableStyle = (0, _react.useMemo)(() => ({
top: 40,
flex: 1,
height: '100%',
position: 'relative'
}), []);
// Memoized handler functions
const handleLeftTap = (0, _react.useCallback)(e => {
handleTap(e, 'left');
}, [handleTap]);
const handleRightTap = (0, _react.useCallback)(e => {
handleTap(e, 'right');
}, [handleTap]);
if (disableGesture) {
return null;
}
return /*#__PURE__*/_react.default.createElement(_reactNativeGestureHandler.GestureHandlerRootView, {
style: containerStyle
}, /*#__PURE__*/_react.default.createElement(_reactNativeGestureHandler.GestureDetector, {
gesture: panGesture
}, /*#__PURE__*/_react.default.createElement(_reactNative.View, {
style: gestureContainerStyle
}, /*#__PURE__*/_react.default.createElement(_reactNative.Pressable, {
onPress: handleLeftTap,
style: leftPressableStyle
}, /*#__PURE__*/_react.default.createElement(Ripple, {
visible: rippleVisible && isLeftRipple,
showControls: showControls,
isLeft: true,
totalTime: totalSkipTime
})), /*#__PURE__*/_react.default.createElement(_reactNative.Pressable, {
onPress: handleRightTap,
style: rightPressableStyle,
onLongPress: () => {
setPlayback(2);
show2xToast();
},
onPressOut: () => {
setPlayback(1);
hideToast();
}
}, /*#__PURE__*/_react.default.createElement(Ripple, {
visible: rippleVisible && !isLeftRipple,
showControls: showControls,
isLeft: false,
totalTime: totalSkipTime
})))), toastMessage ? /*#__PURE__*/_react.default.createElement(_reactNativeReanimated.default.View, {
style: [toastContainerStyle, toastAnimatedStyle],
pointerEvents: "none"
}, /*#__PURE__*/_react.default.createElement(_reactNative.Text, {
style: toastTextStyle
}, toastMessage)) : null, /*#__PURE__*/_react.default.createElement(ControlOverlay, {
value: displayVolume,
isVisible: isVolumeVisible,
isVolume: true
}), /*#__PURE__*/_react.default.createElement(ControlOverlay, {
value: displayBrightness,
isVisible: isBrightnessVisible,
isVolume: false
}));
};
var _default = exports.default = Gestures;
//# sourceMappingURL=Gestures.js.map