@whereby.com/browser-sdk 2.13.0 → 3.0.1
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/cdn/{v2-embed.js → v3-embed.js} +6 -6
- package/dist/cdn/v3-react.js +3 -0
- package/dist/react/index.d.ts +67 -18
- package/dist/react/index.esm.js +1546 -114
- package/package.json +7 -7
- package/dist/cdn/v2-react.js +0 -3
package/dist/react/index.esm.js
CHANGED
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
-
import {
|
|
3
|
-
import { debounce, selectChatMessages, selectCloudRecordingRaw, selectLocalParticipantRaw, selectLocalMediaStream, selectRemoteParticipants, selectScreenshares, selectRoomConnectionStatus, selectStreamingRaw, selectWaitingParticipants,
|
|
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
|
-
|
|
41
|
-
|
|
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
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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
|
-
|
|
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 = "
|
|
148
|
+
const browserSdkVersion = "3.0.1";
|
|
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
|
|
119
|
-
const
|
|
120
|
-
|
|
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
|
-
|
|
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
|
-
|
|
142
|
-
|
|
171
|
+
}, [roomUrl, roomConnectionOptions]);
|
|
172
|
+
React.useEffect(() => {
|
|
143
173
|
return () => {
|
|
144
|
-
|
|
145
|
-
unsubscribe();
|
|
174
|
+
dispatch(doAppStop());
|
|
146
175
|
};
|
|
147
176
|
}, []);
|
|
148
|
-
React.
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
const
|
|
164
|
-
const
|
|
165
|
-
const
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
const
|
|
169
|
-
const
|
|
170
|
-
const
|
|
171
|
-
const
|
|
172
|
-
const
|
|
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
|
|
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,1458 @@ 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
|
|
243
|
-
|
|
244
|
-
|
|
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
|
-
|
|
252
|
-
store.dispatch(doStopLocalMedia());
|
|
257
|
+
dispatch(doStopLocalMedia());
|
|
253
258
|
};
|
|
254
259
|
}, []);
|
|
255
|
-
const setCameraDevice = useCallback((deviceId) =>
|
|
256
|
-
const setMicrophoneDevice = useCallback((deviceId) =>
|
|
257
|
-
const
|
|
258
|
-
const
|
|
259
|
-
const
|
|
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
|
-
|
|
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 spotlightedParticipants;
|
|
1517
|
+
}, [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, videoGridGap = 8, } = {}) {
|
|
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,
|
|
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 }) {
|
|
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
|
+
}, []);
|
|
1662
|
+
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: {
|
|
1663
|
+
borderRadius: "8px",
|
|
1664
|
+
} })) : (React.createElement(VideoMutedIndicator, { isSmallCell: false, displayName: (participant === null || participant === void 0 ? void 0 : participant.displayName) || "Guest", withRoundedCorners: true }))));
|
|
1665
|
+
}
|
|
1666
|
+
function Grid({ renderParticipant, stageParticipantLimit, videoGridGap }) {
|
|
1667
|
+
const gridRef = React.useRef(null);
|
|
1668
|
+
const { cellViewsVideoGrid, cellViewsInPresentationGrid, cellViewsInSubgrid, videoStage, setContainerBounds, setClientAspectRatios, } = useGrid({ activeVideosSubgridTrigger: 12, stageParticipantLimit, videoGridGap });
|
|
1669
|
+
const presentationGridContent = React.useMemo(() => cellViewsInPresentationGrid.map((cellView) => renderCellView({
|
|
1670
|
+
cellView,
|
|
1671
|
+
onSetClientAspectRatio: ({ aspectRatio, clientId }) => setClientAspectRatios((prev) => (Object.assign(Object.assign({}, prev), { [clientId]: aspectRatio }))),
|
|
1672
|
+
})), [cellViewsInPresentationGrid]);
|
|
1673
|
+
const gridContent = React.useMemo(() => cellViewsVideoGrid.map((cellView) => renderCellView({
|
|
1674
|
+
cellView,
|
|
1675
|
+
onSetClientAspectRatio: ({ aspectRatio, clientId }) => setClientAspectRatios((prev) => (Object.assign(Object.assign({}, prev), { [clientId]: aspectRatio }))),
|
|
1676
|
+
})), [cellViewsVideoGrid]);
|
|
1677
|
+
const subgridContent = React.useMemo(() => cellViewsInSubgrid.map((cellView) => renderCellView({
|
|
1678
|
+
cellView,
|
|
1679
|
+
onSetClientAspectRatio: ({ aspectRatio, clientId }) => setClientAspectRatios((prev) => (Object.assign(Object.assign({}, prev), { [clientId]: aspectRatio }))),
|
|
1680
|
+
})), [cellViewsInSubgrid]);
|
|
1681
|
+
React.useEffect(() => {
|
|
1682
|
+
if (!gridRef.current) {
|
|
1683
|
+
return;
|
|
1684
|
+
}
|
|
1685
|
+
const resizeObserver = new ResizeObserver(debounce(() => {
|
|
1686
|
+
var _a, _b, _c, _d;
|
|
1687
|
+
setContainerBounds({
|
|
1688
|
+
width: (_b = (_a = gridRef.current) === null || _a === void 0 ? void 0 : _a.clientWidth) !== null && _b !== void 0 ? _b : 640,
|
|
1689
|
+
height: (_d = (_c = gridRef.current) === null || _c === void 0 ? void 0 : _c.clientHeight) !== null && _d !== void 0 ? _d : 480,
|
|
1690
|
+
});
|
|
1691
|
+
}, { delay: 60, edges: false }));
|
|
1692
|
+
resizeObserver.observe(gridRef.current);
|
|
1693
|
+
return () => {
|
|
1694
|
+
resizeObserver.disconnect();
|
|
1695
|
+
};
|
|
1696
|
+
}, []);
|
|
1697
|
+
return (React.createElement("div", { ref: gridRef, style: {
|
|
1698
|
+
width: "100%",
|
|
1699
|
+
height: "100%",
|
|
1700
|
+
position: "relative",
|
|
1701
|
+
} },
|
|
1702
|
+
React.createElement(VideoStageLayout, { layoutVideoStage: videoStage, presentationGridContent: presentationGridContent, gridContent: gridContent, subgridContent: subgridContent })));
|
|
1703
|
+
}
|
|
1704
|
+
|
|
1705
|
+
export { Grid as VideoGrid, VideoView, Provider as WherebyProvider, useLocalMedia, useRoomConnection };
|