'use client'; import { useCallback, useEffect, useRef, useState } from 'react'; import { Loader2, Video, X } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog'; import { cn } from '@/lib/utils'; export type ChatVideoCaptureMode = 'video' | 'note'; const MAX_DURATION_MS: Record = { note: 60_000, video: 5 * 60_000 }; function pickRecorderMimeType() { const candidates = ['video/webm;codecs=vp9,opus', 'video/webm;codecs=vp8,opus', 'video/webm', 'video/mp4']; return candidates.find((type) => MediaRecorder.isTypeSupported(type)) ?? ''; } function formatTimer(ms: number) { const total = Math.max(0, Math.floor(ms / 1000)); const mins = Math.floor(total / 60); const secs = total % 60; return `${mins}:${secs.toString().padStart(2, '0')}`; } interface ChatVideoCaptureDialogProps { open: boolean; mode: ChatVideoCaptureMode; onClose: () => void; onCaptured: (file: File, durationMs: number) => void; } export function ChatVideoCaptureDialog({ open, mode, onClose, onCaptured }: ChatVideoCaptureDialogProps) { const videoRef = useRef(null); const streamRef = useRef(null); const recorderRef = useRef(null); const chunksRef = useRef([]); const startedAtRef = useRef(null); const timerRef = useRef(null); const [initializing, setInitializing] = useState(false); const [recording, setRecording] = useState(false); const [elapsedMs, setElapsedMs] = useState(0); const [error, setError] = useState(null); const stopStream = useCallback(() => { streamRef.current?.getTracks().forEach((track) => track.stop()); streamRef.current = null; if (videoRef.current) { videoRef.current.srcObject = null; } }, []); const cleanup = useCallback(() => { if (timerRef.current !== null) { window.clearInterval(timerRef.current); timerRef.current = null; } if (recorderRef.current && recorderRef.current.state !== 'inactive') { recorderRef.current.stop(); } recorderRef.current = null; chunksRef.current = []; startedAtRef.current = null; setRecording(false); setElapsedMs(0); stopStream(); }, [stopStream]); useEffect(() => { if (!open) { cleanup(); setError(null); return; } let cancelled = false; setInitializing(true); setError(null); void (async () => { try { const stream = await navigator.mediaDevices.getUserMedia({ video: { facingMode: mode === 'note' ? 'user' : 'environment' }, audio: true }); if (cancelled) { stream.getTracks().forEach((track) => track.stop()); return; } streamRef.current = stream; if (videoRef.current) { videoRef.current.srcObject = stream; await videoRef.current.play(); } } catch { if (!cancelled) { setError('Не удалось получить доступ к камере и микрофону'); } } finally { if (!cancelled) setInitializing(false); } })(); return () => { cancelled = true; cleanup(); }; }, [cleanup, mode, open]); function stopRecording() { recorderRef.current?.stop(); recorderRef.current = null; if (timerRef.current !== null) { window.clearInterval(timerRef.current); timerRef.current = null; } setRecording(false); } function startRecording() { const stream = streamRef.current; if (!stream || recording) return; const mimeType = pickRecorderMimeType(); const recorder = mimeType ? new MediaRecorder(stream, { mimeType }) : new MediaRecorder(stream); chunksRef.current = []; recorder.ondataavailable = (event) => { if (event.data.size > 0) chunksRef.current.push(event.data); }; recorder.onstop = () => { const durationMs = startedAtRef.current ? Date.now() - startedAtRef.current : 0; startedAtRef.current = null; const blob = new Blob(chunksRef.current, { type: recorder.mimeType || 'video/webm' }); const extension = blob.type.includes('mp4') ? 'mp4' : 'webm'; const prefix = mode === 'note' ? 'video-note' : 'video'; const file = new File([blob], `${prefix}-${Date.now()}.${extension}`, { type: blob.type || 'video/webm' }); onCaptured(file, durationMs); onClose(); }; recorderRef.current = recorder; startedAtRef.current = Date.now(); setElapsedMs(0); setRecording(true); recorder.start(250); timerRef.current = window.setInterval(() => { const startedAt = startedAtRef.current; if (!startedAt) return; const nextElapsed = Date.now() - startedAt; setElapsedMs(nextElapsed); if (nextElapsed >= MAX_DURATION_MS[mode]) { stopRecording(); } }, 200); } const title = mode === 'note' ? 'Запись кружка' : 'Запись видео'; const hint = mode === 'note' ? 'Короткое круглое видеосообщение до 1 минуты' : 'Видеосообщение до 5 минут'; return ( !next && onClose()}>
{title}

{hint}

{error ? (

{error}

) : (
)}
); }