From 5069b008e2f6b4222297c616634fbbe16db3ce7b Mon Sep 17 00:00:00 2001 From: Robin Date: Thu, 8 Aug 2024 12:11:53 -0400 Subject: [PATCH] Avoid duplicating the video of someone in the spotlight We've gotten feedback that it's distracting whenever the same video is shown in two places on screen. This fixes the spotlight case by showing only the avatar of anyone who is already visible in the spotlight. It also makes sense to hide the speaking indicators in spotlight layouts, I think, because this information is redundant to the spotlight tile. --- src/room/InCallView.tsx | 10 +++++++++- src/state/CallViewModel.ts | 21 ++++++++++++++++++++- src/tile/GridTile.tsx | 5 ++++- 3 files changed, 33 insertions(+), 3 deletions(-) diff --git a/src/room/InCallView.tsx b/src/room/InCallView.tsx index 581502fc..dd2a0e75 100644 --- a/src/room/InCallView.tsx +++ b/src/room/InCallView.tsx @@ -35,7 +35,7 @@ import { import useMeasure from "react-use-measure"; import { MatrixRTCSession } from "matrix-js-sdk/src/matrixrtc/MatrixRTCSession"; import classNames from "classnames"; -import { BehaviorSubject } from "rxjs"; +import { BehaviorSubject, of } from "rxjs"; import { useObservableEagerState } from "observable-hooks"; import LogoMark from "../icons/LogoMark.svg?react"; @@ -298,6 +298,13 @@ export const InCallView: FC = ({ const onToggleExpanded = useObservableEagerState( vm.toggleSpotlightExpanded, ); + const showVideo = useObservableEagerState( + useMemo( + () => + model.type === "grid" ? vm.showGridVideo(model.vm) : of(true), + [model], + ), + ); const showSpeakingIndicatorsValue = useObservableEagerState( vm.showSpeakingIndicators, ); @@ -314,6 +321,7 @@ export const InCallView: FC = ({ targetHeight={targetHeight} className={classNames(className, styles.tile)} style={style} + showVideo={showVideo} showSpeakingIndicators={showSpeakingIndicatorsValue} /> ) : ( diff --git a/src/state/CallViewModel.ts b/src/state/CallViewModel.ts index acd145b9..9bd1b00e 100644 --- a/src/state/CallViewModel.ts +++ b/src/state/CallViewModel.ts @@ -691,8 +691,27 @@ export class CallViewModel extends ViewModel { shareReplay(1), ); + /** + * Determines whether video should be shown for a certain piece of media + * appearing in the grid. + */ + public showGridVideo(vm: MediaViewModel): Observable { + return this.layout.pipe( + map( + (l) => + !( + (l.type === "spotlight-landscape" || + l.type === "spotlight-portrait") && + // This media is already visible in the spotlight; avoid duplication + l.spotlight.some((spotlightVm) => spotlightVm === vm) + ), + ), + distinctUntilChanged(), + ); + } + public showSpeakingIndicators: Observable = this.layout.pipe( - map((l) => l.type !== "one-on-one" && l.type !== "spotlight-expanded"), + map((l) => l.type !== "one-on-one" && !l.type.startsWith("spotlight-")), distinctUntilChanged(), shareReplay(1), ); diff --git a/src/tile/GridTile.tsx b/src/tile/GridTile.tsx index f1e0c72b..01e0f420 100644 --- a/src/tile/GridTile.tsx +++ b/src/tile/GridTile.tsx @@ -60,6 +60,7 @@ interface TileProps { targetWidth: number; targetHeight: number; displayName: string; + showVideo: boolean; showSpeakingIndicators: boolean; } @@ -74,6 +75,7 @@ const UserMediaTile = forwardRef( ( { vm, + showVideo, showSpeakingIndicators, menuStart, menuEnd, @@ -120,7 +122,7 @@ const UserMediaTile = forwardRef( video={video} member={vm.member} unencryptedWarning={unencryptedWarning} - videoEnabled={videoEnabled} + videoEnabled={videoEnabled && showVideo} videoFit={cropVideo ? "cover" : "contain"} className={classNames(className, styles.tile, { [styles.speaking]: showSpeakingIndicators && speaking, @@ -279,6 +281,7 @@ interface GridTileProps { targetHeight: number; className?: string; style?: ComponentProps["style"]; + showVideo: boolean; showSpeakingIndicators: boolean; }