Spotlite tile order (#1118)

* Manage spotlight tile slots for speaker and presenter
This commit is contained in:
Enrico Schwendig 2023-06-16 18:57:47 +02:00 committed by GitHub
parent 7f5ec069a5
commit f13c0f07cc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 105 additions and 11 deletions

View file

@ -440,19 +440,29 @@ function useParticipantTiles(
})
);
const someoneIsPresenting = sfuParticipants.some((p) => {
!p.isLocal && p.isScreenShareEnabled;
});
const hasPresenter =
sfuParticipants.find((p) => p.isScreenShareEnabled) !== undefined;
const speakActiveTime = new Date();
speakActiveTime.setSeconds(speakActiveTime.getSeconds() - 10);
// Iterate over SFU participants (those who actually are present from the SFU perspective) and create tiles for them.
const tiles: TileDescriptor<ItemData>[] = sfuParticipants.flatMap(
(sfuParticipant) => {
const hadSpokedInTime =
!hasPresenter && sfuParticipant.lastSpokeAt
? sfuParticipant.lastSpokeAt > speakActiveTime
: false;
const id = sfuParticipant.identity;
const member = matrixParticipants.get(id);
const userMediaTile = {
id,
focused: !someoneIsPresenting && sfuParticipant.isSpeaking,
focused: false,
isPresenter: sfuParticipant.isScreenShareEnabled,
isSpeaker:
(sfuParticipant.isSpeaking || hadSpokedInTime) &&
!sfuParticipant.isLocal,
hasVideo: sfuParticipant.isCameraEnabled,
local: sfuParticipant.isLocal,
data: {
member,

View file

@ -57,6 +57,9 @@ interface Tile<T> {
item: TileDescriptor<T>;
remove: boolean;
focused: boolean;
isPresenter: boolean;
isSpeaker: boolean;
hasVideo: boolean;
}
export interface TileSpring {
@ -688,9 +691,41 @@ function getSubGridPositions(
return newTilePositions;
}
// Calculates the number of possible tiles that can be displayed
function displayedTileCount(
layout: Layout,
tileCount,
gridWidth: number,
gridHeight: number
): number {
let displayedTile = -1;
if (layout === "freedom") {
return displayedTile;
}
if (tileCount < 2) {
return displayedTile;
}
const gridAspectRatio = gridWidth / gridHeight;
if (gridAspectRatio < 1) {
// Vertical layout (mobile)
const spotlightTileHeight = (gridHeight - GAP * 3) * (4 / 5);
const spectatorTileSize = gridHeight - GAP * 3 - spotlightTileHeight;
displayedTile = Math.round(gridWidth / spectatorTileSize);
} else {
const spotlightTileWidth = ((gridWidth - GAP * 3) * 4) / 5;
const spectatorTileWidth = gridWidth - GAP * 3 - spotlightTileWidth;
const spectatorTileHeight = spectatorTileWidth * (9 / 16);
displayedTile = Math.round(gridHeight / spectatorTileHeight);
}
return displayedTile;
}
// Sets the 'order' property on tiles based on the layout param and
// other properties of the tiles, eg. 'focused' and 'presenter'
function reorderTiles<T>(tiles: Tile<T>[], layout: Layout) {
function reorderTiles<T>(tiles: Tile<T>[], layout: Layout, displayedTile = -1) {
// We use a special layout for 1:1 to always put the local tile first.
// We only do this if there are two tiles (obviously) and exactly one
// of them is local: during startup we can have tiles from other users
@ -707,16 +742,35 @@ function reorderTiles<T>(tiles: Tile<T>[], layout: Layout) {
tiles.forEach((tile) => (tile.order = tile.item.local ? 0 : 1));
} else {
const focusedTiles: Tile<T>[] = [];
const presenterTiles: Tile<T>[] = [];
const speakerTiles: Tile<T>[] = [];
const onlyVideoTiles: Tile<T>[] = [];
const otherTiles: Tile<T>[] = [];
const orderedTiles: Tile<T>[] = new Array(tiles.length);
tiles.forEach((tile) => (orderedTiles[tile.order] = tile));
orderedTiles.forEach((tile) =>
(tile.focused ? focusedTiles : otherTiles).push(tile)
);
orderedTiles.forEach((tile) => {
if (tile.focused) {
focusedTiles.push(tile);
} else if (tile.isPresenter) {
presenterTiles.push(tile);
} else if (tile.isSpeaker && displayedTile < tile.order) {
speakerTiles.push(tile);
} else if (tile.hasVideo) {
onlyVideoTiles.push(tile);
} else {
otherTiles.push(tile);
}
});
[...focusedTiles, ...otherTiles].forEach((tile, i) => (tile.order = i));
[
...focusedTiles,
...presenterTiles,
...speakerTiles,
...onlyVideoTiles,
...otherTiles,
].forEach((tile, i) => (tile.order = i));
}
}
@ -754,6 +808,9 @@ export interface VideoGridProps<T> {
export interface TileDescriptor<T> {
id: string;
focused: boolean;
isPresenter: boolean;
isSpeaker: boolean;
hasVideo: boolean;
local: boolean;
data: T;
}
@ -806,10 +863,19 @@ export function VideoGrid<T>({
}
let focused: boolean;
let isSpeaker: boolean;
let isPresenter: boolean;
let hasVideo: boolean;
if (layout === "spotlight") {
focused = item.focused;
isPresenter = item.isPresenter;
isSpeaker = item.isSpeaker;
hasVideo = item.hasVideo;
} else {
focused = layout === lastLayoutRef.current ? tile.focused : false;
isPresenter = false;
isSpeaker = false;
hasVideo = false;
}
newTiles.push({
@ -818,6 +884,9 @@ export function VideoGrid<T>({
item,
remove,
focused,
isSpeaker: isSpeaker,
isPresenter: isPresenter,
hasVideo: hasVideo,
});
}
@ -838,6 +907,9 @@ export function VideoGrid<T>({
item,
remove: false,
focused: layout === "spotlight" && item.focused,
isPresenter: item.isPresenter,
isSpeaker: item.isSpeaker,
hasVideo: item.hasVideo,
};
if (existingTile) {
@ -849,7 +921,19 @@ export function VideoGrid<T>({
}
}
reorderTiles(newTiles, layout);
const presenter = newTiles.find((t) => t.isPresenter);
let displayedTile = -1;
// Only on screen share we will not move active displayed speaker
if (presenter !== undefined) {
displayedTile = displayedTileCount(
layout,
newTiles.length,
gridBounds.width,
gridBounds.height
);
}
reorderTiles(newTiles, layout, displayedTile);
if (removedTileKeys.size > 0) {
setTimeout(() => {