streamscreen to player refactor

This commit is contained in:
tapframe 2025-12-11 14:42:05 +05:30
parent 6bdc998496
commit 3801e80dd9
5 changed files with 743 additions and 732 deletions

View file

@ -541,7 +541,7 @@ const SeriesContentComponent: React.FC<SeriesContentProps> = ({
return null;
}
if (__DEV__) console.log('[SeriesContent] renderSeasonSelector called, current view mode:', seasonViewMode);
const seasons = Object.keys(groupedEpisodes).map(Number).sort((a, b) => a - b);
@ -630,7 +630,7 @@ const SeriesContentComponent: React.FC<SeriesContentProps> = ({
if (seasonViewMode === 'text') {
// Text-only view
if (__DEV__) console.log('[SeriesContent] Rendering text view for season:', season, 'View mode ref:', seasonViewMode);
return (
<View
key={season}
@ -668,7 +668,7 @@ const SeriesContentComponent: React.FC<SeriesContentProps> = ({
}
// Poster view (current implementation)
if (__DEV__) console.log('[SeriesContent] Rendering poster view for season:', season, 'View mode ref:', seasonViewMode);
return (
<View
key={season}
@ -796,7 +796,7 @@ const SeriesContentComponent: React.FC<SeriesContentProps> = ({
const effectiveVote = imdbRating ?? tmdbRating ?? 0;
const isImdbRating = imdbRating !== null;
logger.log(`[SeriesContent] Vertical card S${episode.season_number}E${episode.episode_number}: IMDb=${imdbRating}, TMDB=${tmdbRating}, effective=${effectiveVote}, isImdb=${isImdbRating}`);
const effectiveRuntime = tmdbOverride?.runtime ?? (episode as any).runtime;
if (!episode.still_path && tmdbOverride?.still_path) {
@ -1067,8 +1067,6 @@ const SeriesContentComponent: React.FC<SeriesContentProps> = ({
const isImdbRating = imdbRating !== null;
const effectiveRuntime = tmdbOverride?.runtime ?? (episode as any).runtime;
logger.log(`[SeriesContent] Horizontal card S${episode.season_number}E${episode.episode_number}: IMDb=${imdbRating}, TMDB=${tmdbRating}, effective=${effectiveVote}, isImdb=${isImdbRating}`);
const formatDate = (dateString: string) => {
const date = new Date(dateString);
return date.toLocaleDateString('en-US', {

View file

@ -1,5 +1,5 @@
import React, { useState, useRef, useEffect, useMemo, useCallback } from 'react';
import { View, TouchableOpacity, TouchableWithoutFeedback, Dimensions, Animated, ActivityIndicator, Platform, NativeModules, StatusBar, Text, StyleSheet, Modal, AppState, Image } from 'react-native';
import { View, TouchableOpacity, TouchableWithoutFeedback, Dimensions, Animated, ActivityIndicator, Platform, NativeModules, StatusBar, Text, StyleSheet, Modal, AppState, Image, InteractionManager } from 'react-native';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import Video, { VideoRef, SelectedTrack, SelectedTrackType, BufferingStrategyType, ViewType } from 'react-native-video';
import FastImage from '@d11/react-native-fast-image';
@ -641,6 +641,8 @@ const AndroidVideoPlayer: React.FC = () => {
// Prefetch backdrop and title logo for faster loading screen appearance
useEffect(() => {
// Defer prefetching until after navigation animation completes
const task = InteractionManager.runAfterInteractions(() => {
if (backdrop && typeof backdrop === 'string') {
// Reset loading state
setIsBackdropLoaded(false);
@ -667,9 +669,13 @@ const AndroidVideoPlayer: React.FC = () => {
setIsBackdropLoaded(true);
backdropImageOpacityAnim.setValue(0);
}
});
return () => task.cancel();
}, [backdrop]);
useEffect(() => {
// Defer logo prefetch until after navigation animation
const task = InteractionManager.runAfterInteractions(() => {
const logoUrl = (metadata && (metadata as any).logo) as string | undefined;
if (logoUrl && typeof logoUrl === 'string') {
try {
@ -678,6 +684,8 @@ const AndroidVideoPlayer: React.FC = () => {
// Silently ignore logo prefetch errors
}
}
});
return () => task.cancel();
}, [metadata]);
// Resolve current episode description for series

View file

@ -1,5 +1,5 @@
import React, { useState, useRef, useEffect, useMemo, useCallback } from 'react';
import { View, TouchableOpacity, Dimensions, Animated, ActivityIndicator, Platform, NativeModules, StatusBar, Text, StyleSheet, Modal, AppState } from 'react-native';
import { View, TouchableOpacity, Dimensions, Animated, ActivityIndicator, Platform, NativeModules, StatusBar, Text, StyleSheet, Modal, AppState, InteractionManager } from 'react-native';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { useNavigation, useRoute, RouteProp, useFocusEffect } from '@react-navigation/native';
import FastImage from '@d11/react-native-fast-image';
@ -342,6 +342,8 @@ const KSPlayerCore: React.FC = () => {
// Load custom backdrop on mount
// Prefetch backdrop and title logo for faster loading screen appearance
useEffect(() => {
// Defer prefetching until after navigation animation completes
const task = InteractionManager.runAfterInteractions(() => {
if (backdrop && typeof backdrop === 'string') {
// Reset loading state
setIsBackdropLoaded(false);
@ -368,9 +370,13 @@ const KSPlayerCore: React.FC = () => {
setIsBackdropLoaded(true);
backdropImageOpacityAnim.setValue(0);
}
});
return () => task.cancel();
}, [backdrop]);
useEffect(() => {
// Defer logo prefetch until after navigation animation
const task = InteractionManager.runAfterInteractions(() => {
const logoUrl = (metadata && (metadata as any).logo) as string | undefined;
if (logoUrl && typeof logoUrl === 'string') {
try {
@ -379,6 +385,8 @@ const KSPlayerCore: React.FC = () => {
// Silently ignore logo prefetch errors
}
}
});
return () => task.cancel();
}, [metadata]);
// Log video source configuration with headers

View file

@ -1210,7 +1210,7 @@ const InnerNavigator = ({ initialRouteName }: { initialRouteName?: keyof RootSta
options={{
animation: 'default',
animationDuration: 0,
// Force fullscreen presentation on iPad
// fullScreenModal required for proper video rendering on iOS
presentation: 'fullScreenModal',
// Disable gestures during video playback
gestureEnabled: false,

View file

@ -169,7 +169,7 @@ export const StreamsScreen = () => {
try {
setAlertTitle(title);
setAlertMessage(message);
setAlertActions(actions && actions.length > 0 ? actions : [{ label: 'OK', onPress: () => {} }]);
setAlertActions(actions && actions.length > 0 ? actions : [{ label: 'OK', onPress: () => { } }]);
setAlertVisible(true);
} catch (error) {
console.warn('[StreamsScreen] Error showing alert:', error);
@ -180,7 +180,7 @@ export const StreamsScreen = () => {
// Track when we started fetching streams so we can show an extended loading state
const [streamsLoadStart, setStreamsLoadStart] = useState<number | null>(null);
const [providerLoadTimes, setProviderLoadTimes] = useState<{[key: string]: number}>({});
const [providerLoadTimes, setProviderLoadTimes] = useState<{ [key: string]: number }>({});
// Prevent excessive re-renders by using this guard
const guardedSetState = useCallback((setter: () => void) => {
@ -228,7 +228,7 @@ export const StreamsScreen = () => {
} = useMetadata({ id, type });
// Get backdrop from metadata assets
const setMetadataStub = useCallback(() => {}, []);
const setMetadataStub = useCallback(() => { }, []);
const memoizedSettings = useMemo(() => settings, [settings.logoSourcePreference, settings.tmdbLanguagePreference, settings.enrichMetadataWithTMDB]);
const { bannerImage } = useMetadataAssets(metadata, id, type, imdbId, memoizedSettings, setMetadataStub);
@ -240,7 +240,7 @@ export const StreamsScreen = () => {
// Add state for provider loading status
const [loadingProviders, setLoadingProviders] = useState<{[key: string]: boolean}>({});
const [loadingProviders, setLoadingProviders] = useState<{ [key: string]: boolean }>({});
// Add state for more detailed provider loading tracking
const [providerStatus, setProviderStatus] = useState<{
@ -296,7 +296,7 @@ export const StreamsScreen = () => {
const map: Record<string, string> = {};
// No direct way to iterate Map keys safely without exposing it; copy known ids on demand during render
setScraperLogos(prev => prev); // no-op to ensure consistency
}).catch(() => {});
}).catch(() => { });
}
};
preloadScraperLogos();
@ -826,9 +826,6 @@ export const StreamsScreen = () => {
streamName: stream.name || stream.title
});
// Add 50ms delay before navigating to player
await new Promise(resolve => setTimeout(resolve, 50));
// Prepare available streams for the change source feature
const streamsToPass = (type === 'series' || (type === 'other' && selectedEpisode)) ? episodeStreams : groupedStreams;
@ -881,7 +878,7 @@ export const StreamsScreen = () => {
if (!videoType && /xprime/i.test(providerId)) {
videoType = 'm3u8';
}
} catch {}
} catch { }
// Simple platform check - iOS uses KSPlayerCore, Android uses AndroidVideoPlayer
const playerRoute = Platform.OS === 'ios' ? 'PlayerIOS' : 'PlayerAndroid';
@ -920,7 +917,7 @@ export const StreamsScreen = () => {
if (typeof stream.url === 'string' && stream.url.startsWith('magnet:')) {
try {
openAlert('Not supported', 'Torrent streaming is not supported yet.');
} catch (_e) {}
} catch (_e) { }
return;
}
// If stream is actually MKV format, force the in-app VLC-based player on iOS
@ -1137,7 +1134,7 @@ export const StreamsScreen = () => {
clearTimeout(renderTimer);
};
}
return () => {};
return () => { };
}, [])
);
@ -1687,7 +1684,7 @@ export const StreamsScreen = () => {
}
// Deduplicate and prefetch
Array.from(new Set(urls)).forEach(u => {
RNImage.prefetch(u).catch(() => {});
RNImage.prefetch(u).catch(() => { });
});
}, [episodeImage, bannerImage, metadata]);
@ -1945,7 +1942,7 @@ export const StreamsScreen = () => {
</View>
)}
{currentEpisode && (
{currentEpisode && (
<View style={[
styles.streamsHeroContainer,
!settings.enableStreamsBackdrop && { backgroundColor: colors.darkBackground }
@ -2076,7 +2073,7 @@ export const StreamsScreen = () => {
)}
{/* Update the streams/loading state display logic */}
{ showNoSourcesError ? (
{showNoSourcesError ? (
<View
style={styles.noStreams}
>