Skip to content

Commit 80f07a5

Browse files
committed
Add a 'waiting for video' state to media tiles
This will show if the call is waiting for media to connect (in practice doesn't actually seem to happen all that often) but also show if the media connection is lost, with the js-sdk change. Requires matrix-org/matrix-js-sdk#2880 Fixes: #669
1 parent 6ef41b9 commit 80f07a5

File tree

7 files changed

+98
-12
lines changed

7 files changed

+98
-12
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@
4545
"i18next": "^21.10.0",
4646
"i18next-browser-languagedetector": "^6.1.8",
4747
"i18next-http-backend": "^1.4.4",
48-
"matrix-js-sdk": "github:matrix-org/matrix-js-sdk#c6ee258789c9e01d328b5d9158b5b372e3a0da82",
48+
"matrix-js-sdk": "github:matrix-org/matrix-js-sdk#3f1c3392d45b0fc054c3788cc6c043cd5b4fb730",
4949
"matrix-widget-api": "^1.0.0",
5050
"mermaid": "^8.13.8",
5151
"normalize.css": "^8.0.1",

src/room/GroupCallView.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ export function GroupCallView({
7979
isScreensharing,
8080
screenshareFeeds,
8181
participants,
82+
calls,
8283
unencryptedEventsFromUsers,
8384
} = useGroupCall(groupCall);
8485

@@ -235,6 +236,7 @@ export function GroupCallView({
235236
roomName={groupCall.room.name}
236237
avatarUrl={avatarUrl}
237238
participants={participants}
239+
calls={calls}
238240
microphoneMuted={microphoneMuted}
239241
localVideoMuted={localVideoMuted}
240242
toggleLocalVideoMuted={toggleLocalVideoMuted}

src/room/InCallView.tsx

Lines changed: 71 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,13 @@ See the License for the specific language governing permissions and
1414
limitations under the License.
1515
*/
1616

17-
import React, { useEffect, useCallback, useMemo, useRef } from "react";
17+
import React, {
18+
useEffect,
19+
useCallback,
20+
useMemo,
21+
useRef,
22+
useState,
23+
} from "react";
1824
import { usePreventScroll } from "@react-aria/overlays";
1925
import useMeasure from "react-use-measure";
2026
import { ResizeObserver } from "@juggle/resize-observer";
@@ -25,6 +31,11 @@ import { CallFeed } from "matrix-js-sdk/src/webrtc/callFeed";
2531
import classNames from "classnames";
2632
import { useTranslation } from "react-i18next";
2733
import { JoinRule } from "matrix-js-sdk/src/@types/partials";
34+
import {
35+
CallEvent,
36+
CallState,
37+
MatrixCall,
38+
} from "matrix-js-sdk/src/webrtc/call";
2839

2940
import type { IWidgetApiRequest } from "matrix-widget-api";
3041
import styles from "./InCallView.module.css";
@@ -73,6 +84,7 @@ interface Props {
7384
client: MatrixClient;
7485
groupCall: GroupCall;
7586
participants: RoomMember[];
87+
calls: MatrixCall[];
7688
roomName: string;
7789
avatarUrl: string;
7890
microphoneMuted: boolean;
@@ -90,6 +102,12 @@ interface Props {
90102
hideHeader: boolean;
91103
}
92104

105+
export enum ConnectionState {
106+
ESTABLISHING_CALL = "establishing call", // call hasn't been established yet
107+
WAIT_MEDIA = "wait_media", // call is set up, waiting for ICE to connect
108+
CONNECTED = "connected", // media is flowing
109+
}
110+
93111
// Represents something that should get a tile on the layout,
94112
// ie. a user's video feed or a screen share feed.
95113
export interface TileDescriptor {
@@ -99,12 +117,14 @@ export interface TileDescriptor {
99117
presenter: boolean;
100118
callFeed?: CallFeed;
101119
isLocal?: boolean;
120+
connectionState: ConnectionState;
102121
}
103122

104123
export function InCallView({
105124
client,
106125
groupCall,
107126
participants,
127+
calls,
108128
roomName,
109129
avatarUrl,
110130
microphoneMuted,
@@ -154,6 +174,46 @@ export function InCallView({
154174

155175
const { hideScreensharing } = useUrlParams();
156176

177+
const makeConnectionStatesMap = useCallback(() => {
178+
const newConnStates = new Map<string, ConnectionState>();
179+
for (const participant of participants) {
180+
const userCall = groupCall.getCallByUserId(participant.userId);
181+
const feed = userMediaFeeds.find((f) => f.userId === participant.userId);
182+
let connectionState = ConnectionState.ESTABLISHING_CALL;
183+
if (feed && feed.isLocal()) {
184+
connectionState = ConnectionState.CONNECTED;
185+
} else if (userCall) {
186+
if (userCall.state === CallState.Connected) {
187+
connectionState = ConnectionState.CONNECTED;
188+
} else if (userCall.state === CallState.Connecting) {
189+
connectionState = ConnectionState.WAIT_MEDIA;
190+
}
191+
}
192+
newConnStates.set(participant.userId, connectionState);
193+
}
194+
return newConnStates;
195+
}, [groupCall, participants, userMediaFeeds]);
196+
197+
const [connStates, setConnStates] = useState(
198+
new Map<string, ConnectionState>()
199+
);
200+
201+
const updateConnectionStates = useCallback(() => {
202+
setConnStates(makeConnectionStatesMap());
203+
}, [setConnStates, makeConnectionStatesMap]);
204+
205+
useEffect(() => {
206+
for (const call of calls) {
207+
call.on(CallEvent.State, updateConnectionStates);
208+
}
209+
210+
return () => {
211+
for (const call of calls) {
212+
call.off(CallEvent.State, updateConnectionStates);
213+
}
214+
};
215+
}, [calls, updateConnectionStates]);
216+
157217
useEffect(() => {
158218
widget?.api.transport.send(
159219
layout === "freedom"
@@ -208,6 +268,7 @@ export function InCallView({
208268
focused: screenshareFeeds.length === 0 && p.userId === activeSpeaker,
209269
isLocal: p.userId === client.getUserId(),
210270
presenter: false,
271+
connectionState: connStates.get(p.userId),
211272
});
212273
}
213274

@@ -231,11 +292,19 @@ export function InCallView({
231292
focused: true,
232293
isLocal: screenshareFeed.isLocal(),
233294
presenter: false,
295+
connectionState: ConnectionState.CONNECTED, // by definition since the screen shares arrived on the same connection
234296
});
235297
}
236298

237299
return tileDescriptors;
238-
}, [client, participants, userMediaFeeds, activeSpeaker, screenshareFeeds]);
300+
}, [
301+
client,
302+
participants,
303+
userMediaFeeds,
304+
activeSpeaker,
305+
screenshareFeeds,
306+
connStates,
307+
]);
239308

240309
// The maximised participant: either the participant that the user has
241310
// manually put in fullscreen, or the focused (active) participant if the

src/video-grid/VideoGrid.stories.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import { RoomMember } from "matrix-js-sdk";
2121
import { VideoGrid, useVideoGridLayout } from "./VideoGrid";
2222
import { VideoTile } from "./VideoTile";
2323
import { Button } from "../button";
24-
import { TileDescriptor } from "../room/InCallView";
24+
import { ConnectionState, TileDescriptor } from "../room/InCallView";
2525

2626
export default {
2727
title: "VideoGrid",
@@ -41,6 +41,7 @@ export const ParticipantsTest = () => {
4141
member: new RoomMember("!fake:room.id", `@user${i}:fake.dummy`),
4242
focused: false,
4343
presenter: false,
44+
connectionState: ConnectionState.CONNECTED,
4445
})),
4546
[participantCount]
4647
);
@@ -79,7 +80,7 @@ export const ParticipantsTest = () => {
7980
key={item.id}
8081
name={`User ${item.id}`}
8182
disableSpeakingIndicator={items.length < 3}
82-
hasFeed={true}
83+
connectionState={ConnectionState.CONNECTED}
8384
{...rest}
8485
/>
8586
)}

src/video-grid/VideoTile.tsx

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,11 @@ import styles from "./VideoTile.module.css";
2323
import { ReactComponent as MicMutedIcon } from "../icons/MicMuted.svg";
2424
import { ReactComponent as VideoMutedIcon } from "../icons/VideoMuted.svg";
2525
import { AudioButton, FullscreenButton } from "../button/Button";
26+
import { ConnectionState } from "../room/InCallView";
2627

2728
interface Props {
2829
name: string;
29-
hasFeed: Boolean;
30+
connectionState: ConnectionState;
3031
speaking?: boolean;
3132
audioMuted?: boolean;
3233
videoMuted?: boolean;
@@ -48,7 +49,7 @@ export const VideoTile = forwardRef<HTMLDivElement, Props>(
4849
(
4950
{
5051
name,
51-
hasFeed,
52+
connectionState,
5253
speaking,
5354
audioMuted,
5455
videoMuted,
@@ -72,7 +73,7 @@ export const VideoTile = forwardRef<HTMLDivElement, Props>(
7273
const { t } = useTranslation();
7374

7475
const toolbarButtons: JSX.Element[] = [];
75-
if (hasFeed && !isLocal) {
76+
if (connectionState == ConnectionState.CONNECTED && !isLocal) {
7677
toolbarButtons.push(
7778
<AudioButton
7879
key="localVolume"
@@ -94,7 +95,20 @@ export const VideoTile = forwardRef<HTMLDivElement, Props>(
9495
}
9596
}
9697

97-
const caption = hasFeed ? name : t("{{name}} (Connecting...)", { name });
98+
let caption: string;
99+
switch (connectionState) {
100+
case ConnectionState.ESTABLISHING_CALL:
101+
caption = t("{{name}} (Connecting...)", { name });
102+
103+
break;
104+
case ConnectionState.WAIT_MEDIA:
105+
// not strictly true, but probably easier to understand than, "Waiting for media"
106+
caption = t("{{name}} (Waiting for video...)", { name });
107+
break;
108+
case ConnectionState.CONNECTED:
109+
caption = name;
110+
break;
111+
}
98112

99113
return (
100114
<animated.div

src/video-grid/VideoTileContainer.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ export function VideoTileContainer({
9898
videoMuted={videoMuted}
9999
screenshare={purpose === SDPStreamMetadataPurpose.Screenshare}
100100
name={rawDisplayName}
101-
hasFeed={Boolean(item.callFeed)}
101+
connectionState={item.connectionState}
102102
ref={tileRef}
103103
mediaRef={mediaRef}
104104
avatar={getAvatar && getAvatar(item.member, width, height)}

yarn.lock

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10196,9 +10196,9 @@ matrix-events-sdk@0.0.1:
1019610196
resolved "https://registry.yarnpkg.com/matrix-events-sdk/-/matrix-events-sdk-0.0.1.tgz#c8c38911e2cb29023b0bbac8d6f32e0de2c957dd"
1019710197
integrity sha512-1QEOsXO+bhyCroIe2/A5OwaxHvBm7EsSQ46DEDn8RBIfQwN5HWBpFvyWWR4QY0KHPPnnJdI99wgRiAl7Ad5qaA==
1019810198

10199-
"matrix-js-sdk@github:matrix-org/matrix-js-sdk#c6ee258789c9e01d328b5d9158b5b372e3a0da82":
10199+
"matrix-js-sdk@github:matrix-org/matrix-js-sdk#3f1c3392d45b0fc054c3788cc6c043cd5b4fb730":
1020010200
version "21.1.0"
10201-
resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/c6ee258789c9e01d328b5d9158b5b372e3a0da82"
10201+
resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/3f1c3392d45b0fc054c3788cc6c043cd5b4fb730"
1020210202
dependencies:
1020310203
"@babel/runtime" "^7.12.5"
1020410204
"@types/sdp-transform" "^2.4.5"

0 commit comments

Comments
 (0)