added bandwidth controls to ParticipantProvider

This commit is contained in:
Jon 2021-07-23 14:49:30 +01:00
parent dc2dc63a98
commit c322312343
6 changed files with 91 additions and 10 deletions

View File

@ -59,6 +59,7 @@ export const SpeakerTile = ({ participant, screenRef }) => {
participant={participant}
style={style}
videoFit={videoFit}
showActiveSpeaker={false}
onVideoResize={handleNativeAspectRatio}
/>
);

View File

@ -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;

View File

@ -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

View File

@ -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 && (
<div className="name">
{participant.isMicMuted && <IconMicMute />}
{participant.name}
{participant.name} - {layer}
</div>
)}
{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;

View File

@ -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 }) => {

View File

@ -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;