Files
element-call-Github/src/RTCConnectionStats.tsx
T
Robin f97dd2adab Show the whole transport in debug UI and logs
A transport may soon carry a LiveKit SFU `url` instead of, or as well as,
a `livekit_service_url`, so a service URL alone no longer identifies it.
Pass the whole transport to the tiles' debug stats and print it as JSON in
log lines instead of picking out the service URL.

Element Call's media view models carry the member's raw transport from the SDK's TransportMetadata to the tiles.
2026-10-14 10:23:29 +02:00

132 lines
4.0 KiB
TypeScript

/*
Copyright 2023, 2024 New Vector Ltd.
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE in the repository root for full details.
*/
import { useState, type FC } from "react";
import { Button, Text } from "@vector-im/compound-web";
import {
MicOnSolidIcon,
VideoCallSolidIcon,
} from "@vector-im/compound-design-tokens/assets/web/icons";
import classNames from "classnames";
import { Modal } from "./Modal";
import styles from "./RTCConnectionStats.module.css";
import mediaViewStyles from "../src/tile/MediaView.module.css";
import { type Transport } from "matrix-js-sdk/lib/matrixrtc";
interface Props {
audio?: RTCInboundRtpStreamStats | RTCOutboundRtpStreamStats;
video?: RTCInboundRtpStreamStats | RTCOutboundRtpStreamStats;
transport?: Transport;
rtcBackendIdentity?: string;
}
// This is only used in developer mode for debugging purposes, so we don't need full localization
export const RTCConnectionStats: FC<Props> = ({
audio,
video,
transport,
rtcBackendIdentity,
}) => {
const [showModal, setShowModal] = useState(false);
const [modalContents, setModalContents] = useState<
"video" | "audio" | "none"
>("none");
const showFullModal = (contents: "video" | "audio"): void => {
setShowModal(true);
setModalContents(contents);
};
const onDismissModal = (): void => {
setShowModal(false);
setModalContents("none");
};
return (
<div className={classNames(mediaViewStyles.nameTag, styles.statsPill)}>
<Modal
title="RTC Connection Stats"
open={showModal}
onDismiss={onDismissModal}
>
<div className={styles.modal}>
<pre>
{modalContents !== "none" &&
JSON.stringify(
modalContents === "video" ? video : audio,
null,
2,
)}
</pre>
</div>
</Modal>
<Text as="span" size="xs" title="rtcBackendIdentity">
rtcBackendIdentity:{rtcBackendIdentity}
</Text>
{transport && (
<div>
<Text as="span" size="xs" title="transport">
&nbsp;{JSON.stringify(transport)}
</Text>
</div>
)}
{audio && (
<div>
<Button
onClick={() => showFullModal("audio")}
size="md"
kind="tertiary"
Icon={MicOnSolidIcon}
>
{"jitter" in audio && typeof audio.jitter === "number" && (
<Text as="span" size="xs" title="jitter">
&nbsp;{(audio.jitter * 1000).toFixed(0)}ms
</Text>
)}
</Button>
</div>
)}
{video && (
<div>
<Button
onClick={() => showFullModal("video")}
size="md"
kind="tertiary"
Icon={VideoCallSolidIcon}
>
{!!video?.framesPerSecond && (
<Text as="span" size="xs" title="frame rate">
&nbsp;{video.framesPerSecond.toFixed(0)}fps
</Text>
)}
{"jitter" in video && typeof video.jitter === "number" && (
<Text as="span" size="xs" title="jitter">
&nbsp;{(video.jitter * 1000).toFixed(0)}ms
</Text>
)}
{"frameHeight" in video &&
typeof video.frameHeight === "number" &&
"frameWidth" in video &&
typeof video.frameWidth === "number" && (
<Text as="span" size="xs" title="frame size">
&nbsp;{video.frameWidth}x{video.frameHeight}
</Text>
)}
{"qualityLimitationReason" in video &&
typeof video.qualityLimitationReason === "string" &&
video.qualityLimitationReason !== "none" && (
<Text as="span" size="xs" title="quality limitation reason">
&nbsp;{video.qualityLimitationReason}
</Text>
)}
</Button>
</div>
)}
</div>
);
};