@@ -31,7 +31,6 @@ import {
3131import type EventEmitter from "events" ;
3232import type { IApp } from "../stores/WidgetStore" ;
3333import SettingsStore from "../settings/SettingsStore" ;
34- import MediaDeviceHandler , { MediaDeviceKindEnum } from "../MediaDeviceHandler" ;
3534import { timeout } from "../utils/promise" ;
3635import WidgetUtils from "../utils/WidgetUtils" ;
3736import { WidgetType } from "../widgets/WidgetType" ;
@@ -192,47 +191,17 @@ export abstract class Call extends TypedEventEmitter<CallEvent, CallEventHandler
192191 */
193192 public abstract clean ( ) : Promise < void > ;
194193
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-
207194 /**
208195 * Contacts the widget to disconnect from the call.
209196 */
210197 protected abstract performDisconnection ( ) : Promise < void > ;
211198
212199 /**
213200 * 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.
219202 * Only call this if the call state is: ConnectionState.Disconnected.
220203 */
221204 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-
236205 const messagingStore = WidgetMessagingStore . instance ;
237206 this . messaging = messagingStore . getMessagingForUid ( this . widgetUid ) ?? null ;
238207 if ( ! this . messaging ) {
@@ -253,13 +222,23 @@ export abstract class Call extends TypedEventEmitter<CallEvent, CallEventHandler
253222 throw new Error ( `Failed to bind call widget in room ${ this . roomId } : ${ e } ` ) ;
254223 }
255224 }
256- await this . performConnection ( audioInput , videoInput ) ;
225+ }
257226
227+ protected setConnected ( ) : void {
258228 this . room . on ( RoomEvent . MyMembership , this . onMyMembership ) ;
259229 window . addEventListener ( "beforeunload" , this . beforeUnload ) ;
260230 this . connectionState = ConnectionState . Connected ;
261231 }
262232
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+
263242 /**
264243 * Disconnects the user from the call.
265244 */
@@ -272,15 +251,6 @@ export abstract class Call extends TypedEventEmitter<CallEvent, CallEventHandler
272251 this . close ( ) ;
273252 }
274253
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-
284254 /**
285255 * Stops further communication with the widget and tells the UI to close.
286256 */
@@ -466,66 +436,10 @@ export class JitsiCall extends Call {
466436 } ) ;
467437 }
468438
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 ) ;
499442 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-
529443 ActiveWidgetStore . instance . on ( ActiveWidgetStoreEvent . Dock , this . onDock ) ;
530444 ActiveWidgetStore . instance . on ( ActiveWidgetStoreEvent . Undock , this . onUndock ) ;
531445 }
@@ -548,18 +462,17 @@ export class JitsiCall extends Call {
548462 }
549463 }
550464
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 ) ;
554468 ActiveWidgetStore . instance . off ( ActiveWidgetStoreEvent . Dock , this . onDock ) ;
555469 ActiveWidgetStore . instance . off ( ActiveWidgetStoreEvent . Undock , this . onUndock ) ;
556-
557- super . setDisconnected ( ) ;
470+ super . close ( ) ;
558471 }
559472
560473 public destroy ( ) : void {
561474 this . room . off ( RoomStateEvent . Update , this . onRoomState ) ;
562- this . on ( CallEvent . ConnectionState , this . onConnectionState ) ;
475+ this . off ( CallEvent . ConnectionState , this . onConnectionState ) ;
563476 if ( this . participantsExpirationTimer !== null ) {
564477 clearTimeout ( this . participantsExpirationTimer ) ;
565478 this . participantsExpirationTimer = null ;
@@ -611,27 +524,21 @@ export class JitsiCall extends Call {
611524 await this . messaging ! . transport . send ( ElementWidgetActions . SpotlightLayout , { } ) ;
612525 } ;
613526
527+ private readonly onJoin = ( ev : CustomEvent < IWidgetApiRequest > ) : void => {
528+ ev . preventDefault ( ) ;
529+ this . messaging ! . transport . reply ( ev . detail , { } ) ; // ack
530+ this . setConnected ( ) ;
531+ } ;
532+
614533 private readonly onHangup = async ( ev : CustomEvent < IWidgetApiRequest > ) : Promise < void > => {
615534 // If we're already in the middle of a client-initiated disconnection,
616535 // ignore the event
617536 if ( this . connectionState === ConnectionState . Disconnecting ) return ;
618537
619538 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-
627539 this . messaging ! . transport . reply ( ev . detail , { } ) ; // ack
628540 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 ( ) ;
635542 } ;
636543}
637544
@@ -855,55 +762,38 @@ export class ElementCall extends Call {
855762 ElementCall . createOrGetCallWidget ( room . roomId , room . client , skipLobby , isVideoRoom ( room ) ) ;
856763 }
857764
858- protected async performConnection (
859- audioInput : MediaDeviceInfo | null ,
860- videoInput : MediaDeviceInfo | null ,
861- ) : Promise < void > {
765+ public async start ( ) : Promise < void > {
766+ await super . start ( ) ;
767+ this . messaging ! . on ( `action:${ ElementWidgetActions . JoinCall } ` , this . onJoin ) ;
862768 this . messaging ! . on ( `action:${ ElementWidgetActions . HangupCall } ` , this . onHangup ) ;
863- this . messaging ! . once ( `action:${ ElementWidgetActions . Close } ` , this . onClose ) ;
769+ this . messaging ! . on ( `action:${ ElementWidgetActions . Close } ` , this . onClose ) ;
864770 this . messaging ! . on ( `action:${ ElementWidgetActions . DeviceMute } ` , this . onDeviceMute ) ;
865-
866- // TODO: if the widget informs us when the join button is clicked (widget action), so we can
867- // - set state to connecting
868- // - send call notify
869- const session = this . client . matrixRTC . getActiveRoomSession ( this . room ) ;
870- if ( session ) {
871- await waitForEvent (
872- session ,
873- MatrixRTCSessionEvent . MembershipsChanged ,
874- ( _ , newMemberships : CallMembership [ ] ) =>
875- newMemberships . some ( ( m ) => m . sender === this . client . getUserId ( ) ) ,
876- false , // allow user to wait as long as they want (no timeout)
877- ) ;
878- } else {
879- await waitForEvent (
880- this . client . matrixRTC ,
881- MatrixRTCSessionManagerEvents . SessionStarted ,
882- ( roomId : string , session : MatrixRTCSession ) =>
883- this . session . callId === session . callId && roomId === this . roomId ,
884- false , // allow user to wait as long as they want (no timeout)
885- ) ;
886- }
887771 }
888772
889773 protected async performDisconnection ( ) : Promise < void > {
774+ const response = waitForEvent (
775+ this . messaging ! ,
776+ `action:${ ElementWidgetActions . HangupCall } ` ,
777+ ( ev : CustomEvent < IWidgetApiRequest > ) => {
778+ ev . preventDefault ( ) ;
779+ this . messaging ! . transport . reply ( ev . detail , { } ) ; // ack
780+ return true ;
781+ } ,
782+ ) ;
783+ const request = this . messaging ! . transport . send ( ElementWidgetActions . HangupCall , { } ) ;
890784 try {
891- await this . messaging ! . transport . send ( ElementWidgetActions . HangupCall , { } ) ;
892- await waitForEvent (
893- this . session ,
894- MatrixRTCSessionEvent . MembershipsChanged ,
895- ( _ , newMemberships : CallMembership [ ] ) =>
896- ! newMemberships . some ( ( m ) => m . sender === this . client . getUserId ( ) ) ,
897- ) ;
785+ await Promise . all ( [ request , response ] ) ;
898786 } catch ( e ) {
899787 throw new Error ( `Failed to hangup call in room ${ this . roomId } : ${ e } ` ) ;
900788 }
901789 }
902790
903- public setDisconnected ( ) : void {
791+ public close ( ) : void {
792+ this . messaging ! . off ( `action:${ ElementWidgetActions . JoinCall } ` , this . onJoin ) ;
904793 this . messaging ! . off ( `action:${ ElementWidgetActions . HangupCall } ` , this . onHangup ) ;
794+ this . messaging ! . off ( `action:${ ElementWidgetActions . Close } ` , this . onClose ) ;
905795 this . messaging ! . off ( `action:${ ElementWidgetActions . DeviceMute } ` , this . onDeviceMute ) ;
906- super . setDisconnected ( ) ;
796+ super . close ( ) ;
907797 }
908798
909799 public destroy ( ) : void {
@@ -950,15 +840,20 @@ export class ElementCall extends Call {
950840 this . messaging ! . transport . reply ( ev . detail , { } ) ; // ack
951841 } ;
952842
843+ private readonly onJoin = ( ev : CustomEvent < IWidgetApiRequest > ) : void => {
844+ ev . preventDefault ( ) ;
845+ this . messaging ! . transport . reply ( ev . detail , { } ) ; // ack
846+ this . setConnected ( ) ;
847+ } ;
848+
953849 private readonly onHangup = async ( ev : CustomEvent < IWidgetApiRequest > ) : Promise < void > => {
850+ // If we're already in the middle of a client-initiated disconnection,
851+ // ignore the event
852+ if ( this . connectionState === ConnectionState . Disconnecting ) return ;
853+
954854 ev . preventDefault ( ) ;
955855 this . messaging ! . transport . reply ( ev . detail , { } ) ; // ack
956856 this . setDisconnected ( ) ;
957- // In video rooms we immediately want to reconnect after hangup
958- // This starts the lobby again and connects to all signals from EC.
959- if ( isVideoRoom ( this . room ) ) {
960- this . start ( ) ;
961- }
962857 } ;
963858
964859 private readonly onClose = async ( ev : CustomEvent < IWidgetApiRequest > ) : Promise < void > => {
0 commit comments