Streamscreen new changes update

This commit is contained in:
tapframe 2025-10-23 14:51:41 +05:30
parent c5590639b1
commit f90752bdb7
3 changed files with 186 additions and 14 deletions

View file

@ -86,6 +86,7 @@ export interface AppSettings {
useCachedStreams: boolean; // Enable/disable direct player navigation from Continue Watching cache
openMetadataScreenWhenCacheDisabled: boolean; // When cache disabled, open MetadataScreen instead of StreamsScreen
streamCacheTTL: number; // Stream cache duration in milliseconds (default: 1 hour)
enableStreamsBackdrop: boolean; // Enable blurred backdrop background on StreamsScreen mobile
}
export const DEFAULT_SETTINGS: AppSettings = {
@ -145,6 +146,7 @@ export const DEFAULT_SETTINGS: AppSettings = {
useCachedStreams: false, // Enable by default
openMetadataScreenWhenCacheDisabled: true, // Default to StreamsScreen when cache disabled
streamCacheTTL: 60 * 60 * 1000, // Default: 1 hour in milliseconds
enableStreamsBackdrop: true, // Enable by default (new behavior)
};
const SETTINGS_STORAGE_KEY = 'app_settings';

View file

@ -518,9 +518,24 @@ const SettingsScreen: React.FC = () => {
onValueChange={(value) => updateSetting('episodeLayoutStyle', value ? 'horizontal' : 'vertical')}
/>
)}
isLast={true}
isLast={isTablet}
isTablet={isTablet}
/>
{!isTablet && (
<SettingItem
title="Streams Backdrop"
description="Show blurred backdrop on mobile streams"
icon="image"
renderControl={() => (
<CustomSwitch
value={settings?.enableStreamsBackdrop ?? true}
onValueChange={(value) => updateSetting('enableStreamsBackdrop', value)}
/>
)}
isLast={true}
isTablet={isTablet}
/>
)}
</SettingsCard>
);

View file

@ -50,6 +50,7 @@ import CustomAlert from '../components/CustomAlert';
import { useToast } from '../contexts/ToastContext';
import { useDownloads } from '../contexts/DownloadsContext';
import { streamCacheService } from '../services/streamCacheService';
import { useDominantColor } from '../hooks/useDominantColor';
import { PaperProvider } from 'react-native-paper';
import { BlurView as ExpoBlurView } from 'expo-blur';
import TabletStreamsLayout from '../components/TabletStreamsLayout';
@ -1549,6 +1550,48 @@ export const StreamsScreen = () => {
return r;
}, [currentEpisode, tmdbEpisodeOverride?.runtime]);
// Mobile backdrop source selection logic
const mobileBackdropSource = useMemo(() => {
// For series episodes: prioritize episodeImage, fallback to bannerImage
if (type === 'series' || (type === 'other' && selectedEpisode)) {
if (episodeImage) {
return episodeImage;
}
if (bannerImage) {
return bannerImage;
}
}
// For movies: prioritize bannerImage
if (type === 'movie') {
if (bannerImage) {
return bannerImage;
}
}
// For other types or when no specific image available
return bannerImage || episodeImage;
}, [type, selectedEpisode, episodeImage, bannerImage]);
// Backdrop source for color extraction - only episodes, not movies
const colorExtractionSource = useMemo(() => {
// Only extract colors if backdrop is enabled
if (!settings.enableStreamsBackdrop) {
return null;
}
if (type === 'series' || (type === 'other' && selectedEpisode)) {
// Only use episodeImage - don't fallback to bannerImage
// This ensures we get episode-specific colors, not show-wide colors
return episodeImage || null;
}
// Return null for movies - no color extraction
return null;
}, [type, selectedEpisode, episodeImage, settings.enableStreamsBackdrop]);
// Extract dominant color from backdrop for gradient
const { dominantColor } = useDominantColor(colorExtractionSource);
// Prefetch hero/backdrop and title logo when StreamsScreen opens
useEffect(() => {
const urls: string[] = [];
@ -1563,6 +1606,33 @@ export const StreamsScreen = () => {
});
}, [episodeImage, bannerImage, metadata]);
// Helper to create gradient colors from dominant color
const createGradientColors = useCallback((baseColor: string | null): [string, string, string, string, string] => {
if (!baseColor || baseColor === '#1a1a1a') {
// Fallback to black gradient with stronger bottom edge
return ['rgba(0,0,0,0)', 'rgba(0,0,0,0.3)', 'rgba(0,0,0,0.6)', 'rgba(0,0,0,0.85)', 'rgba(0,0,0,0.95)'];
}
// Convert hex to RGB
const r = parseInt(baseColor.substr(1, 2), 16);
const g = parseInt(baseColor.substr(3, 2), 16);
const b = parseInt(baseColor.substr(5, 2), 16);
// Create gradient stops with much stronger opacity at bottom
return [
`rgba(${r},${g},${b},0)`,
`rgba(${r},${g},${b},0.3)`,
`rgba(${r},${g},${b},0.6)`,
`rgba(${r},${g},${b},0.85)`,
`rgba(${r},${g},${b},0.95)`,
];
}, []);
const gradientColors = useMemo(() =>
createGradientColors(dominantColor),
[dominantColor, createGradientColors]
);
const isLoading = metadata?.videos && metadata.videos.length > 1 && selectedEpisode ? loadingEpisodeStreams : loadingStreams;
const streams = metadata?.videos && metadata.videos.length > 1 && selectedEpisode ? episodeStreams : groupedStreams;
@ -1695,8 +1765,52 @@ export const StreamsScreen = () => {
) : (
// PHONE LAYOUT (existing structure)
<>
{/* Full Screen Background for Mobile */}
{settings.enableStreamsBackdrop ? (
<View style={StyleSheet.absoluteFill}>
{mobileBackdropSource ? (
<AnimatedImage
source={{ uri: mobileBackdropSource }}
style={styles.mobileFullScreenBackground}
contentFit="cover"
/>
) : (
<View style={styles.mobileNoBackdropBackground} />
)}
{Platform.OS === 'android' && AndroidBlurView ? (
<AndroidBlurView
blurAmount={15}
blurRadius={8}
overlayColor={"rgba(0,0,0,0.8)"}
style={StyleSheet.absoluteFill}
/>
) : (
<ExpoBlurView
intensity={60}
tint="dark"
style={StyleSheet.absoluteFill}
/>
)}
{/* Dark overlay to reduce brightness */}
{Platform.OS === 'ios' && (
<View style={[
StyleSheet.absoluteFill,
{ backgroundColor: dominantColor && dominantColor !== '#1a1a1a'
? `${dominantColor}CC` // Add 80% opacity (CC in hex)
: 'rgba(0,0,0,0.8)'
}
]} />
)}
</View>
) : (
<View style={[StyleSheet.absoluteFill, { backgroundColor: colors.darkBackground }]} />
)}
{type === 'movie' && metadata && (
<View style={[styles.movieTitleContainer]}>
<View style={[
styles.movieTitleContainer,
!settings.enableStreamsBackdrop && { backgroundColor: colors.darkBackground }
]}>
<View style={styles.movieTitleContent}>
{metadata.logo && !movieLogoError ? (
<FastImage
@ -1715,7 +1829,10 @@ export const StreamsScreen = () => {
)}
{metadata?.videos && metadata.videos.length > 1 && selectedEpisode && (
<View style={[styles.streamsHeroContainer]}>
<View style={[
styles.streamsHeroContainer,
!settings.enableStreamsBackdrop && { backgroundColor: colors.darkBackground }
]}>
<View style={StyleSheet.absoluteFill}>
<View
style={StyleSheet.absoluteFill}
@ -1725,11 +1842,11 @@ export const StreamsScreen = () => {
style={styles.streamsHeroBackground}
contentFit="cover"
/>
<LinearGradient
colors={['rgba(0,0,0,0)', 'rgba(0,0,0,0.3)', 'rgba(0,0,0,0.5)', 'rgba(0,0,0,0.7)', colors.darkBackground]}
locations={[0, 0.4, 0.6, 0.8, 1]}
style={styles.streamsHeroGradient}
>
<LinearGradient
colors={gradientColors}
locations={[0, 0.4, 0.6, 0.8, 1]}
style={styles.streamsHeroGradient}
>
<View style={styles.streamsHeroContent}>
{currentEpisode ? (
<View style={styles.streamsHeroInfo}>
@ -1781,9 +1898,29 @@ export const StreamsScreen = () => {
</View>
)}
{/* Gradient overlay to blend hero section with streams container */}
{metadata?.videos && metadata.videos.length > 1 && selectedEpisode && settings.enableStreamsBackdrop && (
<View style={styles.heroBlendOverlay}>
<LinearGradient
colors={[
dominantColor && dominantColor !== '#1a1a1a'
? `rgba(${parseInt(dominantColor.substr(1, 2), 16)},${parseInt(dominantColor.substr(3, 2), 16)},${parseInt(dominantColor.substr(5, 2), 16)},0.95)`
: 'rgba(0,0,0,0.95)',
dominantColor && dominantColor !== '#1a1a1a'
? `rgba(${parseInt(dominantColor.substr(1, 2), 16)},${parseInt(dominantColor.substr(3, 2), 16)},${parseInt(dominantColor.substr(5, 2), 16)},0.7)`
: 'rgba(0,0,0,0.7)',
'transparent'
]}
locations={[0, 0.5, 1]}
style={StyleSheet.absoluteFill}
/>
</View>
)}
<View style={[
styles.streamsMainContent,
type === 'movie' && styles.streamsMainContentMovie
type === 'movie' && styles.streamsMainContentMovie,
!settings.enableStreamsBackdrop && { backgroundColor: colors.darkBackground }
]}>
<View style={[styles.filterContainer]}>
{!streamsEmpty && (
@ -1969,7 +2106,7 @@ export const StreamsScreen = () => {
const createStyles = (colors: any) => StyleSheet.create({
container: {
flex: 1,
backgroundColor: colors.darkBackground,
backgroundColor: 'transparent',
// iOS-specific fixes for navigation transition glitches
...(Platform.OS === 'ios' && {
// Ensure the background is properly rendered during transitions
@ -2004,7 +2141,7 @@ const createStyles = (colors: any) => StyleSheet.create({
},
streamsMainContent: {
flex: 1,
backgroundColor: colors.darkBackground,
backgroundColor: 'transparent',
paddingTop: 12,
zIndex: 1,
// iOS-specific fixes for navigation transition glitches
@ -2225,13 +2362,13 @@ const createStyles = (colors: any) => StyleSheet.create({
height: 220, // Fixed height to prevent layout shift
marginBottom: 0,
position: 'relative',
backgroundColor: colors.black,
backgroundColor: 'transparent',
pointerEvents: 'box-none',
},
streamsHeroBackground: {
width: '100%',
height: '100%',
backgroundColor: colors.black,
backgroundColor: 'transparent',
},
streamsHeroGradient: {
...StyleSheet.absoluteFillObject,
@ -2349,7 +2486,7 @@ const createStyles = (colors: any) => StyleSheet.create({
movieTitleContainer: {
width: '100%',
height: 140,
backgroundColor: colors.darkBackground,
backgroundColor: 'transparent',
pointerEvents: 'box-none',
justifyContent: 'center',
paddingTop: Platform.OS === 'android' ? 65 : 35,
@ -2556,6 +2693,24 @@ const createStyles = (colors: any) => StyleSheet.create({
backButtonContainerTablet: {
zIndex: 3,
},
mobileFullScreenBackground: {
...StyleSheet.absoluteFillObject,
width: '100%',
height: '100%',
},
mobileNoBackdropBackground: {
...StyleSheet.absoluteFillObject,
backgroundColor: colors.darkBackground,
},
heroBlendOverlay: {
position: 'absolute',
top: 220, // Height of hero container
left: 0,
right: 0,
height: 60, // Extend gradient 60px into streams area
zIndex: 0,
pointerEvents: 'none',
},
});
export default memo(StreamsScreen);