Skip to content
This repository was archived by the owner on Aug 8, 2024. It is now read-only.

Commit b654ef4

Browse files
authored
feat: sync webapp
2 parents 63822fe + 13c13d0 commit b654ef4

File tree

19 files changed

+299
-104
lines changed

19 files changed

+299
-104
lines changed

package.json

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,12 @@
99
"src"
1010
],
1111
"dependencies": {
12-
"@100mslive/hls-player": "0.1.3",
13-
"@100mslive/hms-noise-suppression": "0.9.3",
14-
"@100mslive/hms-virtual-background": "1.11.3",
15-
"@100mslive/react-icons": "0.8.3",
16-
"@100mslive/react-sdk": "0.8.3",
17-
"@100mslive/react-ui": "0.8.3",
12+
"@100mslive/hls-player": "0.1.5",
13+
"@100mslive/hms-noise-suppression": "0.9.5",
14+
"@100mslive/hms-virtual-background": "1.11.5",
15+
"@100mslive/react-icons": "0.8.5",
16+
"@100mslive/react-sdk": "0.8.5",
17+
"@100mslive/react-ui": "0.8.5",
1818
"@emoji-mart/data": "^1.0.6",
1919
"@emoji-mart/react": "^1.0.1",
2020
"@tldraw/tldraw": "^1.18.4",

src/common/PeersSorter.js

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import { selectDominantSpeaker } from "@100mslive/hms-video-store";
2+
3+
class PeersSorter {
4+
listeners = new Set();
5+
storeUnsubscribe;
6+
7+
constructor(store) {
8+
this.store = store;
9+
this.peers = new Map();
10+
this.lruPeers = new Set();
11+
this.speaker = undefined;
12+
}
13+
14+
setPeersAndTilesPerPage = ({ peers, tilesPerPage }) => {
15+
this.tilesPerPage = tilesPerPage;
16+
const peerIds = new Set(peers.map(peer => peer.id));
17+
// remove existing peers which are no longer provided
18+
this.peers.forEach((_, key) => {
19+
if (!peerIds.has(key)) {
20+
this.peers.delete(key);
21+
}
22+
});
23+
this.lruPeers = new Set(
24+
[...this.lruPeers].filter(peerId => peerIds.has(peerId))
25+
);
26+
peers.forEach(peer => {
27+
this.peers.set(peer.id, peer);
28+
if (this.lruPeers.size < tilesPerPage) {
29+
this.lruPeers.add(peer.id);
30+
}
31+
});
32+
if (!this.storeUnsubscribe) {
33+
this.storeUnsubscribe = this.store.subscribe(
34+
this.onDominantSpeakerChange,
35+
selectDominantSpeaker
36+
);
37+
}
38+
this.moveSpeakerToFront(this.speaker);
39+
};
40+
41+
onUpdate = cb => {
42+
this.listeners.add(cb);
43+
};
44+
45+
stop = () => {
46+
this.updateListeners();
47+
this.listeners.clear();
48+
this.storeUnsubscribe?.();
49+
};
50+
51+
moveSpeakerToFront = speaker => {
52+
if (!speaker) {
53+
this.updateListeners();
54+
return;
55+
}
56+
if (
57+
this.lruPeers.has(speaker.id) &&
58+
this.lruPeers.size <= this.tilesPerPage
59+
) {
60+
this.updateListeners();
61+
return;
62+
}
63+
// delete to insert at beginning
64+
this.lruPeers.delete(speaker.id);
65+
let lruPeerArray = Array.from(this.lruPeers);
66+
while (lruPeerArray.length >= this.tilesPerPage) {
67+
lruPeerArray.pop();
68+
}
69+
this.lruPeers = new Set([speaker.id, ...lruPeerArray]);
70+
this.updateListeners();
71+
};
72+
73+
onDominantSpeakerChange = speaker => {
74+
if (speaker && speaker.id !== this?.speaker?.id) {
75+
this.speaker = speaker;
76+
this.moveSpeakerToFront(speaker);
77+
}
78+
};
79+
80+
updateListeners = () => {
81+
const orderedPeers = [];
82+
this.lruPeers.forEach(key => {
83+
const peer = this.peers.get(key);
84+
if (peer) {
85+
orderedPeers.push(peer);
86+
}
87+
});
88+
this.peers.forEach(peer => {
89+
if (!this.lruPeers.has(peer.id) && peer) {
90+
orderedPeers.push(peer);
91+
}
92+
});
93+
this.listeners.forEach(listener => listener?.(orderedPeers));
94+
};
95+
}
96+
97+
export default PeersSorter;

src/common/constants.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,7 @@ export const UI_SETTINGS = {
142142
showStatsOnTiles: "showStatsOnTiles",
143143
enableAmbientMusic: "enableAmbientMusic",
144144
mirrorLocalVideo: "mirrorLocalVideo",
145+
activeSpeakerSorting: "activeSpeakerSorting",
145146
hideLocalVideo: "hideLocalVideo",
146147
};
147148

src/common/useSortedPeers.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { useEffect, useRef, useState } from "react";
2+
import { useHMSVanillaStore } from "@100mslive/react-sdk";
3+
import PeersSorter from "./PeersSorter";
4+
import { useActiveSpeakerSorting } from "../components/AppData/useUISettings";
5+
6+
function useSortedPeers({ peers, maxTileCount = 9 }) {
7+
const [sortedPeers, setSortedPeers] = useState([]);
8+
const store = useHMSVanillaStore();
9+
const activeSpeakerSorting = useActiveSpeakerSorting();
10+
const peerSortedRef = useRef(new PeersSorter(store));
11+
peerSortedRef.current.onUpdate(setSortedPeers);
12+
13+
useEffect(() => {
14+
const peersSorter = peerSortedRef.current;
15+
if (peers?.length > 0 && maxTileCount && activeSpeakerSorting) {
16+
peersSorter.setPeersAndTilesPerPage({
17+
peers,
18+
tilesPerPage: maxTileCount,
19+
});
20+
} else if (!activeSpeakerSorting) {
21+
peersSorter.stop();
22+
}
23+
}, [maxTileCount, peers, activeSpeakerSorting]);
24+
25+
return activeSpeakerSorting ? sortedPeers : peers;
26+
}
27+
28+
export default useSortedPeers;

src/components/AppData/AppData.jsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ const initialAppData = {
5656
[UI_SETTINGS.enableAmbientMusic]: false,
5757
[UI_SETTINGS.uiViewMode]: UI_MODE_GRID,
5858
[UI_SETTINGS.mirrorLocalVideo]: true,
59+
[UI_SETTINGS.activeSpeakerSorting]: process.env.REACT_APP_ENV === "qa",
5960
[UI_SETTINGS.hideLocalVideo]: false,
6061
},
6162
[APP_DATA.subscribedNotifications]: {

src/components/AppData/useUISettings.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,11 @@ export const useIsHeadless = () => {
5858
return isHeadless;
5959
};
6060

61+
export const useActiveSpeakerSorting = () => {
62+
const activeSpeakerSorting = useUISettings(UI_SETTINGS.activeSpeakerSorting);
63+
return activeSpeakerSorting;
64+
};
65+
6166
export const useHLSViewerRole = () => {
6267
return useHMSStore(selectAppData(APP_DATA.hlsViewerRole));
6368
};

src/components/Chat/ChatBody.jsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,7 @@ const ChatMessage = React.memo(
223223
r: messageType ? "$1" : undefined,
224224
px: messageType ? "$4" : "$2",
225225
py: messageType ? "$4" : 0,
226+
userSelect: "none",
226227
}}
227228
key={message.time}
228229
data-testid="chat_msg"
@@ -274,7 +275,9 @@ const ChatMessage = React.memo(
274275
mt: "$2",
275276
wordBreak: "break-word",
276277
whiteSpace: "pre-wrap",
278+
userSelect: "all",
277279
}}
280+
onClick={e => e.stopPropagation()}
278281
>
279282
<AnnotisedMessage message={message.message} />
280283
</Text>
Lines changed: 44 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
import { useEffect } from "react";
1+
import { useEffect, useState } from "react";
22
import { logMessage } from "zipyai";
33
import {
44
HMSNotificationTypes,
55
useHMSNotifications,
66
} from "@100mslive/react-sdk";
7+
import { Dialog, Flex, Loading, Text } from "@100mslive/react-ui";
78
import { ToastConfig } from "../Toast/ToastConfig";
89
import { ToastManager } from "../Toast/ToastManager";
910

@@ -12,22 +13,59 @@ const notificationTypes = [
1213
HMSNotificationTypes.RECONNECTING,
1314
];
1415
let notificationId = null;
16+
17+
const isQA = process.env.REACT_APP_ENV === "qa";
1518
export const ReconnectNotifications = () => {
1619
const notification = useHMSNotifications(notificationTypes);
20+
const [open, setOpen] = useState(false);
1721
useEffect(() => {
1822
if (notification?.type === HMSNotificationTypes.RECONNECTED) {
1923
logMessage("Reconnected");
2024
notificationId = ToastManager.replaceToast(
2125
notificationId,
2226
ToastConfig.RECONNECTED.single()
2327
);
28+
setOpen(false);
2429
} else if (notification?.type === HMSNotificationTypes.RECONNECTING) {
2530
logMessage("Reconnecting");
26-
notificationId = ToastManager.replaceToast(
27-
notificationId,
28-
ToastConfig.RECONNECTING.single(notification.data.message)
29-
);
31+
if (isQA) {
32+
ToastManager.removeToast(notificationId);
33+
setOpen(true);
34+
} else {
35+
notificationId = ToastManager.replaceToast(
36+
notificationId,
37+
ToastConfig.RECONNECTING.single(notification.data.message)
38+
);
39+
}
3040
}
3141
}, [notification]);
32-
return null;
42+
if (!open || !isQA) return null;
43+
return (
44+
<Dialog.Root open={open} modal={true}>
45+
<Dialog.Portal container={document.getElementById("conferencing")}>
46+
<Dialog.Overlay />
47+
<Dialog.Content
48+
css={{
49+
width: "fit-content",
50+
maxWidth: "80%",
51+
p: "$4 $8",
52+
position: "relative",
53+
top: "unset",
54+
bottom: "$9",
55+
transform: "translate(-50%, -100%)",
56+
animation: "none !important",
57+
}}
58+
>
59+
<Flex align="center">
60+
<div style={{ display: "inline", margin: "0.25rem" }}>
61+
<Loading size={16} />
62+
</div>
63+
<Text css={{ fontSize: "$space$8", color: "$textHighEmp" }}>
64+
You lost your network connection. Trying to reconnect.
65+
</Text>
66+
</Flex>
67+
</Dialog.Content>
68+
</Dialog.Portal>
69+
</Dialog.Root>
70+
);
3371
};

src/components/Settings/LayoutSettings.jsx

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,14 @@ export const LayoutSettings = () => {
2020
const isLocalVideoEnabled = useHMSStore(selectIsLocalVideoEnabled);
2121
const isLocalScreenShared = useHMSStore(selectIsLocalScreenShared);
2222
const [
23-
{ isAudioOnly, uiViewMode, maxTileCount, mirrorLocalVideo, hideLocalVideo },
23+
{
24+
isAudioOnly,
25+
uiViewMode,
26+
maxTileCount,
27+
mirrorLocalVideo,
28+
activeSpeakerSorting,
29+
hideLocalVideo,
30+
},
2431
setUISettings,
2532
] = useSetUiSettings();
2633
const toggleIsAudioOnly = useCallback(
@@ -49,6 +56,16 @@ export const LayoutSettings = () => {
4956
id="activeSpeakerMode"
5057
label="Active Speaker Mode"
5158
/>
59+
<SwitchWithLabel
60+
label="Active Speaker Sorting"
61+
id="activeSpeakerSortingMode"
62+
checked={activeSpeakerSorting}
63+
onChange={value => {
64+
setUISettings({
65+
[UI_SETTINGS.activeSpeakerSorting]: value,
66+
});
67+
}}
68+
/>
5269
<SwitchWithLabel
5370
label="Audio Only Mode"
5471
id="audioOnlyMode"

src/components/Streaming/RTMPStreaming.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ const StartRTMP = () => {
194194
value => `${value.rtmpURL}/${value.streamKey}`
195195
)
196196
: [];
197-
hmsActions.startRTMPOrRecording({
197+
await hmsActions.startRTMPOrRecording({
198198
rtmpURLs: urls,
199199
meetingURL: recordingUrl,
200200
resolution: getResolution(resolution),

0 commit comments

Comments
 (0)