@whereby.com/browser-sdk 2.0.0 → 2.1.0-beta2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -0
- package/dist/cdn/{v2-embed.js → v2-embed-beta2.js} +2 -2
- package/dist/cdn/v2-react-beta2.js +3 -0
- package/dist/embed/index.d.ts +63 -3
- package/dist/embed/index.esm.js +3 -3
- package/dist/react/index.d.ts +32 -7
- package/dist/react/index.esm.js +804 -17
- package/package.json +2 -1
package/dist/react/index.esm.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
-
import
|
|
2
|
+
import { useState, useEffect, useCallback } from 'react';
|
|
3
3
|
import { createListenerMiddleware, createSlice, createAsyncThunk, createAction, createSelector, isAnyOf, combineReducers, configureStore } from '@reduxjs/toolkit';
|
|
4
4
|
import { io } from 'socket.io-client';
|
|
5
5
|
import adapter from 'webrtc-adapter';
|
|
@@ -85,19 +85,26 @@ function debounce(fn, { delay = 500, edges } = {}) {
|
|
|
85
85
|
}
|
|
86
86
|
|
|
87
87
|
var VideoView = (_a) => {
|
|
88
|
-
var { muted, mirror = false, stream, onResize } = _a, rest = __rest(_a, ["muted", "mirror", "stream", "onResize"]);
|
|
89
|
-
const videoEl = useRef(null);
|
|
90
|
-
useEffect(() => {
|
|
91
|
-
if (!videoEl.current
|
|
88
|
+
var { muted, mirror = false, stream, onResize, onSetAspectRatio } = _a, rest = __rest(_a, ["muted", "mirror", "stream", "onResize", "onSetAspectRatio"]);
|
|
89
|
+
const videoEl = React.useRef(null);
|
|
90
|
+
React.useEffect(() => {
|
|
91
|
+
if (!videoEl.current) {
|
|
92
92
|
return;
|
|
93
93
|
}
|
|
94
94
|
const resizeObserver = new ResizeObserver(debounce(() => {
|
|
95
95
|
if (videoEl.current && (stream === null || stream === void 0 ? void 0 : stream.id)) {
|
|
96
|
-
onResize
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
96
|
+
if (onResize) {
|
|
97
|
+
onResize({
|
|
98
|
+
width: videoEl.current.clientWidth,
|
|
99
|
+
height: videoEl.current.clientHeight,
|
|
100
|
+
stream,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
const h = videoEl.current.videoHeight;
|
|
104
|
+
const w = videoEl.current.videoWidth;
|
|
105
|
+
if (w && h && w + h > 20 && onSetAspectRatio) {
|
|
106
|
+
onSetAspectRatio({ aspectRatio: w / h });
|
|
107
|
+
}
|
|
101
108
|
}
|
|
102
109
|
}, { delay: 1000, edges: true }));
|
|
103
110
|
resizeObserver.observe(videoEl.current);
|
|
@@ -105,7 +112,7 @@ var VideoView = (_a) => {
|
|
|
105
112
|
resizeObserver.disconnect();
|
|
106
113
|
};
|
|
107
114
|
}, [stream]);
|
|
108
|
-
useEffect(() => {
|
|
115
|
+
React.useEffect(() => {
|
|
109
116
|
if (!videoEl.current) {
|
|
110
117
|
return;
|
|
111
118
|
}
|
|
@@ -118,7 +125,7 @@ var VideoView = (_a) => {
|
|
|
118
125
|
videoEl.current.muted = Boolean(muted);
|
|
119
126
|
}
|
|
120
127
|
}, [muted, stream, videoEl]);
|
|
121
|
-
return (
|
|
128
|
+
return (React.createElement("video", Object.assign({ ref: videoEl, autoPlay: true, playsInline: true }, rest, { style: Object.assign({ transform: mirror ? "scaleX(-1)" : "none", width: "100%", height: "100%" }, rest.style) })));
|
|
122
129
|
};
|
|
123
130
|
|
|
124
131
|
const listenerMiddleware = createListenerMiddleware();
|
|
@@ -1434,7 +1441,7 @@ const doStartLocalMedia = createAppAsyncThunk("localMedia/doStartLocalMedia", (p
|
|
|
1434
1441
|
const onDeviceChange = debounce(() => {
|
|
1435
1442
|
dispatch(doUpdateDeviceList());
|
|
1436
1443
|
}, { delay: 500 });
|
|
1437
|
-
if (
|
|
1444
|
+
if (navigator.mediaDevices) {
|
|
1438
1445
|
navigator.mediaDevices.addEventListener("devicechange", onDeviceChange);
|
|
1439
1446
|
}
|
|
1440
1447
|
// Resolve if existing stream is passed
|
|
@@ -1466,7 +1473,7 @@ const doStopLocalMedia = createAppThunk(() => (dispatch, getState) => {
|
|
|
1466
1473
|
stream === null || stream === void 0 ? void 0 : stream.getTracks().forEach((track) => {
|
|
1467
1474
|
track.stop();
|
|
1468
1475
|
});
|
|
1469
|
-
if (
|
|
1476
|
+
if (navigator.mediaDevices && onDeviceChange) {
|
|
1470
1477
|
navigator.mediaDevices.removeEventListener("devicechange", onDeviceChange);
|
|
1471
1478
|
}
|
|
1472
1479
|
dispatch(localMediaStopped());
|
|
@@ -7586,7 +7593,7 @@ const doRejectWaitingParticipant = createAppThunk((payload) => (dispatch, getSta
|
|
|
7586
7593
|
const selectWaitingParticipants = (state) => state.waitingParticipants.waitingParticipants;
|
|
7587
7594
|
|
|
7588
7595
|
var _a;
|
|
7589
|
-
const IS_DEV = (_a =
|
|
7596
|
+
const IS_DEV = (_a = undefined === "true") !== null && _a !== void 0 ? _a : false;
|
|
7590
7597
|
const rootReducer = combineReducers({
|
|
7591
7598
|
app: appSlice.reducer,
|
|
7592
7599
|
chat: chatSlice.reducer,
|
|
@@ -8683,7 +8690,7 @@ const selectRoomConnectionState = createSelector(selectChatMessages, selectCloud
|
|
|
8683
8690
|
return state;
|
|
8684
8691
|
});
|
|
8685
8692
|
|
|
8686
|
-
const sdkVersion = "2.
|
|
8693
|
+
const sdkVersion = "2.1.0-beta2";
|
|
8687
8694
|
|
|
8688
8695
|
const initialState$1 = {
|
|
8689
8696
|
chatMessages: [],
|
|
@@ -8835,4 +8842,784 @@ function useLocalMedia(optionsOrStream = { audio: true, video: true }) {
|
|
|
8835
8842
|
};
|
|
8836
8843
|
}
|
|
8837
8844
|
|
|
8838
|
-
|
|
8845
|
+
// Source of truth layout related constants
|
|
8846
|
+
const VIDEO_CONTROLS_MIN_WIDTH$1 = 7 * 60;
|
|
8847
|
+
var layoutConstants = {
|
|
8848
|
+
// Minimum window size before we start floating the toolbars
|
|
8849
|
+
MIN_WINDOW_HEIGHT: 320,
|
|
8850
|
+
MIN_WINDOW_WIDTH: 320,
|
|
8851
|
+
// Breakpoints
|
|
8852
|
+
DESKTOP_BREAKPOINT: 1025,
|
|
8853
|
+
TABLET_BREAKPOINT: 750,
|
|
8854
|
+
PHONE_BREAKPOINT: 500,
|
|
8855
|
+
// Room layout
|
|
8856
|
+
TOP_TOOLBAR_HEIGHT: 40 + 8 * 2,
|
|
8857
|
+
BOTTOM_TOOLBAR_HEIGHT: 70 + 4 * 3,
|
|
8858
|
+
SIDEBAR_WIDTH: 375,
|
|
8859
|
+
VIDEO_CONTROLS_MIN_WIDTH: VIDEO_CONTROLS_MIN_WIDTH$1,
|
|
8860
|
+
ROOM_FOOTER_MIN_WIDTH: 60 * 3 + VIDEO_CONTROLS_MIN_WIDTH$1,
|
|
8861
|
+
FLOATING_VIDEO_CONTROLS_BOTTOM_MARGIN: 20,
|
|
8862
|
+
WATERMARK_BAR_HEIGHT: 32,
|
|
8863
|
+
// Breakout stage (no active group)
|
|
8864
|
+
BREAKOUT_STAGE_BACKDROP_HEADER_HEIGHT: 20 + 8,
|
|
8865
|
+
BREAKOUT_STAGE_BACKDROP_FOOTER_HEIGHT: 8 + 40 + 8,
|
|
8866
|
+
// Subgrid
|
|
8867
|
+
SUBGRID_EMPTY_STAGE_MAX_WIDTH: 800,
|
|
8868
|
+
// Groups grid
|
|
8869
|
+
GROUPS_CELL_MARGIN: 8,
|
|
8870
|
+
GROUPS_CELL_PADDING: 12,
|
|
8871
|
+
GROUPS_CELL_NAV_HEIGHT: 48 + 8,
|
|
8872
|
+
GROUPS_CELL_AVATAR_WRAPPER_BOTTOM_MARGIN: 8,
|
|
8873
|
+
GROUPS_CELL_AVATAR_GRID_GAP: 8,
|
|
8874
|
+
GROUPS_CELL_MIN_WIDTH: 360,
|
|
8875
|
+
GROUPS_CELL_MAX_WIDTH: 600,
|
|
8876
|
+
// Groups table
|
|
8877
|
+
GROUPS_ROW_HEIGHT: 72,
|
|
8878
|
+
GROUPS_ROW_GAP: 1,
|
|
8879
|
+
// Foldable screen
|
|
8880
|
+
FOLDABLE_SCREEN_STAGE_PADDING: 8,
|
|
8881
|
+
};
|
|
8882
|
+
|
|
8883
|
+
function makeOrigin({ top = 0, left = 0 } = {}) {
|
|
8884
|
+
return {
|
|
8885
|
+
top,
|
|
8886
|
+
left,
|
|
8887
|
+
};
|
|
8888
|
+
}
|
|
8889
|
+
function makeBounds({ width = 0, height = 0 } = {}) {
|
|
8890
|
+
return {
|
|
8891
|
+
width: Math.max(width, 0),
|
|
8892
|
+
height: Math.max(height, 0),
|
|
8893
|
+
};
|
|
8894
|
+
}
|
|
8895
|
+
function makeFrame({ top = 0, left = 0, width = 0, height = 0 } = {}) {
|
|
8896
|
+
return {
|
|
8897
|
+
bounds: makeBounds({ width, height }),
|
|
8898
|
+
origin: makeOrigin({ top, left }),
|
|
8899
|
+
};
|
|
8900
|
+
}
|
|
8901
|
+
function makeBox({ top = 0, left = 0, bottom = 0, right = 0 } = {}) {
|
|
8902
|
+
return {
|
|
8903
|
+
top,
|
|
8904
|
+
left,
|
|
8905
|
+
bottom,
|
|
8906
|
+
right,
|
|
8907
|
+
};
|
|
8908
|
+
}
|
|
8909
|
+
|
|
8910
|
+
function fitToBounds(aspectRatio, containerSize) {
|
|
8911
|
+
const { width, height } = containerSize;
|
|
8912
|
+
const contentHeight = height;
|
|
8913
|
+
const contentWidth = contentHeight * aspectRatio;
|
|
8914
|
+
const scale = Math.min(width / contentWidth, height / contentHeight);
|
|
8915
|
+
const adjustedWidth = contentWidth * scale;
|
|
8916
|
+
const adjustedHeight = contentHeight * scale;
|
|
8917
|
+
return { width: adjustedWidth, height: adjustedHeight };
|
|
8918
|
+
}
|
|
8919
|
+
const cellContentArea = ({ width, height, rows, cols, aspectRatio, }) => {
|
|
8920
|
+
const bounds = fitToBounds(aspectRatio, { width: width / cols, height: height / rows });
|
|
8921
|
+
return Math.round(bounds.width * bounds.height);
|
|
8922
|
+
};
|
|
8923
|
+
const getWeightedSplitCount = ({ vertical, width, height, count, aspectRatio, }) => {
|
|
8924
|
+
// Calculate cell content areas for 1, 2 and 3 (columns|rows) layouts
|
|
8925
|
+
// and pick the largest one:
|
|
8926
|
+
const choices = [1, 2, 3].map((rowCols) => cellContentArea({
|
|
8927
|
+
width,
|
|
8928
|
+
height,
|
|
8929
|
+
rows: vertical ? Math.ceil(count / rowCols) : rowCols,
|
|
8930
|
+
cols: vertical ? rowCols : Math.ceil(count / rowCols),
|
|
8931
|
+
aspectRatio,
|
|
8932
|
+
}));
|
|
8933
|
+
const closest = Math.max(...choices);
|
|
8934
|
+
const splits = choices.indexOf(closest) + 1;
|
|
8935
|
+
return { splits, weight: closest };
|
|
8936
|
+
};
|
|
8937
|
+
const getGridSplits = ({ width, height, count, aspectRatio, }) => {
|
|
8938
|
+
// Try both vertical and horizontal layout and pick the one that yields the
|
|
8939
|
+
// biggest video cells:
|
|
8940
|
+
const verticalPick = getWeightedSplitCount({ vertical: true, width, height, count, aspectRatio });
|
|
8941
|
+
const horizontalPick = getWeightedSplitCount({ vertical: false, width, height, count, aspectRatio });
|
|
8942
|
+
if (verticalPick.weight > horizontalPick.weight) {
|
|
8943
|
+
return { splits: verticalPick.splits, vertical: true };
|
|
8944
|
+
}
|
|
8945
|
+
return { splits: horizontalPick.splits, vertical: false };
|
|
8946
|
+
};
|
|
8947
|
+
function getGridSizeForCount({ count, width, height, aspectRatio, }) {
|
|
8948
|
+
if (count <= 1) {
|
|
8949
|
+
return {
|
|
8950
|
+
rows: 1,
|
|
8951
|
+
cols: 1,
|
|
8952
|
+
};
|
|
8953
|
+
}
|
|
8954
|
+
const { splits, vertical } = getGridSplits({ width, height, count, aspectRatio });
|
|
8955
|
+
if (vertical) {
|
|
8956
|
+
return {
|
|
8957
|
+
rows: Math.ceil(count / splits),
|
|
8958
|
+
cols: splits,
|
|
8959
|
+
};
|
|
8960
|
+
}
|
|
8961
|
+
return {
|
|
8962
|
+
rows: splits,
|
|
8963
|
+
cols: Math.ceil(count / splits),
|
|
8964
|
+
};
|
|
8965
|
+
}
|
|
8966
|
+
|
|
8967
|
+
const WIDE_AR = 16 / 9;
|
|
8968
|
+
const NORMAL_AR = 4 / 3;
|
|
8969
|
+
const clamp = ({ value, min, max }) => Math.min(Math.max(value, min), max);
|
|
8970
|
+
function hasDuplicates(...array) {
|
|
8971
|
+
return new Set(array).size !== array.length;
|
|
8972
|
+
}
|
|
8973
|
+
function findMostCommon(arr) {
|
|
8974
|
+
return arr.sort((a, b) => arr.filter((v) => v === a).length - arr.filter((v) => v === b).length).pop();
|
|
8975
|
+
}
|
|
8976
|
+
// Grid cells are all the same aspect ratio (not to be confused with the video cells)
|
|
8977
|
+
// Pick the best ratio given a list of the video cell ratios:
|
|
8978
|
+
function pickCellAspectRatio({ choices = [] }) {
|
|
8979
|
+
// If all cells are the same aspect ratio use that:
|
|
8980
|
+
const minAr = Math.min(...choices);
|
|
8981
|
+
const maxAr = Math.max(...choices);
|
|
8982
|
+
let chosenAr = null;
|
|
8983
|
+
if (minAr === maxAr) {
|
|
8984
|
+
chosenAr = minAr;
|
|
8985
|
+
}
|
|
8986
|
+
else {
|
|
8987
|
+
// Otherwise we're in a mixed grid.
|
|
8988
|
+
// We ideally want to make the majority ratio look nice. Pick the most common
|
|
8989
|
+
// ratio but limit it to wide cells. If we don't have a majority choice
|
|
8990
|
+
// just go with the widest:
|
|
8991
|
+
const dominantAr = hasDuplicates(choices) ? findMostCommon(choices) : maxAr;
|
|
8992
|
+
chosenAr = clamp({ value: dominantAr || maxAr, min: NORMAL_AR, max: WIDE_AR });
|
|
8993
|
+
}
|
|
8994
|
+
return {
|
|
8995
|
+
minAr,
|
|
8996
|
+
maxAr,
|
|
8997
|
+
chosenAr,
|
|
8998
|
+
};
|
|
8999
|
+
}
|
|
9000
|
+
// Calculate how much we need to move the last row horizontally so it
|
|
9001
|
+
// becomes centered:
|
|
9002
|
+
function getCenterPadding({ rows, cols, cellWidth, index, cellCount, gridGap, }) {
|
|
9003
|
+
const max = rows * cols;
|
|
9004
|
+
const leftOver = max - cellCount;
|
|
9005
|
+
if (!leftOver) {
|
|
9006
|
+
return 0;
|
|
9007
|
+
}
|
|
9008
|
+
const lastIndex = max - leftOver - 1;
|
|
9009
|
+
const firstIndex = lastIndex - (cols - leftOver) + 1;
|
|
9010
|
+
const lastRowPadding = (leftOver * cellWidth) / 2 + gridGap;
|
|
9011
|
+
return index >= firstIndex && index <= lastIndex ? lastRowPadding : 0;
|
|
9012
|
+
}
|
|
9013
|
+
function getCellBounds({ width, height, rows, cols, gridGap, aspectRatio, }) {
|
|
9014
|
+
// Naively calculate the cell size based on grid and container size:
|
|
9015
|
+
const cellWidth = (width - (cols - 1) * gridGap) / cols;
|
|
9016
|
+
const cellHeight = (height - (rows - 1) * gridGap) / rows;
|
|
9017
|
+
const ar = cellWidth / cellHeight;
|
|
9018
|
+
// Knowing the target cell aspect ratio, pull any extra space
|
|
9019
|
+
// into the grid padding:
|
|
9020
|
+
let horizontalCorrection = 0;
|
|
9021
|
+
let verticalCorrection = 0;
|
|
9022
|
+
if (aspectRatio < ar) {
|
|
9023
|
+
horizontalCorrection = cellWidth - cellHeight * aspectRatio;
|
|
9024
|
+
}
|
|
9025
|
+
else if (aspectRatio > ar) {
|
|
9026
|
+
verticalCorrection = cellHeight - cellWidth / aspectRatio;
|
|
9027
|
+
}
|
|
9028
|
+
const totalHorizontalCorrection = horizontalCorrection * cols;
|
|
9029
|
+
const totalVerticalCorrection = verticalCorrection * rows;
|
|
9030
|
+
return {
|
|
9031
|
+
cellWidth: cellWidth - horizontalCorrection,
|
|
9032
|
+
cellHeight: cellHeight - verticalCorrection,
|
|
9033
|
+
extraHorizontalPadding: totalHorizontalCorrection / 2,
|
|
9034
|
+
extraVerticalPadding: totalVerticalCorrection / 2,
|
|
9035
|
+
};
|
|
9036
|
+
}
|
|
9037
|
+
function calculateLayout$1({ width, height, cellCount, gridGap, cellAspectRatios = [NORMAL_AR], paddings = makeBox(), }) {
|
|
9038
|
+
// Handle empty grid:
|
|
9039
|
+
if (!cellCount) {
|
|
9040
|
+
return {
|
|
9041
|
+
cellCount,
|
|
9042
|
+
cellHeight: 0,
|
|
9043
|
+
cellWidth: 0,
|
|
9044
|
+
cols: 0,
|
|
9045
|
+
rows: 0,
|
|
9046
|
+
extraHorizontalPadding: 0,
|
|
9047
|
+
extraVerticalPadding: 0,
|
|
9048
|
+
gridGap,
|
|
9049
|
+
paddings,
|
|
9050
|
+
};
|
|
9051
|
+
}
|
|
9052
|
+
const contentWidth = width - (paddings.left + paddings.right);
|
|
9053
|
+
const contentHeight = height - (paddings.top + paddings.bottom);
|
|
9054
|
+
const cellAspectRatioTuple = pickCellAspectRatio({
|
|
9055
|
+
choices: cellAspectRatios,
|
|
9056
|
+
});
|
|
9057
|
+
let cellAspectRatio = cellAspectRatioTuple.chosenAr;
|
|
9058
|
+
const { rows, cols } = getGridSizeForCount({
|
|
9059
|
+
count: cellCount,
|
|
9060
|
+
width: contentWidth,
|
|
9061
|
+
height: contentHeight,
|
|
9062
|
+
aspectRatio: cellAspectRatio,
|
|
9063
|
+
});
|
|
9064
|
+
// Special case 1 col / row:
|
|
9065
|
+
// Divvy up available all space (within reason)
|
|
9066
|
+
if (rows === 1) {
|
|
9067
|
+
cellAspectRatio = clamp({
|
|
9068
|
+
value: contentWidth / cols / contentHeight,
|
|
9069
|
+
min: Math.min(cellAspectRatioTuple.chosenAr, cellAspectRatioTuple.maxAr),
|
|
9070
|
+
max: Math.max(cellAspectRatioTuple.chosenAr, cellAspectRatioTuple.maxAr),
|
|
9071
|
+
});
|
|
9072
|
+
}
|
|
9073
|
+
else if (cols === 1) {
|
|
9074
|
+
cellAspectRatio = clamp({
|
|
9075
|
+
value: contentWidth / (contentHeight / rows),
|
|
9076
|
+
min: Math.min(cellAspectRatioTuple.chosenAr, cellAspectRatioTuple.maxAr),
|
|
9077
|
+
max: Math.max(cellAspectRatioTuple.chosenAr, cellAspectRatioTuple.maxAr),
|
|
9078
|
+
});
|
|
9079
|
+
}
|
|
9080
|
+
const { cellWidth, cellHeight, extraHorizontalPadding, extraVerticalPadding } = getCellBounds({
|
|
9081
|
+
width: contentWidth,
|
|
9082
|
+
height: contentHeight,
|
|
9083
|
+
rows,
|
|
9084
|
+
cols,
|
|
9085
|
+
gridGap,
|
|
9086
|
+
aspectRatio: cellAspectRatio,
|
|
9087
|
+
});
|
|
9088
|
+
return {
|
|
9089
|
+
cellCount,
|
|
9090
|
+
cellHeight,
|
|
9091
|
+
cellWidth,
|
|
9092
|
+
cols,
|
|
9093
|
+
rows,
|
|
9094
|
+
extraHorizontalPadding,
|
|
9095
|
+
extraVerticalPadding,
|
|
9096
|
+
// pass through
|
|
9097
|
+
gridGap,
|
|
9098
|
+
paddings,
|
|
9099
|
+
};
|
|
9100
|
+
}
|
|
9101
|
+
function getCellPropsAtIndexForLayout({ index, layout, }) {
|
|
9102
|
+
const { cellWidth, cellHeight, rows, cols, cellCount, gridGap } = layout;
|
|
9103
|
+
const top = Math.floor(index / cols);
|
|
9104
|
+
const left = Math.floor(index % cols);
|
|
9105
|
+
const leftPadding = getCenterPadding({ rows, cols, cellWidth, index, cellCount, gridGap });
|
|
9106
|
+
return {
|
|
9107
|
+
top: top * cellHeight + top * gridGap,
|
|
9108
|
+
left: left * cellWidth + left * gridGap + leftPadding,
|
|
9109
|
+
width: cellWidth,
|
|
9110
|
+
height: cellHeight,
|
|
9111
|
+
};
|
|
9112
|
+
}
|
|
9113
|
+
|
|
9114
|
+
const { BOTTOM_TOOLBAR_HEIGHT, VIDEO_CONTROLS_MIN_WIDTH, TABLET_BREAKPOINT } = layoutConstants;
|
|
9115
|
+
const MIN_GRID_HEIGHT = 200;
|
|
9116
|
+
const MIN_GRID_WIDTH = 300;
|
|
9117
|
+
const FLOATING_VIDEO_SIZE = 200;
|
|
9118
|
+
const CONSTRAINED_OVERFLOW_TRIGGER = 12;
|
|
9119
|
+
function getMinGridBounds({ cellCount }) {
|
|
9120
|
+
// Reduce min grid dimensions if we have 6 videos or less
|
|
9121
|
+
const isSmallGrid = cellCount <= 6;
|
|
9122
|
+
const minGridHeight = isSmallGrid ? MIN_GRID_HEIGHT - 50 : MIN_GRID_HEIGHT;
|
|
9123
|
+
const minGridWidth = isSmallGrid ? MIN_GRID_WIDTH - 50 : MIN_GRID_WIDTH;
|
|
9124
|
+
return makeBounds({ width: minGridWidth, height: minGridHeight });
|
|
9125
|
+
}
|
|
9126
|
+
function fitSupersizedContent({ bounds, aspectRatio, minGridContainerBounds, hasPresentationGrid, }) {
|
|
9127
|
+
const { width, height } = bounds;
|
|
9128
|
+
// If we don't have any grids take up whole stage
|
|
9129
|
+
const hasVideoGrid = minGridContainerBounds.width > 0;
|
|
9130
|
+
if (!hasVideoGrid) {
|
|
9131
|
+
return {
|
|
9132
|
+
isPortrait: width <= height,
|
|
9133
|
+
supersizedContentBounds: bounds,
|
|
9134
|
+
};
|
|
9135
|
+
}
|
|
9136
|
+
// Calculate minimum supersized content bounds - take up at least half the
|
|
9137
|
+
// available area:
|
|
9138
|
+
const minHorizontalSupersizedContentWidth = Math.round(width / 2);
|
|
9139
|
+
const minVerticalSupersizedContentHeight = Math.round(height / 2);
|
|
9140
|
+
// Calculate maximum supersized content bounds
|
|
9141
|
+
const maxHorizontalSupersizedContentWidth = Math.max(width - minGridContainerBounds.width, 0);
|
|
9142
|
+
const maxVerticalSupersizedContentHeight = Math.max(height - minGridContainerBounds.height, 0);
|
|
9143
|
+
let isPortrait = maxHorizontalSupersizedContentWidth <= maxVerticalSupersizedContentHeight;
|
|
9144
|
+
let horizontalCorrection = 0;
|
|
9145
|
+
let verticalCorrection = 0;
|
|
9146
|
+
// Do we have an aspect ratio? If not give up all available space (ex some integrations)
|
|
9147
|
+
if (aspectRatio) {
|
|
9148
|
+
// Calculate fit bounds for both portrait and landscape layouts:
|
|
9149
|
+
// 1. grid to the left of content
|
|
9150
|
+
const horizontalContentBounds = fitToBounds(aspectRatio, {
|
|
9151
|
+
width: maxHorizontalSupersizedContentWidth,
|
|
9152
|
+
height,
|
|
9153
|
+
});
|
|
9154
|
+
// 2. grid below content
|
|
9155
|
+
const verticalContentBounds = fitToBounds(aspectRatio, {
|
|
9156
|
+
width,
|
|
9157
|
+
height: maxVerticalSupersizedContentHeight,
|
|
9158
|
+
});
|
|
9159
|
+
// Pick direction that gives content most space:
|
|
9160
|
+
const isPortraitContent = aspectRatio <= 1.0;
|
|
9161
|
+
isPortrait = isPortraitContent
|
|
9162
|
+
? verticalContentBounds.height > horizontalContentBounds.height
|
|
9163
|
+
: verticalContentBounds.width > horizontalContentBounds.width;
|
|
9164
|
+
// Give wasted space back to the video grid:
|
|
9165
|
+
if (isPortrait) {
|
|
9166
|
+
const wastedSpace = maxVerticalSupersizedContentHeight -
|
|
9167
|
+
Math.max(verticalContentBounds.height, minVerticalSupersizedContentHeight);
|
|
9168
|
+
verticalCorrection = Math.max(wastedSpace, 0);
|
|
9169
|
+
}
|
|
9170
|
+
else {
|
|
9171
|
+
const wastedSpace = maxHorizontalSupersizedContentWidth -
|
|
9172
|
+
Math.max(horizontalContentBounds.width, minHorizontalSupersizedContentWidth);
|
|
9173
|
+
horizontalCorrection = Math.max(wastedSpace, 0);
|
|
9174
|
+
}
|
|
9175
|
+
}
|
|
9176
|
+
else if (hasPresentationGrid) {
|
|
9177
|
+
// If we have more than one presentation grid cell we naively favor portrait orientation
|
|
9178
|
+
// unless it gets too squished:
|
|
9179
|
+
isPortrait = maxHorizontalSupersizedContentWidth / maxVerticalSupersizedContentHeight >= 5;
|
|
9180
|
+
}
|
|
9181
|
+
const supersizedContentBounds = {
|
|
9182
|
+
width: isPortrait ? width : maxHorizontalSupersizedContentWidth - horizontalCorrection,
|
|
9183
|
+
height: isPortrait ? maxVerticalSupersizedContentHeight - verticalCorrection : height,
|
|
9184
|
+
};
|
|
9185
|
+
return {
|
|
9186
|
+
isPortrait,
|
|
9187
|
+
supersizedContentBounds,
|
|
9188
|
+
};
|
|
9189
|
+
}
|
|
9190
|
+
// The stage layout is the base room layout
|
|
9191
|
+
// It divides the stage area between a videos container (made up of video grid +
|
|
9192
|
+
// presentation grid)
|
|
9193
|
+
function calculateStageLayout({ containerBounds, containerOrigin, hasConstrainedOverflow, hasPresentationContent, hasVideoContent, isPortrait, }) {
|
|
9194
|
+
const hasVideos = hasPresentationContent || hasVideoContent;
|
|
9195
|
+
// Sanity checks
|
|
9196
|
+
// Do we have anything to calculate?
|
|
9197
|
+
if (!hasVideos) {
|
|
9198
|
+
return {
|
|
9199
|
+
isPortrait,
|
|
9200
|
+
videosContainer: makeFrame(),
|
|
9201
|
+
hasOverflow: false,
|
|
9202
|
+
};
|
|
9203
|
+
}
|
|
9204
|
+
return {
|
|
9205
|
+
isPortrait,
|
|
9206
|
+
videosContainer: makeFrame(Object.assign(Object.assign({}, containerBounds), containerOrigin)),
|
|
9207
|
+
hasOverflow: hasConstrainedOverflow,
|
|
9208
|
+
};
|
|
9209
|
+
}
|
|
9210
|
+
function calculateVideosContainerLayout({ containerBounds, containerOrigin, gridGap, supersizedContentAspectRatio, hasPresentationContent, hasPresentationGrid, hasVideoContent, minGridBounds, }) {
|
|
9211
|
+
const { width, height } = containerBounds;
|
|
9212
|
+
let isPortrait = width <= height;
|
|
9213
|
+
let presentationGridBounds = makeBounds();
|
|
9214
|
+
let presentationGridOrigin = makeOrigin();
|
|
9215
|
+
let videoGridBounds = hasVideoContent ? Object.assign({}, containerBounds) : makeBounds();
|
|
9216
|
+
let videoGridOrigin = hasVideoContent ? Object.assign({}, containerOrigin) : makeOrigin();
|
|
9217
|
+
if (hasPresentationContent) {
|
|
9218
|
+
// Fit supersized content
|
|
9219
|
+
const minGridContainerBounds = makeBounds({
|
|
9220
|
+
width: hasVideoContent ? minGridBounds.width + gridGap : 0,
|
|
9221
|
+
height: hasVideoContent ? minGridBounds.height + gridGap : 0,
|
|
9222
|
+
});
|
|
9223
|
+
const supersizedContentLayout = fitSupersizedContent({
|
|
9224
|
+
bounds: containerBounds,
|
|
9225
|
+
aspectRatio: supersizedContentAspectRatio,
|
|
9226
|
+
minGridContainerBounds,
|
|
9227
|
+
hasPresentationGrid,
|
|
9228
|
+
});
|
|
9229
|
+
isPortrait = supersizedContentLayout.isPortrait;
|
|
9230
|
+
presentationGridBounds = supersizedContentLayout.supersizedContentBounds;
|
|
9231
|
+
presentationGridOrigin = Object.assign({}, containerOrigin);
|
|
9232
|
+
if (hasVideoContent) {
|
|
9233
|
+
videoGridBounds = makeBounds({
|
|
9234
|
+
width: isPortrait
|
|
9235
|
+
? containerBounds.width
|
|
9236
|
+
: containerBounds.width - presentationGridBounds.width - gridGap,
|
|
9237
|
+
height: isPortrait
|
|
9238
|
+
? containerBounds.height - presentationGridBounds.height - gridGap
|
|
9239
|
+
: containerBounds.height,
|
|
9240
|
+
});
|
|
9241
|
+
videoGridOrigin = makeOrigin({
|
|
9242
|
+
top: isPortrait ? containerOrigin.top + presentationGridBounds.height + gridGap : containerOrigin.top,
|
|
9243
|
+
left: isPortrait ? containerOrigin.left : containerOrigin.left + presentationGridBounds.width + gridGap,
|
|
9244
|
+
});
|
|
9245
|
+
}
|
|
9246
|
+
}
|
|
9247
|
+
return {
|
|
9248
|
+
isPortrait,
|
|
9249
|
+
presentationGrid: Object.assign({}, makeFrame(Object.assign(Object.assign({}, presentationGridBounds), presentationGridOrigin))),
|
|
9250
|
+
videoGrid: makeFrame(Object.assign(Object.assign({}, videoGridBounds), videoGridOrigin)),
|
|
9251
|
+
};
|
|
9252
|
+
}
|
|
9253
|
+
function calculateGridLayout({ containerBounds, paddings = makeBox(), videos, isConstrained, maxGridWidth, gridGap, }) {
|
|
9254
|
+
const { width, height } = containerBounds;
|
|
9255
|
+
const cappedWidth = maxGridWidth ? Math.min(width, maxGridWidth) : width;
|
|
9256
|
+
const cellCount = videos.length;
|
|
9257
|
+
let videoCells = null;
|
|
9258
|
+
const cellAspectRatios = videos.map((video) => video.aspectRatio);
|
|
9259
|
+
const minGridBounds = getMinGridBounds({ cellCount });
|
|
9260
|
+
// Cap grid to a sane width (on very wide monitors)
|
|
9261
|
+
const gridLayout = calculateLayout$1({
|
|
9262
|
+
width: cappedWidth,
|
|
9263
|
+
height,
|
|
9264
|
+
cellCount,
|
|
9265
|
+
gridGap,
|
|
9266
|
+
cellAspectRatios,
|
|
9267
|
+
paddings,
|
|
9268
|
+
});
|
|
9269
|
+
videoCells = videos.map((video, index) => {
|
|
9270
|
+
const cellProps = getCellPropsAtIndexForLayout({ index, layout: gridLayout });
|
|
9271
|
+
const isSmallCell = gridLayout.cellWidth < minGridBounds.width;
|
|
9272
|
+
const shouldZoom = isConstrained || isSmallCell;
|
|
9273
|
+
const aspectRatio = shouldZoom ? gridLayout.cellWidth / gridLayout.cellHeight : video.aspectRatio;
|
|
9274
|
+
return {
|
|
9275
|
+
clientId: video.clientId,
|
|
9276
|
+
isDraggable: video.isDraggable,
|
|
9277
|
+
origin: makeOrigin({
|
|
9278
|
+
top: cellProps.top,
|
|
9279
|
+
left: cellProps.left,
|
|
9280
|
+
}),
|
|
9281
|
+
bounds: makeBounds({
|
|
9282
|
+
width: cellProps.width,
|
|
9283
|
+
height: cellProps.height,
|
|
9284
|
+
}),
|
|
9285
|
+
aspectRatio,
|
|
9286
|
+
isSmallCell,
|
|
9287
|
+
};
|
|
9288
|
+
});
|
|
9289
|
+
return {
|
|
9290
|
+
videoCells,
|
|
9291
|
+
extraHorizontalPadding:
|
|
9292
|
+
// If we hit the max width, pass up as extra space
|
|
9293
|
+
width !== cappedWidth
|
|
9294
|
+
? gridLayout.extraHorizontalPadding + (width - cappedWidth) / 2
|
|
9295
|
+
: gridLayout.extraHorizontalPadding,
|
|
9296
|
+
extraVerticalPadding: gridLayout.extraVerticalPadding,
|
|
9297
|
+
paddings: gridLayout.paddings,
|
|
9298
|
+
gridGap,
|
|
9299
|
+
};
|
|
9300
|
+
}
|
|
9301
|
+
function calculateFloatingLayout({ roomBounds, containerFrame, floatingVideo, videoControlsHeight, margin = 8, }) {
|
|
9302
|
+
if (!floatingVideo) {
|
|
9303
|
+
return null;
|
|
9304
|
+
}
|
|
9305
|
+
const bounds = fitToBounds(floatingVideo.aspectRatio, {
|
|
9306
|
+
width: FLOATING_VIDEO_SIZE,
|
|
9307
|
+
height: FLOATING_VIDEO_SIZE,
|
|
9308
|
+
});
|
|
9309
|
+
// Determine if we should position above the video controls or not
|
|
9310
|
+
const isFloating = !(roomBounds.height - containerFrame.bounds.height - containerFrame.origin.top);
|
|
9311
|
+
const isConstrained = containerFrame.bounds.width - (bounds.width + margin) * 2 < VIDEO_CONTROLS_MIN_WIDTH;
|
|
9312
|
+
let verticalOffset = 0;
|
|
9313
|
+
if (isFloating && isConstrained) {
|
|
9314
|
+
// Pull up above floating video controls
|
|
9315
|
+
verticalOffset = videoControlsHeight * -1;
|
|
9316
|
+
}
|
|
9317
|
+
else if (!isFloating && !isConstrained) {
|
|
9318
|
+
// Push down over the bottom toolbar
|
|
9319
|
+
verticalOffset = videoControlsHeight;
|
|
9320
|
+
}
|
|
9321
|
+
const origin = makeOrigin({
|
|
9322
|
+
top: containerFrame.origin.top + (containerFrame.bounds.height - bounds.height - margin) + verticalOffset,
|
|
9323
|
+
left: containerFrame.origin.left + (containerFrame.bounds.width - bounds.width - margin),
|
|
9324
|
+
});
|
|
9325
|
+
const videoCell = {
|
|
9326
|
+
clientId: floatingVideo.clientId,
|
|
9327
|
+
isDraggable: floatingVideo.isDraggable,
|
|
9328
|
+
origin,
|
|
9329
|
+
bounds,
|
|
9330
|
+
aspectRatio: floatingVideo.aspectRatio,
|
|
9331
|
+
isSmallCell: true,
|
|
9332
|
+
};
|
|
9333
|
+
return videoCell;
|
|
9334
|
+
}
|
|
9335
|
+
function rebalanceLayoutPaddedAreas({ a, b, gridGap, isPortrait, }) {
|
|
9336
|
+
const aPad = isPortrait ? a.vertical : a.horizontal;
|
|
9337
|
+
const bPad = isPortrait ? b.vertical : b.horizontal;
|
|
9338
|
+
if (aPad === bPad) {
|
|
9339
|
+
return { a: 0, b: 0 };
|
|
9340
|
+
}
|
|
9341
|
+
const sArea = aPad < bPad ? a : b;
|
|
9342
|
+
const sAreaPad = isPortrait ? sArea.vertical : sArea.horizontal;
|
|
9343
|
+
const spaceBetween = gridGap + (aPad + bPad);
|
|
9344
|
+
const offset = (spaceBetween + sAreaPad) / 2 - sAreaPad;
|
|
9345
|
+
return {
|
|
9346
|
+
a: sArea === a ? offset : 0,
|
|
9347
|
+
b: sArea === b ? offset : 0,
|
|
9348
|
+
};
|
|
9349
|
+
}
|
|
9350
|
+
function rebalanceLayoutInPlace({ videosContainerLayout, gridLayout, presentationGridLayout, gridGap, }) {
|
|
9351
|
+
const hasPresentationGrid = videosContainerLayout.presentationGrid.bounds.width > 0;
|
|
9352
|
+
const hasVideoGrid = videosContainerLayout.videoGrid.bounds.width > 0;
|
|
9353
|
+
// Rebalance video containers if we have both presentationGrid and videoGrid bounds,
|
|
9354
|
+
// unless we have a breakout no group stage:
|
|
9355
|
+
if (hasPresentationGrid && hasVideoGrid) {
|
|
9356
|
+
const correction = rebalanceLayoutPaddedAreas({
|
|
9357
|
+
a: {
|
|
9358
|
+
horizontal: presentationGridLayout.extraHorizontalPadding,
|
|
9359
|
+
vertical: presentationGridLayout.extraVerticalPadding,
|
|
9360
|
+
},
|
|
9361
|
+
b: {
|
|
9362
|
+
horizontal: gridLayout.extraHorizontalPadding,
|
|
9363
|
+
vertical: gridLayout.extraVerticalPadding,
|
|
9364
|
+
},
|
|
9365
|
+
gridGap,
|
|
9366
|
+
isPortrait: videosContainerLayout.isPortrait,
|
|
9367
|
+
});
|
|
9368
|
+
// Update in place:
|
|
9369
|
+
if (videosContainerLayout.isPortrait) {
|
|
9370
|
+
videosContainerLayout.presentationGrid.origin.top += correction.a;
|
|
9371
|
+
videosContainerLayout.videoGrid.origin.top -= correction.b;
|
|
9372
|
+
// Save off how much we moved the grid over to be used in the next phase:
|
|
9373
|
+
correction.b;
|
|
9374
|
+
}
|
|
9375
|
+
else {
|
|
9376
|
+
videosContainerLayout.presentationGrid.origin.left += correction.a;
|
|
9377
|
+
videosContainerLayout.videoGrid.origin.left -= correction.b;
|
|
9378
|
+
// Save off how much we moved the grid over to be used in the next phase:
|
|
9379
|
+
correction.b;
|
|
9380
|
+
}
|
|
9381
|
+
}
|
|
9382
|
+
}
|
|
9383
|
+
function calculateGridLayouts({ gridGap, isConstrained, presentationVideos, videos, videosContainerLayout, gridLayoutPaddings = makeBox(), presentationGridLayoutPaddings = makeBox(), maxGridWidth, }) {
|
|
9384
|
+
// Lay out video cells in provided video containers:
|
|
9385
|
+
const gridLayout = calculateGridLayout({
|
|
9386
|
+
containerBounds: videosContainerLayout.videoGrid.bounds,
|
|
9387
|
+
gridGap,
|
|
9388
|
+
isConstrained,
|
|
9389
|
+
maxGridWidth,
|
|
9390
|
+
paddings: gridLayoutPaddings,
|
|
9391
|
+
videos,
|
|
9392
|
+
});
|
|
9393
|
+
const presentationGridLayout = calculateGridLayout({
|
|
9394
|
+
containerBounds: videosContainerLayout.presentationGrid.bounds,
|
|
9395
|
+
gridGap,
|
|
9396
|
+
isConstrained,
|
|
9397
|
+
maxGridWidth,
|
|
9398
|
+
paddings: presentationGridLayoutPaddings,
|
|
9399
|
+
videos: presentationVideos,
|
|
9400
|
+
});
|
|
9401
|
+
return { gridLayout, presentationGridLayout };
|
|
9402
|
+
}
|
|
9403
|
+
function calculateLayout({ floatingVideo = null, frame, gridGap = 0, isConstrained = false, isMaximizeMode = false, paddings = makeBox(), presentationVideos = [], rebalanceLayout = false, roomBounds, roomLayoutHasOverlow = false, videoControlsHeight = 0, videos = [], videoGridGap = 0, }) {
|
|
9404
|
+
const hasPresentationContent = !!presentationVideos.length;
|
|
9405
|
+
const hasPresentationGrid = presentationVideos.length > 1;
|
|
9406
|
+
const supersizedContentAspectRatio = hasPresentationContent && !hasPresentationGrid ? presentationVideos[0].aspectRatio : 1;
|
|
9407
|
+
const hasVideoContent = !!videos.length;
|
|
9408
|
+
const width = frame.bounds.width - paddings.left - paddings.right;
|
|
9409
|
+
let height = frame.bounds.height - paddings.top - paddings.bottom;
|
|
9410
|
+
const maxGridWidth = Math.max(25 * 88, (80 / 100) * width); // go up to 80vw after a sane max width
|
|
9411
|
+
// On mobile, we set a hard limit on 12 videos, and overflows after that.
|
|
9412
|
+
const hasConstrainedOverflow = (isConstrained && videos.length > CONSTRAINED_OVERFLOW_TRIGGER) || false;
|
|
9413
|
+
const lineHeight = height / 4;
|
|
9414
|
+
const extraLines = Math.ceil((videos.length - CONSTRAINED_OVERFLOW_TRIGGER) / 3);
|
|
9415
|
+
height = hasConstrainedOverflow ? height + lineHeight * extraLines : height;
|
|
9416
|
+
const stageBounds = makeBounds({ width, height });
|
|
9417
|
+
const stageOrigin = makeOrigin({ top: paddings.top, left: paddings.left });
|
|
9418
|
+
const _minBounds = getMinGridBounds({ cellCount: videos.length });
|
|
9419
|
+
const minGridBounds = _minBounds;
|
|
9420
|
+
const isSmallScreen = roomBounds.width < TABLET_BREAKPOINT || roomBounds.height < TABLET_BREAKPOINT;
|
|
9421
|
+
const forceStageLayoutPortrait = isMaximizeMode;
|
|
9422
|
+
const stageLayoutIsPortrait = forceStageLayoutPortrait ||
|
|
9423
|
+
!(hasPresentationContent || hasVideoContent) ||
|
|
9424
|
+
stageBounds.width <= stageBounds.height;
|
|
9425
|
+
const stableStageLayoutProps = {
|
|
9426
|
+
cellPaddings: { top: 4, left: 4, bottom: 4, right: 4 },
|
|
9427
|
+
containerBounds: stageBounds,
|
|
9428
|
+
containerOrigin: stageOrigin,
|
|
9429
|
+
gridGap,
|
|
9430
|
+
hasPresentationContent,
|
|
9431
|
+
hasVideoContent,
|
|
9432
|
+
isConstrained,
|
|
9433
|
+
isMaximizeMode,
|
|
9434
|
+
isSmallScreen,
|
|
9435
|
+
maxGridWidth,
|
|
9436
|
+
};
|
|
9437
|
+
let stageLayout = calculateStageLayout(Object.assign(Object.assign({}, stableStageLayoutProps), { isPortrait: stageLayoutIsPortrait, hasConstrainedOverflow }));
|
|
9438
|
+
// Prevent yo-yo-ing between overflow and non overflow states:
|
|
9439
|
+
// - if we're not in a forced overflow state and main room layout has overflow already (prev we could not fit) and now we can fit,
|
|
9440
|
+
// - double check by re-running the stage layout with the non overflow bounds:
|
|
9441
|
+
let forceRerunAsOverflow = false;
|
|
9442
|
+
if (roomLayoutHasOverlow && !stageLayout.hasOverflow) {
|
|
9443
|
+
const _stageLayout = calculateStageLayout(Object.assign(Object.assign({}, stableStageLayoutProps), { containerBounds: makeBounds({
|
|
9444
|
+
width: stageBounds.width,
|
|
9445
|
+
height: stageBounds.height - BOTTOM_TOOLBAR_HEIGHT, // override "stable" prop
|
|
9446
|
+
}), isPortrait: stageLayoutIsPortrait, hasConstrainedOverflow }));
|
|
9447
|
+
// If it turns out we can't fit, force re-layout as overflow:
|
|
9448
|
+
if (_stageLayout.hasOverflow) {
|
|
9449
|
+
forceRerunAsOverflow = true;
|
|
9450
|
+
}
|
|
9451
|
+
}
|
|
9452
|
+
// If subgrid cannot fit, re-run the stage layout in overflow:
|
|
9453
|
+
if (forceRerunAsOverflow || stageLayout.hasOverflow) {
|
|
9454
|
+
stageLayout = calculateStageLayout(Object.assign(Object.assign({}, stableStageLayoutProps), { isPortrait: true, hasConstrainedOverflow }));
|
|
9455
|
+
}
|
|
9456
|
+
const videosContainerLayout = calculateVideosContainerLayout({
|
|
9457
|
+
containerBounds: stageLayout.videosContainer.bounds,
|
|
9458
|
+
containerOrigin: stageLayout.videosContainer.origin,
|
|
9459
|
+
gridGap,
|
|
9460
|
+
supersizedContentAspectRatio,
|
|
9461
|
+
hasPresentationContent,
|
|
9462
|
+
hasPresentationGrid,
|
|
9463
|
+
hasVideoContent,
|
|
9464
|
+
minGridBounds,
|
|
9465
|
+
});
|
|
9466
|
+
const { gridLayout, presentationGridLayout } = calculateGridLayouts({
|
|
9467
|
+
gridGap: videoGridGap,
|
|
9468
|
+
isConstrained,
|
|
9469
|
+
presentationVideos,
|
|
9470
|
+
videos,
|
|
9471
|
+
videosContainerLayout,
|
|
9472
|
+
maxGridWidth,
|
|
9473
|
+
});
|
|
9474
|
+
const floatingLayout = calculateFloatingLayout({
|
|
9475
|
+
roomBounds,
|
|
9476
|
+
containerFrame: frame,
|
|
9477
|
+
floatingVideo,
|
|
9478
|
+
videoControlsHeight,
|
|
9479
|
+
});
|
|
9480
|
+
// Nudge containers closer to each other to get pleasing layouts with less extreme
|
|
9481
|
+
// negative space. It's opt in because debugging is a lot easier with this behavior off:
|
|
9482
|
+
if (rebalanceLayout) {
|
|
9483
|
+
rebalanceLayoutInPlace({
|
|
9484
|
+
videosContainerLayout,
|
|
9485
|
+
gridLayout,
|
|
9486
|
+
presentationGridLayout,
|
|
9487
|
+
gridGap,
|
|
9488
|
+
});
|
|
9489
|
+
}
|
|
9490
|
+
return {
|
|
9491
|
+
isPortrait: stageLayout.isPortrait,
|
|
9492
|
+
hasOverflow: stageLayout.hasOverflow,
|
|
9493
|
+
bounds: makeBounds({
|
|
9494
|
+
height: frame.bounds.height,
|
|
9495
|
+
width: frame.bounds.width,
|
|
9496
|
+
}),
|
|
9497
|
+
gridGap,
|
|
9498
|
+
presentationGrid: Object.assign(Object.assign({}, videosContainerLayout.presentationGrid), { cells: presentationGridLayout.videoCells, paddings: makeBox({
|
|
9499
|
+
top: presentationGridLayout.paddings.top + presentationGridLayout.extraVerticalPadding,
|
|
9500
|
+
bottom: presentationGridLayout.paddings.bottom + presentationGridLayout.extraVerticalPadding,
|
|
9501
|
+
left: presentationGridLayout.paddings.left + presentationGridLayout.extraHorizontalPadding,
|
|
9502
|
+
right: presentationGridLayout.paddings.right + presentationGridLayout.extraHorizontalPadding,
|
|
9503
|
+
}) }),
|
|
9504
|
+
videoGrid: Object.assign(Object.assign({}, videosContainerLayout.videoGrid), { cells: gridLayout.videoCells, paddings: makeBox({
|
|
9505
|
+
top: gridLayout.paddings.top + gridLayout.extraVerticalPadding,
|
|
9506
|
+
bottom: gridLayout.paddings.bottom + gridLayout.extraVerticalPadding,
|
|
9507
|
+
left: gridLayout.paddings.left + gridLayout.extraHorizontalPadding,
|
|
9508
|
+
right: gridLayout.paddings.right + gridLayout.extraHorizontalPadding,
|
|
9509
|
+
}) }),
|
|
9510
|
+
floatingContent: Object.assign(Object.assign({}, floatingLayout), floatingVideo),
|
|
9511
|
+
};
|
|
9512
|
+
}
|
|
9513
|
+
|
|
9514
|
+
function makeVideoCellView({ aspectRatio, avatarSize, cellPaddings, client = undefined, isDraggable = true, isPlaceholder = false, isSubgrid = false, }) {
|
|
9515
|
+
return {
|
|
9516
|
+
aspectRatio: aspectRatio || 16 / 9,
|
|
9517
|
+
avatarSize,
|
|
9518
|
+
cellPaddings,
|
|
9519
|
+
client,
|
|
9520
|
+
clientId: (client === null || client === void 0 ? void 0 : client.id) || "",
|
|
9521
|
+
isDraggable,
|
|
9522
|
+
isPlaceholder,
|
|
9523
|
+
isSubgrid,
|
|
9524
|
+
type: "video",
|
|
9525
|
+
};
|
|
9526
|
+
}
|
|
9527
|
+
|
|
9528
|
+
function GridVideoCellView({ cell, participant, render, onSetAspectRatio, onResize, }) {
|
|
9529
|
+
const handleAspectRatioChange = React.useCallback(({ ar }) => {
|
|
9530
|
+
if (ar !== cell.aspectRatio) {
|
|
9531
|
+
onSetAspectRatio({ aspectRatio: ar });
|
|
9532
|
+
}
|
|
9533
|
+
}, [cell.aspectRatio, onSetAspectRatio]);
|
|
9534
|
+
return (React.createElement("div", { style: {
|
|
9535
|
+
position: "absolute",
|
|
9536
|
+
width: cell.bounds.width,
|
|
9537
|
+
height: cell.bounds.height,
|
|
9538
|
+
boxSizing: "border-box",
|
|
9539
|
+
top: cell.origin.top,
|
|
9540
|
+
left: cell.origin.left,
|
|
9541
|
+
} }, render ? (render()) : participant.stream ? (React.createElement(VideoView, { stream: participant.stream, onSetAspectRatio: ({ aspectRatio }) => handleAspectRatioChange({ ar: aspectRatio }), onResize: onResize })) : null));
|
|
9542
|
+
}
|
|
9543
|
+
function Grid({ roomConnection, renderParticipant, videoGridGap = 0 }) {
|
|
9544
|
+
const { remoteParticipants, localParticipant } = roomConnection.state;
|
|
9545
|
+
const gridRef = React.useRef(null);
|
|
9546
|
+
const [containerFrame, setContainerFrame] = React.useState(null);
|
|
9547
|
+
const [aspectRatios, setAspectRatios] = React.useState([]);
|
|
9548
|
+
// Calculate container frame on resize
|
|
9549
|
+
React.useEffect(() => {
|
|
9550
|
+
if (!gridRef.current) {
|
|
9551
|
+
return;
|
|
9552
|
+
}
|
|
9553
|
+
const resizeObserver = new ResizeObserver(debounce(() => {
|
|
9554
|
+
var _a, _b;
|
|
9555
|
+
setContainerFrame(makeFrame({
|
|
9556
|
+
width: (_a = gridRef.current) === null || _a === void 0 ? void 0 : _a.clientWidth,
|
|
9557
|
+
height: (_b = gridRef.current) === null || _b === void 0 ? void 0 : _b.clientHeight,
|
|
9558
|
+
}));
|
|
9559
|
+
}, { delay: 60 }));
|
|
9560
|
+
resizeObserver.observe(gridRef.current);
|
|
9561
|
+
return () => {
|
|
9562
|
+
resizeObserver.disconnect();
|
|
9563
|
+
};
|
|
9564
|
+
}, []);
|
|
9565
|
+
// Merge local and remote participants
|
|
9566
|
+
const participants = React.useMemo(() => {
|
|
9567
|
+
return [...(localParticipant ? [localParticipant] : []), ...remoteParticipants];
|
|
9568
|
+
}, [remoteParticipants, localParticipant]);
|
|
9569
|
+
// Make video cells
|
|
9570
|
+
const videoCells = React.useMemo(() => {
|
|
9571
|
+
return participants.map((participant) => {
|
|
9572
|
+
var _a;
|
|
9573
|
+
const aspectRatio = (_a = aspectRatios.find((item) => item.clientId === (participant === null || participant === void 0 ? void 0 : participant.id))) === null || _a === void 0 ? void 0 : _a.aspectRatio;
|
|
9574
|
+
return makeVideoCellView({
|
|
9575
|
+
aspectRatio: aspectRatio !== null && aspectRatio !== void 0 ? aspectRatio : 16 / 9,
|
|
9576
|
+
avatarSize: 0,
|
|
9577
|
+
cellPaddings: 10,
|
|
9578
|
+
client: participant,
|
|
9579
|
+
});
|
|
9580
|
+
});
|
|
9581
|
+
}, [participants, aspectRatios]);
|
|
9582
|
+
// Calculate stage layout
|
|
9583
|
+
const stageLayout = React.useMemo(() => {
|
|
9584
|
+
if (!containerFrame)
|
|
9585
|
+
return null;
|
|
9586
|
+
return calculateLayout({
|
|
9587
|
+
frame: containerFrame,
|
|
9588
|
+
gridGap: 0,
|
|
9589
|
+
isConstrained: false,
|
|
9590
|
+
roomBounds: containerFrame.bounds,
|
|
9591
|
+
videos: videoCells,
|
|
9592
|
+
videoGridGap,
|
|
9593
|
+
});
|
|
9594
|
+
}, [containerFrame, videoCells, videoGridGap]);
|
|
9595
|
+
// Handle resize
|
|
9596
|
+
const handleResize = React.useCallback(({ width, height, stream }) => {
|
|
9597
|
+
if (!roomConnection._ref)
|
|
9598
|
+
return;
|
|
9599
|
+
roomConnection._ref.dispatch(doRtcReportStreamResolution({ streamId: stream.id, width, height }));
|
|
9600
|
+
}, [localParticipant, roomConnection._ref]);
|
|
9601
|
+
return (React.createElement("div", { ref: gridRef, style: {
|
|
9602
|
+
width: "100%",
|
|
9603
|
+
height: "100%",
|
|
9604
|
+
position: "relative",
|
|
9605
|
+
} }, participants.map((participant, i) => {
|
|
9606
|
+
const cell = stageLayout === null || stageLayout === void 0 ? void 0 : stageLayout.videoGrid.cells[i];
|
|
9607
|
+
if (!cell || !participant || !participant.stream || !cell.clientId)
|
|
9608
|
+
return null;
|
|
9609
|
+
return (React.createElement(GridVideoCellView, { key: cell.clientId, cell: cell, participant: participant, render: renderParticipant ? () => renderParticipant({ cell, participant }) : undefined, onResize: handleResize, onSetAspectRatio: ({ aspectRatio }) => {
|
|
9610
|
+
setAspectRatios((prev) => {
|
|
9611
|
+
const index = prev.findIndex((item) => item.clientId === cell.clientId);
|
|
9612
|
+
if (index === -1) {
|
|
9613
|
+
return [...prev, { clientId: cell.clientId, aspectRatio }];
|
|
9614
|
+
}
|
|
9615
|
+
return [
|
|
9616
|
+
...prev.slice(0, index),
|
|
9617
|
+
{ clientId: cell.clientId, aspectRatio },
|
|
9618
|
+
...prev.slice(index + 1),
|
|
9619
|
+
];
|
|
9620
|
+
});
|
|
9621
|
+
} }));
|
|
9622
|
+
})));
|
|
9623
|
+
}
|
|
9624
|
+
|
|
9625
|
+
export { Grid as VideoGrid, VideoView, sdkVersion, useLocalMedia, useRoomConnection };
|