@@ -32,7 +32,6 @@ import type EventEmitter from "events";
32
32
import type { IApp } from "../stores/WidgetStore" ;
33
33
import SdkConfig , { DEFAULTS } from "../SdkConfig" ;
34
34
import SettingsStore from "../settings/SettingsStore" ;
35
- import MediaDeviceHandler , { MediaDeviceKindEnum } from "../MediaDeviceHandler" ;
36
35
import { timeout } from "../utils/promise" ;
37
36
import WidgetUtils from "../utils/WidgetUtils" ;
38
37
import { WidgetType } from "../widgets/WidgetType" ;
@@ -192,47 +191,17 @@ export abstract class Call extends TypedEventEmitter<CallEvent, CallEventHandler
192
191
*/
193
192
public abstract clean ( ) : Promise < void > ;
194
193
195
- /**
196
- * Contacts the widget to connect to the call or prompt the user to connect to the call.
197
- * @param {MediaDeviceInfo | null } audioInput The audio input to use, or
198
- * null to start muted.
199
- * @param {MediaDeviceInfo | null } audioInput The video input to use, or
200
- * null to start muted.
201
- */
202
- protected abstract performConnection (
203
- audioInput : MediaDeviceInfo | null ,
204
- videoInput : MediaDeviceInfo | null ,
205
- ) : Promise < void > ;
206
-
207
194
/**
208
195
* Contacts the widget to disconnect from the call.
209
196
*/
210
197
protected abstract performDisconnection ( ) : Promise < void > ;
211
198
212
199
/**
213
200
* Starts the communication between the widget and the call.
214
- * The call then waits for the necessary requirements to actually perform the connection
215
- * or connects right away depending on the call type. (Jitsi, Legacy, ElementCall...)
216
- * It uses the media devices set in MediaDeviceHandler.
217
- * The widget associated with the call must be active
218
- * for this to succeed.
201
+ * The widget associated with the call must be active for this to succeed.
219
202
* Only call this if the call state is: ConnectionState.Disconnected.
220
203
*/
221
204
public async start ( ) : Promise < void > {
222
- const { [ MediaDeviceKindEnum . AudioInput ] : audioInputs , [ MediaDeviceKindEnum . VideoInput ] : videoInputs } =
223
- ( await MediaDeviceHandler . getDevices ( ) ) ! ;
224
-
225
- let audioInput : MediaDeviceInfo | null = null ;
226
- if ( ! MediaDeviceHandler . startWithAudioMuted ) {
227
- const deviceId = MediaDeviceHandler . getAudioInput ( ) ;
228
- audioInput = audioInputs . find ( ( d ) => d . deviceId === deviceId ) ?? audioInputs [ 0 ] ?? null ;
229
- }
230
- let videoInput : MediaDeviceInfo | null = null ;
231
- if ( ! MediaDeviceHandler . startWithVideoMuted ) {
232
- const deviceId = MediaDeviceHandler . getVideoInput ( ) ;
233
- videoInput = videoInputs . find ( ( d ) => d . deviceId === deviceId ) ?? videoInputs [ 0 ] ?? null ;
234
- }
235
-
236
205
const messagingStore = WidgetMessagingStore . instance ;
237
206
this . messaging = messagingStore . getMessagingForUid ( this . widgetUid ) ?? null ;
238
207
if ( ! this . messaging ) {
@@ -253,13 +222,23 @@ export abstract class Call extends TypedEventEmitter<CallEvent, CallEventHandler
253
222
throw new Error ( `Failed to bind call widget in room ${ this . roomId } : ${ e } ` ) ;
254
223
}
255
224
}
256
- await this . performConnection ( audioInput , videoInput ) ;
225
+ }
257
226
227
+ protected setConnected ( ) : void {
258
228
this . room . on ( RoomEvent . MyMembership , this . onMyMembership ) ;
259
229
window . addEventListener ( "beforeunload" , this . beforeUnload ) ;
260
230
this . connectionState = ConnectionState . Connected ;
261
231
}
262
232
233
+ /**
234
+ * Manually marks the call as disconnected.
235
+ */
236
+ protected setDisconnected ( ) : void {
237
+ this . room . off ( RoomEvent . MyMembership , this . onMyMembership ) ;
238
+ window . removeEventListener ( "beforeunload" , this . beforeUnload ) ;
239
+ this . connectionState = ConnectionState . Disconnected ;
240
+ }
241
+
263
242
/**
264
243
* Disconnects the user from the call.
265
244
*/
@@ -272,15 +251,6 @@ export abstract class Call extends TypedEventEmitter<CallEvent, CallEventHandler
272
251
this . close ( ) ;
273
252
}
274
253
275
- /**
276
- * Manually marks the call as disconnected.
277
- */
278
- public setDisconnected ( ) : void {
279
- this . room . off ( RoomEvent . MyMembership , this . onMyMembership ) ;
280
- window . removeEventListener ( "beforeunload" , this . beforeUnload ) ;
281
- this . connectionState = ConnectionState . Disconnected ;
282
- }
283
-
284
254
/**
285
255
* Stops further communication with the widget and tells the UI to close.
286
256
*/
@@ -466,66 +436,10 @@ export class JitsiCall extends Call {
466
436
} ) ;
467
437
}
468
438
469
- protected async performConnection (
470
- audioInput : MediaDeviceInfo | null ,
471
- videoInput : MediaDeviceInfo | null ,
472
- ) : Promise < void > {
473
- // Ensure that the messaging doesn't get stopped while we're waiting for responses
474
- const dontStopMessaging = new Promise < void > ( ( resolve , reject ) => {
475
- const messagingStore = WidgetMessagingStore . instance ;
476
-
477
- const listener = ( uid : string ) : void => {
478
- if ( uid === this . widgetUid ) {
479
- cleanup ( ) ;
480
- reject ( new Error ( "Messaging stopped" ) ) ;
481
- }
482
- } ;
483
- const done = ( ) : void => {
484
- cleanup ( ) ;
485
- resolve ( ) ;
486
- } ;
487
- const cleanup = ( ) : void => {
488
- messagingStore . off ( WidgetMessagingStoreEvent . StopMessaging , listener ) ;
489
- this . off ( CallEvent . ConnectionState , done ) ;
490
- } ;
491
-
492
- messagingStore . on ( WidgetMessagingStoreEvent . StopMessaging , listener ) ;
493
- this . on ( CallEvent . ConnectionState , done ) ;
494
- } ) ;
495
-
496
- // Empirically, it's possible for Jitsi Meet to crash instantly at startup,
497
- // sending a hangup event that races with the rest of this method, so we need
498
- // to add the hangup listener now rather than later
439
+ public async start ( ) : Promise < void > {
440
+ await super . start ( ) ;
441
+ this . messaging ! . on ( `action:${ ElementWidgetActions . JoinCall } ` , this . onJoin ) ;
499
442
this . messaging ! . on ( `action:${ ElementWidgetActions . HangupCall } ` , this . onHangup ) ;
500
-
501
- // Actually perform the join
502
- const response = waitForEvent (
503
- this . messaging ! ,
504
- `action:${ ElementWidgetActions . JoinCall } ` ,
505
- ( ev : CustomEvent < IWidgetApiRequest > ) => {
506
- ev . preventDefault ( ) ;
507
- this . messaging ! . transport . reply ( ev . detail , { } ) ; // ack
508
- return true ;
509
- } ,
510
- ) ;
511
- const request = this . messaging ! . transport . send ( ElementWidgetActions . JoinCall , {
512
- audioInput : audioInput ?. label ?? null ,
513
- videoInput : videoInput ?. label ?? null ,
514
- } ) ;
515
- try {
516
- await Promise . race ( [ Promise . all ( [ request , response ] ) , dontStopMessaging ] ) ;
517
- } catch ( e ) {
518
- // If it timed out, clean up our advance preparations
519
- this . messaging ! . off ( `action:${ ElementWidgetActions . HangupCall } ` , this . onHangup ) ;
520
-
521
- if ( this . messaging ! . transport . ready ) {
522
- // The messaging still exists, which means Jitsi might still be going in the background
523
- this . messaging ! . transport . send ( ElementWidgetActions . HangupCall , { force : true } ) ;
524
- }
525
-
526
- throw new Error ( `Failed to join call in room ${ this . roomId } : ${ e } ` ) ;
527
- }
528
-
529
443
ActiveWidgetStore . instance . on ( ActiveWidgetStoreEvent . Dock , this . onDock ) ;
530
444
ActiveWidgetStore . instance . on ( ActiveWidgetStoreEvent . Undock , this . onUndock ) ;
531
445
}
@@ -548,18 +462,17 @@ export class JitsiCall extends Call {
548
462
}
549
463
}
550
464
551
- public setDisconnected ( ) : void {
552
- // During tests this.messaging can be undefined
553
- this . messaging ? .off ( `action:${ ElementWidgetActions . HangupCall } ` , this . onHangup ) ;
465
+ public close ( ) : void {
466
+ this . messaging ! . off ( `action: ${ ElementWidgetActions . JoinCall } ` , this . onJoin ) ;
467
+ this . messaging ! . off ( `action:${ ElementWidgetActions . HangupCall } ` , this . onHangup ) ;
554
468
ActiveWidgetStore . instance . off ( ActiveWidgetStoreEvent . Dock , this . onDock ) ;
555
469
ActiveWidgetStore . instance . off ( ActiveWidgetStoreEvent . Undock , this . onUndock ) ;
556
-
557
- super . setDisconnected ( ) ;
470
+ super . close ( ) ;
558
471
}
559
472
560
473
public destroy ( ) : void {
561
474
this . room . off ( RoomStateEvent . Update , this . onRoomState ) ;
562
- this . on ( CallEvent . ConnectionState , this . onConnectionState ) ;
475
+ this . off ( CallEvent . ConnectionState , this . onConnectionState ) ;
563
476
if ( this . participantsExpirationTimer !== null ) {
564
477
clearTimeout ( this . participantsExpirationTimer ) ;
565
478
this . participantsExpirationTimer = null ;
@@ -611,27 +524,21 @@ export class JitsiCall extends Call {
611
524
await this . messaging ! . transport . send ( ElementWidgetActions . SpotlightLayout , { } ) ;
612
525
} ;
613
526
527
+ private readonly onJoin = ( ev : CustomEvent < IWidgetApiRequest > ) : void => {
528
+ ev . preventDefault ( ) ;
529
+ this . messaging ! . transport . reply ( ev . detail , { } ) ; // ack
530
+ this . setConnected ( ) ;
531
+ } ;
532
+
614
533
private readonly onHangup = async ( ev : CustomEvent < IWidgetApiRequest > ) : Promise < void > => {
615
534
// If we're already in the middle of a client-initiated disconnection,
616
535
// ignore the event
617
536
if ( this . connectionState === ConnectionState . Disconnecting ) return ;
618
537
619
538
ev . preventDefault ( ) ;
620
-
621
- // In case this hangup is caused by Jitsi Meet crashing at startup,
622
- // wait for the connection event in order to avoid racing
623
- if ( this . connectionState === ConnectionState . Disconnected ) {
624
- await waitForEvent ( this , CallEvent . ConnectionState ) ;
625
- }
626
-
627
539
this . messaging ! . transport . reply ( ev . detail , { } ) ; // ack
628
540
this . setDisconnected ( ) ;
629
- this . close ( ) ;
630
- // In video rooms we immediately want to restart the call after hangup
631
- // The lobby will be shown again and it connects to all signals from Jitsi.
632
- if ( isVideoRoom ( this . room ) ) {
633
- this . start ( ) ;
634
- }
541
+ if ( ! isVideoRoom ( this . room ) ) this . close ( ) ;
635
542
} ;
636
543
}
637
544
@@ -827,55 +734,38 @@ export class ElementCall extends Call {
827
734
ElementCall . createOrGetCallWidget ( room . roomId , room . client , skipLobby , isVideoRoom ( room ) ) ;
828
735
}
829
736
830
- protected async performConnection (
831
- audioInput : MediaDeviceInfo | null ,
832
- videoInput : MediaDeviceInfo | null ,
833
- ) : Promise < void > {
737
+ public async start ( ) : Promise < void > {
738
+ await super . start ( ) ;
739
+ this . messaging ! . on ( `action:${ ElementWidgetActions . JoinCall } ` , this . onJoin ) ;
834
740
this . messaging ! . on ( `action:${ ElementWidgetActions . HangupCall } ` , this . onHangup ) ;
835
- this . messaging ! . once ( `action:${ ElementWidgetActions . Close } ` , this . onClose ) ;
741
+ this . messaging ! . on ( `action:${ ElementWidgetActions . Close } ` , this . onClose ) ;
836
742
this . messaging ! . on ( `action:${ ElementWidgetActions . DeviceMute } ` , this . onDeviceMute ) ;
837
-
838
- // TODO: if the widget informs us when the join button is clicked (widget action), so we can
839
- // - set state to connecting
840
- // - send call notify
841
- const session = this . client . matrixRTC . getActiveRoomSession ( this . room ) ;
842
- if ( session ) {
843
- await waitForEvent (
844
- session ,
845
- MatrixRTCSessionEvent . MembershipsChanged ,
846
- ( _ , newMemberships : CallMembership [ ] ) =>
847
- newMemberships . some ( ( m ) => m . sender === this . client . getUserId ( ) ) ,
848
- false , // allow user to wait as long as they want (no timeout)
849
- ) ;
850
- } else {
851
- await waitForEvent (
852
- this . client . matrixRTC ,
853
- MatrixRTCSessionManagerEvents . SessionStarted ,
854
- ( roomId : string , session : MatrixRTCSession ) =>
855
- this . session . callId === session . callId && roomId === this . roomId ,
856
- false , // allow user to wait as long as they want (no timeout)
857
- ) ;
858
- }
859
743
}
860
744
861
745
protected async performDisconnection ( ) : Promise < void > {
746
+ const response = waitForEvent (
747
+ this . messaging ! ,
748
+ `action:${ ElementWidgetActions . HangupCall } ` ,
749
+ ( ev : CustomEvent < IWidgetApiRequest > ) => {
750
+ ev . preventDefault ( ) ;
751
+ this . messaging ! . transport . reply ( ev . detail , { } ) ; // ack
752
+ return true ;
753
+ } ,
754
+ ) ;
755
+ const request = this . messaging ! . transport . send ( ElementWidgetActions . HangupCall , { } ) ;
862
756
try {
863
- await this . messaging ! . transport . send ( ElementWidgetActions . HangupCall , { } ) ;
864
- await waitForEvent (
865
- this . session ,
866
- MatrixRTCSessionEvent . MembershipsChanged ,
867
- ( _ , newMemberships : CallMembership [ ] ) =>
868
- ! newMemberships . some ( ( m ) => m . sender === this . client . getUserId ( ) ) ,
869
- ) ;
757
+ await Promise . all ( [ request , response ] ) ;
870
758
} catch ( e ) {
871
759
throw new Error ( `Failed to hangup call in room ${ this . roomId } : ${ e } ` ) ;
872
760
}
873
761
}
874
762
875
- public setDisconnected ( ) : void {
763
+ public close ( ) : void {
764
+ this . messaging ! . off ( `action:${ ElementWidgetActions . JoinCall } ` , this . onJoin ) ;
876
765
this . messaging ! . off ( `action:${ ElementWidgetActions . HangupCall } ` , this . onHangup ) ;
766
+ this . messaging ! . off ( `action:${ ElementWidgetActions . Close } ` , this . onClose ) ;
877
767
this . messaging ! . off ( `action:${ ElementWidgetActions . DeviceMute } ` , this . onDeviceMute ) ;
878
- super . setDisconnected ( ) ;
768
+ super . close ( ) ;
879
769
}
880
770
881
771
public destroy ( ) : void {
@@ -922,15 +812,20 @@ export class ElementCall extends Call {
922
812
this . messaging ! . transport . reply ( ev . detail , { } ) ; // ack
923
813
} ;
924
814
815
+ private readonly onJoin = ( ev : CustomEvent < IWidgetApiRequest > ) : void => {
816
+ ev . preventDefault ( ) ;
817
+ this . messaging ! . transport . reply ( ev . detail , { } ) ; // ack
818
+ this . setConnected ( ) ;
819
+ } ;
820
+
925
821
private readonly onHangup = async ( ev : CustomEvent < IWidgetApiRequest > ) : Promise < void > => {
822
+ // If we're already in the middle of a client-initiated disconnection,
823
+ // ignore the event
824
+ if ( this . connectionState === ConnectionState . Disconnecting ) return ;
825
+
926
826
ev . preventDefault ( ) ;
927
827
this . messaging ! . transport . reply ( ev . detail , { } ) ; // ack
928
828
this . setDisconnected ( ) ;
929
- // In video rooms we immediately want to reconnect after hangup
930
- // This starts the lobby again and connects to all signals from EC.
931
- if ( isVideoRoom ( this . room ) ) {
932
- this . start ( ) ;
933
- }
934
829
} ;
935
830
936
831
private readonly onClose = async ( ev : CustomEvent < IWidgetApiRequest > ) : Promise < void > => {
0 commit comments