mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-05-13 13:30:46 +00:00
bug fixes android and ios
This commit is contained in:
parent
181cdaecb5
commit
dbbee06a55
6 changed files with 228 additions and 214 deletions
|
|
@ -861,18 +861,21 @@ const AndroidVideoPlayer: React.FC = () => {
|
|||
// Re-apply immersive mode on layout changes to keep system bars hidden
|
||||
enableImmersiveMode();
|
||||
});
|
||||
const initializePlayer = async () => {
|
||||
StatusBar.setHidden(true, 'none');
|
||||
enableImmersiveMode();
|
||||
startOpeningAnimation();
|
||||
|
||||
// Initialize current volume and brightness levels
|
||||
// Volume starts at 1.0 (full volume) - React Native Video handles this natively
|
||||
setVolume(1.0);
|
||||
if (DEBUG_MODE) {
|
||||
logger.log(`[AndroidVideoPlayer] Initial volume: 1.0 (native)`);
|
||||
}
|
||||
// Immediate player setup - UI critical
|
||||
StatusBar.setHidden(true, 'none');
|
||||
enableImmersiveMode();
|
||||
startOpeningAnimation();
|
||||
|
||||
// Initialize volume immediately (no async)
|
||||
setVolume(1.0);
|
||||
if (DEBUG_MODE) {
|
||||
logger.log(`[AndroidVideoPlayer] Initial volume: 1.0 (native)`);
|
||||
}
|
||||
|
||||
// Defer brightness initialization until after navigation animation completes
|
||||
// This prevents sluggish player entry
|
||||
const brightnessTask = InteractionManager.runAfterInteractions(async () => {
|
||||
try {
|
||||
// Capture Android system brightness and mode to restore later
|
||||
if (Platform.OS === 'android') {
|
||||
|
|
@ -900,10 +903,11 @@ const AndroidVideoPlayer: React.FC = () => {
|
|||
// Fallback to 1.0 if brightness API fails
|
||||
setBrightness(1.0);
|
||||
}
|
||||
};
|
||||
initializePlayer();
|
||||
});
|
||||
|
||||
return () => {
|
||||
subscription?.remove();
|
||||
brightnessTask.cancel();
|
||||
disableImmersiveMode();
|
||||
};
|
||||
}, []);
|
||||
|
|
@ -1772,49 +1776,46 @@ const AndroidVideoPlayer: React.FC = () => {
|
|||
}
|
||||
};
|
||||
|
||||
await restoreSystemBrightness();
|
||||
// Don't await brightness restoration - do it in background
|
||||
restoreSystemBrightness();
|
||||
|
||||
// Navigate immediately without delay
|
||||
ScreenOrientation.unlockAsync().then(() => {
|
||||
// On tablets keep rotation unlocked; on phones, return to portrait
|
||||
const { width: dw, height: dh } = Dimensions.get('window');
|
||||
const isTablet = Math.min(dw, dh) >= 768 || ((Platform as any).isPad === true);
|
||||
if (!isTablet) {
|
||||
setTimeout(() => {
|
||||
ScreenOrientation.lockAsync(ScreenOrientation.OrientationLock.PORTRAIT_UP).catch(() => { });
|
||||
}, 50);
|
||||
} else {
|
||||
ScreenOrientation.unlockAsync().catch(() => { });
|
||||
}
|
||||
disableImmersiveMode();
|
||||
// Disable immersive mode immediately (synchronous)
|
||||
disableImmersiveMode();
|
||||
|
||||
// Simple back navigation (StreamsScreen should be below Player)
|
||||
if ((navigation as any).canGoBack && (navigation as any).canGoBack()) {
|
||||
(navigation as any).goBack();
|
||||
} else {
|
||||
// Fallback to Streams if stack isn't present
|
||||
(navigation as any).navigate('Streams', { id, type, episodeId, fromPlayer: true });
|
||||
}
|
||||
}).catch(() => {
|
||||
// Fallback: still try to restore portrait on phones then navigate
|
||||
const { width: dw, height: dh } = Dimensions.get('window');
|
||||
const isTablet = Math.min(dw, dh) >= 768 || ((Platform as any).isPad === true);
|
||||
if (!isTablet) {
|
||||
setTimeout(() => {
|
||||
ScreenOrientation.lockAsync(ScreenOrientation.OrientationLock.PORTRAIT_UP).catch(() => { });
|
||||
}, 50);
|
||||
} else {
|
||||
ScreenOrientation.unlockAsync().catch(() => { });
|
||||
}
|
||||
disableImmersiveMode();
|
||||
// Navigate IMMEDIATELY - don't wait for orientation changes
|
||||
if ((navigation as any).canGoBack && (navigation as any).canGoBack()) {
|
||||
(navigation as any).goBack();
|
||||
} else {
|
||||
// Fallback to Streams if stack isn't present
|
||||
(navigation as any).navigate('Streams', { id, type, episodeId, fromPlayer: true });
|
||||
}
|
||||
|
||||
// Simple back navigation fallback path
|
||||
if ((navigation as any).canGoBack && (navigation as any).canGoBack()) {
|
||||
(navigation as any).goBack();
|
||||
} else {
|
||||
(navigation as any).navigate('Streams', { id, type, episodeId, fromPlayer: true });
|
||||
}
|
||||
});
|
||||
// Fire orientation changes in background - don't await
|
||||
ScreenOrientation.unlockAsync()
|
||||
.then(() => {
|
||||
// On tablets keep rotation unlocked; on phones, return to portrait
|
||||
const { width: dw, height: dh } = Dimensions.get('window');
|
||||
const isTablet = Math.min(dw, dh) >= 768 || ((Platform as any).isPad === true);
|
||||
if (!isTablet) {
|
||||
setTimeout(() => {
|
||||
ScreenOrientation.lockAsync(ScreenOrientation.OrientationLock.PORTRAIT_UP).catch(() => { });
|
||||
}, 50);
|
||||
} else {
|
||||
ScreenOrientation.unlockAsync().catch(() => { });
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
// Fallback: still try to restore portrait on phones
|
||||
const { width: dw, height: dh } = Dimensions.get('window');
|
||||
const isTablet = Math.min(dw, dh) >= 768 || ((Platform as any).isPad === true);
|
||||
if (!isTablet) {
|
||||
setTimeout(() => {
|
||||
ScreenOrientation.lockAsync(ScreenOrientation.OrientationLock.PORTRAIT_UP).catch(() => { });
|
||||
}, 50);
|
||||
} else {
|
||||
ScreenOrientation.unlockAsync().catch(() => { });
|
||||
}
|
||||
});
|
||||
|
||||
// Send Trakt sync in background (don't await)
|
||||
const backgroundSync = async () => {
|
||||
|
|
|
|||
|
|
@ -589,20 +589,19 @@ const KSPlayerCore: React.FC = () => {
|
|||
|
||||
// Force landscape orientation after opening animation completes
|
||||
useEffect(() => {
|
||||
const lockOrientation = async () => {
|
||||
try {
|
||||
await ScreenOrientation.lockAsync(ScreenOrientation.OrientationLock.LANDSCAPE);
|
||||
if (__DEV__) logger.log('[VideoPlayer] Locked to landscape orientation');
|
||||
} catch (error) {
|
||||
logger.warn('[VideoPlayer] Failed to lock orientation:', error);
|
||||
}
|
||||
};
|
||||
|
||||
// Lock orientation after opening animation completes to prevent glitches
|
||||
// Defer orientation lock until after navigation animation to prevent sluggishness
|
||||
if (isOpeningAnimationComplete) {
|
||||
lockOrientation();
|
||||
const task = InteractionManager.runAfterInteractions(() => {
|
||||
ScreenOrientation.lockAsync(ScreenOrientation.OrientationLock.LANDSCAPE)
|
||||
.then(() => {
|
||||
if (__DEV__) logger.log('[VideoPlayer] Locked to landscape orientation');
|
||||
})
|
||||
.catch((error) => {
|
||||
logger.warn('[VideoPlayer] Failed to lock orientation:', error);
|
||||
});
|
||||
});
|
||||
return () => task.cancel();
|
||||
}
|
||||
|
||||
return () => {
|
||||
// Do not unlock orientation here; we unlock explicitly on close to avoid mid-transition flips
|
||||
};
|
||||
|
|
@ -616,21 +615,24 @@ const KSPlayerCore: React.FC = () => {
|
|||
enableImmersiveMode();
|
||||
}
|
||||
});
|
||||
const initializePlayer = async () => {
|
||||
StatusBar.setHidden(true, 'none');
|
||||
// Enable immersive mode after opening animation to prevent glitches
|
||||
if (isOpeningAnimationComplete) {
|
||||
enableImmersiveMode();
|
||||
}
|
||||
startOpeningAnimation();
|
||||
|
||||
// Initialize current volume and brightness levels
|
||||
// Volume starts at 100 (full volume) for KSPlayer
|
||||
setVolume(100);
|
||||
if (DEBUG_MODE) {
|
||||
logger.log(`[VideoPlayer] Initial volume: 100 (KSPlayer native)`);
|
||||
}
|
||||
// Immediate player setup - UI critical
|
||||
StatusBar.setHidden(true, 'none');
|
||||
// Enable immersive mode after opening animation to prevent glitches
|
||||
if (isOpeningAnimationComplete) {
|
||||
enableImmersiveMode();
|
||||
}
|
||||
startOpeningAnimation();
|
||||
|
||||
// Initialize volume immediately (no async)
|
||||
setVolume(100);
|
||||
if (DEBUG_MODE) {
|
||||
logger.log(`[VideoPlayer] Initial volume: 100 (KSPlayer native)`);
|
||||
}
|
||||
|
||||
// Defer brightness initialization until after navigation animation completes
|
||||
// This prevents sluggish player entry
|
||||
const brightnessTask = InteractionManager.runAfterInteractions(async () => {
|
||||
try {
|
||||
const currentBrightness = await Brightness.getBrightnessAsync();
|
||||
setBrightness(currentBrightness);
|
||||
|
|
@ -642,10 +644,11 @@ const KSPlayerCore: React.FC = () => {
|
|||
// Fallback to 1.0 if brightness API fails
|
||||
setBrightness(1.0);
|
||||
}
|
||||
};
|
||||
initializePlayer();
|
||||
});
|
||||
|
||||
return () => {
|
||||
subscription?.remove();
|
||||
brightnessTask.cancel();
|
||||
disableImmersiveMode();
|
||||
};
|
||||
}, [isOpeningAnimationComplete]);
|
||||
|
|
@ -1381,32 +1384,32 @@ const KSPlayerCore: React.FC = () => {
|
|||
logger.log(`[VideoPlayer] Current progress: ${actualCurrentTime}/${duration} (${progressPercent.toFixed(1)}%)`);
|
||||
|
||||
// Cleanup and navigate back immediately without delay
|
||||
const cleanup = async () => {
|
||||
try {
|
||||
// Unlock orientation first
|
||||
await ScreenOrientation.unlockAsync();
|
||||
logger.log('[VideoPlayer] Orientation unlocked');
|
||||
} catch (orientationError) {
|
||||
logger.warn('[VideoPlayer] Failed to unlock orientation:', orientationError);
|
||||
}
|
||||
|
||||
// On iOS tablets, keep rotation unlocked; on phones, return to portrait
|
||||
if (Platform.OS === 'ios') {
|
||||
const { width: dw, height: dh } = Dimensions.get('window');
|
||||
const isTablet = (Platform as any).isPad === true || Math.min(dw, dh) >= 768;
|
||||
setTimeout(() => {
|
||||
if (isTablet) {
|
||||
ScreenOrientation.unlockAsync().catch(() => { });
|
||||
} else {
|
||||
ScreenOrientation.lockAsync(ScreenOrientation.OrientationLock.PORTRAIT_UP).catch(() => { });
|
||||
const cleanup = () => {
|
||||
// Fire orientation changes in background - don't await them
|
||||
ScreenOrientation.unlockAsync()
|
||||
.then(() => {
|
||||
logger.log('[VideoPlayer] Orientation unlocked');
|
||||
// On iOS tablets, keep rotation unlocked; on phones, return to portrait
|
||||
if (Platform.OS === 'ios') {
|
||||
const { width: dw, height: dh } = Dimensions.get('window');
|
||||
const isTablet = (Platform as any).isPad === true || Math.min(dw, dh) >= 768;
|
||||
setTimeout(() => {
|
||||
if (isTablet) {
|
||||
ScreenOrientation.unlockAsync().catch(() => { });
|
||||
} else {
|
||||
ScreenOrientation.lockAsync(ScreenOrientation.OrientationLock.PORTRAIT_UP).catch(() => { });
|
||||
}
|
||||
}, 50);
|
||||
}
|
||||
}, 50);
|
||||
}
|
||||
})
|
||||
.catch((orientationError: any) => {
|
||||
logger.warn('[VideoPlayer] Failed to unlock orientation:', orientationError);
|
||||
});
|
||||
|
||||
// Disable immersive mode
|
||||
// Disable immersive mode (synchronous)
|
||||
disableImmersiveMode();
|
||||
|
||||
// Navigate back to previous screen (StreamsScreen expected to be below Player)
|
||||
// Navigate back IMMEDIATELY - don't wait for orientation
|
||||
try {
|
||||
if (navigation.canGoBack()) {
|
||||
navigation.goBack();
|
||||
|
|
|
|||
|
|
@ -135,7 +135,7 @@ export const DEFAULT_SETTINGS: AppSettings = {
|
|||
showPosterTitles: true,
|
||||
enableHomeHeroBackground: true,
|
||||
// Trailer settings
|
||||
showTrailers: true, // Enable trailers by default
|
||||
showTrailers: false, // Trailers disabled by default
|
||||
trailerMuted: true, // Default to muted for better user experience
|
||||
// AI
|
||||
aiChatEnabled: false,
|
||||
|
|
|
|||
|
|
@ -25,7 +25,6 @@ export function useTraktIntegration() {
|
|||
const [collectionShows, setCollectionShows] = useState<TraktCollectionItem[]>([]);
|
||||
const [continueWatching, setContinueWatching] = useState<TraktPlaybackItem[]>([]);
|
||||
const [ratedContent, setRatedContent] = useState<TraktRatingItem[]>([]);
|
||||
const [lastAuthCheck, setLastAuthCheck] = useState<number>(Date.now());
|
||||
|
||||
// State for real-time status tracking
|
||||
const [watchlistItems, setWatchlistItems] = useState<Set<string>>(new Set());
|
||||
|
|
@ -50,8 +49,7 @@ export function useTraktIntegration() {
|
|||
setUserProfile(null);
|
||||
}
|
||||
|
||||
// Update the last auth check timestamp to trigger dependent components to update
|
||||
setLastAuthCheck(Date.now());
|
||||
|
||||
} catch (error) {
|
||||
logger.error('[useTraktIntegration] Error checking auth status:', error);
|
||||
} finally {
|
||||
|
|
@ -534,10 +532,10 @@ export function useTraktIntegration() {
|
|||
|
||||
updatePromises.push(
|
||||
storageService.mergeWithTraktProgress(
|
||||
id,
|
||||
type,
|
||||
item.progress,
|
||||
item.paused_at,
|
||||
id,
|
||||
type,
|
||||
item.progress,
|
||||
item.paused_at,
|
||||
episodeId,
|
||||
exactTime
|
||||
)
|
||||
|
|
@ -556,10 +554,10 @@ export function useTraktIntegration() {
|
|||
|
||||
updatePromises.push(
|
||||
storageService.mergeWithTraktProgress(
|
||||
id,
|
||||
'movie',
|
||||
100, // 100% progress for watched items
|
||||
watchedAt
|
||||
id,
|
||||
'movie',
|
||||
100, // 100% progress for watched items
|
||||
watchedAt
|
||||
)
|
||||
);
|
||||
}
|
||||
|
|
@ -620,12 +618,9 @@ export function useTraktIntegration() {
|
|||
};
|
||||
}, [isAuthenticated, fetchAndMergeTraktProgress]);
|
||||
|
||||
// Trigger sync when auth status is manually refreshed (for login scenarios)
|
||||
useEffect(() => {
|
||||
if (isAuthenticated) {
|
||||
fetchAndMergeTraktProgress();
|
||||
}
|
||||
}, [lastAuthCheck, isAuthenticated, fetchAndMergeTraktProgress]);
|
||||
// Note: Auth check sync removed - fetchAndMergeTraktProgress is already called
|
||||
// by the isAuthenticated useEffect (lines 595-602) and app focus sync (lines 605-621)
|
||||
// Having another useEffect on lastAuthCheck caused infinite update depth errors
|
||||
|
||||
// Manual force sync function for testing/troubleshooting
|
||||
const forceSyncTraktProgress = useCallback(async (): Promise<boolean> => {
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ export const useWatchProgress = (
|
|||
// Find episode in our local episodes array
|
||||
const episode = episodes.find(
|
||||
ep => ep.season_number === parseInt(seasonNum) &&
|
||||
ep.episode_number === parseInt(episodeNum)
|
||||
ep.episode_number === parseInt(episodeNum)
|
||||
);
|
||||
|
||||
if (episode) {
|
||||
|
|
@ -167,19 +167,33 @@ export const useWatchProgress = (
|
|||
return 'Resume';
|
||||
}, [watchProgress]);
|
||||
|
||||
// Subscribe to storage changes for real-time updates
|
||||
// Subscribe to storage changes for real-time updates (with debounce to prevent loops)
|
||||
useEffect(() => {
|
||||
let debounceTimeout: NodeJS.Timeout | null = null;
|
||||
|
||||
const unsubscribe = storageService.subscribeToWatchProgressUpdates(() => {
|
||||
loadWatchProgress();
|
||||
// Debounce rapid updates to prevent infinite loops
|
||||
if (debounceTimeout) {
|
||||
clearTimeout(debounceTimeout);
|
||||
}
|
||||
debounceTimeout = setTimeout(() => {
|
||||
loadWatchProgress();
|
||||
}, 100);
|
||||
});
|
||||
|
||||
return unsubscribe;
|
||||
return () => {
|
||||
if (debounceTimeout) {
|
||||
clearTimeout(debounceTimeout);
|
||||
}
|
||||
unsubscribe();
|
||||
};
|
||||
}, [loadWatchProgress]);
|
||||
|
||||
// Initial load
|
||||
// Initial load - only once on mount
|
||||
useEffect(() => {
|
||||
loadWatchProgress();
|
||||
}, [loadWatchProgress]);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [id, type, episodeId]); // Only re-run when core IDs change, not when loadWatchProgress ref changes
|
||||
|
||||
// Refresh when screen comes into focus
|
||||
useFocusEffect(
|
||||
|
|
@ -188,15 +202,16 @@ export const useWatchProgress = (
|
|||
}, [loadWatchProgress])
|
||||
);
|
||||
|
||||
// Re-load when Trakt authentication status changes
|
||||
// Re-load when Trakt authentication status changes (with guard)
|
||||
useEffect(() => {
|
||||
if (isTraktAuthenticated !== undefined) {
|
||||
// Small delay to ensure Trakt context is fully initialized
|
||||
setTimeout(() => {
|
||||
loadWatchProgress();
|
||||
}, 100);
|
||||
}
|
||||
}, [isTraktAuthenticated, loadWatchProgress]);
|
||||
// Skip on initial mount, only run when isTraktAuthenticated actually changes
|
||||
const timeoutId = setTimeout(() => {
|
||||
loadWatchProgress();
|
||||
}, 200); // Slightly longer delay to avoid race conditions
|
||||
|
||||
return () => clearTimeout(timeoutId);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [isTraktAuthenticated]); // Intentionally exclude loadWatchProgress to prevent loops
|
||||
|
||||
return {
|
||||
watchProgress,
|
||||
|
|
|
|||
|
|
@ -1174,8 +1174,8 @@ const InnerNavigator = ({ initialRouteName }: { initialRouteName?: keyof RootSta
|
|||
component={MetadataScreen}
|
||||
options={{
|
||||
headerShown: false,
|
||||
animation: Platform.OS === 'android' ? 'none' : 'fade',
|
||||
animationDuration: Platform.OS === 'android' ? 0 : 300,
|
||||
animation: Platform.OS === 'android' ? 'fade' : 'fade',
|
||||
animationDuration: Platform.OS === 'android' ? 200 : 300,
|
||||
...(Platform.OS === 'ios' && {
|
||||
cardStyleInterpolator: customFadeInterpolator,
|
||||
animationTypeForReplace: 'push',
|
||||
|
|
@ -1192,8 +1192,8 @@ const InnerNavigator = ({ initialRouteName }: { initialRouteName?: keyof RootSta
|
|||
component={StreamsScreen as any}
|
||||
options={{
|
||||
headerShown: false,
|
||||
animation: Platform.OS === 'ios' ? 'slide_from_bottom' : 'none',
|
||||
animationDuration: Platform.OS === 'android' ? 0 : 300,
|
||||
animation: Platform.OS === 'ios' ? 'slide_from_bottom' : 'fade',
|
||||
animationDuration: Platform.OS === 'android' ? 200 : 300,
|
||||
gestureEnabled: true,
|
||||
gestureDirection: Platform.OS === 'ios' ? 'vertical' : 'horizontal',
|
||||
...(Platform.OS === 'ios' && { presentation: 'modal' }),
|
||||
|
|
@ -1542,8 +1542,8 @@ const InnerNavigator = ({ initialRouteName }: { initialRouteName?: keyof RootSta
|
|||
name="AIChat"
|
||||
component={AIChatScreen}
|
||||
options={{
|
||||
animation: Platform.OS === 'android' ? 'none' : 'slide_from_right',
|
||||
animationDuration: Platform.OS === 'android' ? 220 : 300,
|
||||
animation: Platform.OS === 'android' ? 'fade' : 'slide_from_right',
|
||||
animationDuration: Platform.OS === 'android' ? 200 : 300,
|
||||
presentation: Platform.OS === 'ios' ? 'fullScreenModal' : 'modal',
|
||||
gestureEnabled: true,
|
||||
gestureDirection: Platform.OS === 'ios' ? 'horizontal' : 'vertical',
|
||||
|
|
|
|||
Loading…
Reference in a new issue