@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.
- package/dist/{HLSView-QBQRHXK4.js → HLSView-VU7EAULT.js} +2 -2
- package/dist/Stats/useQoE.d.ts +19 -0
- package/dist/{chunk-GTJZFIWX.js → chunk-G5EI4B2P.js} +164 -117
- package/dist/chunk-G5EI4B2P.js.map +7 -0
- package/dist/index.cjs.js +2829 -2777
- package/dist/index.cjs.js.map +4 -4
- package/dist/index.js +1 -1
- package/dist/meta.cjs.json +82 -23
- package/dist/meta.esbuild.json +85 -26
- package/package.json +6 -6
- package/src/Prebuilt/plugins/FlyingEmoji.jsx +30 -18
- package/src/Stats/Stats.tsx +3 -0
- package/src/Stats/useQoE.ts +79 -0
- package/dist/chunk-GTJZFIWX.js.map +0 -7
- /package/dist/{HLSView-QBQRHXK4.js.map → HLSView-VU7EAULT.js.map} +0 -0
@@ -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
|
-
|
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,
|
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
|
-
|
122
|
-
|
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
|
-
|
132
|
-
|
133
|
-
|
132
|
+
width: 'fit-content',
|
133
|
+
padding: '$2 $4',
|
134
|
+
background: '$surface_bright',
|
135
|
+
borderRadius: '$1',
|
134
136
|
}}
|
135
137
|
>
|
136
|
-
|
137
|
-
|
138
|
-
|
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
|
})}
|
package/src/Stats/Stats.tsx
CHANGED
@@ -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
|
+
};
|