@whereby.com/browser-sdk 2.12.3 → 3.0.0

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.
@@ -1,7 +1,14 @@
1
1
  import * as React from 'react';
2
- import { useState, useEffect, useCallback } from 'react';
3
- import { debounce, selectChatMessages, selectCloudRecordingRaw, selectLocalParticipantRaw, selectLocalMediaStream, selectRemoteParticipants, selectScreenshares, selectRoomConnectionStatus, selectStreamingRaw, selectWaitingParticipants, createServices, createStore, observeStore, doAppStart, doAppStop, doRtcReportStreamResolution, doSendChatMessage, doKnockRoom, doSetDisplayName, toggleCameraEnabled, toggleMicrophoneEnabled, toggleLowDataModeEnabled, doAcceptWaitingParticipant, doRejectWaitingParticipant, doStartCloudRecording, doStartScreenshare, doStopCloudRecording, doStopScreenshare, doLockRoom, doRequestAudioEnable, doKickParticipant, doEndMeeting, selectCameraDeviceError, selectCameraDevices, selectCurrentCameraDeviceId, selectCurrentMicrophoneDeviceId, selectIsSettingCameraDevice, selectIsSettingMicrophoneDevice, selectIsLocalMediaStarting, selectMicrophoneDeviceError, selectMicrophoneDevices, selectSpeakerDevices, selectLocalMediaStartError, doStartLocalMedia, doStopLocalMedia, setCurrentCameraDeviceId, setCurrentMicrophoneDeviceId } from '@whereby.com/core';
2
+ import { Provider as Provider$1, useDispatch, useSelector } from 'react-redux';
3
+ import { createServices, createStore, selectCurrentSpeakerDeviceId, debounce, doRtcReportStreamResolution, selectChatMessages, selectCloudRecordingRaw, selectLocalParticipantRaw, selectLocalMediaStream, selectRemoteParticipants, selectScreenshares, selectRoomConnectionStatus, selectStreamingRaw, selectWaitingParticipants, selectNotificationsEmitter, selectSpotlightedClientViews, doAppStop, doSendChatMessage, doKnockRoom, doSetDisplayName, toggleCameraEnabled, toggleMicrophoneEnabled, toggleLowDataModeEnabled, doSetLocalStickyReaction, doRequestAudioEnable, doAcceptWaitingParticipant, doRejectWaitingParticipant, doStartCloudRecording, doStartScreenshare, doStopCloudRecording, doStopScreenshare, doAppStart, doLockRoom, doSpotlightParticipant, doRemoveSpotlight, doKickParticipant, doEndMeeting, selectCameraDeviceError, selectCameraDevices, selectCurrentCameraDeviceId, selectCurrentMicrophoneDeviceId, selectIsSettingCameraDevice, selectIsSettingMicrophoneDevice, selectIsLocalMediaStarting, selectMicrophoneDeviceError, selectMicrophoneDevices, selectSpeakerDevices, selectLocalMediaStartError, doStartLocalMedia, doStopLocalMedia, setCurrentCameraDeviceId, setCurrentMicrophoneDeviceId, setCurrentSpeakerDeviceId, selectAllClientViews, selectNumParticipants } from '@whereby.com/core';
4
4
  import { createSelector } from '@reduxjs/toolkit';
5
+ import runes from 'runes';
6
+
7
+ function Provider({ children }) {
8
+ const services = createServices();
9
+ const store = createStore({ injectServices: services });
10
+ return React.createElement(Provider$1, { store: store }, children);
11
+ }
5
12
 
6
13
  /******************************************************************************
7
14
  Copyright (c) Microsoft Corporation.
@@ -37,27 +44,50 @@ typeof SuppressedError === "function" ? SuppressedError : function (error, suppr
37
44
  return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
38
45
  };
39
46
 
40
- var VideoView = (_a) => {
41
- var { muted, mirror = false, stream, onResize, onSetAspectRatio } = _a, rest = __rest(_a, ["muted", "mirror", "stream", "onResize", "onSetAspectRatio"]);
47
+ const useAppDispatch = useDispatch.withTypes();
48
+ const useAppSelector = useSelector.withTypes();
49
+
50
+ const VideoView = React.forwardRef((_a, ref) => {
51
+ var { muted, mirror = false, stream, onVideoResize } = _a, rest = __rest(_a, ["muted", "mirror", "stream", "onVideoResize"]);
52
+ const dispatch = useAppDispatch();
53
+ const currentSpeakerId = useAppSelector(selectCurrentSpeakerDeviceId);
42
54
  const videoEl = React.useRef(null);
55
+ const audioEl = React.useRef(null);
56
+ React.useImperativeHandle(ref, () => {
57
+ return Object.assign(videoEl.current, {
58
+ captureAspectRatio: () => {
59
+ if (!videoEl.current) {
60
+ return null;
61
+ }
62
+ const h = videoEl.current.clientHeight;
63
+ const w = videoEl.current.clientWidth;
64
+ if (w && h && w + h > 20) {
65
+ return w / h;
66
+ }
67
+ return null;
68
+ },
69
+ });
70
+ });
43
71
  React.useEffect(() => {
44
72
  if (!videoEl.current) {
45
73
  return;
46
74
  }
47
75
  const resizeObserver = new ResizeObserver(debounce(() => {
48
76
  if (videoEl.current && (stream === null || stream === void 0 ? void 0 : stream.id)) {
49
- if (onResize) {
50
- onResize({
51
- width: videoEl.current.clientWidth,
52
- height: videoEl.current.clientHeight,
77
+ const width = videoEl.current.clientWidth;
78
+ const height = videoEl.current.clientHeight;
79
+ dispatch(doRtcReportStreamResolution({
80
+ streamId: stream.id,
81
+ width,
82
+ height,
83
+ }));
84
+ if (onVideoResize) {
85
+ onVideoResize({
86
+ width,
87
+ height,
53
88
  stream,
54
89
  });
55
90
  }
56
- const h = videoEl.current.videoHeight;
57
- const w = videoEl.current.videoWidth;
58
- if (onSetAspectRatio && w && h && w + h > 20) {
59
- onSetAspectRatio({ aspectRatio: w / h });
60
- }
61
91
  }
62
92
  }, { delay: 1000, edges: true }));
63
93
  resizeObserver.observe(videoEl.current);
@@ -76,38 +106,47 @@ var VideoView = (_a) => {
76
106
  videoEl.current.muted = Boolean(muted);
77
107
  }
78
108
  }, [muted, stream, videoEl]);
79
- 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) })));
80
- };
109
+ React.useEffect(() => {
110
+ if (!audioEl.current || muted || !stream || !currentSpeakerId) {
111
+ return;
112
+ }
113
+ if (audioEl.current.srcObject !== stream) {
114
+ audioEl.current.srcObject = stream;
115
+ }
116
+ if (audioEl.current.setSinkId) {
117
+ audioEl.current.setSinkId(currentSpeakerId);
118
+ }
119
+ }, [stream, audioEl, currentSpeakerId, muted]);
120
+ return (React.createElement(React.Fragment, null,
121
+ 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
+ React.createElement("audio", { ref: audioEl, autoPlay: true, playsInline: true })));
123
+ });
124
+ VideoView.displayName = "VideoView";
81
125
 
82
- const selectRoomConnectionState = createSelector(selectChatMessages, selectCloudRecordingRaw, selectLocalParticipantRaw, selectLocalMediaStream, selectRemoteParticipants, selectScreenshares, selectRoomConnectionStatus, selectStreamingRaw, selectWaitingParticipants, (chatMessages, cloudRecording, localParticipant, localMediaStream, remoteParticipants, screenshares, connectionStatus, streaming, waitingParticipants) => {
126
+ const selectRoomConnectionState = createSelector(selectChatMessages, selectCloudRecordingRaw, selectLocalParticipantRaw, selectLocalMediaStream, selectRemoteParticipants, selectScreenshares, selectRoomConnectionStatus, selectStreamingRaw, selectWaitingParticipants, selectNotificationsEmitter, selectSpotlightedClientViews, (chatMessages, cloudRecording, localParticipant, localMediaStream, remoteParticipants, screenshares, connectionStatus, streaming, waitingParticipants, notificationsEmitter, spotlightedClientViews) => {
83
127
  const state = {
84
128
  chatMessages,
85
129
  cloudRecording: cloudRecording.isRecording ? { status: "recording" } : undefined,
86
- localScreenshareStatus: localParticipant.isScreenSharing ? "active" : undefined,
87
- localParticipant: Object.assign(Object.assign({}, localParticipant), { stream: localMediaStream }),
88
- remoteParticipants,
89
- screenshares,
90
130
  connectionStatus,
131
+ events: notificationsEmitter,
91
132
  liveStream: streaming.isStreaming
92
133
  ? {
93
134
  status: "streaming",
94
135
  startedAt: streaming.startedAt,
95
136
  }
96
137
  : undefined,
138
+ localScreenshareStatus: localParticipant.isScreenSharing ? "active" : undefined,
139
+ localParticipant: Object.assign(Object.assign({}, localParticipant), { stream: localMediaStream }),
140
+ remoteParticipants,
141
+ screenshares,
97
142
  waitingParticipants,
143
+ spotlightedParticipants: spotlightedClientViews,
98
144
  };
99
145
  return state;
100
146
  });
101
147
 
102
- const browserSdkVersion = "2.12.3";
148
+ const browserSdkVersion = "3.0.0";
103
149
 
104
- const initialState$1 = {
105
- chatMessages: [],
106
- remoteParticipants: [],
107
- connectionStatus: "ready",
108
- screenshares: [],
109
- waitingParticipants: [],
110
- };
111
150
  const defaultRoomConnectionOptions = {
112
151
  localMediaOptions: {
113
152
  audio: true,
@@ -115,22 +154,13 @@ const defaultRoomConnectionOptions = {
115
154
  },
116
155
  };
117
156
  function useRoomConnection(roomUrl, roomConnectionOptions = defaultRoomConnectionOptions) {
118
- const [roomConfig, setRoomConfig] = React.useState();
119
- const [store] = React.useState(() => {
120
- if (roomConnectionOptions.localMedia) {
121
- return roomConnectionOptions.localMedia.store;
122
- }
123
- const services = createServices();
124
- return createStore({ injectServices: services });
125
- });
126
- const [boundVideoView, setBoundVideoView] = React.useState();
127
- const [roomConnectionState, setRoomConnectionState] = React.useState(initialState$1);
128
- React.useEffect(() => {
129
- const unsubscribe = observeStore(store, selectRoomConnectionState, setRoomConnectionState);
157
+ const dispatch = useAppDispatch();
158
+ const roomConnectionState = useAppSelector(selectRoomConnectionState);
159
+ const roomConfig = React.useMemo(() => {
130
160
  const url = new URL(roomUrl);
131
161
  const searchParams = new URLSearchParams(url.search);
132
162
  const roomKey = roomConnectionOptions.roomKey || searchParams.get("roomKey");
133
- const roomConfig = {
163
+ return {
134
164
  displayName: roomConnectionOptions.displayName || "Guest",
135
165
  localMediaOptions: roomConnectionOptions.localMedia ? undefined : roomConnectionOptions.localMediaOptions,
136
166
  roomKey,
@@ -138,52 +168,44 @@ function useRoomConnection(roomUrl, roomConnectionOptions = defaultRoomConnectio
138
168
  userAgent: `browser-sdk:${browserSdkVersion}`,
139
169
  externalId: roomConnectionOptions.externalId || null,
140
170
  };
141
- setRoomConfig(roomConfig);
142
- store.dispatch(doAppStart(roomConfig));
171
+ }, [roomUrl, roomConnectionOptions]);
172
+ React.useEffect(() => {
143
173
  return () => {
144
- store.dispatch(doAppStop());
145
- unsubscribe();
174
+ dispatch(doAppStop());
146
175
  };
147
176
  }, []);
148
- React.useEffect(() => {
149
- if (store && !boundVideoView) {
150
- setBoundVideoView(() => (props) => {
151
- return React.createElement(VideoView, Object.assign({}, props, {
152
- onResize: ({ stream, width, height, }) => {
153
- store.dispatch(doRtcReportStreamResolution({
154
- streamId: stream.id,
155
- width,
156
- height,
157
- }));
158
- },
159
- }));
160
- });
161
- }
162
- }, [store, boundVideoView]);
163
- const sendChatMessage = React.useCallback((text) => store.dispatch(doSendChatMessage({ text })), [store]);
164
- const knock = React.useCallback(() => store.dispatch(doKnockRoom()), [store]);
165
- const setDisplayName = React.useCallback((displayName) => store.dispatch(doSetDisplayName({ displayName })), [store]);
166
- const toggleCamera = React.useCallback((enabled) => store.dispatch(toggleCameraEnabled({ enabled })), [store]);
167
- const toggleMicrophone = React.useCallback((enabled) => store.dispatch(toggleMicrophoneEnabled({ enabled })), [store]);
168
- const toggleLowDataMode = React.useCallback((enabled) => store.dispatch(toggleLowDataModeEnabled({ enabled })), [store]);
169
- const acceptWaitingParticipant = React.useCallback((participantId) => store.dispatch(doAcceptWaitingParticipant({ participantId })), [store]);
170
- const rejectWaitingParticipant = React.useCallback((participantId) => store.dispatch(doRejectWaitingParticipant({ participantId })), [store]);
171
- const startCloudRecording = React.useCallback(() => store.dispatch(doStartCloudRecording()), [store]);
172
- const startScreenshare = React.useCallback(() => store.dispatch(doStartScreenshare()), [store]);
173
- const stopCloudRecording = React.useCallback(() => store.dispatch(doStopCloudRecording()), [store]);
174
- const stopScreenshare = React.useCallback(() => store.dispatch(doStopScreenshare()), [store]);
175
- const joinRoom = React.useCallback(() => (roomConfig ? store.dispatch(doAppStart(roomConfig)) : () => { }), [store, roomConfig]);
176
- const leaveRoom = React.useCallback(() => store.dispatch(doAppStop()), [store]);
177
- const lockRoom = React.useCallback((locked) => store.dispatch(doLockRoom({ locked })), [store]);
178
- const muteParticipants = React.useCallback((clientIds) => {
179
- store.dispatch(doRequestAudioEnable({ clientIds, enable: false }));
180
- }, [store]);
181
- const kickParticipant = React.useCallback((clientId) => store.dispatch(doKickParticipant({ clientId })), [store]);
182
- const endMeeting = React.useCallback((stayBehind) => store.dispatch(doEndMeeting({ stayBehind })), [store]);
177
+ const sendChatMessage = React.useCallback((text) => dispatch(doSendChatMessage({ text })), [dispatch]);
178
+ const knock = React.useCallback(() => dispatch(doKnockRoom()), [dispatch]);
179
+ const setDisplayName = React.useCallback((displayName) => dispatch(doSetDisplayName({ displayName })), [dispatch]);
180
+ const toggleCamera = React.useCallback((enabled) => dispatch(toggleCameraEnabled({ enabled })), [dispatch]);
181
+ const toggleMicrophone = React.useCallback((enabled) => dispatch(toggleMicrophoneEnabled({ enabled })), [dispatch]);
182
+ const toggleLowDataMode = React.useCallback((enabled) => dispatch(toggleLowDataModeEnabled({ enabled })), [dispatch]);
183
+ const toggleRaiseHand = React.useCallback((enabled) => dispatch(doSetLocalStickyReaction({ enabled })), [dispatch]);
184
+ const askToSpeak = React.useCallback((participantId) => dispatch(doRequestAudioEnable({ clientIds: [participantId], enable: true })), [dispatch]);
185
+ const acceptWaitingParticipant = React.useCallback((participantId) => dispatch(doAcceptWaitingParticipant({ participantId })), [dispatch]);
186
+ const rejectWaitingParticipant = React.useCallback((participantId) => dispatch(doRejectWaitingParticipant({ participantId })), [dispatch]);
187
+ const startCloudRecording = React.useCallback(() => dispatch(doStartCloudRecording()), [dispatch]);
188
+ const startScreenshare = React.useCallback(() => dispatch(doStartScreenshare()), [dispatch]);
189
+ const stopCloudRecording = React.useCallback(() => dispatch(doStopCloudRecording()), [dispatch]);
190
+ const stopScreenshare = React.useCallback(() => dispatch(doStopScreenshare()), [dispatch]);
191
+ const joinRoom = React.useCallback(() => dispatch(doAppStart(roomConfig)), [dispatch]);
192
+ const leaveRoom = React.useCallback(() => dispatch(doAppStop()), [dispatch]);
193
+ const lockRoom = React.useCallback((locked) => dispatch(doLockRoom({ locked })), [dispatch]);
194
+ const muteParticipants = React.useCallback((participantIds) => {
195
+ dispatch(doRequestAudioEnable({ clientIds: participantIds, enable: false }));
196
+ }, [dispatch]);
197
+ const spotlightParticipant = React.useCallback((participantId) => dispatch(doSpotlightParticipant({ id: participantId })), [dispatch]);
198
+ const removeSpotlight = React.useCallback((participantId) => dispatch(doRemoveSpotlight({ id: participantId })), [dispatch]);
199
+ const kickParticipant = React.useCallback((participantId) => dispatch(doKickParticipant({ clientId: participantId })), [dispatch]);
200
+ const endMeeting = React.useCallback((stayBehind) => dispatch(doEndMeeting({ stayBehind })), [dispatch]);
201
+ const { events } = roomConnectionState, state = __rest(roomConnectionState, ["events"]);
183
202
  return {
184
- state: roomConnectionState,
203
+ state,
204
+ events,
185
205
  actions: {
186
206
  toggleLowDataMode,
207
+ toggleRaiseHand,
208
+ askToSpeak,
187
209
  acceptWaitingParticipant,
188
210
  knock,
189
211
  joinRoom,
@@ -201,20 +223,19 @@ function useRoomConnection(roomUrl, roomConnectionOptions = defaultRoomConnectio
201
223
  stopScreenshare,
202
224
  toggleCamera,
203
225
  toggleMicrophone,
226
+ spotlightParticipant,
227
+ removeSpotlight,
204
228
  },
205
- components: {
206
- VideoView: boundVideoView || VideoView,
207
- },
208
- _ref: store,
209
229
  };
210
230
  }
211
231
 
212
- const selectLocalMediaState = createSelector(selectCameraDeviceError, selectCameraDevices, selectCurrentCameraDeviceId, selectCurrentMicrophoneDeviceId, selectIsSettingCameraDevice, selectIsSettingMicrophoneDevice, selectIsLocalMediaStarting, selectLocalMediaStream, selectMicrophoneDeviceError, selectMicrophoneDevices, selectSpeakerDevices, selectLocalMediaStartError, (cameraDeviceError, cameraDevices, currentCameraDeviceId, currentMicrophoneDeviceId, isSettingCameraDevice, isSettingMicrophoneDevice, isStarting, localStream, microphoneDeviceError, microphoneDevices, speakerDevices, startError) => {
232
+ const selectLocalMediaState = createSelector(selectCameraDeviceError, selectCameraDevices, selectCurrentCameraDeviceId, selectCurrentMicrophoneDeviceId, selectCurrentSpeakerDeviceId, selectIsSettingCameraDevice, selectIsSettingMicrophoneDevice, selectIsLocalMediaStarting, selectLocalMediaStream, selectMicrophoneDeviceError, selectMicrophoneDevices, selectSpeakerDevices, selectLocalMediaStartError, (cameraDeviceError, cameraDevices, currentCameraDeviceId, currentMicrophoneDeviceId, currentSpeakerDeviceId, isSettingCameraDevice, isSettingMicrophoneDevice, isStarting, localStream, microphoneDeviceError, microphoneDevices, speakerDevices, startError) => {
213
233
  const state = {
214
234
  cameraDeviceError,
215
235
  cameraDevices,
216
236
  currentCameraDeviceId,
217
237
  currentMicrophoneDeviceId,
238
+ currentSpeakerDeviceId,
218
239
  isSettingCameraDevice,
219
240
  isSettingMicrophoneDevice,
220
241
  isStarting,
@@ -227,47 +248,1460 @@ const selectLocalMediaState = createSelector(selectCameraDeviceError, selectCame
227
248
  return state;
228
249
  });
229
250
 
230
- const initialState = {
231
- cameraDeviceError: null,
232
- cameraDevices: [],
233
- isSettingCameraDevice: false,
234
- isSettingMicrophoneDevice: false,
235
- isStarting: false,
236
- microphoneDeviceError: null,
237
- microphoneDevices: [],
238
- speakerDevices: [],
239
- startError: null,
240
- };
241
251
  function useLocalMedia(optionsOrStream = { audio: true, video: true }) {
242
- const [store] = useState(() => {
243
- const services = createServices();
244
- return createStore({ injectServices: services });
245
- });
246
- const [localMediaState, setLocalMediaState] = useState(initialState);
247
- useEffect(() => {
248
- const unsubscribe = observeStore(store, selectLocalMediaState, setLocalMediaState);
249
- store.dispatch(doStartLocalMedia(optionsOrStream));
252
+ const dispatch = useAppDispatch();
253
+ const localMediaState = useAppSelector(selectLocalMediaState);
254
+ React.useEffect(() => {
255
+ dispatch(doStartLocalMedia(optionsOrStream));
250
256
  return () => {
251
- unsubscribe();
252
- store.dispatch(doStopLocalMedia());
257
+ dispatch(doStopLocalMedia());
253
258
  };
254
259
  }, []);
255
- const setCameraDevice = useCallback((deviceId) => store.dispatch(setCurrentCameraDeviceId({ deviceId })), [store]);
256
- const setMicrophoneDevice = useCallback((deviceId) => store.dispatch(setCurrentMicrophoneDeviceId({ deviceId })), [store]);
257
- const toggleCamera = useCallback((enabled) => store.dispatch(toggleCameraEnabled({ enabled })), [store]);
258
- const toggleMicrophone = useCallback((enabled) => store.dispatch(toggleMicrophoneEnabled({ enabled })), [store]);
259
- const toggleLowDataMode = useCallback((enabled) => store.dispatch(toggleLowDataModeEnabled({ enabled })), [store]);
260
+ const setCameraDevice = React.useCallback((deviceId) => dispatch(setCurrentCameraDeviceId({ deviceId })), [dispatch]);
261
+ const setMicrophoneDevice = React.useCallback((deviceId) => dispatch(setCurrentMicrophoneDeviceId({ deviceId })), [dispatch]);
262
+ const setSpeakerDevice = React.useCallback((deviceId) => dispatch(setCurrentSpeakerDeviceId({ deviceId })), [dispatch]);
263
+ const toggleCamera = React.useCallback((enabled) => dispatch(toggleCameraEnabled({ enabled })), [dispatch]);
264
+ const toggleMicrophone = React.useCallback((enabled) => dispatch(toggleMicrophoneEnabled({ enabled })), [dispatch]);
265
+ const toggleLowDataMode = React.useCallback((enabled) => dispatch(toggleLowDataModeEnabled({ enabled })), [dispatch]);
260
266
  return {
261
267
  state: localMediaState,
262
268
  actions: {
263
269
  setCameraDevice,
264
270
  setMicrophoneDevice,
271
+ setSpeakerDevice,
265
272
  toggleCameraEnabled: toggleCamera,
266
273
  toggleMicrophoneEnabled: toggleMicrophone,
267
274
  toggleLowDataModeEnabled: toggleLowDataMode,
268
275
  },
269
- store,
270
276
  };
271
277
  }
272
278
 
273
- export { VideoView, useLocalMedia, useRoomConnection };
279
+ const VIDEO_CONTROLS_MIN_WIDTH$1 = 7 * 60;
280
+ var layoutConstants = {
281
+ MIN_WINDOW_HEIGHT: 320,
282
+ MIN_WINDOW_WIDTH: 320,
283
+ DESKTOP_BREAKPOINT: 1025,
284
+ TABLET_BREAKPOINT: 750,
285
+ PHONE_BREAKPOINT: 500,
286
+ TOP_TOOLBAR_HEIGHT: 40 + 8 * 2,
287
+ BOTTOM_TOOLBAR_HEIGHT: 70 + 4 * 3,
288
+ SIDEBAR_WIDTH: 375,
289
+ VIDEO_CONTROLS_MIN_WIDTH: VIDEO_CONTROLS_MIN_WIDTH$1,
290
+ ROOM_FOOTER_MIN_WIDTH: 60 * 3 + VIDEO_CONTROLS_MIN_WIDTH$1,
291
+ FLOATING_VIDEO_CONTROLS_BOTTOM_MARGIN: 20,
292
+ WATERMARK_BAR_HEIGHT: 32,
293
+ BREAKOUT_STAGE_BACKDROP_HEADER_HEIGHT: 20 + 8,
294
+ BREAKOUT_STAGE_BACKDROP_FOOTER_HEIGHT: 8 + 40 + 8,
295
+ SUBGRID_EMPTY_STAGE_MAX_WIDTH: 800,
296
+ GROUPS_CELL_MARGIN: 8,
297
+ GROUPS_CELL_PADDING: 12,
298
+ GROUPS_CELL_NAV_HEIGHT: 48 + 8,
299
+ GROUPS_CELL_AVATAR_WRAPPER_BOTTOM_MARGIN: 8,
300
+ GROUPS_CELL_AVATAR_GRID_GAP: 8,
301
+ GROUPS_CELL_MIN_WIDTH: 360,
302
+ GROUPS_CELL_MAX_WIDTH: 600,
303
+ GROUPS_ROW_HEIGHT: 72,
304
+ GROUPS_ROW_GAP: 1,
305
+ FOLDABLE_SCREEN_STAGE_PADDING: 8,
306
+ };
307
+
308
+ function makeOrigin({ top = 0, left = 0 } = {}) {
309
+ return {
310
+ top,
311
+ left,
312
+ };
313
+ }
314
+ function makeBounds({ width = 0, height = 0 } = {}) {
315
+ return {
316
+ width: Math.max(width, 0),
317
+ height: Math.max(height, 0),
318
+ };
319
+ }
320
+ function makeFrame({ top = 0, left = 0, width = 0, height = 0 } = {}) {
321
+ return {
322
+ bounds: makeBounds({ width, height }),
323
+ origin: makeOrigin({ top, left }),
324
+ };
325
+ }
326
+ function makeBox({ top = 0, left = 0, bottom = 0, right = 0 } = {}) {
327
+ return {
328
+ top,
329
+ left,
330
+ bottom,
331
+ right,
332
+ };
333
+ }
334
+ function hasBounds(bounds) {
335
+ if (!bounds) {
336
+ return false;
337
+ }
338
+ return !(bounds.width <= 0 || bounds.height <= 0);
339
+ }
340
+ function insetBounds({ bounds, fromBounds }) {
341
+ return {
342
+ width: Math.max(fromBounds.width - bounds.width, 0),
343
+ height: Math.max(fromBounds.height - bounds.height, 0),
344
+ };
345
+ }
346
+
347
+ function fitToBounds(aspectRatio, containerSize) {
348
+ const { width, height } = containerSize;
349
+ const contentHeight = height;
350
+ const contentWidth = contentHeight * aspectRatio;
351
+ const scale = Math.min(width / contentWidth, height / contentHeight);
352
+ const adjustedWidth = contentWidth * scale;
353
+ const adjustedHeight = contentHeight * scale;
354
+ return { width: adjustedWidth, height: adjustedHeight };
355
+ }
356
+ const cellContentArea = ({ width, height, rows, cols, aspectRatio, }) => {
357
+ const bounds = fitToBounds(aspectRatio, { width: width / cols, height: height / rows });
358
+ return Math.round(bounds.width * bounds.height);
359
+ };
360
+ const getWeightedSplitCount = ({ vertical, width, height, count, aspectRatio, }) => {
361
+ const choices = [1, 2, 3].map((rowCols) => cellContentArea({
362
+ width,
363
+ height,
364
+ rows: vertical ? Math.ceil(count / rowCols) : rowCols,
365
+ cols: vertical ? rowCols : Math.ceil(count / rowCols),
366
+ aspectRatio,
367
+ }));
368
+ const closest = Math.max(...choices);
369
+ const splits = choices.indexOf(closest) + 1;
370
+ return { splits, weight: closest };
371
+ };
372
+ const getGridSplits = ({ width, height, count, aspectRatio, }) => {
373
+ const verticalPick = getWeightedSplitCount({ vertical: true, width, height, count, aspectRatio });
374
+ const horizontalPick = getWeightedSplitCount({ vertical: false, width, height, count, aspectRatio });
375
+ if (verticalPick.weight > horizontalPick.weight) {
376
+ return { splits: verticalPick.splits, vertical: true };
377
+ }
378
+ return { splits: horizontalPick.splits, vertical: false };
379
+ };
380
+ function getGridSizeForCount({ count, width, height, aspectRatio, }) {
381
+ if (count <= 1) {
382
+ return {
383
+ rows: 1,
384
+ cols: 1,
385
+ };
386
+ }
387
+ const { splits, vertical } = getGridSplits({ width, height, count, aspectRatio });
388
+ if (vertical) {
389
+ return {
390
+ rows: Math.ceil(count / splits),
391
+ cols: splits,
392
+ };
393
+ }
394
+ return {
395
+ rows: splits,
396
+ cols: Math.ceil(count / splits),
397
+ };
398
+ }
399
+
400
+ function VideoCell(_a) {
401
+ var { aspectRatio, children, className, height, style, width, withRoundedCorners = false, withShadow = true } = _a, rest = __rest(_a, ["aspectRatio", "children", "className", "height", "style", "width", "withRoundedCorners", "withShadow"]);
402
+ let contentWidth = width;
403
+ let contentHeight = height;
404
+ let leftOffset = 0;
405
+ let topOffset = 0;
406
+ if (aspectRatio) {
407
+ ({ width: contentWidth, height: contentHeight } = fitToBounds(aspectRatio, {
408
+ width,
409
+ height,
410
+ }));
411
+ leftOffset = (width - contentWidth) / 2;
412
+ topOffset = (height - contentHeight) / 2;
413
+ }
414
+ const contentStyle = Object.assign({ width: `${Math.round(contentWidth)}px`, height: `${Math.round(contentHeight)}px` }, (leftOffset || topOffset
415
+ ? { transform: `translate3d(${Math.round(leftOffset)}px, ${Math.round(topOffset)}px, 0)` }
416
+ : {}));
417
+ return (React.createElement("div", { className: className, style: Object.assign({ position: "absolute", top: 0, left: 0 }, style) },
418
+ React.createElement("div", Object.assign({ style: contentStyle }, rest), React.cloneElement(children, { contentWidth, contentHeight, withRoundedCorners, withShadow }))));
419
+ }
420
+
421
+ function generateStylesFromFrame({ origin, bounds }) {
422
+ return {
423
+ top: Math.round(origin.top),
424
+ left: Math.round(origin.left),
425
+ height: Math.round(bounds.height),
426
+ width: Math.round(bounds.width),
427
+ };
428
+ }
429
+ function renderVideoCell({ cell, child, clientId, style = {}, withRoundedCorners = false, withShadow = false, }) {
430
+ const isHidden = !hasBounds(cell.bounds);
431
+ return (React.createElement(VideoCell, { width: cell.bounds.width, height: cell.bounds.height, aspectRatio: cell.aspectRatio, style: isHidden ? { width: 0, height: 0 } : style, withRoundedCorners: withRoundedCorners, withShadow: withShadow, key: clientId }, child));
432
+ }
433
+ function renderSubgridVideoCells({ content, stageLayout, withRoundedCorners, withShadow, }) {
434
+ const cells = stageLayout.subgrid.cells;
435
+ return content.map((child, index) => {
436
+ var _a, _b, _c, _d;
437
+ const cell = cells[index];
438
+ const style = { height: Math.round(cell.bounds.height), width: Math.round(cell.bounds.width), transform: "" };
439
+ const origin = {
440
+ top: stageLayout.subgrid.origin.top + cell.origin.top,
441
+ left: stageLayout.subgrid.origin.left + cell.origin.left,
442
+ };
443
+ style.transform = `translate3d(${Math.round(origin.left)}px, ${Math.round(origin.top)}px, 0)`;
444
+ const clientId = (_b = (_a = child === null || child === void 0 ? void 0 : child.props) === null || _a === void 0 ? void 0 : _a.participant) === null || _b === void 0 ? void 0 : _b.id;
445
+ const leftPaddings = ((_c = cell.paddings) === null || _c === void 0 ? void 0 : _c.left) || 0;
446
+ const rightPaddings = ((_d = cell.paddings) === null || _d === void 0 ? void 0 : _d.right) || 0;
447
+ const childWithProps = React.cloneElement(child, {
448
+ avatarSize: cell.bounds.width - leftPaddings - rightPaddings,
449
+ canZoom: false,
450
+ cellPaddings: cell.paddings,
451
+ isSmallCell: cell.isSmallCell,
452
+ isZoomedByDefault: false,
453
+ key: clientId || `subgrid-${index}`,
454
+ style,
455
+ });
456
+ return renderVideoCell({
457
+ cell,
458
+ child: childWithProps,
459
+ clientId: clientId || `subgrid-${index}`,
460
+ style,
461
+ withRoundedCorners,
462
+ withShadow,
463
+ });
464
+ });
465
+ }
466
+ function renderPresentationGridVideoCells({ content, isConstrained, stageLayout, withRoundedCorners, withShadow, }) {
467
+ var _a;
468
+ const cells = ((_a = stageLayout.presentationGrid) === null || _a === void 0 ? void 0 : _a.cells) || [];
469
+ return content.map((child, index) => {
470
+ var _a, _b;
471
+ if (!stageLayout.presentationGrid) {
472
+ return null;
473
+ }
474
+ const cell = cells[index];
475
+ const origin = {
476
+ top: stageLayout.presentationGrid.origin.top + stageLayout.presentationGrid.paddings.top + cell.origin.top,
477
+ left: stageLayout.presentationGrid.origin.left +
478
+ stageLayout.presentationGrid.paddings.left +
479
+ cell.origin.left,
480
+ };
481
+ const style = {
482
+ width: Math.round(cell.bounds.width),
483
+ height: Math.round(cell.bounds.height),
484
+ transform: `translate3d(${Math.round(origin.left)}px, ${Math.round(origin.top)}px, 0)`,
485
+ };
486
+ const clientId = (_b = (_a = child === null || child === void 0 ? void 0 : child.props) === null || _a === void 0 ? void 0 : _a.participant) === null || _b === void 0 ? void 0 : _b.id;
487
+ const childWithProps = React.cloneElement(child, {
488
+ isSmallCell: cell.isSmallCell,
489
+ isZoomedByDefault: !!isConstrained && !(child === null || child === void 0 ? void 0 : child.props.participant.isPresentation),
490
+ canZoom: !!isConstrained,
491
+ key: clientId || `presentation-${index}`,
492
+ });
493
+ return renderVideoCell({
494
+ cell,
495
+ child: childWithProps,
496
+ clientId: clientId || `presentation-${index}`,
497
+ style,
498
+ withRoundedCorners,
499
+ withShadow,
500
+ });
501
+ });
502
+ }
503
+ function renderGridVideoCells({ content, isConstrained, stageLayout, withRoundedCorners, withShadow, }) {
504
+ var _a;
505
+ const cells = ((_a = stageLayout.videoGrid) === null || _a === void 0 ? void 0 : _a.cells) || [];
506
+ const gridVideoCells = content.map((child, index) => {
507
+ var _a, _b;
508
+ if (!stageLayout.videoGrid) {
509
+ return null;
510
+ }
511
+ const cell = cells[index];
512
+ const origin = {
513
+ top: stageLayout.videoGrid.origin.top + stageLayout.videoGrid.paddings.top + cell.origin.top,
514
+ left: stageLayout.videoGrid.origin.left + stageLayout.videoGrid.paddings.left + cell.origin.left,
515
+ };
516
+ const style = {
517
+ width: Math.round(cell.bounds.width),
518
+ height: Math.round(cell.bounds.height),
519
+ transform: `translate3d(${Math.round(origin.left)}px, ${Math.round(origin.top)}px, 0)`,
520
+ };
521
+ const clientId = (_b = (_a = child === null || child === void 0 ? void 0 : child.props) === null || _a === void 0 ? void 0 : _a.participant) === null || _b === void 0 ? void 0 : _b.id;
522
+ const childWithProps = React.cloneElement(child, {
523
+ isSmallCell: cell.isSmallCell,
524
+ isZoomedByDefault: !!isConstrained && !(child === null || child === void 0 ? void 0 : child.props.participant.isPresentation),
525
+ canZoom: !!isConstrained,
526
+ key: clientId || `video-${index}`,
527
+ });
528
+ return renderVideoCell({
529
+ cell,
530
+ child: childWithProps,
531
+ clientId: clientId || `video-${index}`,
532
+ withRoundedCorners,
533
+ style,
534
+ withShadow,
535
+ });
536
+ });
537
+ return gridVideoCells;
538
+ }
539
+ function VideoStageLayout({ debug = false, featureRoundedCornersOff = false, gridContent = [], isConstrained = false, layoutOverflowBackdropFrame = makeFrame(), layoutVideoStage: stageLayout, presentationGridContent = [], subgridContent = [], }) {
540
+ const withRoundedCorners = !featureRoundedCornersOff && !isConstrained;
541
+ const cells = [];
542
+ if (gridContent.length) {
543
+ cells.push(...renderGridVideoCells({
544
+ content: gridContent,
545
+ isConstrained,
546
+ stageLayout,
547
+ withRoundedCorners,
548
+ withShadow: !isConstrained,
549
+ }));
550
+ }
551
+ if (presentationGridContent.length) {
552
+ cells.push(...renderPresentationGridVideoCells({
553
+ content: presentationGridContent,
554
+ isConstrained,
555
+ stageLayout,
556
+ withRoundedCorners,
557
+ withShadow: !isConstrained,
558
+ }));
559
+ }
560
+ if (subgridContent.length) {
561
+ cells.push(...renderSubgridVideoCells({
562
+ content: subgridContent,
563
+ stageLayout,
564
+ withRoundedCorners: !featureRoundedCornersOff,
565
+ withShadow: true,
566
+ }));
567
+ }
568
+ return (React.createElement("div", { key: "video-stage-layout", style: {
569
+ width: "100%",
570
+ height: "100%",
571
+ position: "relative",
572
+ } },
573
+ hasBounds(layoutOverflowBackdropFrame.bounds) && (React.createElement("div", { style: generateStylesFromFrame(layoutOverflowBackdropFrame) })),
574
+ cells,
575
+ debug && (React.createElement(React.Fragment, null))));
576
+ }
577
+
578
+ const WIDE_AR = 16 / 9;
579
+ const NORMAL_AR = 4 / 3;
580
+ const clamp = ({ value, min, max }) => Math.min(Math.max(value, min), max);
581
+ function hasDuplicates(...array) {
582
+ return new Set(array).size !== array.length;
583
+ }
584
+ function findMostCommon(arr) {
585
+ return arr.sort((a, b) => arr.filter((v) => v === a).length - arr.filter((v) => v === b).length).pop();
586
+ }
587
+ function pickCellAspectRatio({ choices = [] }) {
588
+ const minAr = Math.min(...choices);
589
+ const maxAr = Math.max(...choices);
590
+ let chosenAr = null;
591
+ if (minAr === maxAr) {
592
+ chosenAr = minAr;
593
+ }
594
+ else {
595
+ const dominantAr = hasDuplicates(choices) ? findMostCommon(choices) : maxAr;
596
+ chosenAr = clamp({ value: dominantAr || maxAr, min: NORMAL_AR, max: WIDE_AR });
597
+ }
598
+ return {
599
+ minAr,
600
+ maxAr,
601
+ chosenAr,
602
+ };
603
+ }
604
+ function getCenterPadding$1({ rows, cols, cellWidth, index, cellCount, gridGap, }) {
605
+ const max = rows * cols;
606
+ const leftOver = max - cellCount;
607
+ if (!leftOver) {
608
+ return 0;
609
+ }
610
+ const lastIndex = max - leftOver - 1;
611
+ const firstIndex = lastIndex - (cols - leftOver) + 1;
612
+ const lastRowPadding = (leftOver * cellWidth) / 2 + gridGap;
613
+ return index >= firstIndex && index <= lastIndex ? lastRowPadding : 0;
614
+ }
615
+ function getCellBounds({ width, height, rows, cols, gridGap, aspectRatio, }) {
616
+ const cellWidth = (width - (cols - 1) * gridGap) / cols;
617
+ const cellHeight = (height - (rows - 1) * gridGap) / rows;
618
+ const ar = cellWidth / cellHeight;
619
+ let horizontalCorrection = 0;
620
+ let verticalCorrection = 0;
621
+ if (aspectRatio < ar) {
622
+ horizontalCorrection = cellWidth - cellHeight * aspectRatio;
623
+ }
624
+ else if (aspectRatio > ar) {
625
+ verticalCorrection = cellHeight - cellWidth / aspectRatio;
626
+ }
627
+ const totalHorizontalCorrection = horizontalCorrection * cols;
628
+ const totalVerticalCorrection = verticalCorrection * rows;
629
+ return {
630
+ cellWidth: cellWidth - horizontalCorrection,
631
+ cellHeight: cellHeight - verticalCorrection,
632
+ extraHorizontalPadding: totalHorizontalCorrection / 2,
633
+ extraVerticalPadding: totalVerticalCorrection / 2,
634
+ };
635
+ }
636
+ function calculateLayout$2({ width, height, cellCount, gridGap, cellAspectRatios = [NORMAL_AR], paddings = makeBox(), }) {
637
+ if (!cellCount) {
638
+ return {
639
+ cellCount,
640
+ cellHeight: 0,
641
+ cellWidth: 0,
642
+ cols: 0,
643
+ rows: 0,
644
+ extraHorizontalPadding: 0,
645
+ extraVerticalPadding: 0,
646
+ gridGap,
647
+ paddings,
648
+ };
649
+ }
650
+ const contentWidth = width - (paddings.left + paddings.right);
651
+ const contentHeight = height - (paddings.top + paddings.bottom);
652
+ const cellAspectRatioTuple = pickCellAspectRatio({
653
+ choices: cellAspectRatios,
654
+ });
655
+ let cellAspectRatio = cellAspectRatioTuple.chosenAr;
656
+ const { rows, cols } = getGridSizeForCount({
657
+ count: cellCount,
658
+ width: contentWidth,
659
+ height: contentHeight,
660
+ aspectRatio: cellAspectRatio,
661
+ });
662
+ if (rows === 1) {
663
+ cellAspectRatio = clamp({
664
+ value: contentWidth / cols / contentHeight,
665
+ min: Math.min(cellAspectRatioTuple.chosenAr, cellAspectRatioTuple.maxAr),
666
+ max: Math.max(cellAspectRatioTuple.chosenAr, cellAspectRatioTuple.maxAr),
667
+ });
668
+ }
669
+ else if (cols === 1) {
670
+ cellAspectRatio = clamp({
671
+ value: contentWidth / (contentHeight / rows),
672
+ min: Math.min(cellAspectRatioTuple.chosenAr, cellAspectRatioTuple.maxAr),
673
+ max: Math.max(cellAspectRatioTuple.chosenAr, cellAspectRatioTuple.maxAr),
674
+ });
675
+ }
676
+ const { cellWidth, cellHeight, extraHorizontalPadding, extraVerticalPadding } = getCellBounds({
677
+ width: contentWidth,
678
+ height: contentHeight,
679
+ rows,
680
+ cols,
681
+ gridGap,
682
+ aspectRatio: cellAspectRatio,
683
+ });
684
+ return {
685
+ cellCount,
686
+ cellHeight,
687
+ cellWidth,
688
+ cols,
689
+ rows,
690
+ extraHorizontalPadding,
691
+ extraVerticalPadding,
692
+ gridGap,
693
+ paddings,
694
+ };
695
+ }
696
+ function getCellPropsAtIndexForLayout$1({ index, layout, }) {
697
+ const { cellWidth, cellHeight, rows, cols, cellCount, gridGap } = layout;
698
+ const top = Math.floor(index / cols);
699
+ const left = Math.floor(index % cols);
700
+ const leftPadding = getCenterPadding$1({ rows, cols, cellWidth, index, cellCount, gridGap });
701
+ return {
702
+ top: top * cellHeight + top * gridGap,
703
+ left: left * cellWidth + left * gridGap + leftPadding,
704
+ width: cellWidth,
705
+ height: cellHeight,
706
+ };
707
+ }
708
+
709
+ function getCenterPadding({ index, isPortrait, rows, cols, cellBounds, cellCount }) {
710
+ const canFit = rows * cols;
711
+ const leftOver = canFit - cellCount;
712
+ if (!leftOver) {
713
+ return 0;
714
+ }
715
+ const lastIndex = canFit - leftOver - 1;
716
+ const dimension = isPortrait ? cols : rows;
717
+ const firstIndex = lastIndex - (dimension - leftOver) + 1;
718
+ if (index < firstIndex || index > lastIndex) {
719
+ return 0;
720
+ }
721
+ return (leftOver * (isPortrait ? cellBounds.width : cellBounds.height)) / 2;
722
+ }
723
+ function calculateLayout$1({ containerBounds, isPortrait, cellCount, cellBounds, paddings = makeBox(), }) {
724
+ let width = 0;
725
+ let height = 0;
726
+ let rows = 0;
727
+ let cols = 0;
728
+ let extraHorizontalPadding = 0;
729
+ let extraVerticalPadding = 0;
730
+ const contentWidth = containerBounds.width - paddings.left - paddings.right;
731
+ const contentHeight = containerBounds.height - paddings.top - paddings.bottom;
732
+ if (cellCount) {
733
+ if (isPortrait) {
734
+ cols = Math.floor(contentWidth / cellBounds.width);
735
+ rows = Math.ceil(cellCount / cols);
736
+ width = contentWidth;
737
+ height = rows * cellBounds.height;
738
+ extraHorizontalPadding = (contentWidth - cols * cellBounds.width) / 2;
739
+ }
740
+ else {
741
+ rows = Math.floor(contentHeight / cellBounds.height);
742
+ cols = Math.ceil(cellCount / rows);
743
+ width = cols * cellBounds.width;
744
+ height = contentHeight;
745
+ extraVerticalPadding = (contentHeight - rows * cellBounds.height) / 2;
746
+ }
747
+ }
748
+ return {
749
+ isPortrait,
750
+ width: width + paddings.left + paddings.right,
751
+ height: height + paddings.top + paddings.bottom,
752
+ cellBounds,
753
+ cellCount,
754
+ rows,
755
+ cols,
756
+ extraHorizontalPadding,
757
+ extraVerticalPadding,
758
+ paddings,
759
+ };
760
+ }
761
+ function getCellPropsAtIndexForLayout({ index, layout }) {
762
+ const { isPortrait, rows, cols, cellCount, cellBounds, extraHorizontalPadding, extraVerticalPadding, paddings } = layout;
763
+ const top = isPortrait ? Math.floor(index / cols) : Math.floor(index % rows);
764
+ const left = isPortrait ? Math.floor(index % cols) : Math.floor(index / rows);
765
+ const centerPadding = getCenterPadding({ index, isPortrait, rows, cols, cellBounds, cellCount });
766
+ return {
767
+ top: paddings.top + extraVerticalPadding + top * cellBounds.height + (isPortrait ? 0 : centerPadding),
768
+ left: paddings.left + extraHorizontalPadding + left * cellBounds.width + (isPortrait ? centerPadding : 0),
769
+ width: cellBounds.width,
770
+ height: cellBounds.height,
771
+ };
772
+ }
773
+
774
+ const { BOTTOM_TOOLBAR_HEIGHT, VIDEO_CONTROLS_MIN_WIDTH, TABLET_BREAKPOINT, SUBGRID_EMPTY_STAGE_MAX_WIDTH } = layoutConstants;
775
+ const MIN_GRID_HEIGHT = 200;
776
+ const MIN_GRID_WIDTH = 300;
777
+ const FLOATING_VIDEO_SIZE = 200;
778
+ const OVERFLOW_ROOM_SUBGRID_TOP_PADDING = 20;
779
+ const CONSTRAINED_OVERFLOW_TRIGGER = 12;
780
+ const SUBGRID_CELL_PADDING_BOX = makeBox({ top: 4, bottom: 4 + 20 + 4, left: 8, right: 8 });
781
+ function getSubgridSizeOptions({ hasOverflow, hasStage }) {
782
+ if (hasOverflow && hasStage) {
783
+ return [80];
784
+ }
785
+ return [80, 60];
786
+ }
787
+ function getMinGridBounds({ cellCount }) {
788
+ const isSmallGrid = cellCount <= 6;
789
+ const minGridHeight = isSmallGrid ? MIN_GRID_HEIGHT - 50 : MIN_GRID_HEIGHT;
790
+ const minGridWidth = isSmallGrid ? MIN_GRID_WIDTH - 50 : MIN_GRID_WIDTH;
791
+ return makeBounds({ width: minGridWidth, height: minGridHeight });
792
+ }
793
+ function fitSupersizedContent({ bounds, aspectRatio, minGridContainerBounds, hasPresentationGrid, }) {
794
+ const { width, height } = bounds;
795
+ const hasVideoGrid = minGridContainerBounds.width > 0;
796
+ if (!hasVideoGrid) {
797
+ return {
798
+ isPortrait: width <= height,
799
+ supersizedContentBounds: bounds,
800
+ };
801
+ }
802
+ const minHorizontalSupersizedContentWidth = Math.round(width / 2);
803
+ const minVerticalSupersizedContentHeight = Math.round(height / 2);
804
+ const maxHorizontalSupersizedContentWidth = Math.max(width - minGridContainerBounds.width, 0);
805
+ const maxVerticalSupersizedContentHeight = Math.max(height - minGridContainerBounds.height, 0);
806
+ let isPortrait = maxHorizontalSupersizedContentWidth <= maxVerticalSupersizedContentHeight;
807
+ let horizontalCorrection = 0;
808
+ let verticalCorrection = 0;
809
+ if (aspectRatio) {
810
+ const horizontalContentBounds = fitToBounds(aspectRatio, {
811
+ width: maxHorizontalSupersizedContentWidth,
812
+ height,
813
+ });
814
+ const verticalContentBounds = fitToBounds(aspectRatio, {
815
+ width,
816
+ height: maxVerticalSupersizedContentHeight,
817
+ });
818
+ const isPortraitContent = aspectRatio <= 1.0;
819
+ isPortrait = isPortraitContent
820
+ ? verticalContentBounds.height > horizontalContentBounds.height
821
+ : verticalContentBounds.width > horizontalContentBounds.width;
822
+ if (isPortrait) {
823
+ const wastedSpace = maxVerticalSupersizedContentHeight -
824
+ Math.max(verticalContentBounds.height, minVerticalSupersizedContentHeight);
825
+ verticalCorrection = Math.max(wastedSpace, 0);
826
+ }
827
+ else {
828
+ const wastedSpace = maxHorizontalSupersizedContentWidth -
829
+ Math.max(horizontalContentBounds.width, minHorizontalSupersizedContentWidth);
830
+ horizontalCorrection = Math.max(wastedSpace, 0);
831
+ }
832
+ }
833
+ else if (hasPresentationGrid) {
834
+ isPortrait = maxHorizontalSupersizedContentWidth / maxVerticalSupersizedContentHeight >= 5;
835
+ }
836
+ const supersizedContentBounds = {
837
+ width: isPortrait ? width : maxHorizontalSupersizedContentWidth - horizontalCorrection,
838
+ height: isPortrait ? maxVerticalSupersizedContentHeight - verticalCorrection : height,
839
+ };
840
+ return {
841
+ isPortrait,
842
+ supersizedContentBounds,
843
+ };
844
+ }
845
+ function findBestSubgridLayout({ containerBounds, isPortrait, cellCount, cellSizeOptions, cellPaddings, paddings, }) {
846
+ let layout;
847
+ for (let i = 0; i < cellSizeOptions.length; i++) {
848
+ const cellSize = cellSizeOptions[i];
849
+ layout = calculateLayout$1({
850
+ containerBounds,
851
+ isPortrait,
852
+ cellCount,
853
+ cellBounds: makeBounds({
854
+ width: cellSize + cellPaddings.left + cellPaddings.right,
855
+ height: cellSize + cellPaddings.top + cellPaddings.bottom,
856
+ }),
857
+ paddings,
858
+ });
859
+ if (layout.width <= containerBounds.width && layout.height <= containerBounds.height) {
860
+ return layout;
861
+ }
862
+ }
863
+ return layout;
864
+ }
865
+ function calculateSubgridLayout({ containerBounds, isPortrait, cellCount, cellSizeOptions, cellPaddings, paddings, }) {
866
+ const layout = findBestSubgridLayout({
867
+ containerBounds,
868
+ isPortrait,
869
+ cellCount,
870
+ cellSizeOptions,
871
+ cellPaddings,
872
+ paddings,
873
+ });
874
+ const isSmallCell = layout.cellBounds.width - cellPaddings.left - cellPaddings.right ===
875
+ cellSizeOptions[cellSizeOptions.length - 1];
876
+ const bounds = makeBounds({
877
+ width: Math.min(layout.width, containerBounds.width),
878
+ height: Math.min(layout.height, containerBounds.height),
879
+ });
880
+ const contentBounds = makeBounds({ width: layout.width, height: layout.height });
881
+ if (cellCount === 0) {
882
+ return {
883
+ bounds,
884
+ contentBounds,
885
+ videoCells: [],
886
+ };
887
+ }
888
+ return {
889
+ bounds,
890
+ contentBounds,
891
+ videoCells: [...Array(cellCount)].map((_, index) => {
892
+ const { top, left, width, height } = getCellPropsAtIndexForLayout({ index, layout });
893
+ return {
894
+ origin: makeOrigin({ top, left }),
895
+ bounds: makeBounds({ width, height }),
896
+ paddings: cellPaddings,
897
+ isSmallCell,
898
+ };
899
+ }),
900
+ };
901
+ }
902
+ function calculateStageLayout({ cellPaddings, cellSizeOptions, containerBounds, containerOrigin, gridGap, hasConstrainedOverflow, hasPresentationContent, hasVideoContent, isConstrained, isMaximizeMode, isPortrait, isSmallScreen, maxGridWidth, shouldOverflowSubgrid, subgridVideos, }) {
903
+ const { width, height } = containerBounds;
904
+ const hasVideos = hasPresentationContent || hasVideoContent;
905
+ const hasSubgrid = subgridVideos.length > 0;
906
+ if (!hasSubgrid && !hasVideos) {
907
+ return {
908
+ isPortrait,
909
+ videosContainer: makeFrame(),
910
+ subgrid: Object.assign(Object.assign({}, makeFrame()), { contentBounds: makeBounds(), cells: [] }),
911
+ hasOverflow: false,
912
+ };
913
+ }
914
+ if (!hasSubgrid) {
915
+ return {
916
+ isPortrait,
917
+ videosContainer: makeFrame(Object.assign(Object.assign({}, containerBounds), containerOrigin)),
918
+ subgrid: Object.assign(Object.assign({}, makeFrame()), { contentBounds: makeBounds(), cells: [] }),
919
+ hasOverflow: hasConstrainedOverflow,
920
+ };
921
+ }
922
+ const topSubgridPadding = shouldOverflowSubgrid ? OVERFLOW_ROOM_SUBGRID_TOP_PADDING : 0;
923
+ let _containerBounds = null;
924
+ if (!hasVideos) {
925
+ _containerBounds = makeBounds({
926
+ width: Math.min(SUBGRID_EMPTY_STAGE_MAX_WIDTH, containerBounds.width),
927
+ height: containerBounds.height,
928
+ });
929
+ }
930
+ else if (shouldOverflowSubgrid) {
931
+ const cellSize = cellSizeOptions[0];
932
+ let visibleSubgridHeight = isMaximizeMode
933
+ ? BOTTOM_TOOLBAR_HEIGHT
934
+ : BOTTOM_TOOLBAR_HEIGHT +
935
+ cellPaddings.top +
936
+ (isConstrained || isSmallScreen ? cellSize / 2 : cellSize) +
937
+ cellPaddings.bottom;
938
+ if (containerBounds.height - visibleSubgridHeight < MIN_GRID_HEIGHT) {
939
+ visibleSubgridHeight = 0;
940
+ }
941
+ _containerBounds = makeBounds({
942
+ width: containerBounds.width,
943
+ height: visibleSubgridHeight,
944
+ });
945
+ }
946
+ else {
947
+ if (isMaximizeMode) {
948
+ _containerBounds = makeBounds({
949
+ width: containerBounds.width,
950
+ height: cellSizeOptions[0] + cellPaddings.top + cellPaddings.bottom,
951
+ });
952
+ }
953
+ else {
954
+ _containerBounds = isPortrait
955
+ ? makeBounds({ width: containerBounds.width, height: containerBounds.height / 2 })
956
+ : makeBounds({ width: containerBounds.width / 2, height: containerBounds.height });
957
+ }
958
+ }
959
+ let leftSubgridPadding = 0;
960
+ if (_containerBounds.width > maxGridWidth) {
961
+ leftSubgridPadding = (_containerBounds.width - maxGridWidth) / 2;
962
+ }
963
+ const subgridLayout = calculateSubgridLayout({
964
+ containerBounds: _containerBounds,
965
+ isPortrait,
966
+ cellCount: subgridVideos.length,
967
+ cellSizeOptions,
968
+ cellPaddings,
969
+ paddings: makeBox({ top: topSubgridPadding, left: leftSubgridPadding, right: leftSubgridPadding }),
970
+ });
971
+ const overflowNeedBounds = makeBounds({
972
+ width: subgridLayout.contentBounds.width - _containerBounds.width,
973
+ height: subgridLayout.contentBounds.height - _containerBounds.height,
974
+ });
975
+ const hasOverflow = overflowNeedBounds.width > 0 || overflowNeedBounds.height > 0;
976
+ let subgridBounds = null;
977
+ if (!hasVideos || shouldOverflowSubgrid || hasOverflow) {
978
+ subgridBounds = subgridLayout.contentBounds;
979
+ }
980
+ else {
981
+ subgridBounds = subgridLayout.bounds;
982
+ }
983
+ let videosContainerBounds;
984
+ let _subgridContainerShareBounds = subgridBounds;
985
+ if (hasVideos) {
986
+ if (shouldOverflowSubgrid) {
987
+ _subgridContainerShareBounds = makeBounds({
988
+ width: 0,
989
+ height: _containerBounds.height + topSubgridPadding + gridGap,
990
+ });
991
+ }
992
+ else {
993
+ _subgridContainerShareBounds = isPortrait
994
+ ? makeBounds({ width: 0, height: subgridLayout.bounds.height + gridGap })
995
+ : makeBounds({ width: subgridLayout.bounds.width + gridGap, height: 0 });
996
+ }
997
+ videosContainerBounds = insetBounds({
998
+ bounds: _subgridContainerShareBounds,
999
+ fromBounds: containerBounds,
1000
+ });
1001
+ }
1002
+ else {
1003
+ videosContainerBounds = makeBounds();
1004
+ }
1005
+ const videosContainerOrigin = Object.assign({}, containerOrigin);
1006
+ let subgridOrigin;
1007
+ if (hasVideos) {
1008
+ subgridOrigin = makeOrigin({
1009
+ top: isPortrait
1010
+ ? Math.max(containerOrigin.top + height - _subgridContainerShareBounds.height + gridGap, 0)
1011
+ : containerOrigin.top,
1012
+ left: isPortrait ? containerOrigin.left : Math.max(containerOrigin.left + width - subgridBounds.width, 0),
1013
+ });
1014
+ }
1015
+ else {
1016
+ subgridOrigin = makeOrigin({
1017
+ top: containerOrigin.top + Math.max(0, (height - subgridBounds.height) / 2),
1018
+ left: containerOrigin.left + Math.max(0, (width - subgridBounds.width) / 2),
1019
+ });
1020
+ }
1021
+ return {
1022
+ isPortrait,
1023
+ videosContainer: makeFrame(Object.assign(Object.assign({}, videosContainerBounds), videosContainerOrigin)),
1024
+ subgrid: Object.assign(Object.assign({}, makeFrame(Object.assign(Object.assign({}, subgridBounds), subgridOrigin))), { cells: subgridLayout.videoCells.map((cell, index) => {
1025
+ var _a, _b;
1026
+ return (Object.assign({ aspectRatio: ((_a = subgridVideos[index]) === null || _a === void 0 ? void 0 : _a.aspectRatio) || 1, type: "video", clientId: (_b = subgridVideos[index]) === null || _b === void 0 ? void 0 : _b.clientId }, cell));
1027
+ }), contentBounds: subgridLayout.contentBounds }),
1028
+ hasOverflow: hasOverflow || shouldOverflowSubgrid || hasConstrainedOverflow,
1029
+ overflowNeedBounds: shouldOverflowSubgrid ? makeBounds() : overflowNeedBounds,
1030
+ };
1031
+ }
1032
+ function calculateVideosContainerLayout({ containerBounds, containerOrigin, gridGap, supersizedContentAspectRatio, hasPresentationContent, hasPresentationGrid, hasVideoContent, minGridBounds, }) {
1033
+ const { width, height } = containerBounds;
1034
+ let isPortrait = width <= height;
1035
+ let presentationGridBounds = makeBounds();
1036
+ let presentationGridOrigin = makeOrigin();
1037
+ let videoGridBounds = hasVideoContent ? Object.assign({}, containerBounds) : makeBounds();
1038
+ let videoGridOrigin = hasVideoContent ? Object.assign({}, containerOrigin) : makeOrigin();
1039
+ if (hasPresentationContent) {
1040
+ const minGridContainerBounds = makeBounds({
1041
+ width: hasVideoContent ? minGridBounds.width + gridGap : 0,
1042
+ height: hasVideoContent ? minGridBounds.height + gridGap : 0,
1043
+ });
1044
+ const supersizedContentLayout = fitSupersizedContent({
1045
+ bounds: containerBounds,
1046
+ aspectRatio: supersizedContentAspectRatio || 1,
1047
+ minGridContainerBounds,
1048
+ hasPresentationGrid,
1049
+ });
1050
+ isPortrait = supersizedContentLayout.isPortrait;
1051
+ presentationGridBounds = supersizedContentLayout.supersizedContentBounds;
1052
+ presentationGridOrigin = Object.assign({}, containerOrigin);
1053
+ if (hasVideoContent) {
1054
+ videoGridBounds = makeBounds({
1055
+ width: isPortrait
1056
+ ? containerBounds.width
1057
+ : containerBounds.width - presentationGridBounds.width - gridGap,
1058
+ height: isPortrait
1059
+ ? containerBounds.height - presentationGridBounds.height - gridGap
1060
+ : containerBounds.height,
1061
+ });
1062
+ videoGridOrigin = makeOrigin({
1063
+ top: isPortrait ? containerOrigin.top + presentationGridBounds.height + gridGap : containerOrigin.top,
1064
+ left: isPortrait ? containerOrigin.left : containerOrigin.left + presentationGridBounds.width + gridGap,
1065
+ });
1066
+ }
1067
+ }
1068
+ return {
1069
+ isPortrait,
1070
+ presentationGrid: Object.assign({}, makeFrame(Object.assign(Object.assign({}, presentationGridBounds), presentationGridOrigin))),
1071
+ videoGrid: makeFrame(Object.assign(Object.assign({}, videoGridBounds), videoGridOrigin)),
1072
+ };
1073
+ }
1074
+ function calculateGridLayout({ containerBounds, paddings = makeBox(), videos, isConstrained, maxGridWidth, gridGap, }) {
1075
+ const { width, height } = containerBounds;
1076
+ const cappedWidth = maxGridWidth ? Math.min(width, maxGridWidth) : width;
1077
+ const cellCount = videos.length;
1078
+ let videoCells = null;
1079
+ const cellAspectRatios = videos.map((video) => video.aspectRatio || 1);
1080
+ const minGridBounds = getMinGridBounds({ cellCount });
1081
+ const gridLayout = calculateLayout$2({
1082
+ width: cappedWidth,
1083
+ height,
1084
+ cellCount,
1085
+ gridGap,
1086
+ cellAspectRatios,
1087
+ paddings,
1088
+ });
1089
+ videoCells = videos.map((video, index) => {
1090
+ const cellProps = getCellPropsAtIndexForLayout$1({ index, layout: gridLayout });
1091
+ const isSmallCell = gridLayout.cellWidth < minGridBounds.width;
1092
+ const shouldZoom = isConstrained || isSmallCell;
1093
+ const aspectRatio = shouldZoom ? gridLayout.cellWidth / gridLayout.cellHeight : video.aspectRatio || 1;
1094
+ return {
1095
+ clientId: video.clientId,
1096
+ isDraggable: video.isDraggable,
1097
+ origin: makeOrigin({
1098
+ top: cellProps.top,
1099
+ left: cellProps.left,
1100
+ }),
1101
+ bounds: makeBounds({
1102
+ width: cellProps.width,
1103
+ height: cellProps.height,
1104
+ }),
1105
+ aspectRatio,
1106
+ isSmallCell,
1107
+ type: "video",
1108
+ };
1109
+ });
1110
+ return {
1111
+ videoCells,
1112
+ extraHorizontalPadding: width !== cappedWidth
1113
+ ? gridLayout.extraHorizontalPadding + (width - cappedWidth) / 2
1114
+ : gridLayout.extraHorizontalPadding,
1115
+ extraVerticalPadding: gridLayout.extraVerticalPadding,
1116
+ paddings: gridLayout.paddings,
1117
+ gridGap,
1118
+ };
1119
+ }
1120
+ function calculateFloatingLayout({ roomBounds, containerFrame, floatingVideo, videoControlsHeight, margin = 8, }) {
1121
+ if (!floatingVideo) {
1122
+ return null;
1123
+ }
1124
+ const bounds = fitToBounds(floatingVideo.aspectRatio || 0, {
1125
+ width: FLOATING_VIDEO_SIZE,
1126
+ height: FLOATING_VIDEO_SIZE,
1127
+ });
1128
+ const isFloating = !(roomBounds.height - containerFrame.bounds.height - containerFrame.origin.top);
1129
+ const isConstrained = containerFrame.bounds.width - (bounds.width + margin) * 2 < VIDEO_CONTROLS_MIN_WIDTH;
1130
+ let verticalOffset = 0;
1131
+ if (isFloating && isConstrained) {
1132
+ verticalOffset = videoControlsHeight * -1;
1133
+ }
1134
+ else if (!isFloating && !isConstrained) {
1135
+ verticalOffset = videoControlsHeight;
1136
+ }
1137
+ const origin = makeOrigin({
1138
+ top: containerFrame.origin.top + (containerFrame.bounds.height - bounds.height - margin) + verticalOffset,
1139
+ left: containerFrame.origin.left + (containerFrame.bounds.width - bounds.width - margin),
1140
+ });
1141
+ const videoCell = {
1142
+ clientId: floatingVideo.clientId,
1143
+ isDraggable: floatingVideo.isDraggable,
1144
+ origin,
1145
+ bounds,
1146
+ aspectRatio: floatingVideo.aspectRatio,
1147
+ isSmallCell: true,
1148
+ };
1149
+ return videoCell;
1150
+ }
1151
+ function rebalanceLayoutPaddedAreas({ a, b, gridGap, isPortrait, }) {
1152
+ const aPad = isPortrait ? a.vertical : a.horizontal;
1153
+ const bPad = isPortrait ? b.vertical : b.horizontal;
1154
+ if (aPad === bPad) {
1155
+ return { a: 0, b: 0 };
1156
+ }
1157
+ const sArea = aPad < bPad ? a : b;
1158
+ const sAreaPad = isPortrait ? sArea.vertical : sArea.horizontal;
1159
+ const spaceBetween = gridGap + (aPad + bPad);
1160
+ const offset = (spaceBetween + sAreaPad) / 2 - sAreaPad;
1161
+ return {
1162
+ a: sArea === a ? offset : 0,
1163
+ b: sArea === b ? offset : 0,
1164
+ };
1165
+ }
1166
+ function rebalanceLayoutInPlace({ videosContainerLayout, gridLayout, presentationGridLayout, stageLayout, gridGap, shouldOverflowSubgrid, }) {
1167
+ const hasPresentationGrid = videosContainerLayout.presentationGrid.bounds.width > 0;
1168
+ const hasVideoGrid = videosContainerLayout.videoGrid.bounds.width > 0;
1169
+ const hasSubgrid = stageLayout.subgrid.bounds.width > 0;
1170
+ const videoGridRebalanceOffset = { vertical: 0, horizontal: 0 };
1171
+ if (hasPresentationGrid && hasVideoGrid) {
1172
+ const correction = rebalanceLayoutPaddedAreas({
1173
+ a: {
1174
+ horizontal: presentationGridLayout.extraHorizontalPadding,
1175
+ vertical: presentationGridLayout.extraVerticalPadding,
1176
+ },
1177
+ b: {
1178
+ horizontal: gridLayout.extraHorizontalPadding,
1179
+ vertical: gridLayout.extraVerticalPadding,
1180
+ },
1181
+ gridGap,
1182
+ isPortrait: videosContainerLayout.isPortrait,
1183
+ });
1184
+ if (videosContainerLayout.isPortrait) {
1185
+ videosContainerLayout.presentationGrid.origin.top += correction.a;
1186
+ videosContainerLayout.videoGrid.origin.top -= correction.b;
1187
+ videoGridRebalanceOffset.vertical = correction.b;
1188
+ }
1189
+ else {
1190
+ videosContainerLayout.presentationGrid.origin.left += correction.a;
1191
+ videosContainerLayout.videoGrid.origin.left -= correction.b;
1192
+ videoGridRebalanceOffset.horizontal = correction.b;
1193
+ }
1194
+ }
1195
+ if (hasSubgrid && (hasPresentationGrid || hasVideoGrid) && !shouldOverflowSubgrid) {
1196
+ const presentationGridArea = {
1197
+ horizontal: presentationGridLayout.extraHorizontalPadding,
1198
+ vertical: presentationGridLayout.extraVerticalPadding,
1199
+ };
1200
+ const gridArea = {
1201
+ horizontal: gridLayout.extraHorizontalPadding + videoGridRebalanceOffset.horizontal,
1202
+ vertical: gridLayout.extraVerticalPadding + videoGridRebalanceOffset.vertical,
1203
+ };
1204
+ let area;
1205
+ const hasBothGrids = hasPresentationGrid && hasVideoGrid;
1206
+ if (hasBothGrids && videosContainerLayout.isPortrait && !stageLayout.isPortrait) {
1207
+ area = presentationGridArea.horizontal < gridArea.horizontal ? presentationGridArea : gridArea;
1208
+ }
1209
+ else if (hasBothGrids && !videosContainerLayout.isPortrait && stageLayout.isPortrait) {
1210
+ area = presentationGridArea.vertical < gridArea.vertical ? presentationGridArea : gridArea;
1211
+ }
1212
+ else if (hasVideoGrid) {
1213
+ area = gridArea;
1214
+ }
1215
+ else {
1216
+ area = presentationGridArea;
1217
+ }
1218
+ const correction = rebalanceLayoutPaddedAreas({
1219
+ a: area,
1220
+ b: {
1221
+ horizontal: 0,
1222
+ vertical: 0,
1223
+ },
1224
+ gridGap,
1225
+ isPortrait: stageLayout.isPortrait,
1226
+ });
1227
+ if (stageLayout.isPortrait) {
1228
+ stageLayout.subgrid.origin.top -= correction.b;
1229
+ }
1230
+ else {
1231
+ stageLayout.subgrid.origin.left -= correction.b;
1232
+ }
1233
+ }
1234
+ }
1235
+ function redistributeEmptySpaceToSubgridInPlace({ vPaddings, hPaddings, stageLayout, videosContainerLayout, }) {
1236
+ if (stageLayout.isPortrait && (vPaddings.presentationGrid || vPaddings.grid)) {
1237
+ const totalVPadding = videosContainerLayout.isPortrait
1238
+ ? vPaddings.presentationGrid * 2 + vPaddings.grid * 2
1239
+ : vPaddings.presentationGrid + vPaddings.grid;
1240
+ stageLayout.videosContainer.bounds.height -= totalVPadding;
1241
+ stageLayout.subgrid.origin.top -= totalVPadding;
1242
+ stageLayout.subgrid.bounds.height += totalVPadding;
1243
+ videosContainerLayout.presentationGrid.bounds.height -= vPaddings.presentationGrid * 2;
1244
+ videosContainerLayout.videoGrid.bounds.height -= vPaddings.grid * 2;
1245
+ if (videosContainerLayout.isPortrait) {
1246
+ videosContainerLayout.videoGrid.origin.top -= vPaddings.presentationGrid;
1247
+ }
1248
+ }
1249
+ if (!stageLayout.isPortrait && (hPaddings.presentationGrid || hPaddings.grid)) {
1250
+ const totalHPadding = videosContainerLayout.isPortrait
1251
+ ? hPaddings.presentationGrid + hPaddings.grid
1252
+ : hPaddings.presentationGrid * 2 + hPaddings.grid * 2;
1253
+ stageLayout.videosContainer.bounds.width -= totalHPadding;
1254
+ stageLayout.subgrid.origin.left -= totalHPadding;
1255
+ stageLayout.subgrid.bounds.width += totalHPadding;
1256
+ videosContainerLayout.presentationGrid.bounds.width -= hPaddings.presentationGrid * 2;
1257
+ videosContainerLayout.videoGrid.bounds.width -= hPaddings.grid * 2;
1258
+ if (!videosContainerLayout.isPortrait) {
1259
+ videosContainerLayout.videoGrid.origin.left -= hPaddings.grid;
1260
+ }
1261
+ }
1262
+ }
1263
+ function findGridsEmptySpaceToRedistribute({ need, stageLayout, gridLayout, presentationGridLayout, videosContainerLayout, }) {
1264
+ let vPaddings = { presentationGrid: 0, grid: 0 };
1265
+ let hPaddings = { presentationGrid: 0, grid: 0 };
1266
+ if (stageLayout.isPortrait) {
1267
+ const minDim = Math.min(gridLayout.extraVerticalPadding, presentationGridLayout.extraVerticalPadding);
1268
+ vPaddings = {
1269
+ presentationGrid: Math.min(videosContainerLayout.isPortrait ? presentationGridLayout.extraVerticalPadding : minDim, need.height),
1270
+ grid: Math.min(videosContainerLayout.isPortrait ? gridLayout.extraVerticalPadding : minDim, need.height),
1271
+ };
1272
+ }
1273
+ else {
1274
+ const minDim = Math.min(gridLayout.extraHorizontalPadding, presentationGridLayout.extraHorizontalPadding);
1275
+ hPaddings = {
1276
+ presentationGrid: Math.min(videosContainerLayout.isPortrait ? minDim : presentationGridLayout.extraHorizontalPadding, need.width),
1277
+ grid: Math.min(videosContainerLayout.isPortrait ? minDim : gridLayout.extraHorizontalPadding, need.width),
1278
+ };
1279
+ }
1280
+ return { vPaddings, hPaddings };
1281
+ }
1282
+ function calculateGridLayouts({ gridGap, isConstrained, presentationVideos, videos, videosContainerLayout, gridLayoutPaddings = makeBox(), presentationGridLayoutPaddings = makeBox(), maxGridWidth, }) {
1283
+ const gridLayout = calculateGridLayout({
1284
+ containerBounds: videosContainerLayout.videoGrid.bounds,
1285
+ gridGap,
1286
+ isConstrained,
1287
+ maxGridWidth,
1288
+ paddings: gridLayoutPaddings,
1289
+ videos,
1290
+ });
1291
+ const presentationGridLayout = calculateGridLayout({
1292
+ containerBounds: videosContainerLayout.presentationGrid.bounds,
1293
+ gridGap,
1294
+ isConstrained,
1295
+ maxGridWidth,
1296
+ paddings: presentationGridLayoutPaddings,
1297
+ videos: presentationVideos,
1298
+ });
1299
+ return { gridLayout, presentationGridLayout };
1300
+ }
1301
+ function calculateLayout({ floatingVideo, frame, gridGap, isConstrained, isMaximizeMode = false, isXLMeetingSize = false, paddings = makeBox(), presentationVideos = [], rebalanceLayout = false, roomBounds, roomLayoutHasOverlow = false, subgridCellPaddings = SUBGRID_CELL_PADDING_BOX, subgridVideos = [], videoControlsHeight = 0, videos = [], videoGridGap, }) {
1302
+ let shouldOverflowSubgrid = isXLMeetingSize && (screen ? screen.width <= 2048 : true);
1303
+ const hasPresentationContent = !!presentationVideos.length;
1304
+ const hasPresentationGrid = presentationVideos.length > 1;
1305
+ const supersizedContentAspectRatio = hasPresentationContent && !hasPresentationGrid ? presentationVideos[0].aspectRatio : null;
1306
+ const hasVideoContent = !!videos.length;
1307
+ const width = frame.bounds.width - paddings.left - paddings.right;
1308
+ let height = frame.bounds.height - paddings.top - paddings.bottom;
1309
+ const maxGridWidth = Math.max(25 * 88, (80 / 100) * width);
1310
+ const hasConstrainedOverflow = isConstrained && videos.length > CONSTRAINED_OVERFLOW_TRIGGER;
1311
+ const lineHeight = height / 4;
1312
+ const extraLines = Math.ceil((videos.length - CONSTRAINED_OVERFLOW_TRIGGER) / 3);
1313
+ height = hasConstrainedOverflow ? height + lineHeight * extraLines : height;
1314
+ const stageBounds = makeBounds({ width, height });
1315
+ const stageOrigin = makeOrigin({ top: paddings.top, left: paddings.left });
1316
+ const _minBounds = getMinGridBounds({ cellCount: videos.length });
1317
+ const minGridBounds = _minBounds;
1318
+ const isSmallScreen = roomBounds.width < TABLET_BREAKPOINT || roomBounds.height < TABLET_BREAKPOINT;
1319
+ const forceStageLayoutPortrait = isMaximizeMode;
1320
+ const stageLayoutIsPortrait = forceStageLayoutPortrait ||
1321
+ shouldOverflowSubgrid ||
1322
+ !(hasPresentationContent || hasVideoContent) ||
1323
+ stageBounds.width <= stageBounds.height;
1324
+ const stableStageLayoutProps = {
1325
+ cellPaddings: subgridCellPaddings,
1326
+ containerBounds: stageBounds,
1327
+ containerOrigin: stageOrigin,
1328
+ gridGap,
1329
+ hasPresentationContent,
1330
+ hasVideoContent,
1331
+ isConstrained,
1332
+ isMaximizeMode,
1333
+ isSmallScreen,
1334
+ subgridVideos,
1335
+ maxGridWidth,
1336
+ };
1337
+ let stageLayout = calculateStageLayout(Object.assign(Object.assign({}, stableStageLayoutProps), { cellSizeOptions: getSubgridSizeOptions({
1338
+ hasOverflow: shouldOverflowSubgrid,
1339
+ hasStage: hasPresentationContent || hasVideoContent,
1340
+ }), isPortrait: stageLayoutIsPortrait, shouldOverflowSubgrid,
1341
+ hasConstrainedOverflow }));
1342
+ let forceRerunAsOverflow = false;
1343
+ if (!shouldOverflowSubgrid && roomLayoutHasOverlow && !stageLayout.hasOverflow) {
1344
+ const _stageLayout = calculateStageLayout(Object.assign(Object.assign({}, stableStageLayoutProps), { containerBounds: makeBounds({
1345
+ width: stageBounds.width,
1346
+ height: stageBounds.height - BOTTOM_TOOLBAR_HEIGHT,
1347
+ }), cellSizeOptions: getSubgridSizeOptions({
1348
+ hasOverflow: shouldOverflowSubgrid,
1349
+ hasStage: hasPresentationContent || hasVideoContent,
1350
+ }), isPortrait: stageLayoutIsPortrait, shouldOverflowSubgrid,
1351
+ hasConstrainedOverflow }));
1352
+ if (_stageLayout.hasOverflow) {
1353
+ forceRerunAsOverflow = true;
1354
+ }
1355
+ }
1356
+ if (forceRerunAsOverflow || (stageLayout.hasOverflow && !shouldOverflowSubgrid)) {
1357
+ shouldOverflowSubgrid = true;
1358
+ stageLayout = calculateStageLayout(Object.assign(Object.assign({}, stableStageLayoutProps), { cellSizeOptions: getSubgridSizeOptions({
1359
+ hasOverflow: true,
1360
+ hasStage: hasPresentationContent || hasVideoContent,
1361
+ }), isPortrait: true, shouldOverflowSubgrid,
1362
+ hasConstrainedOverflow }));
1363
+ }
1364
+ const videosContainerLayout = calculateVideosContainerLayout({
1365
+ containerBounds: stageLayout.videosContainer.bounds,
1366
+ containerOrigin: stageLayout.videosContainer.origin,
1367
+ gridGap,
1368
+ supersizedContentAspectRatio: supersizedContentAspectRatio || 1,
1369
+ hasPresentationContent,
1370
+ hasPresentationGrid,
1371
+ hasVideoContent,
1372
+ minGridBounds,
1373
+ });
1374
+ let { gridLayout, presentationGridLayout } = calculateGridLayouts({
1375
+ gridGap: videoGridGap || gridGap,
1376
+ isConstrained,
1377
+ presentationVideos,
1378
+ videos,
1379
+ videosContainerLayout,
1380
+ maxGridWidth,
1381
+ });
1382
+ if (stageLayout.hasOverflow && !shouldOverflowSubgrid) {
1383
+ const { vPaddings, hPaddings } = findGridsEmptySpaceToRedistribute({
1384
+ need: stageLayout.overflowNeedBounds || makeBounds(),
1385
+ stageLayout,
1386
+ gridLayout,
1387
+ presentationGridLayout,
1388
+ videosContainerLayout,
1389
+ });
1390
+ if (vPaddings.presentationGrid || vPaddings.grid || hPaddings.presentationGrid || hPaddings.grid) {
1391
+ redistributeEmptySpaceToSubgridInPlace({ vPaddings, hPaddings, stageLayout, videosContainerLayout });
1392
+ ({ gridLayout, presentationGridLayout } = calculateGridLayouts({
1393
+ gridGap: videoGridGap || gridGap,
1394
+ isConstrained,
1395
+ presentationVideos,
1396
+ videos,
1397
+ videosContainerLayout,
1398
+ maxGridWidth,
1399
+ }));
1400
+ }
1401
+ }
1402
+ const floatingLayout = calculateFloatingLayout({
1403
+ roomBounds,
1404
+ containerFrame: frame,
1405
+ floatingVideo: floatingVideo || null,
1406
+ videoControlsHeight,
1407
+ });
1408
+ if (rebalanceLayout) {
1409
+ rebalanceLayoutInPlace({
1410
+ videosContainerLayout,
1411
+ gridLayout,
1412
+ presentationGridLayout,
1413
+ stageLayout,
1414
+ gridGap,
1415
+ shouldOverflowSubgrid,
1416
+ });
1417
+ }
1418
+ let overflowBoundsHeight = paddings.top + stageLayout.videosContainer.bounds.height + stageLayout.subgrid.bounds.height + paddings.bottom;
1419
+ if (hasPresentationContent || hasVideoContent) {
1420
+ overflowBoundsHeight += gridGap;
1421
+ }
1422
+ return {
1423
+ isPortrait: stageLayout.isPortrait,
1424
+ hasOverflow: stageLayout.hasOverflow,
1425
+ videosContainer: Object.assign(Object.assign({}, videosContainerLayout), { bounds: makeBounds({
1426
+ width: stageLayout.videosContainer.bounds.width,
1427
+ height: stageLayout.videosContainer.bounds.height,
1428
+ }), origin: makeOrigin({
1429
+ top: stageLayout.videosContainer.origin.top,
1430
+ left: stageLayout.videosContainer.origin.left,
1431
+ }) }),
1432
+ bounds: makeBounds({
1433
+ height: shouldOverflowSubgrid ? overflowBoundsHeight : frame.bounds.height,
1434
+ width: frame.bounds.width,
1435
+ }),
1436
+ gridGap,
1437
+ presentationGrid: Object.assign(Object.assign({}, videosContainerLayout.presentationGrid), { cells: presentationGridLayout.videoCells, paddings: makeBox({
1438
+ top: presentationGridLayout.paddings.top + presentationGridLayout.extraVerticalPadding,
1439
+ bottom: presentationGridLayout.paddings.bottom + presentationGridLayout.extraVerticalPadding,
1440
+ left: presentationGridLayout.paddings.left + presentationGridLayout.extraHorizontalPadding,
1441
+ right: presentationGridLayout.paddings.right + presentationGridLayout.extraHorizontalPadding,
1442
+ }) }),
1443
+ videoGrid: Object.assign(Object.assign({}, videosContainerLayout.videoGrid), { cells: gridLayout.videoCells, paddings: makeBox({
1444
+ top: gridLayout.paddings.top + gridLayout.extraVerticalPadding,
1445
+ bottom: gridLayout.paddings.bottom + gridLayout.extraVerticalPadding,
1446
+ left: gridLayout.paddings.left + gridLayout.extraHorizontalPadding,
1447
+ right: gridLayout.paddings.right + gridLayout.extraHorizontalPadding,
1448
+ }) }),
1449
+ subgrid: stageLayout.subgrid,
1450
+ floatingContent: Object.assign(Object.assign({}, floatingLayout), floatingVideo),
1451
+ };
1452
+ }
1453
+
1454
+ function makeVideoCellView({ aspectRatio, avatarSize, cellPaddings, client = undefined, isDraggable = true, isPlaceholder = false, isSubgrid = false, }) {
1455
+ return {
1456
+ aspectRatio: aspectRatio || 16 / 9,
1457
+ avatarSize,
1458
+ cellPaddings,
1459
+ client,
1460
+ clientId: (client === null || client === void 0 ? void 0 : client.id) || "",
1461
+ isDraggable,
1462
+ isPlaceholder,
1463
+ isSubgrid,
1464
+ type: "video",
1465
+ };
1466
+ }
1467
+
1468
+ const STAGE_PARTICIPANT_LIMIT = 12;
1469
+ const ACTIVE_VIDEO_SUBGRID_TRIGGER = 12;
1470
+
1471
+ function calculateSubgridViews({ clientViews, activeVideosSubgridTrigger, shouldShowSubgrid, spotlightedParticipants, }) {
1472
+ if (!shouldShowSubgrid) {
1473
+ return [];
1474
+ }
1475
+ const hasSpotlights = spotlightedParticipants.length > 0;
1476
+ const hasPresentationStage = hasSpotlights;
1477
+ const notSpotlighted = clientViews.filter((client) => !client.isPresentation && !spotlightedParticipants.includes(client));
1478
+ const noVideoViews = notSpotlighted.filter((client) => !client.isVideoEnabled);
1479
+ const videoLimitReached = notSpotlighted.filter((client) => client.isVideoEnabled).length > activeVideosSubgridTrigger;
1480
+ const unmutedVideos = notSpotlighted.filter((client) => !noVideoViews.includes(client) && client.isAudioEnabled);
1481
+ const mutedVideos = notSpotlighted.filter((client) => !noVideoViews.includes(client) && !client.isAudioEnabled);
1482
+ if (noVideoViews.length && hasPresentationStage) {
1483
+ return [...mutedVideos, ...noVideoViews];
1484
+ }
1485
+ if (videoLimitReached && mutedVideos.length) {
1486
+ const sorted = [...unmutedVideos, ...mutedVideos];
1487
+ const inGrid = sorted.slice(0, activeVideosSubgridTrigger);
1488
+ if (inGrid.length <= activeVideosSubgridTrigger) {
1489
+ return [...mutedVideos.filter((client) => !inGrid.includes(client)), ...noVideoViews];
1490
+ }
1491
+ else {
1492
+ return [...mutedVideos, ...noVideoViews];
1493
+ }
1494
+ }
1495
+ return noVideoViews;
1496
+ }
1497
+ function useGridParticipants({ activeVideosSubgridTrigger = ACTIVE_VIDEO_SUBGRID_TRIGGER, stageParticipantLimit = STAGE_PARTICIPANT_LIMIT, forceSubgrid = true, } = {}) {
1498
+ const allClientViews = useAppSelector(selectAllClientViews);
1499
+ const spotlightedParticipants = useAppSelector(selectSpotlightedClientViews);
1500
+ const numParticipants = useAppSelector(selectNumParticipants);
1501
+ const shouldShowSubgrid = React.useMemo(() => {
1502
+ return forceSubgrid ? true : numParticipants > stageParticipantLimit;
1503
+ }, [forceSubgrid, numParticipants, stageParticipantLimit]);
1504
+ const clientViewsInSubgrid = React.useMemo(() => {
1505
+ return calculateSubgridViews({
1506
+ clientViews: allClientViews,
1507
+ activeVideosSubgridTrigger,
1508
+ shouldShowSubgrid,
1509
+ spotlightedParticipants,
1510
+ });
1511
+ }, [allClientViews, shouldShowSubgrid, activeVideosSubgridTrigger, spotlightedParticipants]);
1512
+ const clientViewsOnStage = React.useMemo(() => {
1513
+ return allClientViews.filter((client) => !clientViewsInSubgrid.includes(client));
1514
+ }, [allClientViews, clientViewsInSubgrid]);
1515
+ const clientViewsInPresentationGrid = React.useMemo(() => {
1516
+ return allClientViews.filter((client) => client.isPresentation).concat(spotlightedParticipants);
1517
+ }, [allClientViews, spotlightedParticipants]);
1518
+ const clientViewsInGrid = React.useMemo(() => {
1519
+ return clientViewsOnStage.filter((client) => !clientViewsInPresentationGrid.includes(client));
1520
+ }, [clientViewsOnStage, clientViewsInPresentationGrid]);
1521
+ return {
1522
+ clientViewsInGrid,
1523
+ clientViewsInPresentationGrid,
1524
+ clientViewsInSubgrid,
1525
+ };
1526
+ }
1527
+
1528
+ function useGrid({ activeVideosSubgridTrigger, forceSubgrid, stageParticipantLimit = STAGE_PARTICIPANT_LIMIT, } = {}) {
1529
+ const [containerBounds, setContainerBounds] = React.useState({ width: 0, height: 0 });
1530
+ const [clientAspectRatios, setClientAspectRatios] = React.useState({});
1531
+ const { clientViewsInGrid, clientViewsInPresentationGrid, clientViewsInSubgrid } = useGridParticipants({
1532
+ activeVideosSubgridTrigger,
1533
+ forceSubgrid,
1534
+ stageParticipantLimit,
1535
+ });
1536
+ const cellViewsVideoGrid = React.useMemo(() => {
1537
+ return clientViewsInGrid.map((client) => {
1538
+ return makeVideoCellView({
1539
+ client,
1540
+ aspectRatio: clientAspectRatios[client.id],
1541
+ avatarSize: 0,
1542
+ cellPaddings: { top: 0, right: 0 },
1543
+ });
1544
+ });
1545
+ }, [clientViewsInGrid, clientAspectRatios]);
1546
+ const cellViewsInPresentationGrid = React.useMemo(() => {
1547
+ return clientViewsInPresentationGrid.map((client) => {
1548
+ return makeVideoCellView({
1549
+ client,
1550
+ aspectRatio: clientAspectRatios[client.id],
1551
+ avatarSize: 0,
1552
+ cellPaddings: { top: 0, right: 0 },
1553
+ });
1554
+ });
1555
+ }, [clientViewsInPresentationGrid, clientAspectRatios]);
1556
+ const cellViewsInSubgrid = React.useMemo(() => {
1557
+ return clientViewsInSubgrid.map((client) => {
1558
+ return makeVideoCellView({
1559
+ client,
1560
+ aspectRatio: clientAspectRatios[client.id],
1561
+ avatarSize: 0,
1562
+ cellPaddings: { top: 0, right: 0 },
1563
+ isSubgrid: true,
1564
+ });
1565
+ });
1566
+ }, [clientViewsInSubgrid, clientAspectRatios]);
1567
+ const containerFrame = React.useMemo(() => {
1568
+ return makeFrame(containerBounds);
1569
+ }, [containerBounds]);
1570
+ const videoStage = React.useMemo(() => {
1571
+ return calculateLayout({
1572
+ frame: containerFrame,
1573
+ gridGap: 8,
1574
+ isConstrained: false,
1575
+ roomBounds: containerFrame.bounds,
1576
+ videos: cellViewsVideoGrid,
1577
+ videoGridGap: 0,
1578
+ presentationVideos: cellViewsInPresentationGrid,
1579
+ subgridVideos: cellViewsInSubgrid,
1580
+ });
1581
+ }, [containerFrame, cellViewsVideoGrid, cellViewsInPresentationGrid, cellViewsInSubgrid]);
1582
+ return {
1583
+ cellViewsVideoGrid,
1584
+ cellViewsInPresentationGrid,
1585
+ cellViewsInSubgrid,
1586
+ videoStage,
1587
+ setContainerBounds,
1588
+ setClientAspectRatios,
1589
+ };
1590
+ }
1591
+
1592
+ const getInitialsFromName = (name = "") => {
1593
+ name = name.trim();
1594
+ if (name) {
1595
+ const initials = name.split(/-| /).map((n) => runes(n)[0]);
1596
+ return initials.slice(0, 3);
1597
+ }
1598
+ return [];
1599
+ };
1600
+ const Initials = ({ name }) => {
1601
+ const initials = getInitialsFromName(name);
1602
+ const fontSize = [0, 56, 48, 40][initials.length] || 32;
1603
+ const initialsStr = initials.join("").toUpperCase();
1604
+ return (React.createElement("svg", { viewBox: "-60 -60 120 120", "aria-hidden": "true", style: {
1605
+ height: "100%",
1606
+ pointerEvents: "none",
1607
+ width: "100%",
1608
+ } },
1609
+ React.createElement("text", { x: 0, y: 0, textAnchor: "middle", dominantBaseline: "central", fontSize: fontSize }, initialsStr)));
1610
+ };
1611
+ function Avatar(_a) {
1612
+ var { avatarUrl, className, size = 40, name, variant = "round" } = _a, rest = __rest(_a, ["avatarUrl", "className", "size", "name", "variant"]);
1613
+ return (React.createElement("div", Object.assign({ style: {
1614
+ height: `${size}px`,
1615
+ width: `${size}px`,
1616
+ userSelect: "none",
1617
+ overflow: "hidden",
1618
+ borderRadius: variant === "round" ? "50%" : "4px",
1619
+ backgroundColor: "#f8e3c8",
1620
+ }, className: className, title: name }, rest), !avatarUrl && name ? (React.createElement(Initials, { name: name })) : (React.createElement("img", { src: avatarUrl || "", alt: "", style: {
1621
+ height: "100%",
1622
+ width: "100%",
1623
+ objectFit: "cover",
1624
+ maxWidth: "initial",
1625
+ } }))));
1626
+ }
1627
+
1628
+ function VideoMutedIndicator({ avatarUrl, displayName, isSmallCell, withRoundedCorners }) {
1629
+ return (React.createElement("div", { style: {
1630
+ display: "flex",
1631
+ flexDirection: "column",
1632
+ alignItems: "center",
1633
+ justifyContent: "center",
1634
+ position: "absolute",
1635
+ top: 0,
1636
+ left: 0,
1637
+ height: "100%",
1638
+ width: "100%",
1639
+ borderRadius: withRoundedCorners ? "8px" : "0",
1640
+ } },
1641
+ React.createElement("div", { style: {
1642
+ height: isSmallCell ? 60 : 80,
1643
+ width: isSmallCell ? 60 : 80,
1644
+ pointerEvents: "none",
1645
+ position: "relative",
1646
+ } },
1647
+ React.createElement(Avatar, { variant: "square", avatarUrl: avatarUrl, name: displayName, size: isSmallCell ? 60 : 80 }))));
1648
+ }
1649
+
1650
+ function renderCellView({ cellView, onSetClientAspectRatio }) {
1651
+ switch (cellView.type) {
1652
+ case "video":
1653
+ return (React.createElement(GridVideoCellView, { aspectRatio: cellView.aspectRatio, participant: cellView.client, isPlaceholder: cellView.isPlaceholder, isSubgrid: cellView.isSubgrid, key: cellView.clientId, onSetClientAspectRatio: onSetClientAspectRatio }));
1654
+ }
1655
+ }
1656
+ function GridVideoCellView({ aspectRatio, participant, render, onSetClientAspectRatio }) {
1657
+ const videoEl = React.useRef(null);
1658
+ const handleResize = React.useCallback(() => {
1659
+ const ar = videoEl.current && videoEl.current.captureAspectRatio();
1660
+ if (ar && ar !== aspectRatio && (participant === null || participant === void 0 ? void 0 : participant.id)) {
1661
+ onSetClientAspectRatio({ aspectRatio: ar, clientId: participant.id });
1662
+ }
1663
+ }, []);
1664
+ return (React.createElement("div", null, render ? (render()) : (participant === null || participant === void 0 ? void 0 : participant.stream) && participant.isVideoEnabled ? (React.createElement(VideoView, { ref: videoEl, stream: participant.stream, onVideoResize: handleResize, style: {
1665
+ borderRadius: "8px",
1666
+ } })) : (React.createElement(VideoMutedIndicator, { isSmallCell: false, displayName: (participant === null || participant === void 0 ? void 0 : participant.displayName) || "Guest", withRoundedCorners: true }))));
1667
+ }
1668
+ function Grid({ renderParticipant, stageParticipantLimit }) {
1669
+ const gridRef = React.useRef(null);
1670
+ const { cellViewsVideoGrid, cellViewsInPresentationGrid, cellViewsInSubgrid, videoStage, setContainerBounds, setClientAspectRatios, } = useGrid({ activeVideosSubgridTrigger: 12, stageParticipantLimit });
1671
+ const presentationGridContent = React.useMemo(() => cellViewsInPresentationGrid.map((cellView) => renderCellView({
1672
+ cellView,
1673
+ onSetClientAspectRatio: ({ aspectRatio, clientId }) => setClientAspectRatios((prev) => (Object.assign(Object.assign({}, prev), { [clientId]: aspectRatio }))),
1674
+ })), [cellViewsInPresentationGrid]);
1675
+ const gridContent = React.useMemo(() => cellViewsVideoGrid.map((cellView) => renderCellView({
1676
+ cellView,
1677
+ onSetClientAspectRatio: ({ aspectRatio, clientId }) => setClientAspectRatios((prev) => (Object.assign(Object.assign({}, prev), { [clientId]: aspectRatio }))),
1678
+ })), [cellViewsVideoGrid]);
1679
+ const subgridContent = React.useMemo(() => cellViewsInSubgrid.map((cellView) => renderCellView({
1680
+ cellView,
1681
+ onSetClientAspectRatio: ({ aspectRatio, clientId }) => setClientAspectRatios((prev) => (Object.assign(Object.assign({}, prev), { [clientId]: aspectRatio }))),
1682
+ })), [cellViewsInSubgrid]);
1683
+ React.useEffect(() => {
1684
+ if (!gridRef.current) {
1685
+ return;
1686
+ }
1687
+ const resizeObserver = new ResizeObserver(debounce(() => {
1688
+ var _a, _b, _c, _d;
1689
+ setContainerBounds({
1690
+ width: (_b = (_a = gridRef.current) === null || _a === void 0 ? void 0 : _a.clientWidth) !== null && _b !== void 0 ? _b : 640,
1691
+ height: (_d = (_c = gridRef.current) === null || _c === void 0 ? void 0 : _c.clientHeight) !== null && _d !== void 0 ? _d : 480,
1692
+ });
1693
+ }, { delay: 60, edges: false }));
1694
+ resizeObserver.observe(gridRef.current);
1695
+ return () => {
1696
+ resizeObserver.disconnect();
1697
+ };
1698
+ }, []);
1699
+ return (React.createElement("div", { ref: gridRef, style: {
1700
+ width: "100%",
1701
+ height: "100%",
1702
+ position: "relative",
1703
+ } },
1704
+ React.createElement(VideoStageLayout, { layoutVideoStage: videoStage, presentationGridContent: presentationGridContent, gridContent: gridContent, subgridContent: subgridContent })));
1705
+ }
1706
+
1707
+ export { Grid as VideoGrid, VideoView, Provider as WherebyProvider, useLocalMedia, useRoomConnection };