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; }