UNPKG

@8man/react-native-media-console

Version:
529 lines (509 loc) 18.6 kB
"use strict"; 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