diff --git a/.vscode/settings.json b/.vscode/settings.json index 7a73a41..7b016a8 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,2 +1,3 @@ { + "java.compile.nullAnalysis.mode": "automatic" } \ No newline at end of file diff --git a/node_modules/react-native-video/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java b/node_modules/react-native-video/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java index 87e436a..f175dec 100644 --- a/node_modules/react-native-video/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java +++ b/node_modules/react-native-video/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java @@ -726,7 +726,7 @@ public class ReactExoplayerView extends FrameLayout implements DefaultRenderersFactory renderersFactory = new DefaultRenderersFactory(getContext()) - .setExtensionRendererMode(DefaultRenderersFactory.EXTENSION_RENDERER_MODE_PREFER) + .setExtensionRendererMode(DefaultRenderersFactory.EXTENSION_RENDERER_MODE_OFF) .setEnableDecoderFallback(true) .forceEnableMediaCodecAsynchronousQueueing(); diff --git a/src/components/player/controls/PlayerControls.tsx b/src/components/player/controls/PlayerControls.tsx index ae7d05a..5794b4d 100644 --- a/src/components/player/controls/PlayerControls.tsx +++ b/src/components/player/controls/PlayerControls.tsx @@ -90,6 +90,156 @@ export const PlayerControls: React.FC = ({ onAirPlayPress, }) => { const { currentTheme } = useTheme(); + + + /* Responsive Spacing */ + const screenWidth = Dimensions.get('window').width; + const buttonSpacing = screenWidth * 0.15; + + const playButtonSize = screenWidth * 0.12; // 12% of screen width + const playIconSize = playButtonSize * 0.6; // 60% of button size + const seekButtonSize = screenWidth * 0.11; // 11% of screen width + const seekIconSize = seekButtonSize * 0.75; // 75% of button size + const seekNumberSize = seekButtonSize * 0.25; // 25% of button size + const arcBorderWidth = seekButtonSize * 0.05; // 5% of button size + + /* Animations - State & Refs */ + const [showBackwardSign, setShowBackwardSign] = React.useState(false); + const [showForwardSign, setShowForwardSign] = React.useState(false); + + /* Separate Animations for Each Button */ + const backwardPressAnim = React.useRef(new Animated.Value(0)).current; + const backwardSlideAnim = React.useRef(new Animated.Value(0)).current; + const backwardScaleAnim = React.useRef(new Animated.Value(1)).current; + const backwardArcOpacity = React.useRef(new Animated.Value(0)).current; + const backwardArcRotation = React.useRef(new Animated.Value(0)).current; + + const forwardPressAnim = React.useRef(new Animated.Value(0)).current; + const forwardSlideAnim = React.useRef(new Animated.Value(0)).current; + const forwardScaleAnim = React.useRef(new Animated.Value(1)).current; + const forwardArcOpacity = React.useRef(new Animated.Value(0)).current; + const forwardArcRotation = React.useRef(new Animated.Value(0)).current; + + const playPressAnim = React.useRef(new Animated.Value(0)).current; + const playIconScale = React.useRef(new Animated.Value(1)).current; + const playIconOpacity = React.useRef(new Animated.Value(1)).current; + + /* Handle Seek with Animation */ + const handleSeekWithAnimation = (seconds: number) => { + const isForward = seconds > 0; + + if (isForward) { + setShowForwardSign(true); + } else { + setShowBackwardSign(true); + } + + const pressAnim = isForward ? forwardPressAnim : backwardPressAnim; + const slideAnim = isForward ? forwardSlideAnim : backwardSlideAnim; + const scaleAnim = isForward ? forwardScaleAnim : backwardScaleAnim; + const arcOpacity = isForward ? forwardArcOpacity : backwardArcOpacity; + const arcRotation = isForward ? forwardArcRotation : backwardArcRotation; + + Animated.parallel([ + // Button press effect (circle flash) + Animated.sequence([ + Animated.timing(pressAnim, { + toValue: 1, + duration: 100, + useNativeDriver: true, + }), + Animated.timing(pressAnim, { + toValue: 0, + duration: 200, + useNativeDriver: true, + }), + ]), + // Number slide out + Animated.sequence([ + Animated.timing(slideAnim, { + toValue: isForward ? (seekButtonSize * 0.75) : -(seekButtonSize * 0.75), + duration: 250, + useNativeDriver: true, + }), + Animated.timing(slideAnim, { + toValue: 0, + duration: 120, + useNativeDriver: true, + }), + ]), + // Button scale pulse + Animated.sequence([ + Animated.timing(scaleAnim, { + toValue: 1.15, + duration: 150, + useNativeDriver: true, + }), + Animated.timing(scaleAnim, { + toValue: 1, + duration: 150, + useNativeDriver: true, + }), + ]), + // Arc sweep animation + Animated.parallel([ + Animated.timing(arcOpacity, { + toValue: 1, + duration: 50, + useNativeDriver: true, + }), + Animated.timing(arcRotation, { + toValue: 1, + duration: 200, + useNativeDriver: true, + }), + ]), + ]).start(() => { + if (isForward) { + setShowForwardSign(false); + } else { + setShowBackwardSign(false); + } + arcOpacity.setValue(0); + arcRotation.setValue(0); + }); + + skip(seconds); + }; + + /* Handle Play/Pause with Animation */ + const handlePlayPauseWithAnimation = () => { + Animated.sequence([ + Animated.timing(playPressAnim, { + toValue: 1, + duration: 100, + useNativeDriver: true, + }), + Animated.timing(playPressAnim, { + toValue: 0, + duration: 200, + useNativeDriver: true, + }), + ]).start(); + + Animated.sequence([ + Animated.timing(playIconScale, { + toValue: 0.85, + duration: 150, + useNativeDriver: true, + }), + Animated.timing(playIconScale, { + toValue: 1, + duration: 150, + useNativeDriver: true, + }), + ]).start(); + + togglePlayback(); + }; + + + + const deviceWidth = Dimensions.get('window').width; const BREAKPOINTS = { phone: 0, tablet: 768, largeTablet: 1024, tv: 1440 } as const; const getDeviceType = (w: number) => { @@ -170,6 +320,179 @@ export const PlayerControls: React.FC = ({ + + {/* Center Controls - CloudStream Style */} + + + {/* Backward Seek Button (-10s) */} + handleSeekWithAnimation(-10)} + activeOpacity={0.7} + > + + + + + + {showBackwardSign ? '-10' : '10'} + + + + + + + + + {/* Play/Pause Button */} + + + + + + + + + + {/* Forward Seek Button (+10s) */} + handleSeekWithAnimation(10)} + activeOpacity={0.7} + > + + + + + + {showForwardSign ? '+10' : '10'} + + + + + + {/* Center Controls (Play/Pause, Skip) */} skip(-10)} style={styles.skipButton}> @@ -185,6 +508,10 @@ export const PlayerControls: React.FC = ({ + + + + {/* Bottom Gradient */}