import React, { Fragment } from 'react';
import {
HMSPeerID,
HMSTrackID,
HMSTrackStats,
RID,
selectConnectionQualityByPeerID,
selectHMSStats,
simulcastMapping,
useHMSStatsStore,
useHMSStore,
} from '@100mslive/react-sdk';
import { Tooltip } from '../Tooltip';
import { formatBytes } from './formatBytes';
import { Stats } from './StyledStats';
import { useQoE } from './useQoE';
export interface VideoTileStatsProps {
videoTrackID?: HMSTrackID;
audioTrackID?: HMSTrackID;
peerID?: HMSPeerID;
isLocal: boolean;
}
/**
* This component can be used to overlay webrtc stats over the Video Tile. For the local tracks it also includes
* remote inbound stats as sent by the SFU in receiver report.
*/
export function VideoTileStats({ videoTrackID, audioTrackID, peerID, isLocal = false }: VideoTileStatsProps) {
const audioSelector = isLocal ? selectHMSStats.localAudioTrackStatsByID : selectHMSStats.trackStatsByID;
const audioTrackStats = useHMSStatsStore(audioSelector(audioTrackID));
const localVideoTrackStats = useHMSStatsStore(selectHMSStats.localVideoTrackStatsByID(videoTrackID));
const remoteVideoTrackStats = useHMSStatsStore(selectHMSStats.trackStatsByID(videoTrackID));
const videoTrackStats = isLocal ? localVideoTrackStats?.[0] : remoteVideoTrackStats;
const downlinkScore = useHMSStore(selectConnectionQualityByPeerID(peerID))?.downlinkQuality;
const availableOutgoingBitrate = useHMSStatsStore(selectHMSStats.availablePublishBitrate);
const qoe = useQoE({ videoTrackID, audioTrackID, isLocal });
// Viewer role - no stats to show
if (!(audioTrackStats || videoTrackStats)) {
return null;
}
return (
{isLocal ? (
{localVideoTrackStats?.map(stat => {
if (!stat) {
return null;
}
const layer = stat.rid ? simulcastMapping[stat.rid as RID] : '';
return (
{layer && }
);
})}
) : (
)}
);
}
const PacketLostAndJitter = ({
audioTrackStats,
videoTrackStats,
}: {
audioTrackStats?: HMSTrackStats;
videoTrackStats?: HMSTrackStats;
}) => {
// for local peer, we'll use the remote inbound stats to get packet loss and jitter, to know whether the track is
// local we check if the stats type has outbound in it as it's being published from local. Both audio and video
// tracks are checked in case the user has permission to publish only one of them.
const isLocalPeer = audioTrackStats?.type.includes('outbound') || videoTrackStats?.type.includes('outbound');
const audioStats = isLocalPeer ? audioTrackStats?.remote : audioTrackStats;
const videoStats = isLocalPeer ? videoTrackStats?.remote : videoTrackStats;
return (
<>
>
);
};
const TrackPacketsLostRow = ({
stats,
label,
}: {
stats?: Pick;
label: string;
}) => {
const packetsLostRate = `${stats?.packetsLostRate ? stats.packetsLostRate.toFixed(2) : 0}/s`;
return (
);
};
const RawStatsRow = ({
label = '',
value = '',
tooltip = '',
show = true,
}: {
label: string;
value?: string | number;
show?: boolean;
tooltip?: string;
}) => {
const statsLabel = {label};
return (
<>
{show ? (
{tooltip ? (
{statsLabel}
) : (
statsLabel
)}
{value === '' ? : {value}}
) : null}
>
);
};
// memoize so only the rows which change rerender
const StatsRow = React.memo(RawStatsRow);
export function isNotNullishAndNot0(value: number | undefined | null) {
return isNotNullish(value) && value !== 0;
}
/**
* Check only for presence(not truthy) of a value.
* Use in places where 0, false need to be considered valid.
*/
export function isNotNullish(value: number | string | undefined | null) {
return value !== undefined && value !== null;
}