/** * HDR Video Player Component * * Улучшенный видеоплеер с поддержкой HDR контента: * - Автоматическое определение HDR формата * - GPU-ускоренное декодирование * - HDR tone mapping для SDR дисплеев * - Оптимизация под различные кодеки */ import { useCallback, useEffect, useRef, useState } from "react" import { toast } from "sonner" import { AspectRatio } from "@/components/ui/aspect-ratio" import { Button } from "@/components/ui/button" import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" import { Label } from "@/components/ui/label" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" import { Slider } from "@/components/ui/slider" import { Switch } from "@/components/ui/switch" import { TooltipProvider } from "@/components/ui/tooltip" import { useProjectSettings } from "@/features/project-settings" import { convertVideoSrc } from "@/lib/tauri-utils" import { getHDRSupportService, HDRMetadata, VideoCodecInfo } from "../services/hdr-support" import { usePlayer } from "../services/player-provider" import { PlayerAIOverlay } from "./player-ai-overlay" import { PlayerControls } from "./player-controls" interface HDRPlayerSettings { hdrEnabled: boolean toneMappingEnabled: boolean targetNits: number gammaCorrection: number saturation: number preferredCodec: string gpuAcceleration: boolean } export function HDRVideoPlayer() { const { settings: { aspectRatio }, } = useProjectSettings() const { currentVideo: video, currentTime } = usePlayer() const videoRef = useRef(null) const canvasRef = useRef(null) const [showHDRControls, setShowHDRControls] = useState(false) // HDR метаданные и codec информация const [hdrMetadata, setHdrMetadata] = useState(null) const [codecInfo, setCodecInfo] = useState(null) const [deviceCapabilities, setDeviceCapabilities] = useState(null) // HDR настройки const [hdrSettings, setHdrSettings] = useState({ hdrEnabled: true, toneMappingEnabled: true, targetNits: 100, // Стандартные SDR мониторы gammaCorrection: 2.2, saturation: 1.0, preferredCodec: "auto", gpuAcceleration: true, }) // Вычисляем соотношение сторон const aspectRatioValue = aspectRatio.value.width / aspectRatio.value.height /** * Инициализация HDR поддержки */ useEffect(() => { const initializeHDRSupport = async () => { try { // Определяем возможности устройства const capabilities = await getHDRSupportService().detectHDRCapabilities() setDeviceCapabilities(capabilities) if (capabilities.isHDRSupported) { setHdrSettings((prev) => ({ ...prev, targetNits: capabilities.maxDisplayMasteringLuminance, })) } } catch (error) { console.warn("HDR initialization failed:", error) } } void initializeHDRSupport() }, []) /** * Анализ видео при загрузке */ useEffect(() => { if (!videoRef.current || !video?.path) return const analyzeVideo = async () => { const videoElement = videoRef.current! try { // Ждем загрузки метаданных if (videoElement.readyState < 1) { await new Promise((resolve) => { videoElement.addEventListener("loadedmetadata", resolve, { once: true }) }) } // Получаем HDR метаданные const metadata = await getHDRSupportService().parseHDRMetadata(videoElement) setHdrMetadata(metadata) // Получаем информацию о кодеке const codec = await getHDRSupportService().getVideoCodecInfo(videoElement) setCodecInfo(codec) // Показываем HDR controls если контент HDR if (metadata.isHdr) { setShowHDRControls(true) toast.info(`HDR ${metadata.format} видео обнаружено`) } } catch (error) { console.error("Video analysis failed:", error) toast.error("Не удалось проанализировать видео") } } void analyzeVideo() }, [video?.path]) /** * Применение HDR tone mapping */ const applyHDRProcessing = useCallback(() => { if (!videoRef.current || !canvasRef.current || !hdrMetadata?.isHdr) return if (!hdrSettings.toneMappingEnabled || !hdrSettings.hdrEnabled) return const success = getHDRSupportService().applyHDRToneMapping(videoRef.current, canvasRef.current, { targetNits: hdrSettings.targetNits, gammaCorrection: hdrSettings.gammaCorrection, saturation: hdrSettings.saturation, }) if (!success) { console.warn("HDR tone mapping failed, falling back to standard rendering") } }, [hdrMetadata, hdrSettings]) /** * Обработка кадров для HDR */ useEffect(() => { if (!videoRef.current || !hdrMetadata?.isHdr) return const videoElement = videoRef.current let animationFrame: number const processFrame = () => { if (hdrSettings.hdrEnabled && hdrSettings.toneMappingEnabled) { applyHDRProcessing() } animationFrame = requestAnimationFrame(processFrame) } if (videoElement.readyState >= 2) { animationFrame = requestAnimationFrame(processFrame) } return () => { if (animationFrame) { cancelAnimationFrame(animationFrame) } } }, [hdrMetadata, hdrSettings, applyHDRProcessing]) /** * Обновление HDR настроек */ const updateHDRSettings = (key: keyof HDRPlayerSettings, value: any) => { setHdrSettings((prev) => ({ ...prev, [key]: value, })) } /** * Выбор оптимального кодека */ const getOptimalCodec = useCallback((): string => { if (!codecInfo || hdrSettings.preferredCodec !== "auto") { return hdrSettings.preferredCodec } // Для HDR контента предпочитаем HEVC if (hdrMetadata?.isHdr && codecInfo.supportedDecoders.includes("h265")) { return "h265" } // Для 4K предпочитаем более эффективные кодеки if (codecInfo.width >= 3840 && codecInfo.supportedDecoders.includes("av1")) { return "av1" } // Fallback на самый поддерживаемый return codecInfo.supportedDecoders[0] || "h264" }, [codecInfo, hdrMetadata, hdrSettings.preferredCodec]) if (!video?.path) { return (
Нет видео
) } return (
) } HDRVideoPlayer.displayName = "HDRVideoPlayer"