mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-04-26 19:12:54 +00:00
improved trailer playback logic
This commit is contained in:
parent
6f24275ff0
commit
220fc6aa21
4 changed files with 85 additions and 9 deletions
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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' }}
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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={() => {}}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue