Add ability to toggle presenter by clicking name

This commit is contained in:
Robert Long 2021-08-26 11:03:48 -07:00
parent a4e8949907
commit 1661c5518b
5 changed files with 354 additions and 70 deletions

View file

@ -339,6 +339,20 @@ export function useVideoRoom(manager, roomId, timeout = 5000) {
setState((prevState) => ({ ...prevState, videoMuted: manager.videoMuted })); setState((prevState) => ({ ...prevState, videoMuted: manager.videoMuted }));
}, [manager]); }, [manager]);
const togglePresenter = useCallback((selectedParticipant) => {
setState((prevState) => ({
...prevState,
participants: prevState.participants.map((participant) =>
participant === selectedParticipant
? {
...participant,
presenter: !participant.presenter,
}
: participant
),
}));
}, []);
return { return {
loading, loading,
joined, joined,
@ -350,6 +364,7 @@ export function useVideoRoom(manager, roomId, timeout = 5000) {
leaveCall, leaveCall,
toggleMuteVideo, toggleMuteVideo,
toggleMuteAudio, toggleMuteAudio,
togglePresenter,
videoMuted, videoMuted,
audioMuted, audioMuted,
}; };

View file

@ -20,6 +20,19 @@ export function GridDemo() {
]); ]);
}, [stream]); }, [stream]);
const togglePresenter = useCallback((selectedParticipant) => {
setParticipants((participants) =>
participants.map((participant) =>
participant === selectedParticipant
? {
...participant,
presenter: !participant.presenter,
}
: participant
)
);
}, []);
const removeParticipant = useCallback((key) => { const removeParticipant = useCallback((key) => {
setParticipants((participants) => setParticipants((participants) =>
participants.filter((participant) => participant.userId !== key) participants.filter((participant) => participant.userId !== key)
@ -43,7 +56,7 @@ export function GridDemo() {
</button> </button>
)} )}
</div> </div>
<VideoGrid participants={participants} /> <VideoGrid participants={participants} onClickNameTag={togglePresenter} />
</div> </div>
); );
} }

View file

@ -48,6 +48,7 @@ export function Room({ manager }) {
leaveCall, leaveCall,
toggleMuteVideo, toggleMuteVideo,
toggleMuteAudio, toggleMuteAudio,
togglePresenter,
videoMuted, videoMuted,
audioMuted, audioMuted,
} = useVideoRoom(manager, roomId); } = useVideoRoom(manager, roomId);
@ -111,7 +112,10 @@ export function Room({ manager }) {
</div> </div>
)} )}
{!loading && room && joined && participants.length > 0 && ( {!loading && room && joined && participants.length > 0 && (
<VideoGrid participants={participants} /> <VideoGrid
participants={participants}
onClickNameTag={togglePresenter}
/>
)} )}
{!loading && room && joined && ( {!loading && room && joined && (
<div className={styles.footer}> <div className={styles.footer}>

View file

@ -36,18 +36,209 @@ function isInside([x, y], targetTile) {
return true; return true;
} }
function getTilePositions(tileCount, gridBounds) { function getTilePositions(tileCount, gridBounds, presenterTileCount) {
const newTilePositions = []; if (tileCount === 0) {
const { width: gridWidth, height: gridHeight } = gridBounds; return [];
const gap = 8; }
if (tileCount > 12) { if (tileCount > 12) {
console.warn("Over 12 tiles is not currently supported"); console.warn("Over 12 tiles is not currently supported");
} }
if (tileCount > 0) { if (presenterTileCount > 3) {
const gridAspectRatio = gridWidth / gridHeight; console.warn("Over 3 presenters is not currently supported");
}
const gridWidth = gridBounds.width;
const gridHeight = gridBounds.height;
const gridAspectRatio = gridWidth / gridHeight;
if (presenterTileCount) {
const subGridTileCount = tileCount - presenterTileCount;
let presenterGridWidth,
presenterGridHeight,
presenterColumnCount,
presenterRowCount,
presenterTileAspectRatio;
let subGridWidth,
subGridHeight,
subGridOffsetLeft,
subGridOffsetTop,
subGridColumnCount,
subGridRowCount,
subGridTileAspectRatio;
if (gridAspectRatio < 3 / 4) {
// Phone
presenterGridWidth = gridWidth;
presenterColumnCount = 1;
presenterRowCount = presenterTileCount;
presenterTileAspectRatio = 16 / 9;
subGridTileAspectRatio = 16 / 9;
if (presenterTileCount > 2) {
presenterColumnCount = 2;
presenterRowCount = 2;
presenterTileAspectRatio = 0;
}
if (subGridTileCount < 3) {
if (presenterTileCount === 1) {
}
subGridColumnCount = presenterTileCount === 1 ? 1 : subGridTileCount;
subGridRowCount = presenterTileCount === 1 ? subGridTileCount : 1;
subGridTileAspectRatio = presenterTileCount === 1 ? 16 / 9 : 0;
} else if (subGridTileCount < 5) {
subGridColumnCount = 2;
subGridRowCount = 2;
} else if (subGridTileCount < 7) {
subGridColumnCount = 2;
subGridRowCount = 3;
} else if (subGridTileCount < 10) {
subGridColumnCount = 3;
subGridRowCount = 3;
} else {
subGridColumnCount = 4;
subGridRowCount = 3;
}
presenterGridHeight = Math.round(
gridHeight *
(1 -
1 /
Math.max(
presenterRowCount + 2 - Math.max(subGridRowCount - 1, 0),
2
))
);
subGridWidth = gridWidth;
subGridHeight = gridHeight - presenterGridHeight;
subGridOffsetTop = presenterGridHeight;
subGridOffsetLeft = 0;
} else if (gridAspectRatio < 1) {
// Tablet
presenterGridWidth = gridWidth;
presenterColumnCount = 1;
presenterRowCount = presenterTileCount;
presenterTileAspectRatio = 16 / 9;
subGridTileAspectRatio = 16 / 9;
if (presenterTileCount > 2) {
presenterColumnCount = 2;
presenterRowCount = 2;
presenterTileAspectRatio = 0;
}
if (subGridTileCount < 3) {
if (presenterTileCount === 1) {
}
subGridColumnCount = presenterTileCount === 1 ? 1 : subGridTileCount;
subGridRowCount = presenterTileCount === 1 ? subGridTileCount : 1;
subGridTileAspectRatio = presenterTileCount === 1 ? 16 / 9 : 0;
} else if (subGridTileCount < 5) {
subGridColumnCount = 2;
subGridRowCount = 2;
} else if (subGridTileCount < 7) {
subGridColumnCount = 2;
subGridRowCount = 3;
} else if (subGridTileCount < 10) {
subGridColumnCount = 3;
subGridRowCount = 3;
} else {
subGridColumnCount = 4;
subGridRowCount = 3;
}
presenterGridHeight = Math.round(
gridHeight *
(1 -
1 /
Math.max(
presenterRowCount + 2 - Math.max(subGridRowCount - 1, 0),
2
))
);
subGridWidth = gridWidth;
subGridHeight = gridHeight - presenterGridHeight;
subGridOffsetTop = presenterGridHeight;
subGridOffsetLeft = 0;
} else if (gridAspectRatio < 17 / 9) {
// Computer
presenterGridWidth = gridWidth * (2 / 3);
presenterGridHeight = gridHeight;
presenterColumnCount = 1;
presenterRowCount = presenterTileCount;
presenterTileAspectRatio = 0;
subGridWidth = gridWidth - presenterGridWidth;
subGridHeight = gridHeight;
subGridColumnCount = Math.ceil(subGridTileCount / 6);
subGridRowCount = Math.ceil(subGridTileCount / subGridColumnCount);
subGridOffsetTop = 0;
subGridOffsetLeft = presenterGridWidth;
subGridTileAspectRatio = 16 / 9;
} else if (gridAspectRatio <= 32 / 9) {
// Ultrawide
presenterGridWidth = gridWidth * (2 / 3);
presenterGridHeight = gridHeight;
presenterColumnCount = 1;
presenterRowCount = presenterTileCount;
presenterTileAspectRatio = 16 / 9;
subGridWidth = gridWidth - presenterGridWidth;
subGridHeight = gridHeight;
subGridColumnCount = Math.ceil(subGridTileCount / 4);
subGridRowCount = Math.ceil(subGridTileCount / subGridColumnCount);
subGridOffsetTop = 0;
subGridOffsetLeft = presenterGridWidth;
subGridTileAspectRatio = 16 / 9;
} else {
// Super Ultrawide
presenterGridWidth = gridWidth * (2 / 3);
presenterGridHeight = gridHeight;
presenterColumnCount = 1;
presenterRowCount = presenterTileCount;
presenterTileAspectRatio = 16 / 9;
subGridWidth = gridWidth - presenterGridWidth;
subGridHeight = gridHeight;
subGridColumnCount = Math.ceil(subGridTileCount / 3);
subGridRowCount = Math.ceil(subGridTileCount / subGridColumnCount);
subGridOffsetTop = 0;
subGridOffsetLeft = presenterGridWidth;
subGridTileAspectRatio = 16 / 9;
}
const presenterPositions = getSubGridPositions(
presenterTileCount,
presenterColumnCount,
presenterRowCount,
presenterTileAspectRatio,
{
width: presenterGridWidth,
height: presenterGridHeight,
}
);
const subGridPositions = getSubGridPositions(
subGridTileCount,
subGridColumnCount,
subGridRowCount,
subGridTileAspectRatio,
{
width: subGridWidth,
height: subGridHeight,
offsetTop: subGridOffsetTop,
offsetLeft: subGridOffsetLeft,
}
);
return [...presenterPositions, ...subGridPositions];
} else {
let columnCount, rowCount; let columnCount, rowCount;
let tileAspectRatio = 16 / 9; let tileAspectRatio = 16 / 9;
@ -151,81 +342,111 @@ function getTilePositions(tileCount, gridBounds) {
} }
} }
const boxWidth = Math.round( return getSubGridPositions(
(gridWidth - gap * (columnCount + 1)) / columnCount tileCount,
); columnCount,
const boxHeight = Math.round( rowCount,
(gridHeight - gap * (rowCount + 1)) / rowCount tileAspectRatio,
gridBounds
); );
}
}
let tileWidth, tileHeight; function getSubGridPositions(
tileCount,
columnCount,
rowCount,
tileAspectRatio,
gridBounds
) {
if (tileCount === 0) {
return [];
}
if (tileAspectRatio) { const newTilePositions = [];
const boxAspectRatio = boxWidth / boxHeight; const gridWidth = gridBounds.width;
const gridHeight = gridBounds.height;
const gridOffsetLeft = gridBounds.offsetLeft || 0;
const gridOffsetTop = gridBounds.offsetTop || 0;
const gap = 8;
if (boxAspectRatio > tileAspectRatio) { const boxWidth = Math.round(
tileWidth = boxHeight * tileAspectRatio; (gridWidth - gap * (columnCount + 1)) / columnCount
tileHeight = boxHeight; );
} else { const boxHeight = Math.round((gridHeight - gap * (rowCount + 1)) / rowCount);
tileWidth = boxWidth;
tileHeight = boxWidth / tileAspectRatio; let tileWidth, tileHeight;
}
if (tileAspectRatio) {
const boxAspectRatio = boxWidth / boxHeight;
if (boxAspectRatio > tileAspectRatio) {
tileWidth = boxHeight * tileAspectRatio;
tileHeight = boxHeight;
} else { } else {
tileWidth = boxWidth; tileWidth = boxWidth;
tileHeight = boxHeight; tileHeight = boxWidth / tileAspectRatio;
}
} else {
tileWidth = boxWidth;
tileHeight = boxHeight;
}
const paddingTop =
(gridHeight - tileHeight * rowCount - gap * (rowCount - 1)) / 2;
const paddingLeft =
(gridWidth - tileWidth * columnCount - gap * (columnCount - 1)) / 2;
for (let i = 0; i < tileCount; i++) {
const verticalIndex = Math.floor(i / columnCount);
const top =
gridOffsetTop +
verticalIndex * tileHeight +
verticalIndex * gap +
paddingTop;
let rowItemCount;
if (verticalIndex + 1 === rowCount && tileCount % columnCount !== 0) {
rowItemCount = tileCount % columnCount;
} else {
rowItemCount = columnCount;
} }
const paddingTop = const horizontalIndex = i % columnCount;
(gridHeight - tileHeight * rowCount - gap * (rowCount - 1)) / 2;
const paddingLeft = let centeringPadding = 0;
(gridWidth - tileWidth * columnCount - gap * (columnCount - 1)) / 2;
for (let i = 0; i < tileCount; i++) { if (rowItemCount < columnCount) {
const verticalIndex = Math.floor(i / columnCount); centeringPadding = Math.round(
const top = verticalIndex * tileHeight + verticalIndex * gap + paddingTop; (gridWidth -
(tileWidth * rowItemCount +
let rowItemCount; (gap * rowItemCount - 1) +
paddingLeft * 2)) /
if (verticalIndex + 1 === rowCount && tileCount % columnCount !== 0) { 2
rowItemCount = tileCount % columnCount; );
} else {
rowItemCount = columnCount;
}
const horizontalIndex = i % columnCount;
let centeringPadding = 0;
if (rowItemCount < columnCount) {
centeringPadding = Math.round(
(gridWidth -
(tileWidth * rowItemCount +
(gap * rowItemCount - 1) +
paddingLeft * 2)) /
2
);
}
const left =
paddingLeft +
centeringPadding +
gap * horizontalIndex +
tileWidth * horizontalIndex;
newTilePositions.push({
width: tileWidth,
height: tileHeight,
x: left,
y: top,
});
} }
const left =
gridOffsetLeft +
paddingLeft +
centeringPadding +
gap * horizontalIndex +
tileWidth * horizontalIndex;
newTilePositions.push({
width: tileWidth,
height: tileHeight,
x: left,
y: top,
});
} }
return newTilePositions; return newTilePositions;
} }
export function VideoGrid({ participants }) { export function VideoGrid({ participants, onClickNameTag }) {
const [{ tiles, tilePositions }, setTileState] = useState({ const [{ tiles, tilePositions }, setTileState] = useState({
tiles: [], tiles: [],
tilePositions: [], tilePositions: [],
@ -239,6 +460,7 @@ export function VideoGrid({ participants }) {
setTileState(({ tiles }) => { setTileState(({ tiles }) => {
const newTiles = []; const newTiles = [];
const removedTileKeys = []; const removedTileKeys = [];
let presenterTileCount = 0;
for (const tile of tiles) { for (const tile of tiles) {
const participant = participants.find( const participant = participants.find(
@ -264,6 +486,10 @@ export function VideoGrid({ participants }) {
} }
for (const participant of participants) { for (const participant of participants) {
if (participant.presenter) {
presenterTileCount++;
}
if (newTiles.some(({ key }) => participant.userId === key)) { if (newTiles.some(({ key }) => participant.userId === key)) {
continue; continue;
} }
@ -276,6 +502,11 @@ export function VideoGrid({ participants }) {
}); });
} }
newTiles.sort(
(a, b) =>
(b.participant.presenter ? 1 : 0) - (a.participant.presenter ? 1 : 0)
);
if (removedTileKeys.length > 0) { if (removedTileKeys.length > 0) {
setTimeout(() => { setTimeout(() => {
if (!isMounted.current) { if (!isMounted.current) {
@ -289,7 +520,11 @@ export function VideoGrid({ participants }) {
return { return {
tiles: newTiles, tiles: newTiles,
tilePositions: getTilePositions(newTiles.length, gridBounds), tilePositions: getTilePositions(
newTiles.length,
gridBounds,
presenterTileCount
),
}; };
}); });
}, 250); }, 250);
@ -297,7 +532,11 @@ export function VideoGrid({ participants }) {
return { return {
tiles: newTiles, tiles: newTiles,
tilePositions: getTilePositions(newTiles.length, gridBounds), tilePositions: getTilePositions(
newTiles.length,
gridBounds,
presenterTileCount
),
}; };
}); });
}, [participants, gridBounds]); }, [participants, gridBounds]);
@ -413,6 +652,7 @@ export function VideoGrid({ participants }) {
...style, ...style,
}} }}
{...tile} {...tile}
onClickNameTag={onClickNameTag}
/> />
); );
})} })}
@ -420,7 +660,13 @@ export function VideoGrid({ participants }) {
); );
} }
function ParticipantTile({ style, participant, remove, ...rest }) { function ParticipantTile({
style,
participant,
remove,
onClickNameTag,
...rest
}) {
const videoRef = useRef(); const videoRef = useRef();
useEffect(() => { useEffect(() => {
@ -445,6 +691,11 @@ function ParticipantTile({ style, participant, remove, ...rest }) {
className={classNames(styles.participantName, { className={classNames(styles.participantName, {
[styles.speaking]: participant.speaking, [styles.speaking]: participant.speaking,
})} })}
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
onClickNameTag(participant);
}}
> >
{participant.speaking ? ( {participant.speaking ? (
<MicIcon /> <MicIcon />

View file

@ -46,6 +46,7 @@ limitations under the License.
justify-content: center; justify-content: center;
border-radius: 8px; border-radius: 8px;
user-select: none; user-select: none;
cursor: pointer;
} }
.participantName > * { .participantName > * {