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
#endif
// PERFORMANCE OPTIMIZATION: Native HDR processing
// Set destination dynamic range based on device capabilities to eliminate unnecessary color conversions
options.destinationDynamicRange = getOptimalDynamicRange()
// HDR handling: Let KSPlayer automatically detect content's native dynamic range
// Setting destinationDynamicRange to nil allows KSPlayer to use the content's actual HDR/SDR mode
// 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
// This approach uses standard audio engineering practices for multi-channel downmixing
@ -804,40 +806,6 @@ class KSPlayerView: UIView {
}
// 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

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
const unsubscribe = catalogService.subscribeToLibraryUpdates((items) => {
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();
}, [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
const [shouldShowTextFallback, setShouldShowTextFallback] = useState<boolean>(!metadata?.logo);
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
useEffect(() => {
// Reset text fallback and timers on logo updates
if (logoWaitTimerRef.current) {
try { clearTimeout(logoWaitTimerRef.current); } catch (_e) {}
logoWaitTimerRef.current = null;
}
// Check if metadata logo has actually changed from what we last processed
const currentMetadataLogo = metadata?.logo;
if (currentMetadataLogo !== lastSyncedLogoRef.current) {
lastSyncedLogoRef.current = currentMetadataLogo;
if (metadata?.logo && metadata.logo !== stableLogoUri) {
setStableLogoUri(metadata.logo);
onStableLogoUriChange?.(metadata.logo);
setLogoHasLoadedSuccessfully(false); // Reset for new logo
logoLoadOpacity.value = 0; // reset fade for new logo
setShouldShowTextFallback(false);
} else if (!metadata?.logo && stableLogoUri) {
// Clear logo if metadata no longer has one
setStableLogoUri(null);
onStableLogoUriChange?.(null);
setLogoHasLoadedSuccessfully(false);
// Start a short grace period before showing text fallback
setShouldShowTextFallback(false);
logoWaitTimerRef.current = setTimeout(() => {
setShouldShowTextFallback(true);
}, 600);
} else if (!metadata?.logo && !stableLogoUri) {
// No logo currently; wait briefly before showing text to avoid flash
setShouldShowTextFallback(false);
logoWaitTimerRef.current = setTimeout(() => {
setShouldShowTextFallback(true);
}, 600);
// Reset text fallback and timers on logo updates
if (logoWaitTimerRef.current) {
try { clearTimeout(logoWaitTimerRef.current); } catch (_e) {}
logoWaitTimerRef.current = null;
}
if (currentMetadataLogo) {
setStableLogoUri(currentMetadataLogo);
onStableLogoUriChange?.(currentMetadataLogo);
setLogoHasLoadedSuccessfully(false); // Reset for new logo
logoLoadOpacity.value = 0; // reset fade for new logo
setShouldShowTextFallback(false);
} else {
// Clear logo if metadata no longer has one
setStableLogoUri(null);
onStableLogoUriChange?.(null);
setLogoHasLoadedSuccessfully(false);
// Start a short grace period before showing text fallback
setShouldShowTextFallback(false);
logoWaitTimerRef.current = setTimeout(() => {
setShouldShowTextFallback(true);
}, 600);
}
}
return () => {
if (logoWaitTimerRef.current) {
try { clearTimeout(logoWaitTimerRef.current); } catch (_e) {}
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
const handleLogoLoad = useCallback(() => {

View file

@ -2168,7 +2168,8 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
useEffect(() => {
const unsubscribe = catalogService.subscribeToLibraryUpdates((libraryItems) => {
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();

View file

@ -928,8 +928,16 @@ class CatalogService {
public subscribeToLibraryUpdates(callback: (items: StreamingContent[]) => void): () => void {
this.librarySubscribers.push(callback);
// Initial callback with current items
this.getLibraryItems().then(items => callback(items));
// Defer initial callback to next tick to avoid synchronous state updates during render
// 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 () => {