Files
IdP/apps/frontend/components/chat/chat-video-player.tsx
lendry f00f3d411d fix
2026-07-10 12:21:44 +03:00

143 lines
4.3 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client';
import { useEffect, useRef, useState } from 'react';
import { Loader2, Maximize2, Pause, Play } from 'lucide-react';
import { ChatVideoLightbox } from '@/components/chat/chat-video-lightbox';
import { cn } from '@/lib/utils';
function formatDuration(seconds: number) {
if (!Number.isFinite(seconds) || seconds <= 0) return '0:00';
const whole = Math.floor(seconds);
const mins = Math.floor(whole / 60);
const secs = whole % 60;
return `${mins}:${secs.toString().padStart(2, '0')}`;
}
export function ChatVideoPlayer({
src,
durationMs,
fileName,
variant = 'theirs',
enableFullscreen = true
}: {
src: string;
durationMs?: number;
fileName?: string;
variant?: 'mine' | 'theirs';
enableFullscreen?: boolean;
}) {
const videoRef = useRef<HTMLVideoElement | null>(null);
const [playing, setPlaying] = useState(false);
const [loading, setLoading] = useState(true);
const [duration, setDuration] = useState(durationMs ? durationMs / 1000 : 0);
const [fullscreenOpen, setFullscreenOpen] = useState(false);
useEffect(() => {
const video = videoRef.current;
if (!video) return;
const onLoaded = () => {
if (Number.isFinite(video.duration) && video.duration > 0) {
setDuration(video.duration);
}
setLoading(false);
};
const onPlay = () => setPlaying(true);
const onPause = () => setPlaying(false);
const onEnded = () => setPlaying(false);
video.addEventListener('loadedmetadata', onLoaded);
video.addEventListener('play', onPlay);
video.addEventListener('pause', onPause);
video.addEventListener('ended', onEnded);
return () => {
video.removeEventListener('loadedmetadata', onLoaded);
video.removeEventListener('play', onPlay);
video.removeEventListener('pause', onPause);
video.removeEventListener('ended', onEnded);
};
}, [src]);
function togglePlayback() {
const video = videoRef.current;
if (!video) return;
if (video.paused) {
void video.play();
} else {
video.pause();
}
}
function openFullscreen(event: React.MouseEvent<HTMLButtonElement>) {
event.stopPropagation();
videoRef.current?.pause();
setPlaying(false);
setFullscreenOpen(true);
}
return (
<>
<div
className={cn(
'group relative max-w-[320px] overflow-hidden rounded-xl',
variant === 'mine' ? 'bg-[#d9f7c8]/40' : 'bg-black/5'
)}
data-chat-interactive="true"
>
<video
ref={videoRef}
src={src}
className="max-h-72 w-full bg-black object-contain"
playsInline
preload="metadata"
onClick={togglePlayback}
/>
<button
type="button"
onClick={togglePlayback}
className="absolute inset-0 flex items-center justify-center bg-black/15 transition hover:bg-black/25"
aria-label={playing ? 'Пауза' : 'Воспроизвести'}
>
{loading ? (
<Loader2 className="h-10 w-10 animate-spin text-white" />
) : playing ? (
<Pause className="h-10 w-10 text-white drop-shadow" />
) : (
<Play className="h-10 w-10 text-white drop-shadow" />
)}
</button>
{enableFullscreen ? (
<button
type="button"
onClick={openFullscreen}
className="absolute right-2 top-2 flex h-8 w-8 items-center justify-center rounded-full bg-black/50 text-white opacity-0 transition group-hover:opacity-100 hover:bg-black/70"
aria-label="Открыть на весь экран"
title="На весь экран"
>
<Maximize2 className="h-4 w-4" />
</button>
) : null}
<div className="absolute bottom-2 left-2 rounded-full bg-black/55 px-2 py-0.5 text-[11px] text-white">
{formatDuration(duration)}
</div>
{fileName ? (
<div className="truncate px-2 py-1 text-xs text-[#667085]" title={fileName}>
{fileName}
</div>
) : null}
</div>
{enableFullscreen ? (
<ChatVideoLightbox
open={fullscreenOpen}
src={src}
fileName={fileName}
durationMs={durationMs}
onClose={() => setFullscreenOpen(false)}
/>
) : null}
</>
);
}