@100mslive/roomkit-react 0.3.8 → 0.3.9-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,9 +1,11 @@
1
1
  import React, { useCallback, useEffect, useMemo, useState } from 'react';
2
2
  import { useMedia } from 'react-use';
3
3
  import {
4
+ selectIsLargeRoom,
4
5
  selectLocalPeerID,
5
6
  selectPeerNameByID,
6
7
  useCustomEvent,
8
+ useHMSActions,
7
9
  useHMSStore,
8
10
  useHMSVanillaStore,
9
11
  } from '@100mslive/react-sdk';
@@ -43,17 +45,23 @@ const getStartingPoints = isMobile => {
43
45
  export function FlyingEmoji() {
44
46
  const localPeerId = useHMSStore(selectLocalPeerID);
45
47
  const vanillaStore = useHMSVanillaStore();
48
+ const hmsActions = useHMSActions();
46
49
  const [emojis, setEmojis] = useState([]);
47
50
  const isMobile = useMedia(cssConfig.media.md);
51
+ const isLargeRoom = useHMSStore(selectIsLargeRoom);
48
52
 
49
53
  const startingPoints = useMemo(() => getStartingPoints(isMobile), [isMobile]);
50
54
 
51
55
  const showFlyingEmoji = useCallback(
52
- ({ emojiId, senderId }) => {
56
+ async ({ emojiId, senderId }) => {
53
57
  if (!emojiId || !senderId || document.hidden) {
54
58
  return;
55
59
  }
56
- const senderPeerName = vanillaStore.getState(selectPeerNameByID(senderId));
60
+ let senderPeerName = vanillaStore.getState(selectPeerNameByID(senderId));
61
+ if (!senderPeerName && isLargeRoom) {
62
+ const sender = await hmsActions.getPeer(senderId);
63
+ senderPeerName = sender?.name;
64
+ }
57
65
  const nameToShow = localPeerId === senderId ? 'You' : senderPeerName;
58
66
  const startingPoint = startingPoints[emojiCount % startingPoints.length];
59
67
  const id = emojiCount++;
@@ -71,7 +79,7 @@ export function FlyingEmoji() {
71
79
  ];
72
80
  });
73
81
  },
74
- [localPeerId, vanillaStore, startingPoints],
82
+ [vanillaStore, isLargeRoom, localPeerId, startingPoints, hmsActions],
75
83
  );
76
84
 
77
85
  useCustomEvent({
@@ -118,24 +126,28 @@ export function FlyingEmoji() {
118
126
  <Box>
119
127
  <em-emoji id={emoji.emojiId} size="48px" set="apple"></em-emoji>
120
128
  </Box>
121
- <Box
122
- css={{
123
- width: 'fit-content',
124
- padding: '$2 $4',
125
- background: '$surface_bright',
126
- borderRadius: '$1',
127
- }}
128
- >
129
- <Text
129
+ {emoji.senderName ? (
130
+ <Box
130
131
  css={{
131
- fontSize: '$space$6',
132
- lineHeight: '$xs',
133
- color: '$on_surface_high',
132
+ width: 'fit-content',
133
+ padding: '$2 $4',
134
+ background: '$surface_bright',
135
+ borderRadius: '$1',
134
136
  }}
135
137
  >
136
- {emoji.senderName}
137
- </Text>
138
- </Box>
138
+ <Text
139
+ css={{
140
+ fontSize: '$space$6',
141
+ lineHeight: '$xs',
142
+ color: '$on_surface_high',
143
+ }}
144
+ >
145
+ {emoji.senderName}
146
+ </Text>
147
+ </Box>
148
+ ) : (
149
+ ''
150
+ )}
139
151
  </Flex>
140
152
  );
141
153
  })}
@@ -13,6 +13,7 @@ import {
13
13
  import { Tooltip } from '../Tooltip';
14
14
  import { formatBytes } from './formatBytes';
15
15
  import { Stats } from './StyledStats';
16
+ import { useQoE } from './useQoE';
16
17
 
17
18
  export interface VideoTileStatsProps {
18
19
  videoTrackID?: HMSTrackID;
@@ -33,6 +34,7 @@ export function VideoTileStats({ videoTrackID, audioTrackID, peerID, isLocal = f
33
34
  const videoTrackStats = isLocal ? localVideoTrackStats?.[0] : remoteVideoTrackStats;
34
35
  const downlinkScore = useHMSStore(selectConnectionQualityByPeerID(peerID))?.downlinkQuality;
35
36
  const availableOutgoingBitrate = useHMSStatsStore(selectHMSStats.availablePublishBitrate);
37
+ const qoe = useQoE({ videoTrackID, audioTrackID, isLocal });
36
38
 
37
39
  // Viewer role - no stats to show
38
40
  if (!(audioTrackStats || videoTrackStats)) {
@@ -87,6 +89,7 @@ export function VideoTileStats({ videoTrackID, audioTrackID, peerID, isLocal = f
87
89
  </Fragment>
88
90
  ) : (
89
91
  <Fragment>
92
+ <StatsRow show={isNotNullish(qoe)} label="QoE" value={qoe} />
90
93
  <StatsRow
91
94
  show={isNotNullishAndNot0(videoTrackStats?.frameWidth)}
92
95
  label="Width"
@@ -0,0 +1,79 @@
1
+ import { useRef } from 'react';
2
+ import { usePrevious } from 'react-use';
3
+ import { HMSTrackID, selectHMSStats, useHMSStatsStore } from '@100mslive/react-sdk';
4
+
5
+ interface UseQoEProps {
6
+ videoTrackID?: HMSTrackID;
7
+ audioTrackID?: HMSTrackID;
8
+ isLocal?: boolean;
9
+ }
10
+
11
+ const EXPECTED_RESOLUTION = 1280 * 720;
12
+
13
+ const clip = (value: number, min_value: number, max_value: number) => {
14
+ return Math.max(Math.min(value, max_value), min_value);
15
+ };
16
+
17
+ /**
18
+ * calculate QoE using 5 params:
19
+ * p1:freeze_duration_norm
20
+ * p2:resolution_norm
21
+ * p3:fps_norm
22
+ * p4:delay_norm
23
+ * p5:audio_concealed_norm
24
+ * the formula is 5*(p1)^3 * (p2)^0.3 * (p3)^0.5 * (p4)^1 * (p5)*2
25
+ *
26
+ * https://github.com/100mslive/webrtc-benchmark/blob/daily/sssd.py#L112
27
+ */
28
+ export const useQoE = ({ videoTrackID, audioTrackID, isLocal = false }: UseQoEProps) => {
29
+ const audioTrackStats = useHMSStatsStore(selectHMSStats.trackStatsByID(audioTrackID));
30
+ const videoTrackStats = useHMSStatsStore(selectHMSStats.trackStatsByID(videoTrackID));
31
+ const prevVideoTrackStats = usePrevious(videoTrackStats);
32
+ const prevAudioTrackStats = usePrevious(audioTrackStats);
33
+
34
+ const prevJitterBufferDelayMs = useRef<number>(0);
35
+
36
+ if (isLocal || (videoTrackID && !videoTrackStats) || (audioTrackID && !audioTrackStats)) {
37
+ return;
38
+ }
39
+
40
+ const resolutionNorm =
41
+ ((videoTrackStats?.frameWidth || 0) * (videoTrackStats?.frameHeight || 0)) / EXPECTED_RESOLUTION;
42
+
43
+ const framesDecodedInLastSec =
44
+ videoTrackStats?.framesDecoded && prevVideoTrackStats?.framesDecoded
45
+ ? videoTrackStats.framesDecoded - prevVideoTrackStats.framesDecoded
46
+ : 0;
47
+ let freezeDurationNorm =
48
+ 1 - ((videoTrackStats?.totalFreezesDuration || 0) - (prevVideoTrackStats?.totalFreezesDuration || 0));
49
+ freezeDurationNorm = freezeDurationNorm < 0 ? 0.5 : freezeDurationNorm;
50
+ freezeDurationNorm = framesDecodedInLastSec === 0 ? 0 : freezeDurationNorm;
51
+
52
+ const fpsNorm = framesDecodedInLastSec / 30;
53
+
54
+ const prevJBDelay = prevVideoTrackStats?.jitterBufferDelay || 0;
55
+ const prevJBEmittedCount = prevVideoTrackStats?.jitterBufferEmittedCount || 0;
56
+ const currentJBDelay = (videoTrackStats?.jitterBufferDelay || 0) - prevJBDelay;
57
+ const currentJBEmittedCount = (videoTrackStats?.jitterBufferEmittedCount || 0) - prevJBEmittedCount;
58
+
59
+ const jitterBufferDelayMs =
60
+ currentJBEmittedCount > 0 ? (currentJBDelay * 1000) / currentJBEmittedCount : prevJitterBufferDelayMs.current;
61
+ prevJitterBufferDelayMs.current = jitterBufferDelayMs;
62
+ const delayNorm = 1 - Math.min(1, jitterBufferDelayMs / 2000);
63
+
64
+ const prevConcealedSamples =
65
+ (prevAudioTrackStats?.concealedSamples || 0) - (prevAudioTrackStats?.silentConcealedSamples || 0);
66
+ const currentConcealedSamples =
67
+ (audioTrackStats?.concealedSamples || 0) - (audioTrackStats?.silentConcealedSamples || 0) - prevConcealedSamples;
68
+
69
+ const audioConcealedNorm = 1 - currentConcealedSamples / 48000;
70
+
71
+ return (
72
+ 5 *
73
+ clip(freezeDurationNorm, 0, 1) ** 3 *
74
+ clip(resolutionNorm, 0, 1) ** 0.3 *
75
+ clip(fpsNorm, 0, 1) ** 0.2 *
76
+ clip(delayNorm, 0, 1) ** 0.5 *
77
+ clip(audioConcealedNorm, 0, 1) ** 2
78
+ ).toFixed(2);
79
+ };