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

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+ };