@@ -95,6 +95,10 @@ import { shallowEquals } from "../utils/array";
95
95
import { calculateDisplayName , shouldDisambiguate } from "../utils/displayname" ;
96
96
import { type MediaDevices } from "./MediaDevices" ;
97
97
98
+ interface CallViewModelOptions {
99
+ encryptionSystem : EncryptionSystem ;
100
+ autoLeaveWhenOthersLeft ?: boolean ;
101
+ }
98
102
// How long we wait after a focus switch before showing the real participant
99
103
// list again
100
104
const POST_FOCUS_PARTICIPANT_UPDATE_DELAY_MS = 3000 ;
@@ -461,21 +465,27 @@ export class CallViewModel extends ViewModel {
461
465
} ,
462
466
) ;
463
467
464
- /**
465
- * Displaynames for each member of the call. This will disambiguate
466
- * any displaynames that clashes with another member. Only members
467
- * joined to the call are considered here.
468
- */
469
- public readonly memberDisplaynames$ = merge (
468
+ private readonly memberships$ : Observable < CallMembership [ ] > = merge (
470
469
// Handle call membership changes.
471
470
fromEvent ( this . matrixRTCSession , MatrixRTCSessionEvent . MembershipsChanged ) ,
472
471
// Handle room membership changes (and displayname updates)
473
472
fromEvent ( this . matrixRTCSession . room , RoomStateEvent . Members ) ,
474
473
) . pipe (
475
- startWith ( null ) ,
474
+ startWith ( this . matrixRTCSession . memberships ) ,
476
475
map ( ( ) => {
476
+ return this . matrixRTCSession . memberships ;
477
+ } ) ,
478
+ ) ;
479
+
480
+ /**
481
+ * Displaynames for each member of the call. This will disambiguate
482
+ * any displaynames that clashes with another member. Only members
483
+ * joined to the call are considered here.
484
+ */
485
+ public readonly memberDisplaynames$ = this . memberships$ . pipe (
486
+ map ( ( memberships ) => {
477
487
const displaynameMap = new Map < string , string > ( ) ;
478
- const { room, memberships } = this . matrixRTCSession ;
488
+ const { room } = this . matrixRTCSession ;
479
489
480
490
// We only consider RTC members for disambiguation as they are the only visible members.
481
491
for ( const rtcMember of memberships ) {
@@ -577,7 +587,7 @@ export class CallViewModel extends ViewModel {
577
587
indexedMediaId ,
578
588
member ,
579
589
participant ,
580
- this . encryptionSystem ,
590
+ this . options . encryptionSystem ,
581
591
this . livekitRoom ,
582
592
this . memberDisplaynames$ . pipe (
583
593
map ( ( m ) => m . get ( matrixIdentifier ) ?? "[👻]" ) ,
@@ -600,7 +610,7 @@ export class CallViewModel extends ViewModel {
600
610
screenShareId ,
601
611
member ,
602
612
participant ,
603
- this . encryptionSystem ,
613
+ this . options . encryptionSystem ,
604
614
this . livekitRoom ,
605
615
this . memberDisplaynames$ . pipe (
606
616
map ( ( m ) => m . get ( matrixIdentifier ) ?? "[👻]" ) ,
@@ -641,7 +651,7 @@ export class CallViewModel extends ViewModel {
641
651
nonMemberId ,
642
652
undefined ,
643
653
participant ,
644
- this . encryptionSystem ,
654
+ this . options . encryptionSystem ,
645
655
this . livekitRoom ,
646
656
this . memberDisplaynames$ . pipe (
647
657
map ( ( m ) => m . get ( participant . identity ) ?? "[👻]" ) ,
@@ -686,18 +696,29 @@ export class CallViewModel extends ViewModel {
686
696
) ,
687
697
) ;
688
698
689
- public readonly memberChanges$ = this . userMedia$
690
- . pipe ( map ( ( mediaItems ) => mediaItems . map ( ( m ) => m . id ) ) )
691
- . pipe (
692
- scan < string [ ] , { ids : string [ ] ; joined : string [ ] ; left : string [ ] } > (
693
- ( prev , ids ) => {
694
- const left = prev . ids . filter ( ( id ) => ! ids . includes ( id ) ) ;
695
- const joined = ids . filter ( ( id ) => ! prev . ids . includes ( id ) ) ;
696
- return { ids, joined, left } ;
697
- } ,
698
- { ids : [ ] , joined : [ ] , left : [ ] } ,
699
- ) ,
700
- ) ;
699
+ public readonly memberChanges$ = this . userMedia$ . pipe (
700
+ map ( ( mediaItems ) => mediaItems . map ( ( m ) => m . id ) ) ,
701
+ scan < string [ ] , { ids : string [ ] ; joined : string [ ] ; left : string [ ] } > (
702
+ ( prev , ids ) => {
703
+ const left = prev . ids . filter ( ( id ) => ! ids . includes ( id ) ) ;
704
+ const joined = ids . filter ( ( id ) => ! prev . ids . includes ( id ) ) ;
705
+ return { ids, joined, left } ;
706
+ } ,
707
+ { ids : [ ] , joined : [ ] , left : [ ] } ,
708
+ ) ,
709
+ ) ;
710
+
711
+ public readonly allOthersLeft$ = this . memberChanges$ . pipe (
712
+ map ( ( { ids, left } ) => ids . length === 0 && left . length > 0 ) ,
713
+ startWith ( false ) ,
714
+ distinctUntilChanged ( ) ,
715
+ ) ;
716
+
717
+ public readonly autoLeaveWhenOthersLeft$ = this . allOthersLeft$ . pipe (
718
+ distinctUntilChanged ( ) ,
719
+ filter ( ( leave ) => ( leave && this . options . autoLeaveWhenOthersLeft ) ?? false ) ,
720
+ map ( ( ) => { } ) ,
721
+ ) ;
701
722
702
723
/**
703
724
* List of MediaItems that we want to display, that are of type ScreenShare
@@ -1383,7 +1404,7 @@ export class CallViewModel extends ViewModel {
1383
1404
private readonly matrixRTCSession : MatrixRTCSession ,
1384
1405
private readonly livekitRoom : LivekitRoom ,
1385
1406
private readonly mediaDevices : MediaDevices ,
1386
- private readonly encryptionSystem : EncryptionSystem ,
1407
+ private readonly options : CallViewModelOptions ,
1387
1408
private readonly connectionState$ : Observable < ECConnectionState > ,
1388
1409
private readonly handsRaisedSubject$ : Observable <
1389
1410
Record < string , RaisedHandInfo >
0 commit comments