improved trailer playback logic

This commit is contained in:
tapframe 2025-08-31 16:01:38 +05:30
parent 6f24275ff0
commit 220fc6aa21
4 changed files with 85 additions and 9 deletions

View file

@ -8,6 +8,7 @@ import {
Platform, Platform,
InteractionManager, InteractionManager,
} from 'react-native'; } from 'react-native';
import { MaterialIcons } from '@expo/vector-icons'; import { MaterialIcons } from '@expo/vector-icons';
import { LinearGradient } from 'expo-linear-gradient'; import { LinearGradient } from 'expo-linear-gradient';
import { Image } from 'expo-image'; import { Image } from 'expo-image';
@ -709,6 +710,7 @@ const HeroSection: React.FC<HeroSectionProps> = memo(({
const [isTrailerPlaying, setIsTrailerPlaying] = useState(false); const [isTrailerPlaying, setIsTrailerPlaying] = useState(false);
const [trailerReady, setTrailerReady] = useState(false); const [trailerReady, setTrailerReady] = useState(false);
const [trailerPreloaded, setTrailerPreloaded] = useState(false); const [trailerPreloaded, setTrailerPreloaded] = useState(false);
const trailerVideoRef = useRef<any>(null);
const imageOpacity = useSharedValue(1); const imageOpacity = useSharedValue(1);
const imageLoadOpacity = useSharedValue(0); const imageLoadOpacity = useSharedValue(0);
const shimmerOpacity = useSharedValue(0.3); const shimmerOpacity = useSharedValue(0.3);
@ -747,6 +749,18 @@ const HeroSection: React.FC<HeroSectionProps> = memo(({
trailerOpacity.value = withTiming(1, { duration: 500 }); trailerOpacity.value = withTiming(1, { duration: 500 });
}, [thumbnailOpacity, trailerOpacity, trailerPreloaded]); }, [thumbnailOpacity, trailerOpacity, trailerPreloaded]);
// Handle fullscreen toggle
const handleFullscreenToggle = useCallback(async () => {
try {
if (trailerVideoRef.current) {
// Use the native fullscreen player
await trailerVideoRef.current.presentFullscreenPlayer();
}
} catch (error) {
logger.error('HeroSection', 'Error toggling fullscreen:', error);
}
}, []);
// Handle trailer error - fade back to thumbnail // Handle trailer error - fade back to thumbnail
const handleTrailerError = useCallback(() => { const handleTrailerError = useCallback(() => {
setTrailerError(true); setTrailerError(true);
@ -973,6 +987,8 @@ const HeroSection: React.FC<HeroSectionProps> = memo(({
imageLoadOpacity.value = 0; imageLoadOpacity.value = 0;
shimmerOpacity.value = 0.3; shimmerOpacity.value = 0.3;
interactionComplete.current = false; interactionComplete.current = false;
// Cleanup on unmount
}; };
}, []); }, []);
@ -990,6 +1006,8 @@ const HeroSection: React.FC<HeroSectionProps> = memo(({
} }
}); });
return ( return (
<Animated.View style={[styles.heroSection, heroAnimatedStyle]}> <Animated.View style={[styles.heroSection, heroAnimatedStyle]}>
{/* Optimized Background */} {/* Optimized Background */}
@ -1045,11 +1063,13 @@ const HeroSection: React.FC<HeroSectionProps> = memo(({
opacity: trailerOpacity opacity: trailerOpacity
}]}> }]}>
<TrailerPlayer <TrailerPlayer
ref={trailerVideoRef}
trailerUrl={trailerUrl} trailerUrl={trailerUrl}
autoPlay={true} autoPlay={true}
muted={trailerMuted} muted={trailerMuted}
style={styles.absoluteFill} style={styles.absoluteFill}
hideLoadingSpinner={true} hideLoadingSpinner={true}
onFullscreenToggle={handleFullscreenToggle}
onLoad={handleTrailerReady} onLoad={handleTrailerReady}
onError={handleTrailerError} onError={handleTrailerError}
onPlaybackStatusUpdate={(status) => { onPlaybackStatusUpdate={(status) => {
@ -1061,15 +1081,35 @@ const HeroSection: React.FC<HeroSectionProps> = memo(({
</Animated.View> </Animated.View>
)} )}
{/* Unmute button for trailer */} {/* Trailer control buttons (unmute and fullscreen) */}
{settings?.showTrailers && trailerReady && trailerUrl && ( {settings?.showTrailers && trailerReady && trailerUrl && (
<Animated.View style={{ <Animated.View style={{
position: 'absolute', position: 'absolute',
top: Platform.OS === 'android' ? 40 : 50, top: Platform.OS === 'android' ? 40 : 50,
right: width >= 768 ? 32 : 16, right: width >= 768 ? 32 : 16,
zIndex: 10, zIndex: 10,
opacity: trailerOpacity opacity: trailerOpacity,
flexDirection: 'row',
gap: 8,
}}> }}>
{/* Fullscreen button */}
<TouchableOpacity
onPress={handleFullscreenToggle}
activeOpacity={0.7}
style={{
padding: 8,
backgroundColor: 'rgba(0, 0, 0, 0.5)',
borderRadius: 20,
}}
>
<MaterialIcons
name="fullscreen"
size={24}
color="white"
/>
</TouchableOpacity>
{/* Unmute button */}
<TouchableOpacity <TouchableOpacity
onPress={() => { onPress={() => {
setTrailerMuted(!trailerMuted); setTrailerMuted(!trailerMuted);
@ -1207,6 +1247,7 @@ const styles = StyleSheet.create({
backgroundColor: '#000', backgroundColor: '#000',
overflow: 'hidden', overflow: 'hidden',
}, },
absoluteFill: { absoluteFill: {
position: 'absolute', position: 'absolute',
top: 0, top: 0,

View file

@ -1,4 +1,4 @@
import React, { useState } from 'react'; import React, { useState, useEffect } from 'react';
import { import {
View, View,
Text, Text,
@ -12,6 +12,7 @@ import Animated, {
FadeIn, FadeIn,
} from 'react-native-reanimated'; } from 'react-native-reanimated';
import { useTheme } from '../../contexts/ThemeContext'; import { useTheme } from '../../contexts/ThemeContext';
import { isMDBListEnabled } from '../../screens/MDBListSettingsScreen';
// MetadataSourceSelector removed // MetadataSourceSelector removed
interface MetadataDetailsProps { interface MetadataDetailsProps {
@ -34,6 +35,20 @@ const MetadataDetails: React.FC<MetadataDetailsProps> = ({
}) => { }) => {
const { currentTheme } = useTheme(); const { currentTheme } = useTheme();
const [isFullDescriptionOpen, setIsFullDescriptionOpen] = useState(false); const [isFullDescriptionOpen, setIsFullDescriptionOpen] = useState(false);
const [isMDBEnabled, setIsMDBEnabled] = useState(false);
useEffect(() => {
const checkMDBListEnabled = async () => {
try {
const enabled = await isMDBListEnabled();
setIsMDBEnabled(enabled);
} catch (error) {
setIsMDBEnabled(false); // Default to disabled if there's an error
}
};
checkMDBListEnabled();
}, []);
return ( return (
<> <>
@ -60,7 +75,7 @@ const MetadataDetails: React.FC<MetadataDetailsProps> = ({
{metadata.certification && ( {metadata.certification && (
<Text style={[styles.metaText, { color: currentTheme.colors.text }]}>{metadata.certification}</Text> <Text style={[styles.metaText, { color: currentTheme.colors.text }]}>{metadata.certification}</Text>
)} )}
{metadata.imdbRating && ( {metadata.imdbRating && !isMDBEnabled && (
<View style={styles.ratingContainer}> <View style={styles.ratingContainer}>
<Image <Image
source={{ uri: 'https://upload.wikimedia.org/wikipedia/commons/thumb/6/69/IMDB_Logo_2016.svg/575px-IMDB_Logo_2016.svg.png' }} source={{ uri: 'https://upload.wikimedia.org/wikipedia/commons/thumb/6/69/IMDB_Logo_2016.svg/575px-IMDB_Logo_2016.svg.png' }}

View file

@ -34,9 +34,10 @@ interface TrailerPlayerProps {
onPlaybackStatusUpdate?: (status: { isLoaded: boolean; didJustFinish: boolean }) => void; onPlaybackStatusUpdate?: (status: { isLoaded: boolean; didJustFinish: boolean }) => void;
style?: any; style?: any;
hideLoadingSpinner?: boolean; hideLoadingSpinner?: boolean;
onFullscreenToggle?: () => void;
} }
const TrailerPlayer: React.FC<TrailerPlayerProps> = memo(({ const TrailerPlayer = React.forwardRef<any, TrailerPlayerProps>(({
trailerUrl, trailerUrl,
autoPlay = true, autoPlay = true,
muted = true, muted = true,
@ -47,7 +48,8 @@ const TrailerPlayer: React.FC<TrailerPlayerProps> = memo(({
onPlaybackStatusUpdate, onPlaybackStatusUpdate,
style, style,
hideLoadingSpinner = false, hideLoadingSpinner = false,
}) => { onFullscreenToggle,
}, ref) => {
const { currentTheme } = useTheme(); const { currentTheme } = useTheme();
const videoRef = useRef<VideoRef>(null); const videoRef = useRef<VideoRef>(null);
@ -171,6 +173,15 @@ const TrailerPlayer: React.FC<TrailerPlayerProps> = memo(({
}; };
}, []); }, []);
// Forward the ref to the video element
React.useImperativeHandle(ref, () => ({
presentFullscreenPlayer: () => {
if (videoRef.current) {
return videoRef.current.presentFullscreenPlayer();
}
}
}));
// Animated styles // Animated styles
const controlsAnimatedStyle = useAnimatedStyle(() => ({ const controlsAnimatedStyle = useAnimatedStyle(() => ({
opacity: controlsOpacity.value, opacity: controlsOpacity.value,
@ -287,6 +298,16 @@ const TrailerPlayer: React.FC<TrailerPlayerProps> = memo(({
color="white" color="white"
/> />
</TouchableOpacity> </TouchableOpacity>
{onFullscreenToggle && (
<TouchableOpacity style={styles.controlButton} onPress={onFullscreenToggle}>
<MaterialIcons
name="fullscreen"
size={isTablet ? 32 : 24}
color="white"
/>
</TouchableOpacity>
)}
</View> </View>
</View> </View>
</LinearGradient> </LinearGradient>
@ -296,6 +317,8 @@ const TrailerPlayer: React.FC<TrailerPlayerProps> = memo(({
); );
}); });
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { container: {
flex: 1, flex: 1,

View file

@ -735,7 +735,6 @@ const LibraryScreen = () => {
numColumns={numColumns} numColumns={numColumns}
contentContainerStyle={styles.listContainer} contentContainerStyle={styles.listContainer}
showsVerticalScrollIndicator={false} showsVerticalScrollIndicator={false}
columnWrapperStyle={styles.columnWrapper}
onEndReachedThreshold={0.7} onEndReachedThreshold={0.7}
onEndReached={() => {}} onEndReached={() => {}}
/> />
@ -776,7 +775,6 @@ const LibraryScreen = () => {
renderItem={({ item }) => renderTraktItem({ item })} renderItem={({ item }) => renderTraktItem({ item })}
keyExtractor={(item) => `${item.type}-${item.id}`} keyExtractor={(item) => `${item.type}-${item.id}`}
numColumns={numColumns} numColumns={numColumns}
columnWrapperStyle={styles.row}
style={styles.traktContainer} style={styles.traktContainer}
contentContainerStyle={{ paddingBottom: insets.bottom + 80 }} contentContainerStyle={{ paddingBottom: insets.bottom + 80 }}
showsVerticalScrollIndicator={false} showsVerticalScrollIndicator={false}
@ -874,7 +872,6 @@ const LibraryScreen = () => {
numColumns={numColumns} numColumns={numColumns}
contentContainerStyle={styles.listContainer} contentContainerStyle={styles.listContainer}
showsVerticalScrollIndicator={false} showsVerticalScrollIndicator={false}
columnWrapperStyle={styles.columnWrapper}
onEndReachedThreshold={0.7} onEndReachedThreshold={0.7}
onEndReached={() => {}} onEndReached={() => {}}
/> />