Merge pull request #335 from robintown/drag-local-video
Make local video in 1:1 calls draggable
This commit is contained in:
commit
548ea7220b
1 changed files with 95 additions and 52 deletions
|
@ -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) => ({
|
||||||
|
|
Loading…
Reference in a new issue