This commit is contained in:
Hugh Nimmo-Smith
2024-11-14 13:51:49 +00:00
parent eafd8fd8b6
commit f6a641b478

View File

@@ -379,7 +379,6 @@ export class CallViewModel extends ViewModel {
/** /**
* The raw list of RemoteParticipants as reported by LiveKit * The raw list of RemoteParticipants as reported by LiveKit
*/ */
private readonly rawRemoteParticipants: Observable<RemoteParticipant[]> = private readonly rawRemoteParticipants: Observable<RemoteParticipant[]> =
connectedParticipantsObserver(this.livekitRoom).pipe(this.scope.state()); connectedParticipantsObserver(this.livekitRoom).pipe(this.scope.state());
@@ -573,42 +572,41 @@ export class CallViewModel extends ViewModel {
this.scope.state(), this.scope.state(),
); );
private readonly spotlightSpeaker: Observable< private readonly spotlightSpeaker: Observable<UserMediaViewModel | null> =
UserMediaViewModel | undefined this.userMedia.pipe(
> = this.userMedia.pipe( switchMap((mediaItems) =>
switchMap((mediaItems) => mediaItems.length === 0
mediaItems.length === 0 ? of([])
? of([]) : combineLatest(
: combineLatest( mediaItems.map((m) =>
mediaItems.map((m) => m.vm.speaking.pipe(map((s) => [m, s] as const)),
m.vm.speaking.pipe(map((s) => [m, s] as const)), ),
), ),
), ),
), scan<(readonly [UserMedia, boolean])[], UserMedia | undefined, null>(
scan<(readonly [UserMedia, boolean])[], UserMedia | undefined, null>( (prev, mediaItems) => {
(prev, mediaItems) => { // Only remote users that are still in the call should be sticky
// Only remote users that are still in the call should be sticky const [stickyMedia, stickySpeaking] =
const [stickyMedia, stickySpeaking] = (!prev?.vm.local && mediaItems.find(([m]) => m === prev)) || [];
(!prev?.vm.local && mediaItems.find(([m]) => m === prev)) || []; // Decide who to spotlight:
// Decide who to spotlight: // If the previous speaker is still speaking, stick with them rather
// If the previous speaker is still speaking, stick with them rather // than switching eagerly to someone else
// than switching eagerly to someone else return stickySpeaking
return stickySpeaking ? stickyMedia!
? stickyMedia! : // Otherwise, select any remote user who is speaking
: // Otherwise, select any remote user who is speaking (mediaItems.find(([m, s]) => !m.vm.local && s)?.[0] ??
(mediaItems.find(([m, s]) => !m.vm.local && s)?.[0] ?? // Otherwise, stick with the person who was last speaking
// Otherwise, stick with the person who was last speaking stickyMedia ??
stickyMedia ?? // Otherwise, spotlight an arbitrary remote user
// Otherwise, spotlight an arbitrary remote user mediaItems.find(([m]) => !m.vm.local)?.[0] ??
mediaItems.find(([m]) => !m.vm.local)?.[0] ?? // Otherwise, spotlight the local user
// Otherwise, spotlight the local user mediaItems.find(([m]) => m.vm.local)?.[0]);
mediaItems.find(([m]) => m.vm.local)?.[0]); },
}, null,
null, ),
), map((speaker) => speaker?.vm ?? null),
map((speaker) => speaker?.vm), this.scope.state(),
this.scope.state(), );
);
private readonly grid: Observable<UserMediaViewModel[]> = this.userMedia.pipe( private readonly grid: Observable<UserMediaViewModel[]> = this.userMedia.pipe(
switchMap((mediaItems) => { switchMap((mediaItems) => {
@@ -647,7 +645,7 @@ export class CallViewModel extends ViewModel {
); );
private readonly spotlightAndPip: Observable< private readonly spotlightAndPip: Observable<
[Observable<MediaViewModel[]>, Observable<UserMediaViewModel | undefined>] [Observable<MediaViewModel[]>, Observable<UserMediaViewModel | null>]
> = this.screenShares.pipe( > = this.screenShares.pipe(
map((screenShares) => map((screenShares) =>
screenShares.length > 0 screenShares.length > 0
@@ -660,7 +658,7 @@ export class CallViewModel extends ViewModel {
switchMap((speaker) => switchMap((speaker) =>
speaker speaker
? speaker.local ? speaker.local
? of(undefined) ? of(null)
: this.mediaItems.pipe( : this.mediaItems.pipe(
switchMap((mediaItems) => { switchMap((mediaItems) => {
const localUserMedia = mediaItems.find( const localUserMedia = mediaItems.find(
@@ -671,11 +669,11 @@ export class CallViewModel extends ViewModel {
map((alwaysShow) => map((alwaysShow) =>
alwaysShow ? localUserMedia : undefined, alwaysShow ? localUserMedia : undefined,
), ),
) ?? of(undefined) ) ?? of(null)
); );
}), }),
) )
: of(undefined), : of(null),
), ),
), ),
] as const), ] as const),
@@ -689,12 +687,14 @@ export class CallViewModel extends ViewModel {
); );
private readonly hasRemoteScreenShares: Observable<boolean> = private readonly hasRemoteScreenShares: Observable<boolean> =
this.screenShares.pipe( this.spotlight.pipe(
map((ms) => ms.some((m) => !m.vm.local)), map((spotlight) =>
spotlight.some((vm) => !vm.local && vm instanceof ScreenShareViewModel),
),
distinctUntilChanged(), distinctUntilChanged(),
); );
private readonly pip: Observable<UserMediaViewModel | undefined> = private readonly pip: Observable<UserMediaViewModel | null> =
this.spotlightAndPip.pipe(switchMap(([, pip]) => pip)); this.spotlightAndPip.pipe(switchMap(([, pip]) => pip));
private readonly pipEnabled: Observable<boolean> = setPipEnabled.pipe( private readonly pipEnabled: Observable<boolean> = setPipEnabled.pipe(
@@ -780,8 +780,7 @@ export class CallViewModel extends ViewModel {
type: "spotlight-landscape", type: "spotlight-landscape",
spotlight, spotlight,
grid, grid,
}), }));
);
private readonly spotlightPortraitLayoutMedia: Observable<SpotlightPortraitLayoutMedia> = private readonly spotlightPortraitLayoutMedia: Observable<SpotlightPortraitLayoutMedia> =
combineLatest([this.grid, this.spotlight], (grid, spotlight) => ({ combineLatest([this.grid, this.spotlight], (grid, spotlight) => ({