mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-01-11 20:10:25 +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",
|
||||
"axios": "^1.10.0",
|
||||
"base64-js": "^1.5.1",
|
||||
"cheerio": "^1.1.0",
|
||||
"cors": "^2.8.5",
|
||||
"date-fns": "^4.1.0",
|
||||
"eventemitter3": "^5.0.1",
|
||||
"expo": "~52.0.43",
|
||||
|
|
@ -44,8 +46,10 @@
|
|||
"expo-status-bar": "~2.0.1",
|
||||
"expo-system-ui": "^4.0.9",
|
||||
"expo-web-browser": "~14.0.2",
|
||||
"express": "^5.1.0",
|
||||
"lodash": "^4.17.21",
|
||||
"node-fetch": "^2.6.7",
|
||||
"puppeteer": "^24.10.1",
|
||||
"react": "18.3.1",
|
||||
"react-native": "0.76.9",
|
||||
"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 AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import { MaterialIcons } from '@expo/vector-icons';
|
||||
import AndroidVideoPlayer from './AndroidVideoPlayer';
|
||||
|
||||
import {
|
||||
DEFAULT_SUBTITLE_SIZE,
|
||||
|
|
@ -32,6 +33,11 @@ import CustomSubtitles from './subtitles/CustomSubtitles';
|
|||
import SourcesModal from './modals/SourcesModal';
|
||||
|
||||
const VideoPlayer: React.FC = () => {
|
||||
// If on Android, use the AndroidVideoPlayer component
|
||||
if (Platform.OS === 'android') {
|
||||
return <AndroidVideoPlayer />;
|
||||
}
|
||||
|
||||
const navigation = useNavigation();
|
||||
const route = useRoute<RouteProp<RootStackParamList, 'Player'>>();
|
||||
|
||||
|
|
@ -70,6 +76,7 @@ const VideoPlayer: React.FC = () => {
|
|||
const [selectedTextTrack, setSelectedTextTrack] = useState<number>(-1);
|
||||
const [resizeMode, setResizeMode] = useState<ResizeModeType>('stretch');
|
||||
const [buffered, setBuffered] = useState(0);
|
||||
const [seekPosition, setSeekPosition] = useState<number | null>(null);
|
||||
const vlcRef = useRef<any>(null);
|
||||
const [showAudioModal, setShowAudioModal] = useState(false);
|
||||
const [showSubtitleModal, setShowSubtitleModal] = useState(false);
|
||||
|
|
@ -92,6 +99,7 @@ const VideoPlayer: React.FC = () => {
|
|||
const progressAnim = useRef(new Animated.Value(0)).current;
|
||||
const progressBarRef = useRef<View>(null);
|
||||
const [isDragging, setIsDragging] = useState(false);
|
||||
const isSeeking = useRef(false);
|
||||
const seekDebounceTimer = useRef<NodeJS.Timeout | null>(null);
|
||||
const pendingSeekValue = useRef<number | null>(null);
|
||||
const lastSeekTime = useRef<number>(0);
|
||||
|
|
@ -131,7 +139,6 @@ const VideoPlayer: React.FC = () => {
|
|||
left: 0,
|
||||
width: screenWidth,
|
||||
height: screenHeight,
|
||||
backgroundColor: '#000',
|
||||
};
|
||||
};
|
||||
|
||||
|
|
@ -294,66 +301,62 @@ const VideoPlayer: React.FC = () => {
|
|||
};
|
||||
}, [id, type, currentTime, duration]);
|
||||
|
||||
const onPlaying = () => {
|
||||
if (isMounted.current && !isSeeking.current) {
|
||||
setPaused(false);
|
||||
}
|
||||
};
|
||||
|
||||
const onPaused = () => {
|
||||
if (isMounted.current) {
|
||||
setPaused(true);
|
||||
}
|
||||
};
|
||||
|
||||
const seekToTime = (timeInSeconds: number) => {
|
||||
if (!isPlayerReady || duration <= 0 || !vlcRef.current) return;
|
||||
const normalizedPosition = Math.max(0, Math.min(timeInSeconds / duration, 1));
|
||||
|
||||
try {
|
||||
if (Platform.OS === 'android') {
|
||||
// On Android, we need to handle seeking differently to prevent black screens
|
||||
setIsBuffering(true);
|
||||
|
||||
// Set a small timeout to prevent overwhelming the player
|
||||
const now = Date.now();
|
||||
if (now - lastSeekTime.current < 300) {
|
||||
// Throttle seeks that are too close together
|
||||
if (seekDebounceTimer.current) {
|
||||
clearTimeout(seekDebounceTimer.current);
|
||||
}
|
||||
|
||||
seekDebounceTimer.current = setTimeout(() => {
|
||||
if (vlcRef.current) {
|
||||
// Set position instead of using seek on Android
|
||||
vlcRef.current.setPosition(normalizedPosition);
|
||||
lastSeekTime.current = Date.now();
|
||||
|
||||
// Give the player some time to recover
|
||||
setTimeout(() => {
|
||||
setIsBuffering(false);
|
||||
}, 500);
|
||||
}
|
||||
}, 300);
|
||||
return;
|
||||
}
|
||||
|
||||
// Directly set position
|
||||
vlcRef.current.setPosition(normalizedPosition);
|
||||
lastSeekTime.current = now;
|
||||
|
||||
// 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');
|
||||
}
|
||||
if (vlcRef.current && duration > 0 && !isSeeking.current) {
|
||||
if (DEBUG_MODE) {
|
||||
logger.log(`[VideoPlayer] Seeking to ${timeInSeconds.toFixed(2)}s`);
|
||||
}
|
||||
|
||||
isSeeking.current = true;
|
||||
|
||||
// For Android, use direct seeking on VLC player ref instead of seek prop
|
||||
if (Platform.OS === 'android' && vlcRef.current.seek) {
|
||||
// Calculate position as fraction
|
||||
const position = timeInSeconds / duration;
|
||||
vlcRef.current.seek(position);
|
||||
|
||||
// Clear seek state after Android seek
|
||||
setTimeout(() => {
|
||||
if (isMounted.current) {
|
||||
isSeeking.current = false;
|
||||
}
|
||||
}, 300);
|
||||
} else {
|
||||
// iOS fallback - use seek prop
|
||||
const position = timeInSeconds / duration;
|
||||
setSeekPosition(position);
|
||||
|
||||
setTimeout(() => {
|
||||
if (isMounted.current) {
|
||||
setSeekPosition(null);
|
||||
isSeeking.current = false;
|
||||
}
|
||||
}, 500);
|
||||
}
|
||||
} else {
|
||||
if (DEBUG_MODE) {
|
||||
logger.error('[VideoPlayer] Seek failed: Player not ready, duration is zero, or already seeking.');
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('[VideoPlayer] Error during seek operation:', error);
|
||||
setIsBuffering(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleProgressBarTouch = (event: any) => {
|
||||
if (!duration || duration <= 0) return;
|
||||
const { locationX } = event.nativeEvent;
|
||||
processProgressTouch(locationX);
|
||||
if (duration > 0) {
|
||||
const { locationX } = event.nativeEvent;
|
||||
processProgressTouch(locationX);
|
||||
}
|
||||
};
|
||||
|
||||
const handleProgressBarDragStart = () => {
|
||||
|
|
@ -369,18 +372,8 @@ const VideoPlayer: React.FC = () => {
|
|||
const handleProgressBarDragEnd = () => {
|
||||
setIsDragging(false);
|
||||
if (pendingSeekValue.current !== null) {
|
||||
// For Android, add a small delay to ensure UI updates before the seek happens
|
||||
if (Platform.OS === 'android') {
|
||||
setTimeout(() => {
|
||||
if (pendingSeekValue.current !== null) {
|
||||
seekToTime(pendingSeekValue.current);
|
||||
pendingSeekValue.current = null;
|
||||
}
|
||||
}, 150);
|
||||
} else {
|
||||
seekToTime(pendingSeekValue.current);
|
||||
pendingSeekValue.current = null;
|
||||
}
|
||||
seekToTime(pendingSeekValue.current);
|
||||
pendingSeekValue.current = null;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -399,8 +392,11 @@ const VideoPlayer: React.FC = () => {
|
|||
};
|
||||
|
||||
const handleProgress = (event: any) => {
|
||||
if (isDragging) return;
|
||||
if (isDragging || isSeeking.current) return;
|
||||
|
||||
const currentTimeInSeconds = event.currentTime / 1000;
|
||||
|
||||
// Only update if there's a significant change to avoid unnecessary updates
|
||||
if (Math.abs(currentTimeInSeconds - currentTime) > 0.5) {
|
||||
safeSetState(() => setCurrentTime(currentTimeInSeconds));
|
||||
const progressPercent = duration > 0 ? currentTimeInSeconds / duration : 0;
|
||||
|
|
@ -415,67 +411,34 @@ const VideoPlayer: React.FC = () => {
|
|||
};
|
||||
|
||||
const onLoad = (data: any) => {
|
||||
setDuration(data.duration / 1000);
|
||||
if (data.videoSize && data.videoSize.width && data.videoSize.height) {
|
||||
const aspectRatio = data.videoSize.width / data.videoSize.height;
|
||||
setVideoAspectRatio(aspectRatio);
|
||||
const is16x9 = Math.abs(aspectRatio - (16/9)) < 0.1;
|
||||
setIs16by9Content(is16x9);
|
||||
if (is16x9) {
|
||||
setZoomScale(1.1);
|
||||
setLastZoomScale(1.1);
|
||||
} else {
|
||||
setZoomScale(1);
|
||||
setLastZoomScale(1);
|
||||
if (DEBUG_MODE) {
|
||||
logger.log('[VideoPlayer] Video loaded:', data);
|
||||
}
|
||||
if (isMounted.current) {
|
||||
if (data.duration > 0) {
|
||||
setDuration(data.duration / 1000);
|
||||
}
|
||||
const styles = calculateVideoStyles(
|
||||
data.videoSize.width,
|
||||
data.videoSize.height,
|
||||
screenDimensions.width,
|
||||
screenDimensions.height
|
||||
);
|
||||
setCustomVideoStyles(styles);
|
||||
} else {
|
||||
setIs16by9Content(true);
|
||||
setZoomScale(1.1);
|
||||
setLastZoomScale(1.1);
|
||||
const defaultStyles = {
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: screenDimensions.width,
|
||||
height: screenDimensions.height,
|
||||
};
|
||||
setCustomVideoStyles(defaultStyles);
|
||||
setVideoAspectRatio(data.videoSize.width / data.videoSize.height);
|
||||
|
||||
if (data.audioTracks && data.audioTracks.length > 0) {
|
||||
setVlcAudioTracks(data.audioTracks);
|
||||
}
|
||||
if (data.textTracks && data.textTracks.length > 0) {
|
||||
setVlcTextTracks(data.textTracks);
|
||||
}
|
||||
|
||||
setIsVideoLoaded(true);
|
||||
setIsPlayerReady(true);
|
||||
if (initialPosition && !isInitialSeekComplete) {
|
||||
setTimeout(() => {
|
||||
if (vlcRef.current && duration > 0 && isMounted.current) {
|
||||
seekToTime(initialPosition);
|
||||
setIsInitialSeekComplete(true);
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
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) => {
|
||||
|
|
@ -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`);
|
||||
|
||||
if (pendingSeek.position > 0 && vlcRef.current) {
|
||||
// Longer delay for Android to ensure player is stable
|
||||
const delayTime = Platform.OS === 'android' ? 2500 : 1500;
|
||||
const delayTime = Platform.OS === 'android' ? 1500 : 1000;
|
||||
|
||||
setTimeout(() => {
|
||||
if (vlcRef.current && duration > 0 && pendingSeek) {
|
||||
logger.log(`[VideoPlayer] Executing seek to ${pendingSeek.position}s`);
|
||||
|
||||
if (Platform.OS === 'android') {
|
||||
// On Android, wait longer and set isBuffering to improve visual feedback
|
||||
setIsBuffering(true);
|
||||
|
||||
// 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
|
||||
seekToTime(pendingSeek.position);
|
||||
|
||||
if (pendingSeek.shouldPlay) {
|
||||
setTimeout(() => {
|
||||
setIsBuffering(false);
|
||||
|
||||
// Resume playback after a delay if needed
|
||||
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);
|
||||
logger.log('[VideoPlayer] Resuming playback after source change seek');
|
||||
setPaused(false);
|
||||
}, 850); // Delay should be slightly more than seekToTime's internal timeout
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
setPendingSeek(null);
|
||||
setIsChangingSource(false);
|
||||
}, 900);
|
||||
}
|
||||
}, delayTime);
|
||||
} else {
|
||||
|
|
@ -856,9 +783,6 @@ const VideoPlayer: React.FC = () => {
|
|||
setTimeout(() => {
|
||||
logger.log('[VideoPlayer] No seek needed, just resuming playback');
|
||||
setPaused(false);
|
||||
if (vlcRef.current && typeof vlcRef.current.play === 'function') {
|
||||
vlcRef.current.play();
|
||||
}
|
||||
}, 500);
|
||||
}
|
||||
|
||||
|
|
@ -1018,7 +942,6 @@ const VideoPlayer: React.FC = () => {
|
|||
left: 0,
|
||||
width: screenDimensions.width,
|
||||
height: screenDimensions.height,
|
||||
backgroundColor: '#000',
|
||||
}}>
|
||||
<TouchableOpacity
|
||||
style={{ flex: 1 }}
|
||||
|
|
@ -1029,52 +952,21 @@ const VideoPlayer: React.FC = () => {
|
|||
>
|
||||
<VLCPlayer
|
||||
ref={vlcRef}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
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',
|
||||
],
|
||||
}}
|
||||
style={[styles.video, customVideoStyles, { transform: [{ scale: zoomScale }] }]}
|
||||
source={{ uri: currentStreamUrl }}
|
||||
paused={paused}
|
||||
autoplay={true}
|
||||
autoAspectRatio={false}
|
||||
resizeMode={'stretch' as any}
|
||||
audioTrack={selectedAudioTrack || undefined}
|
||||
textTrack={selectedTextTrack === -1 ? undefined : selectedTextTrack}
|
||||
onLoad={onLoad}
|
||||
onProgress={handleProgress}
|
||||
onLoad={onLoad}
|
||||
onEnd={onEnd}
|
||||
onError={handleError}
|
||||
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>
|
||||
</View>
|
||||
|
|
|
|||
Loading…
Reference in a new issue