@@ -26,7 +26,11 @@ import { Heading, Text } from "@vector-im/compound-web";
26
26
import { useTranslation } from "react-i18next" ;
27
27
28
28
import type { IWidgetApiRequest } from "matrix-widget-api" ;
29
- import { widget , ElementWidgetActions , type JoinCallData } from "../widget" ;
29
+ import {
30
+ ElementWidgetActions ,
31
+ type JoinCallData ,
32
+ type WidgetHelpers ,
33
+ } from "../widget" ;
30
34
import { FullScreenView } from "../FullScreenView" ;
31
35
import { LobbyView } from "./LobbyView" ;
32
36
import { type MatrixInfo } from "./VideoPreview" ;
@@ -51,6 +55,9 @@ import { InviteModal } from "./InviteModal";
51
55
import { useUrlParams } from "../UrlParams" ;
52
56
import { E2eeType } from "../e2ee/e2eeType" ;
53
57
import { Link } from "../button/Link" ;
58
+ import { useAudioContext } from "../useAudioContext" ;
59
+ import { callEventAudioSounds } from "./CallEventAudioRenderer" ;
60
+ import { useLatest } from "../useLatest" ;
54
61
55
62
declare global {
56
63
interface Window {
@@ -67,6 +74,7 @@ interface Props {
67
74
hideHeader : boolean ;
68
75
rtcSession : MatrixRTCSession ;
69
76
muteStates : MuteStates ;
77
+ widget : WidgetHelpers | null ;
70
78
}
71
79
72
80
export const GroupCallView : FC < Props > = ( {
@@ -78,10 +86,16 @@ export const GroupCallView: FC<Props> = ({
78
86
hideHeader,
79
87
rtcSession,
80
88
muteStates,
89
+ widget,
81
90
} ) => {
82
91
const memberships = useMatrixRTCSessionMemberships ( rtcSession ) ;
83
92
const isJoined = useMatrixRTCSessionJoinState ( rtcSession ) ;
84
-
93
+ const leaveSoundContext = useLatest (
94
+ useAudioContext ( {
95
+ sounds : callEventAudioSounds ,
96
+ latencyHint : "interactive" ,
97
+ } ) ,
98
+ ) ;
85
99
// This should use `useEffectEvent` (only available in experimental versions)
86
100
useEffect ( ( ) => {
87
101
if ( memberships . length >= MUTE_PARTICIPANT_COUNT )
@@ -195,14 +209,14 @@ export const GroupCallView: FC<Props> = ({
195
209
ev . detail . data as unknown as JoinCallData ,
196
210
) ;
197
211
await enterRTCSession ( rtcSession , perParticipantE2EE ) ;
198
- widget ! . api . transport . reply ( ev . detail , { } ) ;
212
+ widget . api . transport . reply ( ev . detail , { } ) ;
199
213
} ) ( ) . catch ( ( e ) => {
200
214
logger . error ( "Error joining RTC session" , e ) ;
201
215
} ) ;
202
216
} ;
203
217
widget . lazyActions . on ( ElementWidgetActions . JoinCall , onJoin ) ;
204
218
return ( ) : void => {
205
- widget ! . lazyActions . off ( ElementWidgetActions . JoinCall , onJoin ) ;
219
+ widget . lazyActions . off ( ElementWidgetActions . JoinCall , onJoin ) ;
206
220
} ;
207
221
} else {
208
222
// No lobby and no preload: we enter the rtc session right away
@@ -216,29 +230,33 @@ export const GroupCallView: FC<Props> = ({
216
230
void enterRTCSession ( rtcSession , perParticipantE2EE ) ;
217
231
}
218
232
}
219
- } , [ rtcSession , preload , skipLobby , perParticipantE2EE ] ) ;
233
+ } , [ widget , rtcSession , preload , skipLobby , perParticipantE2EE ] ) ;
220
234
221
235
const [ left , setLeft ] = useState ( false ) ;
222
236
const [ leaveError , setLeaveError ] = useState < Error | undefined > ( undefined ) ;
223
237
const history = useHistory ( ) ;
224
238
225
239
const onLeave = useCallback (
226
240
( leaveError ?: Error ) : void => {
227
- setLeaveError ( leaveError ) ;
228
- setLeft ( true ) ;
229
-
241
+ const audioPromise = leaveSoundContext . current ?. playSound ( "left" ) ;
230
242
// In embedded/widget mode the iFrame will be killed right after the call ended prohibiting the posthog event from getting sent,
231
243
// therefore we want the event to be sent instantly without getting queued/batched.
232
244
const sendInstantly = ! ! widget ;
245
+ setLeaveError ( leaveError ) ;
246
+ setLeft ( true ) ;
233
247
PosthogAnalytics . instance . eventCallEnded . track (
234
248
rtcSession . room . roomId ,
235
249
rtcSession . memberships . length ,
236
250
sendInstantly ,
237
251
rtcSession ,
238
252
) ;
239
253
240
- // Only sends matrix leave event. The Livekit session will disconnect once the ActiveCall-view unmounts.
241
- leaveRTCSession ( rtcSession )
254
+ leaveRTCSession (
255
+ rtcSession ,
256
+ // Wait for the sound in widget mode (it's not long)
257
+ sendInstantly && audioPromise ? audioPromise : undefined ,
258
+ )
259
+ // Only sends matrix leave event. The Livekit session will disconnect once the ActiveCall-view unmounts.
242
260
. then ( ( ) => {
243
261
if (
244
262
! isPasswordlessUser &&
@@ -252,29 +270,36 @@ export const GroupCallView: FC<Props> = ({
252
270
logger . error ( "Error leaving RTC session" , e ) ;
253
271
} ) ;
254
272
} ,
255
- [ rtcSession , isPasswordlessUser , confineToRoom , history ] ,
273
+ [
274
+ widget ,
275
+ rtcSession ,
276
+ isPasswordlessUser ,
277
+ confineToRoom ,
278
+ leaveSoundContext ,
279
+ history ,
280
+ ] ,
256
281
) ;
257
282
258
283
useEffect ( ( ) => {
259
284
if ( widget && isJoined ) {
260
285
// set widget to sticky once joined.
261
- widget ! . api . setAlwaysOnScreen ( true ) . catch ( ( e ) => {
286
+ widget . api . setAlwaysOnScreen ( true ) . catch ( ( e ) => {
262
287
logger . error ( "Error calling setAlwaysOnScreen(true)" , e ) ;
263
288
} ) ;
264
289
265
290
const onHangup = ( ev : CustomEvent < IWidgetApiRequest > ) : void => {
266
- widget ! . api . transport . reply ( ev . detail , { } ) ;
291
+ widget . api . transport . reply ( ev . detail , { } ) ;
267
292
// Only sends matrix leave event. The Livekit session will disconnect once the ActiveCall-view unmounts.
268
293
leaveRTCSession ( rtcSession ) . catch ( ( e ) => {
269
294
logger . error ( "Failed to leave RTC session" , e ) ;
270
295
} ) ;
271
296
} ;
272
297
widget . lazyActions . once ( ElementWidgetActions . HangupCall , onHangup ) ;
273
298
return ( ) : void => {
274
- widget ! . lazyActions . off ( ElementWidgetActions . HangupCall , onHangup ) ;
299
+ widget . lazyActions . off ( ElementWidgetActions . HangupCall , onHangup ) ;
275
300
} ;
276
301
}
277
- } , [ isJoined , rtcSession ] ) ;
302
+ } , [ widget , isJoined , rtcSession ] ) ;
278
303
279
304
const onReconnect = useCallback ( ( ) => {
280
305
setLeft ( false ) ;
@@ -367,14 +392,17 @@ export const GroupCallView: FC<Props> = ({
367
392
leaveError
368
393
) {
369
394
return (
370
- < CallEndedView
371
- endedCallId = { rtcSession . room . roomId }
372
- client = { client }
373
- isPasswordlessUser = { isPasswordlessUser }
374
- confineToRoom = { confineToRoom }
375
- leaveError = { leaveError }
376
- reconnect = { onReconnect }
377
- />
395
+ < >
396
+ < CallEndedView
397
+ endedCallId = { rtcSession . room . roomId }
398
+ client = { client }
399
+ isPasswordlessUser = { isPasswordlessUser }
400
+ confineToRoom = { confineToRoom }
401
+ leaveError = { leaveError }
402
+ reconnect = { onReconnect }
403
+ />
404
+ ;
405
+ </ >
378
406
) ;
379
407
} else {
380
408
// If the user is a regular user, we'll have sent them back to the homepage,
0 commit comments