@whereby.com/browser-sdk 2.1.0-beta1 → 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/dist/react/index.esm.js
CHANGED
|
@@ -8690,7 +8690,7 @@ const selectRoomConnectionState = createSelector(selectChatMessages, selectCloud
|
|
|
8690
8690
|
return state;
|
|
8691
8691
|
});
|
|
8692
8692
|
|
|
8693
|
-
const sdkVersion = "2.1.0-
|
|
8693
|
+
const sdkVersion = "2.1.0-beta2";
|
|
8694
8694
|
|
|
8695
8695
|
const initialState$1 = {
|
|
8696
8696
|
chatMessages: [],
|
|
@@ -8842,4 +8842,784 @@ function useLocalMedia(optionsOrStream = { audio: true, video: true }) {
|
|
|
8842
8842
|
};
|
|
8843
8843
|
}
|
|
8844
8844
|
|
|
8845
|
-
|
|
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 };
|