import { useInterval } from "usehooks-ts"; import { m } from "@localizations/messages.js"; import { useRTCStore, useUiStore } from "@hooks/stores"; import { createChartArray, Metric } from "@components/Metric"; import { SettingsSectionHeader } from "@components/SettingsSectionHeader"; import SidebarHeader from "@components/SidebarHeader"; import { someIterable } from "@/utils"; export default function ConnectionStatsSidebar() { const { sidebarView, setSidebarView } = useUiStore(); const { mediaStream, peerConnection, inboundRtpStats: inboundVideoRtpStats, appendInboundRtpStats: appendInboundVideoRtpStats, candidatePairStats: iceCandidatePairStats, appendCandidatePairStats, appendLocalCandidateStats, appendRemoteCandidateStats, appendDiskDataChannelStats, } = useRTCStore(); useInterval(function collectWebRTCStats() { (async () => { if (!mediaStream) return; const videoTrack = mediaStream.getVideoTracks()[0]; if (!videoTrack) return; const stats = await peerConnection?.getStats(); let successfulLocalCandidateId: string | null = null; let successfulRemoteCandidateId: string | null = null; stats?.forEach(report => { if (report.type === "inbound-rtp" && report.kind === "video") { appendInboundVideoRtpStats(report); } else if (report.type === "candidate-pair" && report.nominated) { if (report.state === "succeeded") { successfulLocalCandidateId = report.localCandidateId; successfulRemoteCandidateId = report.remoteCandidateId; } appendCandidatePairStats(report); } else if (report.type === "local-candidate") { // We only want to append the local candidate stats that were used in nominated candidate pair if (successfulLocalCandidateId === report.id) { appendLocalCandidateStats(report); } } else if (report.type === "remote-candidate") { if (successfulRemoteCandidateId === report.id) { appendRemoteCandidateStats(report); } } else if (report.type === "data-channel" && report.label === "disk") { appendDiskDataChannelStats(report); } }); })(); }, 500); const jitterBufferDelay = createChartArray(inboundVideoRtpStats, "jitterBufferDelay"); const jitterBufferEmittedCount = createChartArray( inboundVideoRtpStats, "jitterBufferEmittedCount", ); const jitterBufferAvgDelayData = jitterBufferDelay.map((d, idx) => { if (idx === 0) return { date: d.date, metric: null }; const prevDelay = jitterBufferDelay[idx - 1]?.metric as number | null | undefined; const currDelay = d.metric as number | null | undefined; const prevCountEmitted = (jitterBufferEmittedCount[idx - 1]?.metric as number | null | undefined) ?? null; const currCountEmitted = (jitterBufferEmittedCount[idx]?.metric as number | null | undefined) ?? null; if ( prevDelay == null || currDelay == null || prevCountEmitted == null || currCountEmitted == null ) { return { date: d.date, metric: null }; } const deltaDelay = currDelay - prevDelay; const deltaEmitted = currCountEmitted - prevCountEmitted; // Guard counter resets or no emitted frames if (deltaDelay < 0 || deltaEmitted <= 0) { return { date: d.date, metric: null }; } const valueMs = Math.round((deltaDelay / deltaEmitted) * 1000); return { date: d.date, metric: valueMs }; }); return (