diff --git a/dailyjs/active-speaker/components/SpeakerView/SpeakerTile/SpeakerTile.js b/dailyjs/active-speaker/components/SpeakerView/SpeakerTile/SpeakerTile.js
index d931cb0..88f8dc9 100644
--- a/dailyjs/active-speaker/components/SpeakerView/SpeakerTile/SpeakerTile.js
+++ b/dailyjs/active-speaker/components/SpeakerView/SpeakerTile/SpeakerTile.js
@@ -59,6 +59,7 @@ export const SpeakerTile = ({ participant, screenRef }) => {
participant={participant}
style={style}
videoFit={videoFit}
+ showActiveSpeaker={false}
onVideoResize={handleNativeAspectRatio}
/>
);
diff --git a/dailyjs/basic-call/components/VideoGrid/VideoGrid.js b/dailyjs/basic-call/components/VideoGrid/VideoGrid.js
index 744f185..97a2b3c 100644
--- a/dailyjs/basic-call/components/VideoGrid/VideoGrid.js
+++ b/dailyjs/basic-call/components/VideoGrid/VideoGrid.js
@@ -2,7 +2,7 @@ import React, { useState, useMemo, useEffect, useRef } from 'react';
import Tile from '@dailyjs/shared/components/Tile';
import { DEFAULT_ASPECT_RATIO } from '@dailyjs/shared/constants';
import { useParticipants } from '@dailyjs/shared/contexts/ParticipantsProvider';
-import usePreferredLayer from '@dailyjs/shared/hooks/usePreferredLayer';
+import usePreferredLayerByCount from '@dailyjs/shared/hooks/usePreferredLayerByCount';
import { useDeepCompareMemo } from 'use-deep-compare';
/**
@@ -106,7 +106,7 @@ export const VideoGrid = React.memo(
// Optimise performance by reducing video quality
// when more participants join (if in SFU mode)
- usePreferredLayer(allParticipants);
+ usePreferredLayerByCount(allParticipants);
if (!participants.length) {
return null;
diff --git a/dailyjs/pagination/components/PaginatedVideoGrid/PaginatedVideoGrid.js b/dailyjs/pagination/components/PaginatedVideoGrid/PaginatedVideoGrid.js
index d5caa9c..80648e0 100644
--- a/dailyjs/pagination/components/PaginatedVideoGrid/PaginatedVideoGrid.js
+++ b/dailyjs/pagination/components/PaginatedVideoGrid/PaginatedVideoGrid.js
@@ -11,7 +11,7 @@ import { DEFAULT_ASPECT_RATIO } from '@dailyjs/shared/constants';
import { useParticipants } from '@dailyjs/shared/contexts/ParticipantsProvider';
import { useActiveSpeaker } from '@dailyjs/shared/hooks/useActiveSpeaker';
import { useCamSubscriptions } from '@dailyjs/shared/hooks/useCamSubscriptions';
-import usePreferredLayer from '@dailyjs/shared/hooks/usePreferredLayer';
+import usePreferredLayerByCount from '@dailyjs/shared/hooks/usePreferredLayerByCount';
import { ReactComponent as IconArrow } from '@dailyjs/shared/icons/raquo-md.svg';
import sortByKey from '@dailyjs/shared/lib/sortByKey';
import { useDeepCompareMemo } from 'use-deep-compare';
@@ -181,7 +181,7 @@ export const PaginatedVideoGrid = () => {
);
// Set bandwidth layer based on amount of visible participants
- usePreferredLayer(visibleParticipants);
+ usePreferredLayerByCount(visibleParticipants);
/**
* Handle position updates based on active speaker events
diff --git a/dailyjs/shared/components/Tile/Tile.js b/dailyjs/shared/components/Tile/Tile.js
index ac49960..5014940 100644
--- a/dailyjs/shared/components/Tile/Tile.js
+++ b/dailyjs/shared/components/Tile/Tile.js
@@ -13,6 +13,7 @@ export const Tile = React.memo(
mirrored = true,
showName = true,
showAvatar = true,
+ showActiveSpeaker = true,
aspectRatio = DEFAULT_ASPECT_RATIO,
onVideoResize,
videoFit = 'contain',
@@ -22,6 +23,8 @@ export const Tile = React.memo(
const videoEl = useRef(null);
const [tileAspectRatio, setTileAspectRatio] = useState(aspectRatio);
+ const [layer, setLayer] = useState();
+
/**
* Add optional event listener for resize event so the parent component
* can know the video's native aspect ratio.
@@ -52,10 +55,26 @@ export const Tile = React.memo(
setTileAspectRatio(aspectRatio);
}, [aspectRatio, tileAspectRatio]);
+ useEffect(() => {
+ if (
+ typeof rtcpeers === 'undefined' ||
+ rtcpeers?.getCurrentType() !== 'sfu'
+ )
+ return false;
+
+ const i = setInterval(() => {
+ setLayer(
+ rtcpeers.sfu.consumers[`${participant.id}/cam-video`]?._preferredLayer
+ );
+ }, 1500);
+
+ return () => clearInterval(i);
+ }, [participant]);
+
const cx = classNames('tile', videoFit, {
mirrored,
avatar: showAvatar && !videoTrack,
- active: participant.isActiveSpeaker,
+ active: showActiveSpeaker && participant.isActiveSpeaker,
});
return (
@@ -64,7 +83,7 @@ export const Tile = React.memo(
{showName && (
{participant.isMicMuted && }
- {participant.name}
+ {participant.name} - {layer}
)}
{videoTrack ? (
@@ -92,8 +111,17 @@ export const Tile = React.memo(
box-sizing: border-box;
}
- .tile.active {
+ .tile.active:before {
+ content: '';
+ position: absolute;
+ top: 0px;
+ right: 0px;
+ left: 0px;
+ bottom: 0px;
border: 2px solid var(--primary-default);
+ box-sizing: border-box;
+ pointer-events: none;
+ z-index: 2;
}
.tile .name {
@@ -104,10 +132,11 @@ export const Tile = React.memo(
left: 0px;
z-index: 2;
line-height: 1;
+ font-size: 0.875rem;
color: white;
font-weight: var(--weight-medium);
padding: var(--spacing-xxs);
- text-shadow: 0px 1px 3px rgba(0, 0, 0, 0.35);
+ text-shadow: 0px 1px 3px rgba(0, 0, 0, 0.45);
gap: var(--spacing-xxs);
}
@@ -159,6 +188,7 @@ Tile.propTypes = {
aspectRatio: PropTypes.number,
onVideoResize: PropTypes.func,
videoFit: PropTypes.string,
+ showActiveSpeaker: PropTypes.bool,
};
export default Tile;
diff --git a/dailyjs/shared/contexts/ParticipantsProvider.js b/dailyjs/shared/contexts/ParticipantsProvider.js
index 3e31b45..c26db9e 100644
--- a/dailyjs/shared/contexts/ParticipantsProvider.js
+++ b/dailyjs/shared/contexts/ParticipantsProvider.js
@@ -1,3 +1,5 @@
+/* global rtcpeers */
+
import React, {
createContext,
useCallback,
@@ -7,11 +9,16 @@ import React, {
useState,
useMemo,
} from 'react';
+import {
+ useUIState,
+ VIEW_MODE_SPEAKER,
+} from '@dailyjs/shared/contexts/UIStateProvider';
import PropTypes from 'prop-types';
import { sortByKey } from '../lib/sortByKey';
import { useCallState } from './CallProvider';
+
import {
initialParticipantsState,
isLocalId,
@@ -31,6 +38,7 @@ export const ParticipantsProvider = ({ children }) => {
participantsReducer,
initialParticipantsState
);
+ const { viewMode } = useUIState();
const [participantMarkedForRemoval, setParticipantMarkedForRemoval] =
useState(null);
@@ -47,6 +55,14 @@ export const ParticipantsProvider = ({ children }) => {
*/
const participants = useMemo(() => state.participants, [state.participants]);
+ /**
+ * Array of participant IDs
+ */
+ const participantIds = useMemo(
+ () => participants.map((p) => p.id).join(','),
+ [participants]
+ );
+
/**
* The number of participants, who are not a shared screen
* (technically a shared screen counts as a participant, but we shouldn't tell humans)
@@ -218,6 +234,40 @@ export const ParticipantsProvider = ({ children }) => {
);
}, [callObject, handleNewParticipantsState]);
+ /**
+ * Adjust video quality from the 3 simulcast layers based
+ * on active speaker status. Note: this currently uses
+ * undocumented internal methods (we'll be adding support
+ * for this into our API soon!)
+ */
+ const setBandWidthControls = useCallback(() => {
+ if (typeof rtcpeers === 'undefined') return;
+ const sfu = rtcpeers?.soup;
+ const isSFU = rtcpeers?.currentlyPreferred?.typeName?.() === 'sfu';
+ if (!isSFU) return;
+
+ const ids = participantIds.split(',');
+
+ ids.forEach((id) => {
+ if (isLocalId(id)) return;
+
+ // Speaker view settings based on speaker status or pinned user
+ if (viewMode === VIEW_MODE_SPEAKER) {
+ if (currentSpeaker?.id === id) {
+ sfu.setPreferredLayerForTrack(id, 'cam-video', 2);
+ } else {
+ sfu.setPreferredLayerForTrack(id, 'cam-video', 0);
+ }
+ }
+
+ // Note: grid view settings are handled by the grid view component
+ });
+ }, [currentSpeaker?.id, participantIds, viewMode]);
+
+ useEffect(() => {
+ setBandWidthControls();
+ }, [setBandWidthControls]);
+
useEffect(() => {
if (!callObject) return false;
const handleActiveSpeakerChange = ({ activeSpeaker }) => {
diff --git a/dailyjs/shared/hooks/usePreferredLayer.js b/dailyjs/shared/hooks/usePreferredLayerByCount.js
similarity index 92%
rename from dailyjs/shared/hooks/usePreferredLayer.js
rename to dailyjs/shared/hooks/usePreferredLayerByCount.js
index 3da474d..4e74c9c 100644
--- a/dailyjs/shared/hooks/usePreferredLayer.js
+++ b/dailyjs/shared/hooks/usePreferredLayerByCount.js
@@ -14,7 +14,7 @@ import { useEffect } from 'react';
*
* Note: this will have no effect when not in SFU mode
*/
-export const usePreferredLayer = (participants) => {
+export const usePreferredLayerByCount = (participants) => {
/**
* Set bandwidth layer based on amount of visible participants
*/
@@ -40,4 +40,4 @@ export const usePreferredLayer = (participants) => {
}, [participants]);
};
-export default usePreferredLayer;
+export default usePreferredLayerByCount;