@@ -14,7 +14,13 @@ See the License for the specific language governing permissions and
1414limitations 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" ;
1824import { usePreventScroll } from "@react-aria/overlays" ;
1925import useMeasure from "react-use-measure" ;
2026import { ResizeObserver } from "@juggle/resize-observer" ;
@@ -25,6 +31,11 @@ import { CallFeed } from "matrix-js-sdk/src/webrtc/callFeed";
2531import classNames from "classnames" ;
2632import { useTranslation } from "react-i18next" ;
2733import { 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
2940import type { IWidgetApiRequest } from "matrix-widget-api" ;
3041import 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.
95113export interface TileDescriptor {
@@ -99,12 +117,14 @@ export interface TileDescriptor {
99117 presenter : boolean ;
100118 callFeed ?: CallFeed ;
101119 isLocal ?: boolean ;
120+ connectionState : ConnectionState ;
102121}
103122
104123export 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
0 commit comments