Implement the new spotlight layout

This commit is contained in:
Robin
2024-05-17 16:38:00 -04:00
parent 34c45cb5e2
commit ffbbc74a96
9 changed files with 337 additions and 65 deletions

View File

@@ -28,12 +28,13 @@ import {
import { Room as MatrixRoom, RoomMember } from "matrix-js-sdk/src/matrix";
import { useEffect, useRef } from "react";
import {
BehaviorSubject,
EMPTY,
Observable,
Subject,
audit,
combineLatest,
concat,
concatMap,
distinctUntilChanged,
filter,
map,
@@ -48,6 +49,7 @@ import {
switchMap,
throttleTime,
timer,
withLatestFrom,
zip,
} from "rxjs";
import { logger } from "matrix-js-sdk/src/logger";
@@ -371,6 +373,13 @@ export class CallViewModel extends ViewModel {
private readonly screenShares: Observable<ScreenShare[]> =
this.mediaItems.pipe(
map((ms) => ms.filter((m): m is ScreenShare => m instanceof ScreenShare)),
shareReplay(1),
);
private readonly hasScreenShares: Observable<boolean> =
this.screenShares.pipe(
map((ms) => ms.length > 0),
distinctUntilChanged(),
);
private readonly spotlightSpeaker: Observable<UserMedia | null> =
@@ -385,11 +394,13 @@ export class CallViewModel extends ViewModel {
scan<(readonly [UserMedia, boolean])[], UserMedia | null, null>(
(prev, ms) =>
// Decide who to spotlight:
// If the previous speaker is still speaking, stick with them rather
// than switching eagerly to someone else
ms.find(([m, s]) => m === prev && s)?.[0] ??
// Otherwise, select anyone who is speaking
ms.find(([, s]) => s)?.[0] ??
// If the previous speaker (not the local user) is still speaking,
// stick with them rather than switching eagerly to someone else
(prev === null || prev.vm.local
? null
: ms.find(([m, s]) => m === prev && s)?.[0]) ??
// Otherwise, select any remote user who is speaking
ms.find(([m, s]) => !m.vm.local && s)?.[0] ??
// Otherwise, stick with the person who was last speaking
prev ??
// Otherwise, spotlight the local user
@@ -398,7 +409,8 @@ export class CallViewModel extends ViewModel {
null,
),
distinctUntilChanged(),
throttleTime(800, undefined, { leading: true, trailing: true }),
shareReplay(1),
throttleTime(1600, undefined, { leading: true, trailing: true }),
);
private readonly grid: Observable<UserMediaViewModel[]> = this.userMedia.pipe(
@@ -453,18 +465,31 @@ export class CallViewModel extends ViewModel {
// orientation
private readonly windowMode = of<WindowMode>("normal");
private readonly _gridMode = new BehaviorSubject<GridMode>("grid");
private readonly gridModeUserSelection = new Subject<GridMode>();
/**
* The layout mode of the media tile grid.
*/
public readonly gridMode: Observable<GridMode> = this._gridMode;
public readonly gridMode: Observable<GridMode> = merge(
// Always honor a manual user selection
this.gridModeUserSelection,
// If the user hasn't selected spotlight and somebody starts screen sharing,
// automatically switch to spotlight mode and reset when screen sharing ends
this.hasScreenShares.pipe(
withLatestFrom(this.gridModeUserSelection.pipe(startWith(null))),
concatMap(([hasScreenShares, userSelection]) =>
userSelection === "spotlight"
? EMPTY
: of<GridMode>(hasScreenShares ? "spotlight" : "grid"),
),
),
).pipe(distinctUntilChanged(), shareReplay(1));
public setGridMode(value: GridMode): void {
this._gridMode.next(value);
this.gridModeUserSelection.next(value);
}
public readonly layout: Observable<Layout> = combineLatest(
[this._gridMode, this.windowMode],
[this.gridMode, this.windowMode],
(gridMode, windowMode) => {
switch (windowMode) {
case "full screen":