hdr issue fix ios

This commit is contained in:
tapframe 2025-11-21 16:09:54 +05:30
parent 1a1fdb6fdf
commit 668099b542
5 changed files with 52 additions and 69 deletions

View file

@ -385,9 +385,11 @@ class KSPlayerView: UIView {
options.asynchronousDecompression = true options.asynchronousDecompression = true
#endif #endif
// PERFORMANCE OPTIMIZATION: Native HDR processing // HDR handling: Let KSPlayer automatically detect content's native dynamic range
// Set destination dynamic range based on device capabilities to eliminate unnecessary color conversions // Setting destinationDynamicRange to nil allows KSPlayer to use the content's actual HDR/SDR mode
options.destinationDynamicRange = getOptimalDynamicRange() // This prevents forcing HDR tone mapping on SDR content (which causes oversaturation)
// KSPlayer will automatically detect HDR10/Dolby Vision/HLG from the video format description
options.destinationDynamicRange = nil
// Configure audio for proper dialogue mixing using FFmpeg's pan filter // Configure audio for proper dialogue mixing using FFmpeg's pan filter
// This approach uses standard audio engineering practices for multi-channel downmixing // This approach uses standard audio engineering practices for multi-channel downmixing
@ -804,40 +806,6 @@ class KSPlayerView: UIView {
} }
// MARK: - Performance Optimization Helpers // MARK: - Performance Optimization Helpers
/// Detects device HDR capabilities and returns optimal dynamic range setting
/// This prevents unnecessary color space conversion overhead
private func getOptimalDynamicRange() -> DynamicRange? {
#if canImport(UIKit)
let availableHDRModes = AVPlayer.availableHDRModes
// If no HDR modes available, use SDR (nil will use content's native range)
if availableHDRModes == AVPlayer.HDRMode(rawValue: 0) {
return .sdr
}
// Prefer HDR10 if supported (most common HDR format)
if availableHDRModes.contains(.hdr10) {
return .hdr10
}
// Fallback to Dolby Vision if available
if availableHDRModes.contains(.dolbyVision) {
return .dolbyVision
}
// Fallback to HLG if available
if availableHDRModes.contains(.hlg) {
return .hlg
}
// Default to SDR if no HDR support
return .sdr
#else
// macOS: Check screen capabilities
return .sdr
#endif
}
} }
// MARK: - High Performance KSOptions Subclass // MARK: - High Performance KSOptions Subclass

View file

@ -89,7 +89,9 @@ const ContentItem = ({ item, onPress, shouldLoadImage: shouldLoadImageProp, defe
// Subscribe to library updates and update local state if this item's status changes // Subscribe to library updates and update local state if this item's status changes
const unsubscribe = catalogService.subscribeToLibraryUpdates((items) => { const unsubscribe = catalogService.subscribeToLibraryUpdates((items) => {
const found = items.find((libItem) => libItem.id === item.id && libItem.type === item.type); const found = items.find((libItem) => libItem.id === item.id && libItem.type === item.type);
setInLibrary(!!found); const newInLibrary = !!found;
// Only update state if the value actually changed to prevent unnecessary re-renders
setInLibrary(prev => prev !== newInLibrary ? newInLibrary : prev);
}); });
return () => unsubscribe(); return () => unsubscribe();
}, [item.id, item.type]); }, [item.id, item.type]);

View file

@ -1041,45 +1041,49 @@ const HeroSection: React.FC<HeroSectionProps> = memo(({
// Grace delay before showing text fallback to avoid flashing when logo arrives late // Grace delay before showing text fallback to avoid flashing when logo arrives late
const [shouldShowTextFallback, setShouldShowTextFallback] = useState<boolean>(!metadata?.logo); const [shouldShowTextFallback, setShouldShowTextFallback] = useState<boolean>(!metadata?.logo);
const logoWaitTimerRef = useRef<any>(null); const logoWaitTimerRef = useRef<any>(null);
// Ref to track the last synced logo to break circular dependency with error handling
const lastSyncedLogoRef = useRef<string | undefined>(metadata?.logo);
// Update stable logo URI when metadata logo changes // Update stable logo URI when metadata logo changes
useEffect(() => { useEffect(() => {
// Reset text fallback and timers on logo updates // Check if metadata logo has actually changed from what we last processed
if (logoWaitTimerRef.current) { const currentMetadataLogo = metadata?.logo;
try { clearTimeout(logoWaitTimerRef.current); } catch (_e) {}
logoWaitTimerRef.current = null; if (currentMetadataLogo !== lastSyncedLogoRef.current) {
} lastSyncedLogoRef.current = currentMetadataLogo;
if (metadata?.logo && metadata.logo !== stableLogoUri) { // Reset text fallback and timers on logo updates
setStableLogoUri(metadata.logo); if (logoWaitTimerRef.current) {
onStableLogoUriChange?.(metadata.logo); try { clearTimeout(logoWaitTimerRef.current); } catch (_e) {}
setLogoHasLoadedSuccessfully(false); // Reset for new logo logoWaitTimerRef.current = null;
logoLoadOpacity.value = 0; // reset fade for new logo }
setShouldShowTextFallback(false);
} else if (!metadata?.logo && stableLogoUri) { if (currentMetadataLogo) {
// Clear logo if metadata no longer has one setStableLogoUri(currentMetadataLogo);
setStableLogoUri(null); onStableLogoUriChange?.(currentMetadataLogo);
onStableLogoUriChange?.(null); setLogoHasLoadedSuccessfully(false); // Reset for new logo
setLogoHasLoadedSuccessfully(false); logoLoadOpacity.value = 0; // reset fade for new logo
// Start a short grace period before showing text fallback setShouldShowTextFallback(false);
setShouldShowTextFallback(false); } else {
logoWaitTimerRef.current = setTimeout(() => { // Clear logo if metadata no longer has one
setShouldShowTextFallback(true); setStableLogoUri(null);
}, 600); onStableLogoUriChange?.(null);
} else if (!metadata?.logo && !stableLogoUri) { setLogoHasLoadedSuccessfully(false);
// No logo currently; wait briefly before showing text to avoid flash // Start a short grace period before showing text fallback
setShouldShowTextFallback(false); setShouldShowTextFallback(false);
logoWaitTimerRef.current = setTimeout(() => { logoWaitTimerRef.current = setTimeout(() => {
setShouldShowTextFallback(true); setShouldShowTextFallback(true);
}, 600); }, 600);
}
} }
return () => { return () => {
if (logoWaitTimerRef.current) { if (logoWaitTimerRef.current) {
try { clearTimeout(logoWaitTimerRef.current); } catch (_e) {} try { clearTimeout(logoWaitTimerRef.current); } catch (_e) {}
logoWaitTimerRef.current = null; logoWaitTimerRef.current = null;
} }
}; };
}, [metadata?.logo, stableLogoUri]); }, [metadata?.logo]); // Removed stableLogoUri from dependencies to prevent circular updates on error
// Handle logo load success - once loaded successfully, keep it stable // Handle logo load success - once loaded successfully, keep it stable
const handleLogoLoad = useCallback(() => { const handleLogoLoad = useCallback(() => {

View file

@ -2168,7 +2168,8 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
useEffect(() => { useEffect(() => {
const unsubscribe = catalogService.subscribeToLibraryUpdates((libraryItems) => { const unsubscribe = catalogService.subscribeToLibraryUpdates((libraryItems) => {
const isInLib = libraryItems.some(item => item.id === id); const isInLib = libraryItems.some(item => item.id === id);
setInLibrary(isInLib); // Only update state if the value actually changed to prevent unnecessary re-renders
setInLibrary(prev => prev !== isInLib ? isInLib : prev);
}); });
return () => unsubscribe(); return () => unsubscribe();

View file

@ -928,8 +928,16 @@ class CatalogService {
public subscribeToLibraryUpdates(callback: (items: StreamingContent[]) => void): () => void { public subscribeToLibraryUpdates(callback: (items: StreamingContent[]) => void): () => void {
this.librarySubscribers.push(callback); this.librarySubscribers.push(callback);
// Initial callback with current items // Defer initial callback to next tick to avoid synchronous state updates during render
this.getLibraryItems().then(items => callback(items)); // This prevents infinite loops when the callback triggers setState in useEffect
Promise.resolve().then(() => {
this.getLibraryItems().then(items => {
// Only call if still subscribed (callback might have been unsubscribed)
if (this.librarySubscribers.includes(callback)) {
callback(items);
}
});
});
// Return unsubscribe function // Return unsubscribe function
return () => { return () => {