mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-05-19 08:22:00 +00:00
207 lines
7.7 KiB
TypeScript
207 lines
7.7 KiB
TypeScript
import { useRef, useState } from 'react';
|
|
import { Animated, Platform } from 'react-native';
|
|
import { PanGestureHandlerGestureEvent, State } from 'react-native-gesture-handler';
|
|
import * as Brightness from 'expo-brightness';
|
|
|
|
interface GestureControlConfig {
|
|
volume: number;
|
|
setVolume: (value: number) => void;
|
|
brightness?: number;
|
|
setBrightness?: (value: number) => void;
|
|
volumeRange?: { min: number; max: number }; // Default: { min: 0, max: 1 }
|
|
volumeSensitivity?: number; // Default: 0.006 (iOS), 0.0084 (Android with 1.4x multiplier)
|
|
brightnessSensitivity?: number; // Default: 0.004 (iOS), 0.0056 (Android with 1.4x multiplier)
|
|
overlayTimeout?: number; // Default: 1500ms
|
|
debugMode?: boolean;
|
|
}
|
|
|
|
export const usePlayerGestureControls = (config: GestureControlConfig) => {
|
|
// State for overlays
|
|
const [showVolumeOverlay, setShowVolumeOverlay] = useState(false);
|
|
const [showBrightnessOverlay, setShowBrightnessOverlay] = useState(false);
|
|
const [showResizeModeOverlay, setShowResizeModeOverlay] = useState(false);
|
|
|
|
// Animated values
|
|
const volumeGestureTranslateY = useRef(new Animated.Value(0)).current;
|
|
const brightnessGestureTranslateY = useRef(new Animated.Value(0)).current;
|
|
const volumeOverlayOpacity = useRef(new Animated.Value(0)).current;
|
|
const brightnessOverlayOpacity = useRef(new Animated.Value(0)).current;
|
|
const resizeModeOverlayOpacity = useRef(new Animated.Value(0)).current;
|
|
|
|
// Tracking refs
|
|
const lastVolumeGestureY = useRef(0);
|
|
const lastBrightnessGestureY = useRef(0);
|
|
const volumeOverlayTimeout = useRef<NodeJS.Timeout | null>(null);
|
|
const brightnessOverlayTimeout = useRef<NodeJS.Timeout | null>(null);
|
|
const resizeModeOverlayTimeout = useRef<NodeJS.Timeout | null>(null);
|
|
|
|
// Extract config with defaults and platform adjustments
|
|
const volumeRange = config.volumeRange || { min: 0, max: 1 };
|
|
const baseVolumeSensitivity = config.volumeSensitivity || 0.006;
|
|
const baseBrightnessSensitivity = config.brightnessSensitivity || 0.004;
|
|
const overlayTimeout = config.overlayTimeout || 1500;
|
|
|
|
// Platform-specific sensitivity adjustments
|
|
// Android needs higher sensitivity due to different touch handling
|
|
const platformMultiplier = Platform.OS === 'android' ? 1.6 : 1.0;
|
|
const volumeSensitivity = baseVolumeSensitivity * platformMultiplier;
|
|
const brightnessSensitivity = baseBrightnessSensitivity * platformMultiplier;
|
|
|
|
// Volume gesture handler
|
|
const onVolumeGestureEvent = Animated.event(
|
|
[{ nativeEvent: { translationY: volumeGestureTranslateY } }],
|
|
{
|
|
useNativeDriver: false,
|
|
listener: (event: PanGestureHandlerGestureEvent) => {
|
|
const { translationY, state } = event.nativeEvent;
|
|
|
|
if (state === State.ACTIVE) {
|
|
// Auto-initialize on first active frame
|
|
if (Math.abs(translationY) < 5 && Math.abs(lastVolumeGestureY.current - translationY) > 20) {
|
|
lastVolumeGestureY.current = translationY;
|
|
return;
|
|
}
|
|
|
|
// Calculate delta from last position
|
|
const deltaY = -(translationY - lastVolumeGestureY.current);
|
|
lastVolumeGestureY.current = translationY;
|
|
|
|
// Normalize sensitivity based on volume range
|
|
const rangeMultiplier = volumeRange.max - volumeRange.min;
|
|
const volumeChange = deltaY * volumeSensitivity * rangeMultiplier;
|
|
const newVolume = Math.max(volumeRange.min, Math.min(volumeRange.max, config.volume + volumeChange));
|
|
|
|
config.setVolume(newVolume);
|
|
|
|
if (config.debugMode) {
|
|
console.log(`[GestureControls] Volume set to: ${newVolume} (Platform: ${Platform.OS}, Sensitivity: ${volumeSensitivity})`);
|
|
}
|
|
|
|
// Show overlay
|
|
if (!showVolumeOverlay) {
|
|
setShowVolumeOverlay(true);
|
|
volumeOverlayOpacity.setValue(1);
|
|
}
|
|
|
|
// Reset hide timer
|
|
if (volumeOverlayTimeout.current) {
|
|
clearTimeout(volumeOverlayTimeout.current);
|
|
}
|
|
volumeOverlayTimeout.current = setTimeout(() => {
|
|
Animated.timing(volumeOverlayOpacity, {
|
|
toValue: 0,
|
|
duration: 250,
|
|
useNativeDriver: true,
|
|
}).start(() => setShowVolumeOverlay(false));
|
|
}, overlayTimeout);
|
|
}
|
|
}
|
|
}
|
|
);
|
|
|
|
// Brightness gesture handler - only active if brightness is provided
|
|
const onBrightnessGestureEvent = config.brightness !== undefined && config.setBrightness
|
|
? Animated.event(
|
|
[{ nativeEvent: { translationY: brightnessGestureTranslateY } }],
|
|
{
|
|
useNativeDriver: false,
|
|
listener: (event: PanGestureHandlerGestureEvent) => {
|
|
const { translationY, state } = event.nativeEvent;
|
|
|
|
if (state === State.ACTIVE) {
|
|
// Auto-initialize
|
|
if (Math.abs(translationY) < 5 && Math.abs(lastBrightnessGestureY.current - translationY) > 20) {
|
|
lastBrightnessGestureY.current = translationY;
|
|
return;
|
|
}
|
|
|
|
const deltaY = -(translationY - lastBrightnessGestureY.current);
|
|
lastBrightnessGestureY.current = translationY;
|
|
|
|
const brightnessSensitivity = (config.brightnessSensitivity || 0.004) * platformMultiplier;
|
|
const brightnessChange = deltaY * brightnessSensitivity;
|
|
const currentBrightness = config.brightness as number; // Safe cast as we checked undefined
|
|
const newBrightness = Math.max(0, Math.min(1, currentBrightness + brightnessChange));
|
|
|
|
config.setBrightness!(newBrightness);
|
|
Brightness.setBrightnessAsync(newBrightness).catch(() => { });
|
|
|
|
if (config.debugMode) {
|
|
console.log(`[GestureControls] Device brightness set to: ${newBrightness} (Platform: ${Platform.OS}, Sensitivity: ${brightnessSensitivity})`);
|
|
}
|
|
|
|
if (!showBrightnessOverlay) {
|
|
setShowBrightnessOverlay(true);
|
|
brightnessOverlayOpacity.setValue(1);
|
|
}
|
|
|
|
if (brightnessOverlayTimeout.current) {
|
|
clearTimeout(brightnessOverlayTimeout.current);
|
|
}
|
|
brightnessOverlayTimeout.current = setTimeout(() => {
|
|
Animated.timing(brightnessOverlayOpacity, {
|
|
toValue: 0,
|
|
duration: 250,
|
|
useNativeDriver: true,
|
|
}).start(() => setShowBrightnessOverlay(false));
|
|
}, overlayTimeout);
|
|
}
|
|
}
|
|
}
|
|
)
|
|
: undefined;
|
|
|
|
// Cleanup function
|
|
const cleanup = () => {
|
|
if (volumeOverlayTimeout.current) {
|
|
clearTimeout(volumeOverlayTimeout.current);
|
|
}
|
|
if (brightnessOverlayTimeout.current) {
|
|
clearTimeout(brightnessOverlayTimeout.current);
|
|
}
|
|
if (resizeModeOverlayTimeout.current) {
|
|
clearTimeout(resizeModeOverlayTimeout.current);
|
|
}
|
|
};
|
|
|
|
const showResizeModeOverlayFn = (callback?: () => void) => {
|
|
if (resizeModeOverlayTimeout.current) {
|
|
clearTimeout(resizeModeOverlayTimeout.current);
|
|
}
|
|
setShowResizeModeOverlay(true);
|
|
Animated.timing(resizeModeOverlayOpacity, {
|
|
toValue: 1,
|
|
duration: 100,
|
|
useNativeDriver: true,
|
|
}).start(() => {
|
|
if (callback) callback();
|
|
resizeModeOverlayTimeout.current = setTimeout(() => {
|
|
Animated.timing(resizeModeOverlayOpacity, {
|
|
toValue: 0,
|
|
duration: 200,
|
|
useNativeDriver: true,
|
|
}).start(() => setShowResizeModeOverlay(false));
|
|
}, overlayTimeout);
|
|
});
|
|
};
|
|
|
|
return {
|
|
// Gesture handlers
|
|
onVolumeGestureEvent,
|
|
onBrightnessGestureEvent,
|
|
|
|
// Overlay state
|
|
showVolumeOverlay,
|
|
showBrightnessOverlay,
|
|
showResizeModeOverlay,
|
|
volumeOverlayOpacity,
|
|
brightnessOverlayOpacity,
|
|
resizeModeOverlayOpacity,
|
|
|
|
// Overlay functions
|
|
showResizeModeOverlayFn,
|
|
|
|
// Cleanup
|
|
cleanup,
|
|
};
|
|
};
|