mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-04-21 00:32:04 +00:00
Add new dependencies and enhance VideoPlayer functionality
This update introduces new dependencies including cheerio, cors, express, and puppeteer to support additional features. The VideoPlayer component has been enhanced to improve seeking behavior on Android, with a new AndroidVideoPlayer component for better performance. Additionally, state management for seeking has been refined, ensuring smoother playback and user experience across platforms.
This commit is contained in:
parent
9e03619db7
commit
d62874d20d
4 changed files with 1302 additions and 220 deletions
119
components/AndroidVideoPlayer.tsx
Normal file
119
components/AndroidVideoPlayer.tsx
Normal file
|
|
@ -0,0 +1,119 @@
|
||||||
|
import React, { useEffect, useRef, useState } from 'react';
|
||||||
|
import { Platform } from 'react-native';
|
||||||
|
import Video, { VideoRef, SelectedTrack, BufferingStrategyType } from 'react-native-video';
|
||||||
|
|
||||||
|
interface VideoPlayerProps {
|
||||||
|
src: string;
|
||||||
|
paused: boolean;
|
||||||
|
volume: number;
|
||||||
|
currentTime: number;
|
||||||
|
selectedAudioTrack?: SelectedTrack;
|
||||||
|
selectedTextTrack?: SelectedTrack;
|
||||||
|
onProgress?: (data: { currentTime: number; playableDuration: number }) => void;
|
||||||
|
onLoad?: (data: { duration: number }) => void;
|
||||||
|
onError?: (error: any) => void;
|
||||||
|
onBuffer?: (data: { isBuffering: boolean }) => void;
|
||||||
|
onSeek?: (data: { currentTime: number; seekTime: number }) => void;
|
||||||
|
onEnd?: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const AndroidVideoPlayer: React.FC<VideoPlayerProps> = ({
|
||||||
|
src,
|
||||||
|
paused,
|
||||||
|
volume,
|
||||||
|
currentTime,
|
||||||
|
selectedAudioTrack,
|
||||||
|
selectedTextTrack,
|
||||||
|
onProgress,
|
||||||
|
onLoad,
|
||||||
|
onError,
|
||||||
|
onBuffer,
|
||||||
|
onSeek,
|
||||||
|
onEnd,
|
||||||
|
}) => {
|
||||||
|
const videoRef = useRef<VideoRef>(null);
|
||||||
|
const [isLoaded, setIsLoaded] = useState(false);
|
||||||
|
const [isSeeking, setIsSeeking] = useState(false);
|
||||||
|
const [lastSeekTime, setLastSeekTime] = useState<number>(0);
|
||||||
|
|
||||||
|
// Only render on Android
|
||||||
|
if (Platform.OS !== 'android') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isLoaded && !isSeeking && Math.abs(currentTime - lastSeekTime) > 1) {
|
||||||
|
setIsSeeking(true);
|
||||||
|
videoRef.current?.seek(currentTime);
|
||||||
|
setLastSeekTime(currentTime);
|
||||||
|
}
|
||||||
|
}, [currentTime, isLoaded, isSeeking, lastSeekTime]);
|
||||||
|
|
||||||
|
const handleLoad = (data: any) => {
|
||||||
|
setIsLoaded(true);
|
||||||
|
onLoad?.(data);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleProgress = (data: any) => {
|
||||||
|
if (!isSeeking) {
|
||||||
|
onProgress?.(data);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSeek = (data: any) => {
|
||||||
|
setIsSeeking(false);
|
||||||
|
onSeek?.(data);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleBuffer = (data: any) => {
|
||||||
|
onBuffer?.(data);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleError = (error: any) => {
|
||||||
|
console.error('Video playback error:', error);
|
||||||
|
onError?.(error);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleEnd = () => {
|
||||||
|
onEnd?.();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Video
|
||||||
|
ref={videoRef}
|
||||||
|
source={{ uri: src }}
|
||||||
|
style={{ flex: 1 }}
|
||||||
|
paused={paused}
|
||||||
|
volume={volume}
|
||||||
|
selectedAudioTrack={selectedAudioTrack}
|
||||||
|
selectedTextTrack={selectedTextTrack}
|
||||||
|
onLoad={handleLoad}
|
||||||
|
onProgress={handleProgress}
|
||||||
|
onSeek={handleSeek}
|
||||||
|
onBuffer={handleBuffer}
|
||||||
|
onError={handleError}
|
||||||
|
onEnd={handleEnd}
|
||||||
|
resizeMode="contain"
|
||||||
|
controls={false}
|
||||||
|
playInBackground={false}
|
||||||
|
playWhenInactive={false}
|
||||||
|
progressUpdateInterval={250}
|
||||||
|
allowsExternalPlayback={false}
|
||||||
|
bufferingStrategy={BufferingStrategyType.DEFAULT}
|
||||||
|
ignoreSilentSwitch="ignore"
|
||||||
|
mixWithOthers="inherit"
|
||||||
|
rate={1.0}
|
||||||
|
repeat={false}
|
||||||
|
reportBandwidth={true}
|
||||||
|
textTracks={[]}
|
||||||
|
useTextureView={false}
|
||||||
|
disableFocus={false}
|
||||||
|
minLoadRetryCount={3}
|
||||||
|
automaticallyWaitsToMinimizeStalling={true}
|
||||||
|
hideShutterView={false}
|
||||||
|
shutterColor="#000000"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AndroidVideoPlayer;
|
||||||
|
|
@ -27,6 +27,8 @@
|
||||||
"@types/react-native-video": "^5.0.20",
|
"@types/react-native-video": "^5.0.20",
|
||||||
"axios": "^1.10.0",
|
"axios": "^1.10.0",
|
||||||
"base64-js": "^1.5.1",
|
"base64-js": "^1.5.1",
|
||||||
|
"cheerio": "^1.1.0",
|
||||||
|
"cors": "^2.8.5",
|
||||||
"date-fns": "^4.1.0",
|
"date-fns": "^4.1.0",
|
||||||
"eventemitter3": "^5.0.1",
|
"eventemitter3": "^5.0.1",
|
||||||
"expo": "~52.0.43",
|
"expo": "~52.0.43",
|
||||||
|
|
@ -44,8 +46,10 @@
|
||||||
"expo-status-bar": "~2.0.1",
|
"expo-status-bar": "~2.0.1",
|
||||||
"expo-system-ui": "^4.0.9",
|
"expo-system-ui": "^4.0.9",
|
||||||
"expo-web-browser": "~14.0.2",
|
"expo-web-browser": "~14.0.2",
|
||||||
|
"express": "^5.1.0",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"node-fetch": "^2.6.7",
|
"node-fetch": "^2.6.7",
|
||||||
|
"puppeteer": "^24.10.1",
|
||||||
"react": "18.3.1",
|
"react": "18.3.1",
|
||||||
"react-native": "0.76.9",
|
"react-native": "0.76.9",
|
||||||
"react-native-awesome-slider": "^2.9.0",
|
"react-native-awesome-slider": "^2.9.0",
|
||||||
|
|
|
||||||
1067
src/components/player/AndroidVideoPlayer.tsx
Normal file
1067
src/components/player/AndroidVideoPlayer.tsx
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -10,6 +10,7 @@ import { storageService } from '../../services/storageService';
|
||||||
import { logger } from '../../utils/logger';
|
import { logger } from '../../utils/logger';
|
||||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||||
import { MaterialIcons } from '@expo/vector-icons';
|
import { MaterialIcons } from '@expo/vector-icons';
|
||||||
|
import AndroidVideoPlayer from './AndroidVideoPlayer';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
DEFAULT_SUBTITLE_SIZE,
|
DEFAULT_SUBTITLE_SIZE,
|
||||||
|
|
@ -32,6 +33,11 @@ import CustomSubtitles from './subtitles/CustomSubtitles';
|
||||||
import SourcesModal from './modals/SourcesModal';
|
import SourcesModal from './modals/SourcesModal';
|
||||||
|
|
||||||
const VideoPlayer: React.FC = () => {
|
const VideoPlayer: React.FC = () => {
|
||||||
|
// If on Android, use the AndroidVideoPlayer component
|
||||||
|
if (Platform.OS === 'android') {
|
||||||
|
return <AndroidVideoPlayer />;
|
||||||
|
}
|
||||||
|
|
||||||
const navigation = useNavigation();
|
const navigation = useNavigation();
|
||||||
const route = useRoute<RouteProp<RootStackParamList, 'Player'>>();
|
const route = useRoute<RouteProp<RootStackParamList, 'Player'>>();
|
||||||
|
|
||||||
|
|
@ -70,6 +76,7 @@ const VideoPlayer: React.FC = () => {
|
||||||
const [selectedTextTrack, setSelectedTextTrack] = useState<number>(-1);
|
const [selectedTextTrack, setSelectedTextTrack] = useState<number>(-1);
|
||||||
const [resizeMode, setResizeMode] = useState<ResizeModeType>('stretch');
|
const [resizeMode, setResizeMode] = useState<ResizeModeType>('stretch');
|
||||||
const [buffered, setBuffered] = useState(0);
|
const [buffered, setBuffered] = useState(0);
|
||||||
|
const [seekPosition, setSeekPosition] = useState<number | null>(null);
|
||||||
const vlcRef = useRef<any>(null);
|
const vlcRef = useRef<any>(null);
|
||||||
const [showAudioModal, setShowAudioModal] = useState(false);
|
const [showAudioModal, setShowAudioModal] = useState(false);
|
||||||
const [showSubtitleModal, setShowSubtitleModal] = useState(false);
|
const [showSubtitleModal, setShowSubtitleModal] = useState(false);
|
||||||
|
|
@ -92,6 +99,7 @@ const VideoPlayer: React.FC = () => {
|
||||||
const progressAnim = useRef(new Animated.Value(0)).current;
|
const progressAnim = useRef(new Animated.Value(0)).current;
|
||||||
const progressBarRef = useRef<View>(null);
|
const progressBarRef = useRef<View>(null);
|
||||||
const [isDragging, setIsDragging] = useState(false);
|
const [isDragging, setIsDragging] = useState(false);
|
||||||
|
const isSeeking = useRef(false);
|
||||||
const seekDebounceTimer = useRef<NodeJS.Timeout | null>(null);
|
const seekDebounceTimer = useRef<NodeJS.Timeout | null>(null);
|
||||||
const pendingSeekValue = useRef<number | null>(null);
|
const pendingSeekValue = useRef<number | null>(null);
|
||||||
const lastSeekTime = useRef<number>(0);
|
const lastSeekTime = useRef<number>(0);
|
||||||
|
|
@ -131,7 +139,6 @@ const VideoPlayer: React.FC = () => {
|
||||||
left: 0,
|
left: 0,
|
||||||
width: screenWidth,
|
width: screenWidth,
|
||||||
height: screenHeight,
|
height: screenHeight,
|
||||||
backgroundColor: '#000',
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -294,66 +301,62 @@ const VideoPlayer: React.FC = () => {
|
||||||
};
|
};
|
||||||
}, [id, type, currentTime, duration]);
|
}, [id, type, currentTime, duration]);
|
||||||
|
|
||||||
|
const onPlaying = () => {
|
||||||
|
if (isMounted.current && !isSeeking.current) {
|
||||||
|
setPaused(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onPaused = () => {
|
||||||
|
if (isMounted.current) {
|
||||||
|
setPaused(true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const seekToTime = (timeInSeconds: number) => {
|
const seekToTime = (timeInSeconds: number) => {
|
||||||
if (!isPlayerReady || duration <= 0 || !vlcRef.current) return;
|
if (vlcRef.current && duration > 0 && !isSeeking.current) {
|
||||||
const normalizedPosition = Math.max(0, Math.min(timeInSeconds / duration, 1));
|
if (DEBUG_MODE) {
|
||||||
|
logger.log(`[VideoPlayer] Seeking to ${timeInSeconds.toFixed(2)}s`);
|
||||||
try {
|
}
|
||||||
if (Platform.OS === 'android') {
|
|
||||||
// On Android, we need to handle seeking differently to prevent black screens
|
isSeeking.current = true;
|
||||||
setIsBuffering(true);
|
|
||||||
|
// For Android, use direct seeking on VLC player ref instead of seek prop
|
||||||
// Set a small timeout to prevent overwhelming the player
|
if (Platform.OS === 'android' && vlcRef.current.seek) {
|
||||||
const now = Date.now();
|
// Calculate position as fraction
|
||||||
if (now - lastSeekTime.current < 300) {
|
const position = timeInSeconds / duration;
|
||||||
// Throttle seeks that are too close together
|
vlcRef.current.seek(position);
|
||||||
if (seekDebounceTimer.current) {
|
|
||||||
clearTimeout(seekDebounceTimer.current);
|
// Clear seek state after Android seek
|
||||||
}
|
setTimeout(() => {
|
||||||
|
if (isMounted.current) {
|
||||||
seekDebounceTimer.current = setTimeout(() => {
|
isSeeking.current = false;
|
||||||
if (vlcRef.current) {
|
}
|
||||||
// Set position instead of using seek on Android
|
}, 300);
|
||||||
vlcRef.current.setPosition(normalizedPosition);
|
} else {
|
||||||
lastSeekTime.current = Date.now();
|
// iOS fallback - use seek prop
|
||||||
|
const position = timeInSeconds / duration;
|
||||||
// Give the player some time to recover
|
setSeekPosition(position);
|
||||||
setTimeout(() => {
|
|
||||||
setIsBuffering(false);
|
setTimeout(() => {
|
||||||
}, 500);
|
if (isMounted.current) {
|
||||||
}
|
setSeekPosition(null);
|
||||||
}, 300);
|
isSeeking.current = false;
|
||||||
return;
|
}
|
||||||
}
|
}, 500);
|
||||||
|
}
|
||||||
// Directly set position
|
} else {
|
||||||
vlcRef.current.setPosition(normalizedPosition);
|
if (DEBUG_MODE) {
|
||||||
lastSeekTime.current = now;
|
logger.error('[VideoPlayer] Seek failed: Player not ready, duration is zero, or already seeking.');
|
||||||
|
|
||||||
// Reset buffering state after a delay
|
|
||||||
setTimeout(() => {
|
|
||||||
setIsBuffering(false);
|
|
||||||
}, 500);
|
|
||||||
} else {
|
|
||||||
// For iOS, keep the original behavior
|
|
||||||
if (typeof vlcRef.current.setPosition === 'function') {
|
|
||||||
vlcRef.current.setPosition(normalizedPosition);
|
|
||||||
} else if (typeof vlcRef.current.seek === 'function') {
|
|
||||||
vlcRef.current.seek(normalizedPosition);
|
|
||||||
} else {
|
|
||||||
logger.error('[VideoPlayer] No seek method available on VLC player');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
|
||||||
logger.error('[VideoPlayer] Error during seek operation:', error);
|
|
||||||
setIsBuffering(false);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleProgressBarTouch = (event: any) => {
|
const handleProgressBarTouch = (event: any) => {
|
||||||
if (!duration || duration <= 0) return;
|
if (duration > 0) {
|
||||||
const { locationX } = event.nativeEvent;
|
const { locationX } = event.nativeEvent;
|
||||||
processProgressTouch(locationX);
|
processProgressTouch(locationX);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleProgressBarDragStart = () => {
|
const handleProgressBarDragStart = () => {
|
||||||
|
|
@ -369,18 +372,8 @@ const VideoPlayer: React.FC = () => {
|
||||||
const handleProgressBarDragEnd = () => {
|
const handleProgressBarDragEnd = () => {
|
||||||
setIsDragging(false);
|
setIsDragging(false);
|
||||||
if (pendingSeekValue.current !== null) {
|
if (pendingSeekValue.current !== null) {
|
||||||
// For Android, add a small delay to ensure UI updates before the seek happens
|
seekToTime(pendingSeekValue.current);
|
||||||
if (Platform.OS === 'android') {
|
pendingSeekValue.current = null;
|
||||||
setTimeout(() => {
|
|
||||||
if (pendingSeekValue.current !== null) {
|
|
||||||
seekToTime(pendingSeekValue.current);
|
|
||||||
pendingSeekValue.current = null;
|
|
||||||
}
|
|
||||||
}, 150);
|
|
||||||
} else {
|
|
||||||
seekToTime(pendingSeekValue.current);
|
|
||||||
pendingSeekValue.current = null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -399,8 +392,11 @@ const VideoPlayer: React.FC = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleProgress = (event: any) => {
|
const handleProgress = (event: any) => {
|
||||||
if (isDragging) return;
|
if (isDragging || isSeeking.current) return;
|
||||||
|
|
||||||
const currentTimeInSeconds = event.currentTime / 1000;
|
const currentTimeInSeconds = event.currentTime / 1000;
|
||||||
|
|
||||||
|
// Only update if there's a significant change to avoid unnecessary updates
|
||||||
if (Math.abs(currentTimeInSeconds - currentTime) > 0.5) {
|
if (Math.abs(currentTimeInSeconds - currentTime) > 0.5) {
|
||||||
safeSetState(() => setCurrentTime(currentTimeInSeconds));
|
safeSetState(() => setCurrentTime(currentTimeInSeconds));
|
||||||
const progressPercent = duration > 0 ? currentTimeInSeconds / duration : 0;
|
const progressPercent = duration > 0 ? currentTimeInSeconds / duration : 0;
|
||||||
|
|
@ -415,67 +411,34 @@ const VideoPlayer: React.FC = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const onLoad = (data: any) => {
|
const onLoad = (data: any) => {
|
||||||
setDuration(data.duration / 1000);
|
if (DEBUG_MODE) {
|
||||||
if (data.videoSize && data.videoSize.width && data.videoSize.height) {
|
logger.log('[VideoPlayer] Video loaded:', data);
|
||||||
const aspectRatio = data.videoSize.width / data.videoSize.height;
|
}
|
||||||
setVideoAspectRatio(aspectRatio);
|
if (isMounted.current) {
|
||||||
const is16x9 = Math.abs(aspectRatio - (16/9)) < 0.1;
|
if (data.duration > 0) {
|
||||||
setIs16by9Content(is16x9);
|
setDuration(data.duration / 1000);
|
||||||
if (is16x9) {
|
|
||||||
setZoomScale(1.1);
|
|
||||||
setLastZoomScale(1.1);
|
|
||||||
} else {
|
|
||||||
setZoomScale(1);
|
|
||||||
setLastZoomScale(1);
|
|
||||||
}
|
}
|
||||||
const styles = calculateVideoStyles(
|
setVideoAspectRatio(data.videoSize.width / data.videoSize.height);
|
||||||
data.videoSize.width,
|
|
||||||
data.videoSize.height,
|
if (data.audioTracks && data.audioTracks.length > 0) {
|
||||||
screenDimensions.width,
|
setVlcAudioTracks(data.audioTracks);
|
||||||
screenDimensions.height
|
}
|
||||||
);
|
if (data.textTracks && data.textTracks.length > 0) {
|
||||||
setCustomVideoStyles(styles);
|
setVlcTextTracks(data.textTracks);
|
||||||
} else {
|
}
|
||||||
setIs16by9Content(true);
|
|
||||||
setZoomScale(1.1);
|
setIsVideoLoaded(true);
|
||||||
setLastZoomScale(1.1);
|
setIsPlayerReady(true);
|
||||||
const defaultStyles = {
|
if (initialPosition && !isInitialSeekComplete) {
|
||||||
position: 'absolute',
|
setTimeout(() => {
|
||||||
top: 0,
|
if (vlcRef.current && duration > 0 && isMounted.current) {
|
||||||
left: 0,
|
seekToTime(initialPosition);
|
||||||
width: screenDimensions.width,
|
setIsInitialSeekComplete(true);
|
||||||
height: screenDimensions.height,
|
}
|
||||||
};
|
}, 1000);
|
||||||
setCustomVideoStyles(defaultStyles);
|
}
|
||||||
|
completeOpeningAnimation();
|
||||||
}
|
}
|
||||||
setIsPlayerReady(true);
|
|
||||||
const audioTracksFromLoad = data.audioTracks || [];
|
|
||||||
const textTracksFromLoad = data.textTracks || [];
|
|
||||||
setVlcAudioTracks(audioTracksFromLoad);
|
|
||||||
setVlcTextTracks(textTracksFromLoad);
|
|
||||||
if (audioTracksFromLoad.length > 1) {
|
|
||||||
const firstEnabledAudio = audioTracksFromLoad.find((t: any) => t.id !== -1);
|
|
||||||
if(firstEnabledAudio) {
|
|
||||||
setSelectedAudioTrack(firstEnabledAudio.id);
|
|
||||||
}
|
|
||||||
} else if (audioTracksFromLoad.length > 0) {
|
|
||||||
setSelectedAudioTrack(audioTracksFromLoad[0].id);
|
|
||||||
}
|
|
||||||
if (imdbId && !customSubtitles.length) {
|
|
||||||
setTimeout(() => {
|
|
||||||
fetchAvailableSubtitles(imdbId, true);
|
|
||||||
}, 2000);
|
|
||||||
}
|
|
||||||
if (initialPosition !== null && !isInitialSeekComplete) {
|
|
||||||
setTimeout(() => {
|
|
||||||
if (vlcRef.current && duration > 0 && isMounted.current) {
|
|
||||||
seekToTime(initialPosition);
|
|
||||||
setIsInitialSeekComplete(true);
|
|
||||||
}
|
|
||||||
}, 1000);
|
|
||||||
}
|
|
||||||
setIsVideoLoaded(true);
|
|
||||||
completeOpeningAnimation();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const skip = (seconds: number) => {
|
const skip = (seconds: number) => {
|
||||||
|
|
@ -793,61 +756,25 @@ const VideoPlayer: React.FC = () => {
|
||||||
logger.log(`[VideoPlayer] Player ready after source change, seeking to position: ${pendingSeek.position}s out of ${duration}s total`);
|
logger.log(`[VideoPlayer] Player ready after source change, seeking to position: ${pendingSeek.position}s out of ${duration}s total`);
|
||||||
|
|
||||||
if (pendingSeek.position > 0 && vlcRef.current) {
|
if (pendingSeek.position > 0 && vlcRef.current) {
|
||||||
// Longer delay for Android to ensure player is stable
|
const delayTime = Platform.OS === 'android' ? 1500 : 1000;
|
||||||
const delayTime = Platform.OS === 'android' ? 2500 : 1500;
|
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (vlcRef.current && duration > 0 && pendingSeek) {
|
if (vlcRef.current && duration > 0 && pendingSeek) {
|
||||||
logger.log(`[VideoPlayer] Executing seek to ${pendingSeek.position}s`);
|
logger.log(`[VideoPlayer] Executing seek to ${pendingSeek.position}s`);
|
||||||
|
|
||||||
if (Platform.OS === 'android') {
|
seekToTime(pendingSeek.position);
|
||||||
// On Android, wait longer and set isBuffering to improve visual feedback
|
|
||||||
setIsBuffering(true);
|
if (pendingSeek.shouldPlay) {
|
||||||
|
|
||||||
// For Android, use setPosition directly with normalized value
|
|
||||||
const normalizedPosition = Math.max(0, Math.min(pendingSeek.position / duration, 1));
|
|
||||||
vlcRef.current.setPosition(normalizedPosition);
|
|
||||||
|
|
||||||
// Update the current time
|
|
||||||
setCurrentTime(pendingSeek.position);
|
|
||||||
|
|
||||||
// Give the player time to recover from the seek
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
setIsBuffering(false);
|
logger.log('[VideoPlayer] Resuming playback after source change seek');
|
||||||
|
setPaused(false);
|
||||||
// Resume playback after a delay if needed
|
}, 850); // Delay should be slightly more than seekToTime's internal timeout
|
||||||
if (pendingSeek.shouldPlay) {
|
|
||||||
setPaused(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clean up
|
|
||||||
setPendingSeek(null);
|
|
||||||
setIsChangingSource(false);
|
|
||||||
}, 800);
|
|
||||||
} else {
|
|
||||||
// iOS - use the normal seekToTime function
|
|
||||||
seekToTime(pendingSeek.position);
|
|
||||||
|
|
||||||
// Also update the current time state
|
|
||||||
setCurrentTime(pendingSeek.position);
|
|
||||||
|
|
||||||
// Resume playback if needed
|
|
||||||
if (pendingSeek.shouldPlay) {
|
|
||||||
setTimeout(() => {
|
|
||||||
logger.log('[VideoPlayer] Resuming playback after seek');
|
|
||||||
setPaused(false);
|
|
||||||
if (vlcRef.current && typeof vlcRef.current.play === 'function') {
|
|
||||||
vlcRef.current.play();
|
|
||||||
}
|
|
||||||
}, 700);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clean up
|
|
||||||
setTimeout(() => {
|
|
||||||
setPendingSeek(null);
|
|
||||||
setIsChangingSource(false);
|
|
||||||
}, 800);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
setPendingSeek(null);
|
||||||
|
setIsChangingSource(false);
|
||||||
|
}, 900);
|
||||||
}
|
}
|
||||||
}, delayTime);
|
}, delayTime);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -856,9 +783,6 @@ const VideoPlayer: React.FC = () => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
logger.log('[VideoPlayer] No seek needed, just resuming playback');
|
logger.log('[VideoPlayer] No seek needed, just resuming playback');
|
||||||
setPaused(false);
|
setPaused(false);
|
||||||
if (vlcRef.current && typeof vlcRef.current.play === 'function') {
|
|
||||||
vlcRef.current.play();
|
|
||||||
}
|
|
||||||
}, 500);
|
}, 500);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1018,7 +942,6 @@ const VideoPlayer: React.FC = () => {
|
||||||
left: 0,
|
left: 0,
|
||||||
width: screenDimensions.width,
|
width: screenDimensions.width,
|
||||||
height: screenDimensions.height,
|
height: screenDimensions.height,
|
||||||
backgroundColor: '#000',
|
|
||||||
}}>
|
}}>
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
style={{ flex: 1 }}
|
style={{ flex: 1 }}
|
||||||
|
|
@ -1029,52 +952,21 @@ const VideoPlayer: React.FC = () => {
|
||||||
>
|
>
|
||||||
<VLCPlayer
|
<VLCPlayer
|
||||||
ref={vlcRef}
|
ref={vlcRef}
|
||||||
style={{
|
style={[styles.video, customVideoStyles, { transform: [{ scale: zoomScale }] }]}
|
||||||
position: 'absolute',
|
source={{ uri: currentStreamUrl }}
|
||||||
top: 0,
|
|
||||||
left: 0,
|
|
||||||
width: screenDimensions.width,
|
|
||||||
height: screenDimensions.height,
|
|
||||||
transform: [
|
|
||||||
{ scale: zoomScale },
|
|
||||||
],
|
|
||||||
}}
|
|
||||||
source={{
|
|
||||||
uri: currentStreamUrl,
|
|
||||||
initOptions: Platform.OS === 'android' ? [
|
|
||||||
'--rtsp-tcp',
|
|
||||||
'--network-caching=1500',
|
|
||||||
'--rtsp-caching=1500',
|
|
||||||
'--no-audio-time-stretch',
|
|
||||||
'--clock-jitter=0',
|
|
||||||
'--clock-synchro=0',
|
|
||||||
'--drop-late-frames',
|
|
||||||
'--skip-frames',
|
|
||||||
'--aout=opensles',
|
|
||||||
'--file-caching=1500',
|
|
||||||
'--sout-mux-caching=1500',
|
|
||||||
] : [
|
|
||||||
'--rtsp-tcp',
|
|
||||||
'--network-caching=150',
|
|
||||||
'--rtsp-caching=150',
|
|
||||||
'--no-audio-time-stretch',
|
|
||||||
'--clock-jitter=0',
|
|
||||||
'--clock-synchro=0',
|
|
||||||
'--drop-late-frames',
|
|
||||||
'--skip-frames',
|
|
||||||
],
|
|
||||||
}}
|
|
||||||
paused={paused}
|
paused={paused}
|
||||||
autoplay={true}
|
|
||||||
autoAspectRatio={false}
|
|
||||||
resizeMode={'stretch' as any}
|
|
||||||
audioTrack={selectedAudioTrack || undefined}
|
|
||||||
textTrack={selectedTextTrack === -1 ? undefined : selectedTextTrack}
|
|
||||||
onLoad={onLoad}
|
|
||||||
onProgress={handleProgress}
|
onProgress={handleProgress}
|
||||||
|
onLoad={onLoad}
|
||||||
onEnd={onEnd}
|
onEnd={onEnd}
|
||||||
onError={handleError}
|
onError={handleError}
|
||||||
onBuffering={onBuffering}
|
onBuffering={onBuffering}
|
||||||
|
onPlaying={onPlaying}
|
||||||
|
onPaused={onPaused}
|
||||||
|
resizeMode={resizeMode as any}
|
||||||
|
audioTrack={selectedAudioTrack ?? undefined}
|
||||||
|
textTrack={useCustomSubtitles ? -1 : (selectedTextTrack ?? undefined)}
|
||||||
|
seek={Platform.OS === 'ios' ? (seekPosition ?? undefined) : undefined}
|
||||||
|
autoAspectRatio
|
||||||
/>
|
/>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
</View>
|
</View>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue