"use client"; import { useEffect, useState } from "react"; import type Hls from "hls.js"; import type { CurrentBroadcastResponse } from "@/lib/types"; interface StatsPanelProps { videoRef: React.RefObject; hlsRef: React.RefObject; streamingProtocol?: "hls" | "direct_file"; broadcast?: CurrentBroadcastResponse | null; } interface Stats { protocol: string; resolution: string; bitrate: string; bandwidth: string; buffer: string; offset: string; slotEnds: string; } function fmtSecs(s: number) { const h = Math.floor(s / 3600); const m = Math.floor((s % 3600) / 60); const sec = Math.floor(s % 60); if (h > 0) return `${h}:${String(m).padStart(2, "0")}:${String(sec).padStart(2, "0")}`; return `${String(m).padStart(2, "0")}:${String(sec).padStart(2, "0")}`; } function fmtKbps(bps: number) { if (!bps) return "--"; return `${Math.round(bps / 1000).toLocaleString()} kbps`; } function getBufferAhead(video: HTMLVideoElement) { const ct = video.currentTime; for (let i = 0; i < video.buffered.length; i++) { if (video.buffered.start(i) <= ct && ct <= video.buffered.end(i)) { return video.buffered.end(i) - ct; } } return 0; } export function StatsPanel({ videoRef, hlsRef, streamingProtocol, broadcast }: StatsPanelProps) { const [stats, setStats] = useState({ protocol: "--", resolution: "--", bitrate: "--", bandwidth: "--", buffer: "--", offset: "--", slotEnds: "--", }); useEffect(() => { const update = () => { const video = videoRef.current; const hls = hlsRef.current; const protocol = streamingProtocol === "direct_file" ? "Direct file" : hls ? "HLS (hls.js)" : "HLS (native)"; let resolution = "--"; let bitrate = "--"; let bandwidth = "--"; if (hls) { const level = hls.currentLevel >= 0 ? hls.levels[hls.currentLevel] : null; if (level) { resolution = `${level.width}×${level.height}`; bitrate = fmtKbps(level.bitrate); } if (hls.bandwidthEstimate > 0) { bandwidth = fmtKbps(hls.bandwidthEstimate); } } const buffer = video ? `${getBufferAhead(video).toFixed(1)} s` : "--"; const offset = video ? fmtSecs(video.currentTime) : "--"; let slotEnds = "--"; if (broadcast?.slot.end_at) { const secsLeft = (new Date(broadcast.slot.end_at).getTime() - Date.now()) / 1000; if (secsLeft > 0) { slotEnds = `in ${fmtSecs(secsLeft)}`; } else { slotEnds = "ending…"; } } setStats({ protocol, resolution, bitrate, bandwidth, buffer, offset, slotEnds }); }; update(); const id = setInterval(update, 1000); return () => clearInterval(id); }, [videoRef, hlsRef, streamingProtocol, broadcast]); return (
Stats for nerds LIVE
Protocol {stats.protocol}
Resolution {stats.resolution}
Bitrate {stats.bitrate}
Bandwidth est. {stats.bandwidth}
Buffer {stats.buffer}
Offset {stats.offset}
Slot ends {stats.slotEnds}
); }