Implement the new unified grid layout

Here I've implemented an MVP for the new unified grid layout, which scales smoothly up to arbitrarily many participants. It doesn't yet have a special 1:1 layout, so in spotlight mode and 1:1s, we will still fall back to the legacy grid systems.

Things that happened along the way:
- The part of VideoTile that is common to both spotlight and grid tiles, I refactored into MediaView
- VideoTile renamed to GridTile
- Added SpotlightTile for the new, glassy spotlight designs
- NewVideoGrid renamed to Grid, and refactored to be even more generic
- I extracted the media name logic into a custom React hook
- Deleted the BigGrid experiment
This commit is contained in:
Robin
2024-05-02 18:44:36 -04:00
parent 5ad2a27a92
commit 20602c122b
32 changed files with 1863 additions and 2586 deletions

View File

@@ -154,11 +154,11 @@ class UserMedia {
this.vm = new UserMediaViewModel(id, member, participant, callEncrypted);
this.speaker = this.vm.speaking.pipeState(
// Require 1 s of continuous speaking to become a speaker, and 10 s of
// Require 1 s of continuous speaking to become a speaker, and 60 s of
// continuous silence to stop being considered a speaker
audit((s) =>
merge(
timer(s ? 1000 : 10000),
timer(s ? 1000 : 60000),
// If the speaking flag resets to its original value during this time,
// end the silencing window to stick with that original value
this.vm.speaking.pipe(filter((s1) => s1 !== s)),

View File

@@ -32,7 +32,7 @@ import {
TrackEvent,
facingModeFromLocalTrack,
} from "livekit-client";
import { RoomMember } from "matrix-js-sdk/src/matrix";
import { RoomMember, RoomMemberEvent } from "matrix-js-sdk/src/matrix";
import {
BehaviorSubject,
combineLatest,
@@ -44,8 +44,49 @@ import {
startWith,
switchMap,
} from "rxjs";
import { useTranslation } from "react-i18next";
import { useEffect } from "react";
import { ViewModel } from "./ViewModel";
import { useReactiveState } from "../useReactiveState";
export interface NameData {
/**
* The display name of the participant.
*/
displayName: string;
/**
* The text to be shown on the participant's name tag.
*/
nameTag: string;
}
// TODO: Move this naming logic into the view model
export function useNameData(vm: MediaViewModel): NameData {
const { t } = useTranslation();
const [displayName, setDisplayName] = useReactiveState(
() => vm.member?.rawDisplayName ?? "[👻]",
[vm.member],
);
useEffect(() => {
if (vm.member) {
const updateName = (): void => {
setDisplayName(vm.member!.rawDisplayName);
};
vm.member!.on(RoomMemberEvent.Name, updateName);
return (): void => {
vm.member!.removeListener(RoomMemberEvent.Name, updateName);
};
}
}, [vm.member, setDisplayName]);
const nameTag = vm.local
? t("video_tile.sfu_participant_local")
: displayName;
return { displayName, nameTag };
}
function observeTrackReference(
participant: Participant,
@@ -78,8 +119,9 @@ abstract class BaseMediaViewModel extends ViewModel {
public readonly unencryptedWarning: StateObservable<boolean>;
public constructor(
// TODO: This is only needed for full screen toggling and can be removed as
// soon as that code is moved into the view models
/**
* An opaque identifier for this media.
*/
public readonly id: string,
/**
* The Matrix room member to which this media belongs.

View File

@@ -14,9 +14,11 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { useRef } from "react";
import { Ref, useCallback, useRef } from "react";
import { BehaviorSubject, Observable } from "rxjs";
import { useInitial } from "../useInitial";
/**
* React hook that creates an Observable from a changing value. The Observable
* replays its current value upon subscription and emits whenever the value
@@ -28,3 +30,14 @@ export function useObservable<T>(value: T): Observable<T> {
if (value !== subject.current.value) subject.current.next(value);
return subject.current;
}
/**
* React hook that creates a ref and an Observable that emits any values
* stored in the ref. The Observable replays the value currently stored in the
* ref upon subscription.
*/
export function useObservableRef<T>(initialValue: T): [Observable<T>, Ref<T>] {
const subject = useInitial(() => new BehaviorSubject(initialValue));
const ref = useCallback((value: T) => subject.next(value), [subject]);
return [subject, ref];
}