Merge pull request #335 from robintown/drag-local-video

Make local video in 1:1 calls draggable
This commit is contained in:
Robin 2022-05-18 08:45:22 -04:00 committed by GitHub
commit 548ea7220b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -52,6 +52,8 @@ export function useVideoGridLayout(hasScreenshareFeeds) {
return [layoutRef.current, setLayout]; return [layoutRef.current, setLayout];
} }
const GAP = 8;
function useIsMounted() { function useIsMounted() {
const isMountedRef = useRef(false); const isMountedRef = useRef(false);
@ -79,16 +81,25 @@ function isInside([x, y], targetTile) {
return true; return true;
} }
const getPipGap = (gridAspectRatio) => (gridAspectRatio < 1 ? 12 : 24);
function getTilePositions( function getTilePositions(
tileCount, tileCount,
presenterTileCount, presenterTileCount,
gridWidth, gridWidth,
gridHeight, gridHeight,
pipXRatio,
pipYRatio,
layout layout
) { ) {
if (layout === "freedom") { if (layout === "freedom") {
if (tileCount === 2 && presenterTileCount === 0) { if (tileCount === 2 && presenterTileCount === 0) {
return getOneOnOneLayoutTilePositions(gridWidth, gridHeight); return getOneOnOneLayoutTilePositions(
gridWidth,
gridHeight,
pipXRatio,
pipYRatio
);
} }
return getFreedomLayoutTilePositions( return getFreedomLayoutTilePositions(
@ -102,34 +113,43 @@ function getTilePositions(
} }
} }
function getOneOnOneLayoutTilePositions(gridWidth, gridHeight) { function getOneOnOneLayoutTilePositions(
const gap = 8; gridWidth,
gridHeight,
pipXRatio,
pipYRatio
) {
const gridAspectRatio = gridWidth / gridHeight; const gridAspectRatio = gridWidth / gridHeight;
const pipWidth = gridAspectRatio < 1 ? 114 : 230; const pipWidth = gridAspectRatio < 1 ? 114 : 230;
const pipHeight = gridAspectRatio < 1 ? 163 : 155; const pipHeight = gridAspectRatio < 1 ? 163 : 155;
const pipGap = gridAspectRatio < 1 ? 12 : 24; const pipGap = getPipGap(gridAspectRatio);
const pipMinX = GAP + pipGap;
const pipMinY = GAP + pipGap;
const pipMaxX = gridWidth - pipWidth - GAP - pipGap;
const pipMaxY = gridHeight - pipHeight - GAP - pipGap;
return [ return [
{ {
x: gridWidth - pipWidth - gap - pipGap, // Apply the PiP position as a proportion of the available space
y: gridHeight - pipHeight - gap - pipGap, x: pipMinX + pipXRatio * (pipMaxX - pipMinX),
y: pipMinY + pipYRatio * (pipMaxY - pipMinY),
width: pipWidth, width: pipWidth,
height: pipHeight, height: pipHeight,
zIndex: 1, zIndex: 1,
}, },
{ {
x: gap, x: GAP,
y: gap, y: GAP,
width: gridWidth - gap * 2, width: gridWidth - GAP * 2,
height: gridHeight - gap * 2, height: gridHeight - GAP * 2,
zIndex: 0, zIndex: 0,
}, },
]; ];
} }
function getSpotlightLayoutTilePositions(tileCount, gridWidth, gridHeight) { function getSpotlightLayoutTilePositions(tileCount, gridWidth, gridHeight) {
const gap = 8;
const tilePositions = []; const tilePositions = [];
const gridAspectRatio = gridWidth / gridHeight; const gridAspectRatio = gridWidth / gridHeight;
@ -137,25 +157,25 @@ function getSpotlightLayoutTilePositions(tileCount, gridWidth, gridHeight) {
if (gridAspectRatio < 1) { if (gridAspectRatio < 1) {
// Vertical layout (mobile) // Vertical layout (mobile)
const spotlightTileHeight = const spotlightTileHeight =
tileCount > 1 ? (gridHeight - gap * 3) * (4 / 5) : gridHeight - gap * 2; tileCount > 1 ? (gridHeight - GAP * 3) * (4 / 5) : gridHeight - GAP * 2;
const spectatorTileSize = const spectatorTileSize =
tileCount > 1 ? gridHeight - gap * 3 - spotlightTileHeight : 0; tileCount > 1 ? gridHeight - GAP * 3 - spotlightTileHeight : 0;
for (let i = 0; i < tileCount; i++) { for (let i = 0; i < tileCount; i++) {
if (i === 0) { if (i === 0) {
// Spotlight tile // Spotlight tile
tilePositions.push({ tilePositions.push({
x: gap, x: GAP,
y: gap, y: GAP,
width: gridWidth - gap * 2, width: gridWidth - GAP * 2,
height: spotlightTileHeight, height: spotlightTileHeight,
zIndex: 0, zIndex: 0,
}); });
} else { } else {
// Spectator tile // Spectator tile
tilePositions.push({ tilePositions.push({
x: (gap + spectatorTileSize) * (i - 1) + gap, x: (GAP + spectatorTileSize) * (i - 1) + GAP,
y: spotlightTileHeight + gap * 2, y: spotlightTileHeight + GAP * 2,
width: spectatorTileSize, width: spectatorTileSize,
height: spectatorTileSize, height: spectatorTileSize,
zIndex: 0, zIndex: 0,
@ -165,24 +185,24 @@ function getSpotlightLayoutTilePositions(tileCount, gridWidth, gridHeight) {
} else { } else {
// Horizontal layout (desktop) // Horizontal layout (desktop)
const spotlightTileWidth = const spotlightTileWidth =
tileCount > 1 ? ((gridWidth - gap * 3) * 4) / 5 : gridWidth - gap * 2; tileCount > 1 ? ((gridWidth - GAP * 3) * 4) / 5 : gridWidth - GAP * 2;
const spectatorTileWidth = const spectatorTileWidth =
tileCount > 1 ? gridWidth - gap * 3 - spotlightTileWidth : 0; tileCount > 1 ? gridWidth - GAP * 3 - spotlightTileWidth : 0;
const spectatorTileHeight = spectatorTileWidth * (9 / 16); const spectatorTileHeight = spectatorTileWidth * (9 / 16);
for (let i = 0; i < tileCount; i++) { for (let i = 0; i < tileCount; i++) {
if (i === 0) { if (i === 0) {
tilePositions.push({ tilePositions.push({
x: gap, x: GAP,
y: gap, y: GAP,
width: spotlightTileWidth, width: spotlightTileWidth,
height: gridHeight - gap * 2, height: gridHeight - GAP * 2,
zIndex: 0, zIndex: 0,
}); });
} else { } else {
tilePositions.push({ tilePositions.push({
x: gap * 2 + spotlightTileWidth, x: GAP * 2 + spotlightTileWidth,
y: (gap + spectatorTileHeight) * (i - 1) + gap, y: (GAP + spectatorTileHeight) * (i - 1) + GAP,
width: spectatorTileWidth, width: spectatorTileWidth,
height: spectatorTileHeight, height: spectatorTileHeight,
zIndex: 0, zIndex: 0,
@ -208,8 +228,6 @@ function getFreedomLayoutTilePositions(
console.warn("Over 12 tiles is not currently supported"); console.warn("Over 12 tiles is not currently supported");
} }
const gap = 8;
const { layoutDirection, itemGridRatio } = getGridLayout( const { layoutDirection, itemGridRatio } = getGridLayout(
tileCount, tileCount,
presenterTileCount, presenterTileCount,
@ -242,8 +260,7 @@ function getFreedomLayoutTilePositions(
itemRowCount, itemRowCount,
itemTileAspectRatio, itemTileAspectRatio,
itemGridWidth, itemGridWidth,
itemGridHeight, itemGridHeight
gap
); );
const itemGridBounds = getSubGridBoundingBox(itemGridPositions); const itemGridBounds = getSubGridBoundingBox(itemGridPositions);
@ -256,10 +273,10 @@ function getFreedomLayoutTilePositions(
} else if (layoutDirection === "vertical") { } else if (layoutDirection === "vertical") {
presenterGridWidth = gridWidth; presenterGridWidth = gridWidth;
presenterGridHeight = presenterGridHeight =
gridHeight - (itemGridBounds.height + (itemTileCount ? gap * 2 : 0)); gridHeight - (itemGridBounds.height + (itemTileCount ? GAP * 2 : 0));
} else { } else {
presenterGridWidth = presenterGridWidth =
gridWidth - (itemGridBounds.width + (itemTileCount ? gap * 2 : 0)); gridWidth - (itemGridBounds.width + (itemTileCount ? GAP * 2 : 0));
presenterGridHeight = gridHeight; presenterGridHeight = gridHeight;
} }
@ -279,8 +296,7 @@ function getFreedomLayoutTilePositions(
presenterRowCount, presenterRowCount,
presenterTileAspectRatio, presenterTileAspectRatio,
presenterGridWidth, presenterGridWidth,
presenterGridHeight, presenterGridHeight
gap
); );
const tilePositions = [...presenterGridPositions, ...itemGridPositions]; const tilePositions = [...presenterGridPositions, ...itemGridPositions];
@ -517,8 +533,7 @@ function getSubGridPositions(
rowCount, rowCount,
tileAspectRatio, tileAspectRatio,
gridWidth, gridWidth,
gridHeight, gridHeight
gap
) { ) {
if (tileCount === 0) { if (tileCount === 0) {
return []; return [];
@ -527,9 +542,9 @@ function getSubGridPositions(
const newTilePositions = []; const newTilePositions = [];
const boxWidth = Math.round( const boxWidth = Math.round(
(gridWidth - gap * (columnCount + 1)) / columnCount (gridWidth - GAP * (columnCount + 1)) / columnCount
); );
const boxHeight = Math.round((gridHeight - gap * (rowCount + 1)) / rowCount); const boxHeight = Math.round((gridHeight - GAP * (rowCount + 1)) / rowCount);
let tileWidth; let tileWidth;
let tileHeight; let tileHeight;
@ -551,7 +566,7 @@ function getSubGridPositions(
for (let i = 0; i < tileCount; i++) { for (let i = 0; i < tileCount; i++) {
const verticalIndex = Math.floor(i / columnCount); const verticalIndex = Math.floor(i / columnCount);
const top = verticalIndex * gap + verticalIndex * tileHeight; const top = verticalIndex * GAP + verticalIndex * tileHeight;
let rowItemCount; let rowItemCount;
@ -566,15 +581,15 @@ function getSubGridPositions(
let centeringPadding = 0; let centeringPadding = 0;
if (rowItemCount < columnCount) { if (rowItemCount < columnCount) {
const subgridWidth = tileWidth * columnCount + (gap * columnCount - 1); const subgridWidth = tileWidth * columnCount + (GAP * columnCount - 1);
centeringPadding = Math.round( centeringPadding = Math.round(
(subgridWidth - (tileWidth * rowItemCount + (gap * rowItemCount - 1))) / (subgridWidth - (tileWidth * rowItemCount + (GAP * rowItemCount - 1))) /
2 2
); );
} }
const left = const left =
centeringPadding + gap * horizontalIndex + tileWidth * horizontalIndex; centeringPadding + GAP * horizontalIndex + tileWidth * horizontalIndex;
newTilePositions.push({ newTilePositions.push({
width: tileWidth, width: tileWidth,
@ -611,6 +626,10 @@ export function VideoGrid({
disableAnimations, disableAnimations,
children, children,
}) { }) {
// Place the PiP in the bottom right corner by default
const [pipXRatio, setPipXRatio] = useState(1);
const [pipYRatio, setPipYRatio] = useState(1);
const [{ tiles, tilePositions, scrollPosition }, setTileState] = useState({ const [{ tiles, tilePositions, scrollPosition }, setTileState] = useState({
tiles: [], tiles: [],
tilePositions: [], tilePositions: [],
@ -716,6 +735,8 @@ export function VideoGrid({
presenterTileCount, presenterTileCount,
gridBounds.width, gridBounds.width,
gridBounds.height, gridBounds.height,
pipXRatio,
pipYRatio,
layout layout
), ),
}; };
@ -738,11 +759,13 @@ export function VideoGrid({
presenterTileCount, presenterTileCount,
gridBounds.width, gridBounds.width,
gridBounds.height, gridBounds.height,
pipXRatio,
pipYRatio,
layout layout
), ),
}; };
}); });
}, [items, gridBounds, layout, isMounted]); }, [items, gridBounds, layout, isMounted, pipXRatio, pipYRatio]);
const animate = useCallback( const animate = useCallback(
(tiles) => (tileIndex) => { (tiles) => (tileIndex) => {
@ -876,6 +899,8 @@ export function VideoGrid({
presenterTileCount, presenterTileCount,
gridBounds.width, gridBounds.width,
gridBounds.height, gridBounds.height,
pipXRatio,
pipYRatio,
layout layout
), ),
}; };
@ -885,7 +910,7 @@ export function VideoGrid({
); );
const bindTile = useDrag( const bindTile = useDrag(
({ args: [key], active, xy, movement, tap, event }) => { ({ args: [key], active, xy, movement, tap, last, event }) => {
event.preventDefault(); event.preventDefault();
if (tap) { if (tap) {
@ -893,13 +918,7 @@ export function VideoGrid({
return; return;
} }
if (layout !== "freedom") { if (layout !== "freedom") return;
return;
}
if (layout === "freedom" && tiles.length === 2) {
return;
}
const dragTileIndex = tiles.findIndex((tile) => tile.key === key); const dragTileIndex = tiles.findIndex((tile) => tile.key === key);
const dragTile = tiles[dragTileIndex]; const dragTile = tiles[dragTileIndex];
@ -909,6 +928,30 @@ export function VideoGrid({
const cursorPosition = [xy[0] - gridBounds.left, xy[1] - gridBounds.top]; const cursorPosition = [xy[0] - gridBounds.left, xy[1] - gridBounds.top];
if (layout === "freedom" && tiles.length === 2) {
// We're in 1:1 mode, so only the local tile should be draggable
if (dragTileIndex !== 0) return;
// Only update the position on the last event
if (last) {
const pipGap = getPipGap(gridBounds.width / gridBounds.height);
const pipMinX = GAP + pipGap;
const pipMinY = GAP + pipGap;
const pipMaxX =
gridBounds.width - dragTilePosition.width - GAP - pipGap;
const pipMaxY =
gridBounds.height - dragTilePosition.height - GAP - pipGap;
const newPipXRatio =
(dragTilePosition.x + movement[0] - pipMinX) / (pipMaxX - pipMinX);
const newPipYRatio =
(dragTilePosition.y + movement[1] - pipMinY) / (pipMaxY - pipMinY);
setPipXRatio(Math.max(0, Math.min(1, newPipXRatio)));
setPipYRatio(Math.max(0, Math.min(1, newPipYRatio)));
}
}
for ( for (
let hoverTileIndex = 0; let hoverTileIndex = 0;
hoverTileIndex < tiles.length; hoverTileIndex < tiles.length;
@ -981,8 +1024,8 @@ export function VideoGrid({
if (tilePositions.length > 1) { if (tilePositions.length > 1) {
const lastTile = tilePositions[tilePositions.length - 1]; const lastTile = tilePositions[tilePositions.length - 1];
min = isMobile min = isMobile
? gridBounds.width - lastTile.x - lastTile.width - 8 ? gridBounds.width - lastTile.x - lastTile.width - GAP
: gridBounds.height - lastTile.y - lastTile.height - 8; : gridBounds.height - lastTile.y - lastTile.height - GAP;
} }
setTileState((state) => ({ setTileState((state) => ({