mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-01-11 20:10:25 +00:00
hdr issue fix ios
This commit is contained in:
parent
1a1fdb6fdf
commit
668099b542
5 changed files with 52 additions and 69 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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]);
|
||||
|
|
|
|||
|
|
@ -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(() => {
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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 () => {
|
||||
|
|
|
|||
Loading…
Reference in a new issue