import { Camera, ChevronFirst, ChevronLast, CircleDot, ImagePlay, Maximize2, Minimize2, Pause, Play, StepBack, StepForward, TvMinimalPlay, UnfoldHorizontal, Volume2, VolumeX, } from "lucide-react" import { useCallback, useMemo, useRef, useState } from "react" import { useTranslation } from "react-i18next" import { Button } from "@/components/ui/button" import { Slider } from "@/components/ui/slider" import { MediaFile } from "@/features/media/types/media" import { getFrameTime } from "@/features/media/utils/video" import { useMulticam } from "@/features/multicam" import { cn } from "@/lib/utils" import { useFullscreen } from "../hooks/use-fullscreen" import { usePlayer } from "../services/player-provider" import { PlayerAIControls } from "./player-ai-controls" import { PrerenderControls } from "./prerender-controls" import { VolumeSlider } from "./volume-slider" interface PlayerControlsProps { /** * Текущее время воспроизведения в секундах */ currentTime: number /** * Медиафайл для воспроизведения (опционально) */ file: MediaFile } export function PlayerControls({ currentTime, file }: PlayerControlsProps) { const { t } = useTranslation() const { isPlaying, play, pause, seek, volume, setVolume, isRecording, setIsRecording, setIsSeeking, isChangingCamera, isResizableMode, setIsResizableMode, videoSource, setVideoSource, selectedClipId, } = usePlayer() // Используем состояние для хранения текущего времени воспроизведения const [localDisplayTime, setLocalDisplayTime] = useState(0) const [parallelVideos] = useState([]) const [activeCameraIndex, setActiveCameraIndex] = useState(0) // Используем выбранный клип как базовый для мультикамеры const multicam = useMulticam(selectedClipId || undefined) // Используем хук для отслеживания полноэкранного режима const { isFullscreen, toggleFullscreen } = useFullscreen() // Создаем ref для хранения текущего значения громкости const volumeRef = useRef(volume) // Функция для переключения полноэкранного режима const handleFullscreen = useCallback(() => { // Находим контейнер медиаплеера const playerContainer = document.querySelector(".media-player-container") if (!playerContainer) { console.error("[handleFullscreen] Не найден контейнер медиаплеера") return } toggleFullscreen(playerContainer as HTMLElement) console.log(`[handleFullscreen] ${isFullscreen ? "Выход из" : "Вход в"} полноэкранный режим`) }, [isFullscreen]) // Нормализуем currentTime для отображения, если это Unix timestamp const calculatedDisplayTime = useMemo(() => { if (currentTime > 365 * 24 * 60 * 60) { // Если время больше года в секундах, это, вероятно, Unix timestamp // Используем локальное время для отображения return localDisplayTime } return currentTime }, [currentTime, localDisplayTime]) // Получаем frameTime с помощью функции getFrameTime const frameTime = getFrameTime(file) // Определяем isFirstFrame и isLastFrame на основе мемоизированных значений const isFirstFrame = useMemo(() => { return Math.abs(currentTime - (file.startTime ?? 0)) < frameTime }, [currentTime, file.startTime, frameTime]) const isLastFrame = useMemo(() => { return Math.abs(currentTime - (file.endTime ?? 0)) < frameTime }, [currentTime, file.endTime, frameTime]) const handleTimeChange = useCallback( (value: number[]) => { const newTime = value[0] setLocalDisplayTime(newTime) seek(newTime).catch(console.error) setIsSeeking(true) }, [setLocalDisplayTime, seek, setIsSeeking], ) const handleRecordToggle = useCallback(() => { setIsRecording(!isRecording) }, [isRecording, setIsRecording]) const handlePlayPause = useCallback(() => { if (isPlaying) { pause().catch(console.error) } else { play().catch(console.error) } }, [isPlaying, play, pause]) const handleSkipForward = useCallback(() => { const newTime = Math.min(currentTime + frameTime, file.endTime ?? file.duration ?? 0) setLocalDisplayTime(newTime) seek(newTime).catch(console.error) setIsSeeking(true) }, [currentTime, frameTime, file.endTime, file.duration, setLocalDisplayTime, seek, setIsSeeking]) const handleSkipBackward = useCallback(() => { const newTime = Math.max(currentTime - frameTime, file.startTime ?? 0) setLocalDisplayTime(newTime) seek(newTime).catch(console.error) setIsSeeking(true) }, [currentTime, frameTime, file.startTime, setLocalDisplayTime, seek, setIsSeeking]) const handleChevronFirst = useCallback(() => { const newTime = file.startTime ?? 0 setLocalDisplayTime(newTime) seek(newTime).catch(console.error) setIsSeeking(true) }, [file.startTime, setLocalDisplayTime, seek, setIsSeeking]) const handleChevronLast = useCallback(() => { const newTime = file.endTime ?? 0 setLocalDisplayTime(newTime) seek(newTime).catch(console.error) setIsSeeking(true) }, [file.endTime, setLocalDisplayTime, seek, setIsSeeking]) // Функция для переключения звука (вкл/выкл) const handleToggleMute = useCallback(() => { // Если звук выключен, устанавливаем последнее сохраненное значение или 100% if (volume === 0) { const newVolume = volumeRef.current > 0 ? volumeRef.current : 100 setVolume(newVolume) console.log("[handleToggleMute] Unmute, volume:", newVolume) } else { // Сохраняем текущее значение громкости перед выключением volumeRef.current = volume setVolume(0) console.log("[handleToggleMute] Mute, saved volume:", volumeRef.current) } }, [volume, setVolume]) // Функция для изменения громкости const handleVolumeChange = useCallback( (value: number[]) => { const newVolume = value[0] // Значение уже в диапазоне 0-100 setVolume(newVolume) }, [setVolume], ) // Функция, вызываемая при завершении изменения громкости const handleVolumeChangeEnd = useCallback(() => { console.log("[handleVolumeChangeEnd] Volume change completed:", volume) }, [volume]) // Функция для переключения источника видео const handleToggleSource = useCallback(() => { setVideoSource(videoSource === "timeline" ? "browser" : "timeline") }, [videoSource, setVideoSource]) // Функция для переключения камеры в мультикамерном режиме const handleSwitchCamera = useCallback(() => { // Если есть мультикамерная поддержка, используем хук if (multicam.hasMulticamSupport) { multicam.switchToNextAngle() return } // Иначе используем локальную логику (временно) if (!parallelVideos || parallelVideos.length <= 1) return // Переключаемся на следующую камеру по кругу const nextIndex = (activeCameraIndex + 1) % parallelVideos.length setActiveCameraIndex(nextIndex) // TODO: Здесь нужно будет вызвать функцию переключения на другой клип // через timeline API или player API console.log(`[handleSwitchCamera] Переключение на камеру ${nextIndex + 1}`) }, [activeCameraIndex, parallelVideos, multicam]) return (
{/* Прогресс-бар и время */}
{file.startTime ?? "00:00:00:00"} / {file.duration ?? "00:00:00:00"} {/* Скрытый элемент для обновления компонента при воспроизведении */} {currentTime > 365 * 24 * 60 * 60 && ( {localDisplayTime.toFixed(3)} - {calculatedDisplayTime.toFixed(3)} )}
{/* Левая часть: индикатор источника, кнопки для камер и шаблонов */}
{/* Индикатор источника видео - всегда отображается и работает как переключатель */} {/* Кнопка переключения режима resizable - показываем только если применен шаблон */} {/* Кнопка снимка экрана */}
{/* Центральная часть: кнопки управления воспроизведением */}
{/* Кнопка переключения между камерами - показываем только если есть параллельные видео */} {parallelVideos && parallelVideos.length > 1 && ( )}
{/* Правая часть: AI controls, кнопки управления звуком и полноэкранным режимом */}
{/* AI Controls */}
) }