tahamajs/IE / Projects /front /src /components /messages /VideoNoteBubble.tsx
tahamajs's picture
download
raw
4.23 kB
// src/components/messages/VideoNoteBubble.tsx
import { useState, useRef } from "react";
import { Play, Pause, Download, Maximize2 } from "lucide-react";
interface VideoNoteBubbleProps {
url: string;
duration: number; // seconds
mode: "video" | "audio";
isOwn: boolean;
timestamp: string;
}
export function VideoNoteBubble({ url, duration, mode, isOwn, timestamp }: VideoNoteBubbleProps) {
const [playing, setPlaying] = useState(false);
const [progress, setProgress] = useState(0);
const mediaRef = useRef<HTMLVideoElement | HTMLAudioElement>(null);
const togglePlay = () => {
const el = mediaRef.current;
if (!el) return;
if (playing) {
el.pause();
setPlaying(false);
} else {
el.play();
setPlaying(true);
}
};
const handleTimeUpdate = () => {
if (mediaRef.current && mediaRef.current.duration) {
const pct = (mediaRef.current.currentTime / mediaRef.current.duration) * 100;
setProgress(pct);
}
};
const handleEnded = () => {
setPlaying(false);
setProgress(0);
if (mediaRef.current) {
mediaRef.current.currentTime = 0;
}
};
const formatDuration = (sec: number) => {
const m = Math.floor(sec / 60);
const s = Math.floor(sec % 60);
return `${m}:${s.toString().padStart(2, "0")}`;
};
const containerClasses = `relative group max-w-[280px] ${
isOwn ? "ml-auto" : "mr-auto"
}`;
return (
<div className={containerClasses}>
<div
className={`rounded-2xl overflow-hidden ${
isOwn
? "bg-primary text-white"
: "bg-gray-100 dark:bg-gray-800 text-gray-800 dark:text-gray-200"
}`}
>
{mode === "video" ? (
<video
ref={mediaRef as any}
src={url}
className="w-full rounded-t-2xl cursor-pointer"
onClick={togglePlay}
onTimeUpdate={handleTimeUpdate}
onPlay={() => setPlaying(true)}
onPause={() => setPlaying(false)}
onEnded={handleEnded}
playsInline
preload="metadata"
/>
) : (
<div className="p-3 flex items-center gap-3">
<button
onClick={togglePlay}
className="w-10 h-10 rounded-full bg-white/20 flex items-center justify-center"
>
{playing ? <Pause className="w-5 h-5" /> : <Play className="w-5 h-5" />}
</button>
<div className="flex-1 min-w-0">
<div className="h-1.5 bg-white/30 rounded-full overflow-hidden">
<div
className="h-full bg-white/80 rounded-full transition-all"
style={{ width: `${progress}%` }}
/>
</div>
<span className="text-xs mt-0.5">{formatDuration(duration)}</span>
</div>
</div>
)}
{/* Audio element hidden for audio notes */}
{mode === "audio" && (
<audio
ref={mediaRef as any}
src={url}
onTimeUpdate={handleTimeUpdate}
onPlay={() => setPlaying(true)}
onPause={() => setPlaying(false)}
onEnded={handleEnded}
preload="metadata"
className="hidden"
/>
)}
{/* Controls bar */}
<div className="px-3 py-1.5 flex items-center justify-between text-xs">
<span>
{new Date(timestamp).toLocaleTimeString([], {
hour: "2-digit",
minute: "2-digit",
})}
</span>
<a
href={url}
download
className="hover:underline flex items-center gap-1 opacity-70 hover:opacity-100"
>
<Download className="w-3 h-3" />
</a>
</div>
{/* Play/pause overlay for video */}
{mode === "video" && !playing && (
<button
onClick={togglePlay}
className="absolute inset-0 flex items-center justify-center bg-black/20 opacity-0 group-hover:opacity-100 transition"
>
<Play className="w-10 h-10 text-white" />
</button>
)}
</div>
</div>
);
}

Xet Storage Details

Size:
4.23 kB
·
Xet hash:
1beacfbd5c62a818828fcc60eca945355fd9b97357c9c640af91c4f8ab70e83b

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.