83 lines
3.3 KiB
TypeScript
83 lines
3.3 KiB
TypeScript
"use client";
|
|
|
|
import { useEffect, useRef } from "react";
|
|
import type { Track } from "livekit-client";
|
|
|
|
export function SfuVideoTile({
|
|
videoTrack,
|
|
audioTrack,
|
|
name,
|
|
isLocal = false,
|
|
isHost = false,
|
|
muted = false,
|
|
videoOff = false,
|
|
speaking = false,
|
|
compact = false,
|
|
}: {
|
|
videoTrack?: Track | null;
|
|
audioTrack?: Track | null;
|
|
name: string;
|
|
isLocal?: boolean;
|
|
isHost?: boolean;
|
|
muted?: boolean;
|
|
videoOff?: boolean;
|
|
speaking?: boolean;
|
|
compact?: boolean;
|
|
}) {
|
|
const videoRef = useRef<HTMLVideoElement>(null);
|
|
const audioRef = useRef<HTMLAudioElement>(null);
|
|
|
|
useEffect(() => {
|
|
const videoElement = videoRef.current;
|
|
if (!videoElement || !videoTrack || videoOff) return;
|
|
videoTrack.attach(videoElement);
|
|
return () => {
|
|
videoTrack.detach(videoElement);
|
|
};
|
|
}, [videoTrack, videoOff]);
|
|
|
|
useEffect(() => {
|
|
const audioElement = audioRef.current;
|
|
if (!audioElement || !audioTrack || isLocal) return;
|
|
audioTrack.attach(audioElement);
|
|
return () => {
|
|
audioTrack.detach(audioElement);
|
|
};
|
|
}, [audioTrack, isLocal]);
|
|
|
|
return (
|
|
<div className={`relative aspect-video overflow-hidden rounded-2xl bg-neutral-800 ${speaking ? "ring-2 ring-anouma-rose" : ""}`}>
|
|
{videoTrack && !videoOff ? (
|
|
<video ref={videoRef} autoPlay playsInline muted={isLocal} className="h-full w-full object-cover" />
|
|
) : (
|
|
<div className="flex h-full w-full items-center justify-center bg-neutral-800">
|
|
<div className={`${compact ? "h-10 w-10 text-lg" : "h-16 w-16 text-2xl"} flex items-center justify-center rounded-full bg-neutral-700 font-serif text-white`}>
|
|
{name.charAt(0).toUpperCase()}
|
|
</div>
|
|
</div>
|
|
)}
|
|
{!isLocal && <audio ref={audioRef} autoPlay />}
|
|
<div className="absolute inset-x-0 bottom-0 flex items-center justify-between gap-2 bg-gradient-to-t from-black/75 to-transparent px-3 py-2">
|
|
<span className="truncate text-sm font-medium text-white">{name}{isLocal && " (du)"}</span>
|
|
<div className="flex items-center gap-2 text-white/80">
|
|
{muted && (
|
|
<span aria-label="Mikrofon aus" title="Mikrofon aus" className="flex h-6 w-6 items-center justify-center rounded-full bg-red-500/90 text-white">
|
|
<svg aria-hidden="true" viewBox="0 0 24 24" className="h-3.5 w-3.5 fill-none stroke-current" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
|
<path d="M12 3a3 3 0 0 0-3 3v6a3 3 0 0 0 4.6 2.55M15 9.34V6a3 3 0 0 0-5.94-.6M5 11a7 7 0 0 0 10.54 6.02M12 18v3M8 21h8M4 4l16 16" />
|
|
</svg>
|
|
</span>
|
|
)}
|
|
{videoOff && (
|
|
<span aria-label="Kamera aus" title="Kamera aus" className="flex h-6 w-6 items-center justify-center rounded-full bg-black/50 text-white">
|
|
<svg aria-hidden="true" viewBox="0 0 24 24" className="h-3.5 w-3.5 fill-none stroke-current" strokeWidth="1.9" strokeLinecap="round" strokeLinejoin="round">
|
|
<path d="M4 7h11a2 2 0 0 1 2 2v6a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V9a2 2 0 0 1 2-2Z" /><path d="m17 10 5-3v10l-5-3M4 4l16 16" />
|
|
</svg>
|
|
</span>
|
|
)}
|
|
{isHost && <span className="rounded-full bg-anouma-mauve-dark/90 px-2 py-0.5 text-[10px] font-medium uppercase tracking-wide text-white">Host</span>}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|