@whereby.com/browser-sdk 2.5.0 → 2.7.0-beta.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.
@@ -250,4 +250,711 @@ function useLocalMedia(optionsOrStream = { audio: true, video: true }) {
250
250
  };
251
251
  }
252
252
 
253
- export { VideoView, useLocalMedia, useRoomConnection };
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 };