@whereby.com/browser-sdk 2.7.0-beta.0 → 2.8.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,6 @@
1
1
  import * as React from 'react';
2
2
  import { useState, useEffect, useCallback } from 'react';
3
- import { debounce } from '@whereby.com/core/utils';
4
- import { selectChatMessages, selectCloudRecordingRaw, selectLocalParticipantRaw, selectLocalMediaStream, selectRemoteParticipants, selectScreenshares, selectRoomConnectionStatus, selectStreamingRaw, selectWaitingParticipants, createServices, createStore, observeStore, doAppJoin, sdkVersion, appLeft, doRtcReportStreamResolution, doSendChatMessage, doKnockRoom, doSetDisplayName, toggleCameraEnabled, toggleMicrophoneEnabled, doAcceptWaitingParticipant, doRejectWaitingParticipant, doStartCloudRecording, doStartScreenshare, doStopCloudRecording, doStopScreenshare, selectCameraDeviceError, selectCameraDevices, selectCurrentCameraDeviceId, selectCurrentMicrophoneDeviceId, selectIsSettingCameraDevice, selectIsSettingMicrophoneDevice, selectIsLocalMediaStarting, selectMicrophoneDeviceError, selectMicrophoneDevices, selectSpeakerDevices, selectLocalMediaStartError, doStartLocalMedia, doStopLocalMedia, setCurrentCameraDeviceId, setCurrentMicrophoneDeviceId } from '@whereby.com/core';
3
+ import { debounce, selectChatMessages, selectCloudRecordingRaw, selectLocalParticipantRaw, selectLocalMediaStream, selectRemoteParticipants, selectScreenshares, selectRoomConnectionStatus, selectStreamingRaw, selectWaitingParticipants, createServices, createStore, observeStore, doAppJoin, appLeft, doRtcReportStreamResolution, doSendChatMessage, doKnockRoom, doSetDisplayName, toggleCameraEnabled, toggleMicrophoneEnabled, doAcceptWaitingParticipant, doRejectWaitingParticipant, doStartCloudRecording, doStartScreenshare, doStopCloudRecording, doStopScreenshare, doLockRoom, doRequestAudioEnable, selectCameraDeviceError, selectCameraDevices, selectCurrentCameraDeviceId, selectCurrentMicrophoneDeviceId, selectIsSettingCameraDevice, selectIsSettingMicrophoneDevice, selectIsLocalMediaStarting, selectMicrophoneDeviceError, selectMicrophoneDevices, selectSpeakerDevices, selectLocalMediaStartError, doStartLocalMedia, doStopLocalMedia, setCurrentCameraDeviceId, setCurrentMicrophoneDeviceId } from '@whereby.com/core';
5
4
  import { createSelector } from '@reduxjs/toolkit';
6
5
 
7
6
  /******************************************************************************
@@ -100,6 +99,8 @@ const selectRoomConnectionState = createSelector(selectChatMessages, selectCloud
100
99
  return state;
101
100
  });
102
101
 
102
+ const browserSdkVersion = "2.8.0";
103
+
103
104
  const initialState$1 = {
104
105
  chatMessages: [],
105
106
  remoteParticipants: [],
@@ -127,7 +128,7 @@ function useRoomConnection(roomUrl, roomConnectionOptions = defaultRoomConnectio
127
128
  const unsubscribe = observeStore(store, selectRoomConnectionState, setRoomConnectionState);
128
129
  const url = new URL(roomUrl);
129
130
  const searchParams = new URLSearchParams(url.search);
130
- const roomKey = searchParams.get("roomKey");
131
+ const roomKey = roomConnectionOptions.roomKey || searchParams.get("roomKey");
131
132
  store.dispatch(doAppJoin({
132
133
  displayName: roomConnectionOptions.displayName || "Guest",
133
134
  localMediaOptions: roomConnectionOptions.localMedia
@@ -135,7 +136,7 @@ function useRoomConnection(roomUrl, roomConnectionOptions = defaultRoomConnectio
135
136
  : roomConnectionOptions.localMediaOptions,
136
137
  roomKey,
137
138
  roomUrl,
138
- sdkVersion: sdkVersion,
139
+ userAgent: `browser-sdk:${browserSdkVersion}`,
139
140
  externalId: roomConnectionOptions.externalId || null,
140
141
  }));
141
142
  return () => {
@@ -169,20 +170,26 @@ function useRoomConnection(roomUrl, roomConnectionOptions = defaultRoomConnectio
169
170
  const startScreenshare = React.useCallback(() => store.dispatch(doStartScreenshare()), [store]);
170
171
  const stopCloudRecording = React.useCallback(() => store.dispatch(doStopCloudRecording()), [store]);
171
172
  const stopScreenshare = React.useCallback(() => store.dispatch(doStopScreenshare()), [store]);
173
+ const lockRoom = React.useCallback((locked) => store.dispatch(doLockRoom({ locked })), [store]);
174
+ const muteParticipants = React.useCallback((clientIds) => {
175
+ store.dispatch(doRequestAudioEnable({ clientIds, enable: false }));
176
+ }, [store]);
172
177
  return {
173
178
  state: roomConnectionState,
174
179
  actions: {
175
- sendChatMessage,
176
- knock,
177
- setDisplayName,
178
- toggleCamera,
179
- toggleMicrophone,
180
180
  acceptWaitingParticipant,
181
+ knock,
182
+ lockRoom,
183
+ muteParticipants,
181
184
  rejectWaitingParticipant,
185
+ sendChatMessage,
186
+ setDisplayName,
182
187
  startCloudRecording,
183
188
  startScreenshare,
184
189
  stopCloudRecording,
185
190
  stopScreenshare,
191
+ toggleCamera,
192
+ toggleMicrophone,
186
193
  },
187
194
  components: {
188
195
  VideoView: boundVideoView || VideoView,
@@ -250,711 +257,4 @@ function useLocalMedia(optionsOrStream = { audio: true, video: true }) {
250
257
  };
251
258
  }
252
259
 
253
- const VIDEO_CONTROLS_MIN_WIDTH$1 = 7 * 60;
254
- var layoutConstants = {
255
- MIN_WINDOW_HEIGHT: 320,
256
- MIN_WINDOW_WIDTH: 320,
257
- DESKTOP_BREAKPOINT: 1025,
258
- TABLET_BREAKPOINT: 750,
259
- PHONE_BREAKPOINT: 500,
260
- TOP_TOOLBAR_HEIGHT: 40 + 8 * 2,
261
- BOTTOM_TOOLBAR_HEIGHT: 70 + 4 * 3,
262
- SIDEBAR_WIDTH: 375,
263
- VIDEO_CONTROLS_MIN_WIDTH: VIDEO_CONTROLS_MIN_WIDTH$1,
264
- ROOM_FOOTER_MIN_WIDTH: 60 * 3 + VIDEO_CONTROLS_MIN_WIDTH$1,
265
- FLOATING_VIDEO_CONTROLS_BOTTOM_MARGIN: 20,
266
- WATERMARK_BAR_HEIGHT: 32,
267
- BREAKOUT_STAGE_BACKDROP_HEADER_HEIGHT: 20 + 8,
268
- BREAKOUT_STAGE_BACKDROP_FOOTER_HEIGHT: 8 + 40 + 8,
269
- SUBGRID_EMPTY_STAGE_MAX_WIDTH: 800,
270
- GROUPS_CELL_MARGIN: 8,
271
- GROUPS_CELL_PADDING: 12,
272
- GROUPS_CELL_NAV_HEIGHT: 48 + 8,
273
- GROUPS_CELL_AVATAR_WRAPPER_BOTTOM_MARGIN: 8,
274
- GROUPS_CELL_AVATAR_GRID_GAP: 8,
275
- GROUPS_CELL_MIN_WIDTH: 360,
276
- GROUPS_CELL_MAX_WIDTH: 600,
277
- GROUPS_ROW_HEIGHT: 72,
278
- GROUPS_ROW_GAP: 1,
279
- FOLDABLE_SCREEN_STAGE_PADDING: 8,
280
- };
281
-
282
- function makeOrigin({ top = 0, left = 0 } = {}) {
283
- return {
284
- top,
285
- left,
286
- };
287
- }
288
- function makeBounds({ width = 0, height = 0 } = {}) {
289
- return {
290
- width: Math.max(width, 0),
291
- height: Math.max(height, 0),
292
- };
293
- }
294
- function makeFrame({ top = 0, left = 0, width = 0, height = 0 } = {}) {
295
- return {
296
- bounds: makeBounds({ width, height }),
297
- origin: makeOrigin({ top, left }),
298
- };
299
- }
300
- function makeBox({ top = 0, left = 0, bottom = 0, right = 0 } = {}) {
301
- return {
302
- top,
303
- left,
304
- bottom,
305
- right,
306
- };
307
- }
308
-
309
- function fitToBounds(aspectRatio, containerSize) {
310
- const { width, height } = containerSize;
311
- const contentHeight = height;
312
- const contentWidth = contentHeight * aspectRatio;
313
- const scale = Math.min(width / contentWidth, height / contentHeight);
314
- const adjustedWidth = contentWidth * scale;
315
- const adjustedHeight = contentHeight * scale;
316
- return { width: adjustedWidth, height: adjustedHeight };
317
- }
318
- const cellContentArea = ({ width, height, rows, cols, aspectRatio, }) => {
319
- const bounds = fitToBounds(aspectRatio, { width: width / cols, height: height / rows });
320
- return Math.round(bounds.width * bounds.height);
321
- };
322
- const getWeightedSplitCount = ({ vertical, width, height, count, aspectRatio, }) => {
323
- const choices = [1, 2, 3].map((rowCols) => cellContentArea({
324
- width,
325
- height,
326
- rows: vertical ? Math.ceil(count / rowCols) : rowCols,
327
- cols: vertical ? rowCols : Math.ceil(count / rowCols),
328
- aspectRatio,
329
- }));
330
- const closest = Math.max(...choices);
331
- const splits = choices.indexOf(closest) + 1;
332
- return { splits, weight: closest };
333
- };
334
- const getGridSplits = ({ width, height, count, aspectRatio, }) => {
335
- const verticalPick = getWeightedSplitCount({ vertical: true, width, height, count, aspectRatio });
336
- const horizontalPick = getWeightedSplitCount({ vertical: false, width, height, count, aspectRatio });
337
- if (verticalPick.weight > horizontalPick.weight) {
338
- return { splits: verticalPick.splits, vertical: true };
339
- }
340
- return { splits: horizontalPick.splits, vertical: false };
341
- };
342
- function getGridSizeForCount({ count, width, height, aspectRatio, }) {
343
- if (count <= 1) {
344
- return {
345
- rows: 1,
346
- cols: 1,
347
- };
348
- }
349
- const { splits, vertical } = getGridSplits({ width, height, count, aspectRatio });
350
- if (vertical) {
351
- return {
352
- rows: Math.ceil(count / splits),
353
- cols: splits,
354
- };
355
- }
356
- return {
357
- rows: splits,
358
- cols: Math.ceil(count / splits),
359
- };
360
- }
361
-
362
- const WIDE_AR = 16 / 9;
363
- const NORMAL_AR = 4 / 3;
364
- const clamp = ({ value, min, max }) => Math.min(Math.max(value, min), max);
365
- function hasDuplicates(...array) {
366
- return new Set(array).size !== array.length;
367
- }
368
- function findMostCommon(arr) {
369
- return arr.sort((a, b) => arr.filter((v) => v === a).length - arr.filter((v) => v === b).length).pop();
370
- }
371
- function pickCellAspectRatio({ choices = [] }) {
372
- const minAr = Math.min(...choices);
373
- const maxAr = Math.max(...choices);
374
- let chosenAr = null;
375
- if (minAr === maxAr) {
376
- chosenAr = minAr;
377
- }
378
- else {
379
- const dominantAr = hasDuplicates(choices) ? findMostCommon(choices) : maxAr;
380
- chosenAr = clamp({ value: dominantAr || maxAr, min: NORMAL_AR, max: WIDE_AR });
381
- }
382
- return {
383
- minAr,
384
- maxAr,
385
- chosenAr,
386
- };
387
- }
388
- function getCenterPadding({ rows, cols, cellWidth, index, cellCount, gridGap, }) {
389
- const max = rows * cols;
390
- const leftOver = max - cellCount;
391
- if (!leftOver) {
392
- return 0;
393
- }
394
- const lastIndex = max - leftOver - 1;
395
- const firstIndex = lastIndex - (cols - leftOver) + 1;
396
- const lastRowPadding = (leftOver * cellWidth) / 2 + gridGap;
397
- return index >= firstIndex && index <= lastIndex ? lastRowPadding : 0;
398
- }
399
- function getCellBounds({ width, height, rows, cols, gridGap, aspectRatio, }) {
400
- const cellWidth = (width - (cols - 1) * gridGap) / cols;
401
- const cellHeight = (height - (rows - 1) * gridGap) / rows;
402
- const ar = cellWidth / cellHeight;
403
- let horizontalCorrection = 0;
404
- let verticalCorrection = 0;
405
- if (aspectRatio < ar) {
406
- horizontalCorrection = cellWidth - cellHeight * aspectRatio;
407
- }
408
- else if (aspectRatio > ar) {
409
- verticalCorrection = cellHeight - cellWidth / aspectRatio;
410
- }
411
- const totalHorizontalCorrection = horizontalCorrection * cols;
412
- const totalVerticalCorrection = verticalCorrection * rows;
413
- return {
414
- cellWidth: cellWidth - horizontalCorrection,
415
- cellHeight: cellHeight - verticalCorrection,
416
- extraHorizontalPadding: totalHorizontalCorrection / 2,
417
- extraVerticalPadding: totalVerticalCorrection / 2,
418
- };
419
- }
420
- function calculateLayout$1({ width, height, cellCount, gridGap, cellAspectRatios = [NORMAL_AR], paddings = makeBox(), }) {
421
- if (!cellCount) {
422
- return {
423
- cellCount,
424
- cellHeight: 0,
425
- cellWidth: 0,
426
- cols: 0,
427
- rows: 0,
428
- extraHorizontalPadding: 0,
429
- extraVerticalPadding: 0,
430
- gridGap,
431
- paddings,
432
- };
433
- }
434
- const contentWidth = width - (paddings.left + paddings.right);
435
- const contentHeight = height - (paddings.top + paddings.bottom);
436
- const cellAspectRatioTuple = pickCellAspectRatio({
437
- choices: cellAspectRatios,
438
- });
439
- let cellAspectRatio = cellAspectRatioTuple.chosenAr;
440
- const { rows, cols } = getGridSizeForCount({
441
- count: cellCount,
442
- width: contentWidth,
443
- height: contentHeight,
444
- aspectRatio: cellAspectRatio,
445
- });
446
- if (rows === 1) {
447
- cellAspectRatio = clamp({
448
- value: contentWidth / cols / contentHeight,
449
- min: Math.min(cellAspectRatioTuple.chosenAr, cellAspectRatioTuple.maxAr),
450
- max: Math.max(cellAspectRatioTuple.chosenAr, cellAspectRatioTuple.maxAr),
451
- });
452
- }
453
- else if (cols === 1) {
454
- cellAspectRatio = clamp({
455
- value: contentWidth / (contentHeight / rows),
456
- min: Math.min(cellAspectRatioTuple.chosenAr, cellAspectRatioTuple.maxAr),
457
- max: Math.max(cellAspectRatioTuple.chosenAr, cellAspectRatioTuple.maxAr),
458
- });
459
- }
460
- const { cellWidth, cellHeight, extraHorizontalPadding, extraVerticalPadding } = getCellBounds({
461
- width: contentWidth,
462
- height: contentHeight,
463
- rows,
464
- cols,
465
- gridGap,
466
- aspectRatio: cellAspectRatio,
467
- });
468
- return {
469
- cellCount,
470
- cellHeight,
471
- cellWidth,
472
- cols,
473
- rows,
474
- extraHorizontalPadding,
475
- extraVerticalPadding,
476
- gridGap,
477
- paddings,
478
- };
479
- }
480
- function getCellPropsAtIndexForLayout({ index, layout, }) {
481
- const { cellWidth, cellHeight, rows, cols, cellCount, gridGap } = layout;
482
- const top = Math.floor(index / cols);
483
- const left = Math.floor(index % cols);
484
- const leftPadding = getCenterPadding({ rows, cols, cellWidth, index, cellCount, gridGap });
485
- return {
486
- top: top * cellHeight + top * gridGap,
487
- left: left * cellWidth + left * gridGap + leftPadding,
488
- width: cellWidth,
489
- height: cellHeight,
490
- };
491
- }
492
-
493
- const { BOTTOM_TOOLBAR_HEIGHT, VIDEO_CONTROLS_MIN_WIDTH, TABLET_BREAKPOINT } = layoutConstants;
494
- const MIN_GRID_HEIGHT = 200;
495
- const MIN_GRID_WIDTH = 300;
496
- const FLOATING_VIDEO_SIZE = 200;
497
- const CONSTRAINED_OVERFLOW_TRIGGER = 12;
498
- function getMinGridBounds({ cellCount }) {
499
- const isSmallGrid = cellCount <= 6;
500
- const minGridHeight = isSmallGrid ? MIN_GRID_HEIGHT - 50 : MIN_GRID_HEIGHT;
501
- const minGridWidth = isSmallGrid ? MIN_GRID_WIDTH - 50 : MIN_GRID_WIDTH;
502
- return makeBounds({ width: minGridWidth, height: minGridHeight });
503
- }
504
- function fitSupersizedContent({ bounds, aspectRatio, minGridContainerBounds, hasPresentationGrid, }) {
505
- const { width, height } = bounds;
506
- const hasVideoGrid = minGridContainerBounds.width > 0;
507
- if (!hasVideoGrid) {
508
- return {
509
- isPortrait: width <= height,
510
- supersizedContentBounds: bounds,
511
- };
512
- }
513
- const minHorizontalSupersizedContentWidth = Math.round(width / 2);
514
- const minVerticalSupersizedContentHeight = Math.round(height / 2);
515
- const maxHorizontalSupersizedContentWidth = Math.max(width - minGridContainerBounds.width, 0);
516
- const maxVerticalSupersizedContentHeight = Math.max(height - minGridContainerBounds.height, 0);
517
- let isPortrait = maxHorizontalSupersizedContentWidth <= maxVerticalSupersizedContentHeight;
518
- let horizontalCorrection = 0;
519
- let verticalCorrection = 0;
520
- if (aspectRatio) {
521
- const horizontalContentBounds = fitToBounds(aspectRatio, {
522
- width: maxHorizontalSupersizedContentWidth,
523
- height,
524
- });
525
- const verticalContentBounds = fitToBounds(aspectRatio, {
526
- width,
527
- height: maxVerticalSupersizedContentHeight,
528
- });
529
- const isPortraitContent = aspectRatio <= 1.0;
530
- isPortrait = isPortraitContent
531
- ? verticalContentBounds.height > horizontalContentBounds.height
532
- : verticalContentBounds.width > horizontalContentBounds.width;
533
- if (isPortrait) {
534
- const wastedSpace = maxVerticalSupersizedContentHeight -
535
- Math.max(verticalContentBounds.height, minVerticalSupersizedContentHeight);
536
- verticalCorrection = Math.max(wastedSpace, 0);
537
- }
538
- else {
539
- const wastedSpace = maxHorizontalSupersizedContentWidth -
540
- Math.max(horizontalContentBounds.width, minHorizontalSupersizedContentWidth);
541
- horizontalCorrection = Math.max(wastedSpace, 0);
542
- }
543
- }
544
- else if (hasPresentationGrid) {
545
- isPortrait = maxHorizontalSupersizedContentWidth / maxVerticalSupersizedContentHeight >= 5;
546
- }
547
- const supersizedContentBounds = {
548
- width: isPortrait ? width : maxHorizontalSupersizedContentWidth - horizontalCorrection,
549
- height: isPortrait ? maxVerticalSupersizedContentHeight - verticalCorrection : height,
550
- };
551
- return {
552
- isPortrait,
553
- supersizedContentBounds,
554
- };
555
- }
556
- function calculateStageLayout({ containerBounds, containerOrigin, hasConstrainedOverflow, hasPresentationContent, hasVideoContent, isPortrait, }) {
557
- const hasVideos = hasPresentationContent || hasVideoContent;
558
- if (!hasVideos) {
559
- return {
560
- isPortrait,
561
- videosContainer: makeFrame(),
562
- hasOverflow: false,
563
- };
564
- }
565
- return {
566
- isPortrait,
567
- videosContainer: makeFrame(Object.assign(Object.assign({}, containerBounds), containerOrigin)),
568
- hasOverflow: hasConstrainedOverflow,
569
- };
570
- }
571
- function calculateVideosContainerLayout({ containerBounds, containerOrigin, gridGap, supersizedContentAspectRatio, hasPresentationContent, hasPresentationGrid, hasVideoContent, minGridBounds, }) {
572
- const { width, height } = containerBounds;
573
- let isPortrait = width <= height;
574
- let presentationGridBounds = makeBounds();
575
- let presentationGridOrigin = makeOrigin();
576
- let videoGridBounds = hasVideoContent ? Object.assign({}, containerBounds) : makeBounds();
577
- let videoGridOrigin = hasVideoContent ? Object.assign({}, containerOrigin) : makeOrigin();
578
- if (hasPresentationContent) {
579
- const minGridContainerBounds = makeBounds({
580
- width: hasVideoContent ? minGridBounds.width + gridGap : 0,
581
- height: hasVideoContent ? minGridBounds.height + gridGap : 0,
582
- });
583
- const supersizedContentLayout = fitSupersizedContent({
584
- bounds: containerBounds,
585
- aspectRatio: supersizedContentAspectRatio,
586
- minGridContainerBounds,
587
- hasPresentationGrid,
588
- });
589
- isPortrait = supersizedContentLayout.isPortrait;
590
- presentationGridBounds = supersizedContentLayout.supersizedContentBounds;
591
- presentationGridOrigin = Object.assign({}, containerOrigin);
592
- if (hasVideoContent) {
593
- videoGridBounds = makeBounds({
594
- width: isPortrait
595
- ? containerBounds.width
596
- : containerBounds.width - presentationGridBounds.width - gridGap,
597
- height: isPortrait
598
- ? containerBounds.height - presentationGridBounds.height - gridGap
599
- : containerBounds.height,
600
- });
601
- videoGridOrigin = makeOrigin({
602
- top: isPortrait ? containerOrigin.top + presentationGridBounds.height + gridGap : containerOrigin.top,
603
- left: isPortrait ? containerOrigin.left : containerOrigin.left + presentationGridBounds.width + gridGap,
604
- });
605
- }
606
- }
607
- return {
608
- isPortrait,
609
- presentationGrid: Object.assign({}, makeFrame(Object.assign(Object.assign({}, presentationGridBounds), presentationGridOrigin))),
610
- videoGrid: makeFrame(Object.assign(Object.assign({}, videoGridBounds), videoGridOrigin)),
611
- };
612
- }
613
- function calculateGridLayout({ containerBounds, paddings = makeBox(), videos, isConstrained, maxGridWidth, gridGap, }) {
614
- const { width, height } = containerBounds;
615
- const cappedWidth = maxGridWidth ? Math.min(width, maxGridWidth) : width;
616
- const cellCount = videos.length;
617
- let videoCells = null;
618
- const cellAspectRatios = videos.map((video) => video.aspectRatio);
619
- const minGridBounds = getMinGridBounds({ cellCount });
620
- const gridLayout = calculateLayout$1({
621
- width: cappedWidth,
622
- height,
623
- cellCount,
624
- gridGap,
625
- cellAspectRatios,
626
- paddings,
627
- });
628
- videoCells = videos.map((video, index) => {
629
- const cellProps = getCellPropsAtIndexForLayout({ index, layout: gridLayout });
630
- const isSmallCell = gridLayout.cellWidth < minGridBounds.width;
631
- const shouldZoom = isConstrained || isSmallCell;
632
- const aspectRatio = shouldZoom ? gridLayout.cellWidth / gridLayout.cellHeight : video.aspectRatio;
633
- return {
634
- clientId: video.clientId,
635
- isDraggable: video.isDraggable,
636
- origin: makeOrigin({
637
- top: cellProps.top,
638
- left: cellProps.left,
639
- }),
640
- bounds: makeBounds({
641
- width: cellProps.width,
642
- height: cellProps.height,
643
- }),
644
- aspectRatio,
645
- isSmallCell,
646
- };
647
- });
648
- return {
649
- videoCells,
650
- extraHorizontalPadding: width !== cappedWidth
651
- ? gridLayout.extraHorizontalPadding + (width - cappedWidth) / 2
652
- : gridLayout.extraHorizontalPadding,
653
- extraVerticalPadding: gridLayout.extraVerticalPadding,
654
- paddings: gridLayout.paddings,
655
- gridGap,
656
- };
657
- }
658
- function calculateFloatingLayout({ roomBounds, containerFrame, floatingVideo, videoControlsHeight, margin = 8, }) {
659
- if (!floatingVideo) {
660
- return null;
661
- }
662
- const bounds = fitToBounds(floatingVideo.aspectRatio, {
663
- width: FLOATING_VIDEO_SIZE,
664
- height: FLOATING_VIDEO_SIZE,
665
- });
666
- const isFloating = !(roomBounds.height - containerFrame.bounds.height - containerFrame.origin.top);
667
- const isConstrained = containerFrame.bounds.width - (bounds.width + margin) * 2 < VIDEO_CONTROLS_MIN_WIDTH;
668
- let verticalOffset = 0;
669
- if (isFloating && isConstrained) {
670
- verticalOffset = videoControlsHeight * -1;
671
- }
672
- else if (!isFloating && !isConstrained) {
673
- verticalOffset = videoControlsHeight;
674
- }
675
- const origin = makeOrigin({
676
- top: containerFrame.origin.top + (containerFrame.bounds.height - bounds.height - margin) + verticalOffset,
677
- left: containerFrame.origin.left + (containerFrame.bounds.width - bounds.width - margin),
678
- });
679
- const videoCell = {
680
- clientId: floatingVideo.clientId,
681
- isDraggable: floatingVideo.isDraggable,
682
- origin,
683
- bounds,
684
- aspectRatio: floatingVideo.aspectRatio,
685
- isSmallCell: true,
686
- };
687
- return videoCell;
688
- }
689
- function rebalanceLayoutPaddedAreas({ a, b, gridGap, isPortrait, }) {
690
- const aPad = isPortrait ? a.vertical : a.horizontal;
691
- const bPad = isPortrait ? b.vertical : b.horizontal;
692
- if (aPad === bPad) {
693
- return { a: 0, b: 0 };
694
- }
695
- const sArea = aPad < bPad ? a : b;
696
- const sAreaPad = isPortrait ? sArea.vertical : sArea.horizontal;
697
- const spaceBetween = gridGap + (aPad + bPad);
698
- const offset = (spaceBetween + sAreaPad) / 2 - sAreaPad;
699
- return {
700
- a: sArea === a ? offset : 0,
701
- b: sArea === b ? offset : 0,
702
- };
703
- }
704
- function rebalanceLayoutInPlace({ videosContainerLayout, gridLayout, presentationGridLayout, gridGap, }) {
705
- const hasPresentationGrid = videosContainerLayout.presentationGrid.bounds.width > 0;
706
- const hasVideoGrid = videosContainerLayout.videoGrid.bounds.width > 0;
707
- if (hasPresentationGrid && hasVideoGrid) {
708
- const correction = rebalanceLayoutPaddedAreas({
709
- a: {
710
- horizontal: presentationGridLayout.extraHorizontalPadding,
711
- vertical: presentationGridLayout.extraVerticalPadding,
712
- },
713
- b: {
714
- horizontal: gridLayout.extraHorizontalPadding,
715
- vertical: gridLayout.extraVerticalPadding,
716
- },
717
- gridGap,
718
- isPortrait: videosContainerLayout.isPortrait,
719
- });
720
- if (videosContainerLayout.isPortrait) {
721
- videosContainerLayout.presentationGrid.origin.top += correction.a;
722
- videosContainerLayout.videoGrid.origin.top -= correction.b;
723
- correction.b;
724
- }
725
- else {
726
- videosContainerLayout.presentationGrid.origin.left += correction.a;
727
- videosContainerLayout.videoGrid.origin.left -= correction.b;
728
- correction.b;
729
- }
730
- }
731
- }
732
- function calculateGridLayouts({ gridGap, isConstrained, presentationVideos, videos, videosContainerLayout, gridLayoutPaddings = makeBox(), presentationGridLayoutPaddings = makeBox(), maxGridWidth, }) {
733
- const gridLayout = calculateGridLayout({
734
- containerBounds: videosContainerLayout.videoGrid.bounds,
735
- gridGap,
736
- isConstrained,
737
- maxGridWidth,
738
- paddings: gridLayoutPaddings,
739
- videos,
740
- });
741
- const presentationGridLayout = calculateGridLayout({
742
- containerBounds: videosContainerLayout.presentationGrid.bounds,
743
- gridGap,
744
- isConstrained,
745
- maxGridWidth,
746
- paddings: presentationGridLayoutPaddings,
747
- videos: presentationVideos,
748
- });
749
- return { gridLayout, presentationGridLayout };
750
- }
751
- function calculateLayout({ floatingVideo = null, frame, gridGap = 0, isConstrained = false, isMaximizeMode = false, paddings = makeBox(), presentationVideos = [], rebalanceLayout = false, roomBounds, roomLayoutHasOverlow = false, videoControlsHeight = 0, videos = [], videoGridGap = 0, }) {
752
- const hasPresentationContent = !!presentationVideos.length;
753
- const hasPresentationGrid = presentationVideos.length > 1;
754
- const supersizedContentAspectRatio = hasPresentationContent && !hasPresentationGrid ? presentationVideos[0].aspectRatio : 1;
755
- const hasVideoContent = !!videos.length;
756
- const width = frame.bounds.width - paddings.left - paddings.right;
757
- let height = frame.bounds.height - paddings.top - paddings.bottom;
758
- const maxGridWidth = Math.max(25 * 88, (80 / 100) * width);
759
- const hasConstrainedOverflow = (isConstrained && videos.length > CONSTRAINED_OVERFLOW_TRIGGER) || false;
760
- const lineHeight = height / 4;
761
- const extraLines = Math.ceil((videos.length - CONSTRAINED_OVERFLOW_TRIGGER) / 3);
762
- height = hasConstrainedOverflow ? height + lineHeight * extraLines : height;
763
- const stageBounds = makeBounds({ width, height });
764
- const stageOrigin = makeOrigin({ top: paddings.top, left: paddings.left });
765
- const _minBounds = getMinGridBounds({ cellCount: videos.length });
766
- const minGridBounds = _minBounds;
767
- const isSmallScreen = roomBounds.width < TABLET_BREAKPOINT || roomBounds.height < TABLET_BREAKPOINT;
768
- const forceStageLayoutPortrait = isMaximizeMode;
769
- const stageLayoutIsPortrait = forceStageLayoutPortrait ||
770
- !(hasPresentationContent || hasVideoContent) ||
771
- stageBounds.width <= stageBounds.height;
772
- const stableStageLayoutProps = {
773
- cellPaddings: { top: 4, left: 4, bottom: 4, right: 4 },
774
- containerBounds: stageBounds,
775
- containerOrigin: stageOrigin,
776
- gridGap,
777
- hasPresentationContent,
778
- hasVideoContent,
779
- isConstrained,
780
- isMaximizeMode,
781
- isSmallScreen,
782
- maxGridWidth,
783
- };
784
- let stageLayout = calculateStageLayout(Object.assign(Object.assign({}, stableStageLayoutProps), { isPortrait: stageLayoutIsPortrait, hasConstrainedOverflow }));
785
- let forceRerunAsOverflow = false;
786
- if (roomLayoutHasOverlow && !stageLayout.hasOverflow) {
787
- const _stageLayout = calculateStageLayout(Object.assign(Object.assign({}, stableStageLayoutProps), { containerBounds: makeBounds({
788
- width: stageBounds.width,
789
- height: stageBounds.height - BOTTOM_TOOLBAR_HEIGHT,
790
- }), isPortrait: stageLayoutIsPortrait, hasConstrainedOverflow }));
791
- if (_stageLayout.hasOverflow) {
792
- forceRerunAsOverflow = true;
793
- }
794
- }
795
- if (forceRerunAsOverflow || stageLayout.hasOverflow) {
796
- stageLayout = calculateStageLayout(Object.assign(Object.assign({}, stableStageLayoutProps), { isPortrait: true, hasConstrainedOverflow }));
797
- }
798
- const videosContainerLayout = calculateVideosContainerLayout({
799
- containerBounds: stageLayout.videosContainer.bounds,
800
- containerOrigin: stageLayout.videosContainer.origin,
801
- gridGap,
802
- supersizedContentAspectRatio,
803
- hasPresentationContent,
804
- hasPresentationGrid,
805
- hasVideoContent,
806
- minGridBounds,
807
- });
808
- const { gridLayout, presentationGridLayout } = calculateGridLayouts({
809
- gridGap: videoGridGap,
810
- isConstrained,
811
- presentationVideos,
812
- videos,
813
- videosContainerLayout,
814
- maxGridWidth,
815
- });
816
- const floatingLayout = calculateFloatingLayout({
817
- roomBounds,
818
- containerFrame: frame,
819
- floatingVideo,
820
- videoControlsHeight,
821
- });
822
- if (rebalanceLayout) {
823
- rebalanceLayoutInPlace({
824
- videosContainerLayout,
825
- gridLayout,
826
- presentationGridLayout,
827
- gridGap,
828
- });
829
- }
830
- return {
831
- isPortrait: stageLayout.isPortrait,
832
- hasOverflow: stageLayout.hasOverflow,
833
- bounds: makeBounds({
834
- height: frame.bounds.height,
835
- width: frame.bounds.width,
836
- }),
837
- gridGap,
838
- presentationGrid: Object.assign(Object.assign({}, videosContainerLayout.presentationGrid), { cells: presentationGridLayout.videoCells, paddings: makeBox({
839
- top: presentationGridLayout.paddings.top + presentationGridLayout.extraVerticalPadding,
840
- bottom: presentationGridLayout.paddings.bottom + presentationGridLayout.extraVerticalPadding,
841
- left: presentationGridLayout.paddings.left + presentationGridLayout.extraHorizontalPadding,
842
- right: presentationGridLayout.paddings.right + presentationGridLayout.extraHorizontalPadding,
843
- }) }),
844
- videoGrid: Object.assign(Object.assign({}, videosContainerLayout.videoGrid), { cells: gridLayout.videoCells, paddings: makeBox({
845
- top: gridLayout.paddings.top + gridLayout.extraVerticalPadding,
846
- bottom: gridLayout.paddings.bottom + gridLayout.extraVerticalPadding,
847
- left: gridLayout.paddings.left + gridLayout.extraHorizontalPadding,
848
- right: gridLayout.paddings.right + gridLayout.extraHorizontalPadding,
849
- }) }),
850
- floatingContent: Object.assign(Object.assign({}, floatingLayout), floatingVideo),
851
- };
852
- }
853
-
854
- function makeVideoCellView({ aspectRatio, avatarSize, cellPaddings, client = undefined, isDraggable = true, isPlaceholder = false, isSubgrid = false, }) {
855
- return {
856
- aspectRatio: aspectRatio || 16 / 9,
857
- avatarSize,
858
- cellPaddings,
859
- client,
860
- clientId: (client === null || client === void 0 ? void 0 : client.id) || "",
861
- isDraggable,
862
- isPlaceholder,
863
- isSubgrid,
864
- type: "video",
865
- };
866
- }
867
-
868
- function GridVideoCellView({ cell, participant, render, onSetAspectRatio, onResize, }) {
869
- const handleAspectRatioChange = React.useCallback(({ ar }) => {
870
- if (ar !== cell.aspectRatio) {
871
- onSetAspectRatio({ aspectRatio: ar });
872
- }
873
- }, [cell.aspectRatio, onSetAspectRatio]);
874
- return (React.createElement("div", { style: {
875
- position: "absolute",
876
- width: cell.bounds.width,
877
- height: cell.bounds.height,
878
- boxSizing: "border-box",
879
- top: cell.origin.top,
880
- left: cell.origin.left,
881
- } }, render ? (render()) : participant.stream ? (React.createElement(VideoView, { stream: participant.stream, onSetAspectRatio: ({ aspectRatio }) => handleAspectRatioChange({ ar: aspectRatio }), onResize: onResize })) : null));
882
- }
883
- function Grid({ roomConnection, renderParticipant, videoGridGap = 0 }) {
884
- const { remoteParticipants, localParticipant } = roomConnection.state;
885
- const gridRef = React.useRef(null);
886
- const [containerFrame, setContainerFrame] = React.useState(null);
887
- const [aspectRatios, setAspectRatios] = React.useState([]);
888
- React.useEffect(() => {
889
- if (!gridRef.current) {
890
- return;
891
- }
892
- const resizeObserver = new ResizeObserver(debounce(() => {
893
- var _a, _b;
894
- setContainerFrame(makeFrame({
895
- width: (_a = gridRef.current) === null || _a === void 0 ? void 0 : _a.clientWidth,
896
- height: (_b = gridRef.current) === null || _b === void 0 ? void 0 : _b.clientHeight,
897
- }));
898
- }, { delay: 60 }));
899
- resizeObserver.observe(gridRef.current);
900
- return () => {
901
- resizeObserver.disconnect();
902
- };
903
- }, []);
904
- const participants = React.useMemo(() => {
905
- return [...(localParticipant ? [localParticipant] : []), ...remoteParticipants];
906
- }, [remoteParticipants, localParticipant]);
907
- const videoCells = React.useMemo(() => {
908
- return participants.map((participant) => {
909
- var _a;
910
- const aspectRatio = (_a = aspectRatios.find((item) => item.clientId === (participant === null || participant === void 0 ? void 0 : participant.id))) === null || _a === void 0 ? void 0 : _a.aspectRatio;
911
- return makeVideoCellView({
912
- aspectRatio: aspectRatio !== null && aspectRatio !== void 0 ? aspectRatio : 16 / 9,
913
- avatarSize: 0,
914
- cellPaddings: 10,
915
- client: participant,
916
- });
917
- });
918
- }, [participants, aspectRatios]);
919
- const stageLayout = React.useMemo(() => {
920
- if (!containerFrame)
921
- return null;
922
- return calculateLayout({
923
- frame: containerFrame,
924
- gridGap: 0,
925
- isConstrained: false,
926
- roomBounds: containerFrame.bounds,
927
- videos: videoCells,
928
- videoGridGap,
929
- });
930
- }, [containerFrame, videoCells, videoGridGap]);
931
- const handleResize = React.useCallback(({ width, height, stream }) => {
932
- if (!roomConnection._ref)
933
- return;
934
- roomConnection._ref.dispatch(doRtcReportStreamResolution({ streamId: stream.id, width, height }));
935
- }, [localParticipant, roomConnection._ref]);
936
- return (React.createElement("div", { ref: gridRef, style: {
937
- width: "100%",
938
- height: "100%",
939
- position: "relative",
940
- } }, participants.map((participant, i) => {
941
- const cell = stageLayout === null || stageLayout === void 0 ? void 0 : stageLayout.videoGrid.cells[i];
942
- if (!cell || !participant || !participant.stream || !cell.clientId)
943
- return null;
944
- return (React.createElement(GridVideoCellView, { key: cell.clientId, cell: cell, participant: participant, render: renderParticipant ? () => renderParticipant({ cell, participant }) : undefined, onResize: handleResize, onSetAspectRatio: ({ aspectRatio }) => {
945
- setAspectRatios((prev) => {
946
- const index = prev.findIndex((item) => item.clientId === cell.clientId);
947
- if (index === -1) {
948
- return [...prev, { clientId: cell.clientId, aspectRatio }];
949
- }
950
- return [
951
- ...prev.slice(0, index),
952
- { clientId: cell.clientId, aspectRatio },
953
- ...prev.slice(index + 1),
954
- ];
955
- });
956
- } }));
957
- })));
958
- }
959
-
960
- export { Grid as VideoGrid, VideoView, useLocalMedia, useRoomConnection };
260
+ export { VideoView, useLocalMedia, useRoomConnection };