98 lines
2.9 KiB
TypeScript
98 lines
2.9 KiB
TypeScript
'use client';
|
|
|
|
import { useEffect, useRef, useState } from 'react';
|
|
import { Loader2, Pause, Play } from 'lucide-react';
|
|
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 ChatVideoNotePlayer({
|
|
src,
|
|
durationMs,
|
|
variant = 'theirs'
|
|
}: {
|
|
src: string;
|
|
durationMs?: number;
|
|
variant?: 'mine' | 'theirs';
|
|
}) {
|
|
const videoRef = useRef<HTMLVideoElement | null>(null);
|
|
const [playing, setPlaying] = useState(false);
|
|
const [loading, setLoading] = useState(true);
|
|
const [duration, setDuration] = useState(durationMs ? durationMs / 1000 : 0);
|
|
|
|
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();
|
|
}
|
|
}
|
|
|
|
return (
|
|
<button
|
|
type="button"
|
|
onClick={togglePlayback}
|
|
className={cn(
|
|
'group relative h-[240px] w-[240px] overflow-hidden rounded-full shadow-md transition hover:shadow-lg',
|
|
variant === 'mine' ? 'ring-2 ring-[#7ea06a]/40' : 'ring-2 ring-[#dce3ec]'
|
|
)}
|
|
aria-label={playing ? 'Пауза' : 'Воспроизвести кружок'}
|
|
>
|
|
<video
|
|
ref={videoRef}
|
|
src={src}
|
|
className="h-full w-full object-cover"
|
|
playsInline
|
|
preload="metadata"
|
|
/>
|
|
<span className="absolute inset-0 flex items-center justify-center bg-black/20 transition group-hover:bg-black/30">
|
|
{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" />
|
|
)}
|
|
</span>
|
|
<span className="absolute bottom-4 left-1/2 -translate-x-1/2 rounded-full bg-black/55 px-2 py-0.5 text-[11px] text-white">
|
|
{formatDuration(duration)}
|
|
</span>
|
|
</button>
|
|
);
|
|
}
|