@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.
- 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
|
+
};
|