mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-01-11 20:10:25 +00:00
Remove SkiaProgressSlider from PlayerControls and refactor progress handling for improved touch interaction. Update AndroidVideoPlayer and VideoPlayer components to eliminate seekToTime prop and adjust subtitle handling.
This commit is contained in:
parent
6433acac77
commit
d66764471f
4 changed files with 53 additions and 293 deletions
|
|
@ -1195,7 +1195,6 @@ const AndroidVideoPlayer: React.FC = () => {
|
|||
handleProgressBarDragEnd={handleProgressBarDragEnd}
|
||||
buffered={buffered}
|
||||
formatTime={formatTime}
|
||||
seekToTime={seekToTime}
|
||||
/>
|
||||
|
||||
<CustomSubtitles
|
||||
|
|
|
|||
|
|
@ -141,7 +141,6 @@ const VideoPlayer: React.FC = () => {
|
|||
const [currentSubtitle, setCurrentSubtitle] = useState<string>('');
|
||||
const [subtitleSize, setSubtitleSize] = useState<number>(DEFAULT_SUBTITLE_SIZE);
|
||||
const [useCustomSubtitles, setUseCustomSubtitles] = useState<boolean>(false);
|
||||
const [subtitleBackground, setSubtitleBackground] = useState<boolean>(true); // Add missing state
|
||||
const [isLoadingSubtitles, setIsLoadingSubtitles] = useState<boolean>(false);
|
||||
const [availableSubtitles, setAvailableSubtitles] = useState<WyzieSubtitle[]>([]);
|
||||
const [showSubtitleLanguageModal, setShowSubtitleLanguageModal] = useState<boolean>(false);
|
||||
|
|
@ -904,20 +903,14 @@ const VideoPlayer: React.FC = () => {
|
|||
}, []);
|
||||
|
||||
const increaseSubtitleSize = () => {
|
||||
const newSize = Math.min(subtitleSize + 2, 40);
|
||||
setSubtitleSize(newSize);
|
||||
const newSize = Math.min(subtitleSize + 2, 32);
|
||||
saveSubtitleSize(newSize);
|
||||
};
|
||||
|
||||
const decreaseSubtitleSize = () => {
|
||||
const newSize = Math.max(subtitleSize - 2, 12);
|
||||
setSubtitleSize(newSize);
|
||||
const newSize = Math.max(subtitleSize - 2, 8);
|
||||
saveSubtitleSize(newSize);
|
||||
};
|
||||
|
||||
const toggleSubtitleBackground = () => {
|
||||
setSubtitleBackground(prev => !prev);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (pendingSeek && isPlayerReady && isVideoLoaded && duration > 0) {
|
||||
|
|
@ -1211,7 +1204,6 @@ const VideoPlayer: React.FC = () => {
|
|||
handleProgressBarDragEnd={handleProgressBarDragEnd}
|
||||
buffered={buffered}
|
||||
formatTime={formatTime}
|
||||
seekToTime={seekToTime}
|
||||
/>
|
||||
|
||||
<CustomSubtitles
|
||||
|
|
@ -1219,7 +1211,6 @@ const VideoPlayer: React.FC = () => {
|
|||
currentSubtitle={currentSubtitle}
|
||||
subtitleSize={subtitleSize}
|
||||
zoomScale={zoomScale}
|
||||
subtitleBackground={subtitleBackground}
|
||||
/>
|
||||
|
||||
<ResumeOverlay
|
||||
|
|
@ -1255,13 +1246,11 @@ const VideoPlayer: React.FC = () => {
|
|||
selectedTextTrack={selectedTextTrack}
|
||||
useCustomSubtitles={useCustomSubtitles}
|
||||
subtitleSize={subtitleSize}
|
||||
subtitleBackground={subtitleBackground}
|
||||
fetchAvailableSubtitles={fetchAvailableSubtitles}
|
||||
loadWyzieSubtitle={loadWyzieSubtitle}
|
||||
selectTextTrack={selectTextTrack}
|
||||
increaseSubtitleSize={increaseSubtitleSize}
|
||||
decreaseSubtitleSize={decreaseSubtitleSize}
|
||||
toggleSubtitleBackground={toggleSubtitleBackground}
|
||||
/>
|
||||
|
||||
<SourcesModal
|
||||
|
|
|
|||
|
|
@ -1,10 +1,9 @@
|
|||
import React, { useState } from 'react';
|
||||
import { View, Text, TouchableOpacity, Animated, StyleSheet, Dimensions } from 'react-native';
|
||||
import React from 'react';
|
||||
import { View, Text, TouchableOpacity, Animated, StyleSheet } from 'react-native';
|
||||
import { Ionicons } from '@expo/vector-icons';
|
||||
import { LinearGradient } from 'expo-linear-gradient';
|
||||
import { styles } from '../utils/playerStyles';
|
||||
import { getTrackDisplayName } from '../utils/playerUtils';
|
||||
import { SkiaProgressSlider } from './SkiaProgressSlider';
|
||||
|
||||
interface PlayerControlsProps {
|
||||
showControls: boolean;
|
||||
|
|
@ -40,11 +39,8 @@ interface PlayerControlsProps {
|
|||
handleProgressBarDragEnd: () => void;
|
||||
buffered: number;
|
||||
formatTime: (seconds: number) => string;
|
||||
seekToTime?: (time: number) => void;
|
||||
}
|
||||
|
||||
const { width: screenWidth } = Dimensions.get('window');
|
||||
|
||||
export const PlayerControls: React.FC<PlayerControlsProps> = ({
|
||||
showControls,
|
||||
fadeAnim,
|
||||
|
|
@ -79,61 +75,63 @@ export const PlayerControls: React.FC<PlayerControlsProps> = ({
|
|||
handleProgressBarDragEnd,
|
||||
buffered,
|
||||
formatTime,
|
||||
seekToTime,
|
||||
}) => {
|
||||
// State for tracking preview time during dragging
|
||||
const [previewTime, setPreviewTime] = useState<number | null>(null);
|
||||
const [isDragging, setIsDragging] = useState(false);
|
||||
|
||||
// Calculate slider width based on screen width minus padding
|
||||
const sliderWidth = screenWidth - 40; // 20px padding on each side
|
||||
|
||||
const handleSeek = (time: number) => {
|
||||
if (seekToTime) {
|
||||
seekToTime(time);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSeekPreview = (time: number) => {
|
||||
setPreviewTime(time);
|
||||
};
|
||||
|
||||
const handleSeekStart = () => {
|
||||
setIsDragging(true);
|
||||
handleProgressBarDragStart();
|
||||
};
|
||||
|
||||
const handleSeekEnd = (time: number) => {
|
||||
setIsDragging(false);
|
||||
setPreviewTime(null);
|
||||
handleProgressBarDragEnd();
|
||||
handleSeek(time);
|
||||
};
|
||||
|
||||
// Determine which time to display (preview time while dragging, otherwise current time)
|
||||
const displayTime = isDragging && previewTime !== null ? previewTime : currentTime;
|
||||
|
||||
return (
|
||||
<Animated.View
|
||||
style={[StyleSheet.absoluteFill, { opacity: fadeAnim }]}
|
||||
pointerEvents={showControls ? 'auto' : 'none'}
|
||||
>
|
||||
{/* Progress bar with Skia slider */}
|
||||
{/* Progress bar with enhanced touch handling */}
|
||||
<View style={styles.sliderContainer}>
|
||||
<SkiaProgressSlider
|
||||
currentTime={currentTime}
|
||||
duration={duration}
|
||||
buffered={buffered}
|
||||
onSeek={handleSeek}
|
||||
onSeekStart={handleSeekStart}
|
||||
onSeekEnd={handleSeekEnd}
|
||||
onSeekPreview={handleSeekPreview}
|
||||
width={sliderWidth}
|
||||
/>
|
||||
<View
|
||||
style={styles.progressTouchArea}
|
||||
onTouchStart={handleProgressBarDragStart}
|
||||
onTouchMove={handleProgressBarDragMove}
|
||||
onTouchEnd={handleProgressBarDragEnd}
|
||||
>
|
||||
<TouchableOpacity
|
||||
activeOpacity={0.8}
|
||||
onPress={handleProgressBarTouch}
|
||||
style={{width: '100%'}}
|
||||
>
|
||||
<View
|
||||
ref={progressBarRef}
|
||||
style={styles.progressBarContainer}
|
||||
>
|
||||
{/* Buffered Progress */}
|
||||
<View style={[styles.bufferProgress, {
|
||||
width: `${(buffered / (duration || 1)) * 100}%`
|
||||
}]} />
|
||||
{/* Animated Progress */}
|
||||
<Animated.View
|
||||
style={[
|
||||
styles.progressBarFill,
|
||||
{
|
||||
width: progressAnim.interpolate({
|
||||
inputRange: [0, 1],
|
||||
outputRange: ['0%', '100%']
|
||||
})
|
||||
}
|
||||
]}
|
||||
/>
|
||||
</View>
|
||||
|
||||
{/* Progress Thumb - Moved outside the progressBarContainer */}
|
||||
<Animated.View
|
||||
style={[
|
||||
styles.progressThumb,
|
||||
{
|
||||
left: progressAnim.interpolate({
|
||||
inputRange: [0, 1],
|
||||
outputRange: ['0%', '100%']
|
||||
})
|
||||
}
|
||||
]}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
<View style={styles.timeDisplay}>
|
||||
<Text style={[styles.duration, isDragging && { color: '#E50914' }]}>
|
||||
{formatTime(displayTime)}
|
||||
</Text>
|
||||
<Text style={styles.duration}>{formatTime(currentTime)}</Text>
|
||||
<Text style={styles.duration}>{formatTime(duration)}</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
|
|
|||
|
|
@ -1,226 +0,0 @@
|
|||
import React, { useMemo, useEffect } from 'react';
|
||||
import { View, Dimensions } from 'react-native';
|
||||
import {
|
||||
Canvas,
|
||||
RoundedRect,
|
||||
Circle,
|
||||
Group,
|
||||
Shadow,
|
||||
} from '@shopify/react-native-skia';
|
||||
import {
|
||||
Gesture,
|
||||
GestureDetector,
|
||||
GestureHandlerRootView,
|
||||
} from 'react-native-gesture-handler';
|
||||
import Animated, {
|
||||
useSharedValue,
|
||||
useAnimatedStyle,
|
||||
withSpring,
|
||||
runOnJS,
|
||||
interpolate,
|
||||
} from 'react-native-reanimated';
|
||||
|
||||
interface SkiaProgressSliderProps {
|
||||
currentTime: number;
|
||||
duration: number;
|
||||
buffered: number;
|
||||
onSeek: (time: number) => void;
|
||||
onSeekStart?: () => void;
|
||||
onSeekEnd?: (time: number) => void;
|
||||
onSeekPreview?: (time: number) => void; // New callback for preview time
|
||||
width: number;
|
||||
height?: number;
|
||||
thumbSize?: number;
|
||||
progressColor?: string;
|
||||
backgroundColor?: string;
|
||||
bufferedColor?: string;
|
||||
}
|
||||
|
||||
export const SkiaProgressSlider: React.FC<SkiaProgressSliderProps> = ({
|
||||
currentTime,
|
||||
duration,
|
||||
buffered,
|
||||
onSeek,
|
||||
onSeekStart,
|
||||
onSeekEnd,
|
||||
onSeekPreview,
|
||||
width,
|
||||
height = 4,
|
||||
thumbSize = 16,
|
||||
progressColor = '#E50914',
|
||||
backgroundColor = 'rgba(255, 255, 255, 0.2)',
|
||||
bufferedColor = 'rgba(255, 255, 255, 0.4)',
|
||||
}) => {
|
||||
const progress = useSharedValue(0);
|
||||
const isDragging = useSharedValue(false);
|
||||
const thumbScale = useSharedValue(1);
|
||||
const progressWidth = useSharedValue(0);
|
||||
const previewTimeValue = useSharedValue(0);
|
||||
|
||||
// Add padding to account for thumb size
|
||||
const trackPadding = thumbSize / 2;
|
||||
const trackWidth = width - (trackPadding * 2);
|
||||
|
||||
// Update progress when currentTime changes
|
||||
useEffect(() => {
|
||||
if (!isDragging.value && duration > 0) {
|
||||
const newProgress = (currentTime / duration);
|
||||
progress.value = newProgress;
|
||||
progressWidth.value = newProgress * trackWidth;
|
||||
}
|
||||
}, [currentTime, duration, trackWidth]);
|
||||
|
||||
// Calculate buffered width
|
||||
const bufferedWidth = useMemo(() => {
|
||||
return duration > 0 ? (buffered / duration) * trackWidth : 0;
|
||||
}, [buffered, duration, trackWidth]);
|
||||
|
||||
// Handle seeking with proper coordinate mapping
|
||||
const seekToPosition = (gestureX: number, isPreview: boolean = false) => {
|
||||
'worklet';
|
||||
// Map gesture coordinates to track coordinates
|
||||
const trackX = gestureX - trackPadding;
|
||||
const clampedX = Math.max(0, Math.min(trackX, trackWidth));
|
||||
|
||||
const newProgress = clampedX / trackWidth;
|
||||
progress.value = newProgress;
|
||||
progressWidth.value = clampedX;
|
||||
|
||||
const seekTime = newProgress * duration;
|
||||
previewTimeValue.value = seekTime;
|
||||
|
||||
if (isPreview && onSeekPreview) {
|
||||
runOnJS(onSeekPreview)(seekTime);
|
||||
} else if (!isPreview) {
|
||||
runOnJS(onSeek)(seekTime);
|
||||
}
|
||||
};
|
||||
|
||||
// Pan gesture for dragging
|
||||
const panGesture = Gesture.Pan()
|
||||
.onBegin((e) => {
|
||||
'worklet';
|
||||
isDragging.value = true;
|
||||
thumbScale.value = withSpring(1.2, { damping: 15, stiffness: 400 });
|
||||
// Process the initial touch position immediately
|
||||
seekToPosition(e.x, true);
|
||||
if (onSeekStart) {
|
||||
runOnJS(onSeekStart)();
|
||||
}
|
||||
})
|
||||
.onUpdate((e) => {
|
||||
'worklet';
|
||||
seekToPosition(e.x, true); // Use preview mode during dragging
|
||||
})
|
||||
.onFinalize((e) => {
|
||||
'worklet';
|
||||
isDragging.value = false;
|
||||
thumbScale.value = withSpring(1, { damping: 15, stiffness: 400 });
|
||||
|
||||
// Use the exact same preview time for the final seek to ensure consistency
|
||||
if (onSeekEnd) {
|
||||
runOnJS(onSeekEnd)(previewTimeValue.value);
|
||||
}
|
||||
|
||||
// Final seek when drag ends - use the same calculation as preview
|
||||
runOnJS(onSeek)(previewTimeValue.value);
|
||||
});
|
||||
|
||||
// Tap gesture for seeking
|
||||
const tapGesture = Gesture.Tap()
|
||||
.onEnd((e) => {
|
||||
'worklet';
|
||||
seekToPosition(e.x, false); // Direct seek on tap
|
||||
});
|
||||
|
||||
const composedGesture = Gesture.Simultaneous(tapGesture, panGesture);
|
||||
|
||||
// Animated styles for thumb
|
||||
const animatedThumbStyle = useAnimatedStyle(() => {
|
||||
const thumbX = progress.value * trackWidth + trackPadding;
|
||||
|
||||
return {
|
||||
transform: [
|
||||
{ translateX: thumbX - thumbSize / 2 },
|
||||
{ scale: thumbScale.value }
|
||||
],
|
||||
};
|
||||
});
|
||||
|
||||
// Animated style for progress width
|
||||
const animatedProgressStyle = useAnimatedStyle(() => {
|
||||
return {
|
||||
width: progressWidth.value,
|
||||
};
|
||||
});
|
||||
|
||||
return (
|
||||
<View style={{ width, height: height + thumbSize + 8 }}>
|
||||
<GestureDetector gesture={composedGesture}>
|
||||
<View>
|
||||
<Canvas style={{ width, height: height + thumbSize + 8 }}>
|
||||
<Group transform={[{ translateY: (thumbSize / 2) + 4 }, { translateX: trackPadding }]}>
|
||||
{/* Background track */}
|
||||
<RoundedRect
|
||||
x={0}
|
||||
y={0}
|
||||
width={trackWidth}
|
||||
height={height}
|
||||
r={height / 2}
|
||||
color={backgroundColor}
|
||||
/>
|
||||
|
||||
{/* Buffered progress */}
|
||||
{bufferedWidth > 0 && (
|
||||
<RoundedRect
|
||||
x={0}
|
||||
y={0}
|
||||
width={bufferedWidth}
|
||||
height={height}
|
||||
r={height / 2}
|
||||
color={bufferedColor}
|
||||
/>
|
||||
)}
|
||||
</Group>
|
||||
</Canvas>
|
||||
|
||||
{/* Current progress - using regular view for animation */}
|
||||
<Animated.View
|
||||
style={[
|
||||
{
|
||||
position: 'absolute',
|
||||
top: (thumbSize / 2) + 4,
|
||||
left: trackPadding,
|
||||
height: height,
|
||||
backgroundColor: progressColor,
|
||||
borderRadius: height / 2,
|
||||
},
|
||||
animatedProgressStyle,
|
||||
]}
|
||||
/>
|
||||
|
||||
{/* Animated thumb */}
|
||||
<Animated.View
|
||||
style={[
|
||||
{
|
||||
position: 'absolute',
|
||||
top: 4,
|
||||
left: 0,
|
||||
width: thumbSize,
|
||||
height: thumbSize,
|
||||
borderRadius: thumbSize / 2,
|
||||
backgroundColor: progressColor,
|
||||
shadowColor: '#000',
|
||||
shadowOffset: { width: 0, height: 2 },
|
||||
shadowOpacity: 0.3,
|
||||
shadowRadius: 3,
|
||||
elevation: 4,
|
||||
},
|
||||
animatedThumbStyle,
|
||||
]}
|
||||
/>
|
||||
</View>
|
||||
</GestureDetector>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
Loading…
Reference in a new issue