dailyjs 0.17 bump and receiveSettings methods

This commit is contained in:
Jon 2021-08-17 12:09:39 +01:00
parent 34368d3edf
commit 4e54af6768
12 changed files with 292 additions and 217 deletions

View File

@ -2,7 +2,6 @@ import React, { useState, useMemo, useEffect, useRef } from 'react';
import Tile from '@dailyjs/shared/components/Tile'; import Tile from '@dailyjs/shared/components/Tile';
import { DEFAULT_ASPECT_RATIO } from '@dailyjs/shared/constants'; import { DEFAULT_ASPECT_RATIO } from '@dailyjs/shared/constants';
import { useParticipants } from '@dailyjs/shared/contexts/ParticipantsProvider'; import { useParticipants } from '@dailyjs/shared/contexts/ParticipantsProvider';
import usePreferredLayerByCount from '@dailyjs/shared/hooks/usePreferredLayerByCount';
import { useDeepCompareMemo } from 'use-deep-compare'; import { useDeepCompareMemo } from 'use-deep-compare';
/** /**
@ -19,7 +18,7 @@ import { useDeepCompareMemo } from 'use-deep-compare';
export const VideoGrid = React.memo( export const VideoGrid = React.memo(
() => { () => {
const containerRef = useRef(); const containerRef = useRef();
const { participants, allParticipants } = useParticipants(); const { participants } = useParticipants();
const [dimensions, setDimensions] = useState({ const [dimensions, setDimensions] = useState({
width: 1, width: 1,
height: 1, height: 1,
@ -104,10 +103,6 @@ export const VideoGrid = React.memo(
[layout, participants] [layout, participants]
); );
// Optimise performance by reducing video quality
// when more participants join (if in SFU mode)
usePreferredLayerByCount(allParticipants);
if (!participants.length) { if (!participants.length) {
return null; return null;
} }

View File

@ -8,10 +8,11 @@ import React, {
import { Button } from '@dailyjs/shared/components/Button'; import { Button } from '@dailyjs/shared/components/Button';
import Tile from '@dailyjs/shared/components/Tile'; import Tile from '@dailyjs/shared/components/Tile';
import { DEFAULT_ASPECT_RATIO } from '@dailyjs/shared/constants'; import { DEFAULT_ASPECT_RATIO } from '@dailyjs/shared/constants';
import { useCallState } from '@dailyjs/shared/contexts/CallProvider';
import { useParticipants } from '@dailyjs/shared/contexts/ParticipantsProvider'; import { useParticipants } from '@dailyjs/shared/contexts/ParticipantsProvider';
import { isLocalId } from '@dailyjs/shared/contexts/participantsState';
import { useActiveSpeaker } from '@dailyjs/shared/hooks/useActiveSpeaker'; import { useActiveSpeaker } from '@dailyjs/shared/hooks/useActiveSpeaker';
import { useCamSubscriptions } from '@dailyjs/shared/hooks/useCamSubscriptions'; import { useCamSubscriptions } from '@dailyjs/shared/hooks/useCamSubscriptions';
import usePreferredLayerByCount from '@dailyjs/shared/hooks/usePreferredLayerByCount';
import { ReactComponent as IconArrow } from '@dailyjs/shared/icons/raquo-md.svg'; import { ReactComponent as IconArrow } from '@dailyjs/shared/icons/raquo-md.svg';
import sortByKey from '@dailyjs/shared/lib/sortByKey'; import sortByKey from '@dailyjs/shared/lib/sortByKey';
import { useDeepCompareMemo } from 'use-deep-compare'; import { useDeepCompareMemo } from 'use-deep-compare';
@ -21,6 +22,7 @@ const MIN_TILE_WIDTH = 280;
const MAX_TILES_PER_PAGE = 12; const MAX_TILES_PER_PAGE = 12;
export const PaginatedVideoGrid = () => { export const PaginatedVideoGrid = () => {
const { callObject } = useCallState();
const { const {
activeParticipant, activeParticipant,
participantCount, participantCount,
@ -46,6 +48,8 @@ export const PaginatedVideoGrid = () => {
const gridRef = useRef(null); const gridRef = useRef(null);
// -- Layout / UI
// Update width and height of grid when window is resized // Update width and height of grid when window is resized
useEffect(() => { useEffect(() => {
let frame; let frame;
@ -131,26 +135,27 @@ export const PaginatedVideoGrid = () => {
[page, pageSize, participants] [page, pageSize, participants]
); );
// -- Track subscriptions
/** /**
* Play / pause tracks based on pagination * Play / pause tracks based on pagination
* Note: we pause adjacent page tracks and unsubscribe from everything else * Note: we pause adjacent page tracks and unsubscribe from everything else
* Please refer to project README for more information
*/ */
const camSubscriptions = useMemo(() => { const camSubscriptions = useMemo(() => {
const maxSubs = 3 * pageSize; const maxSubs = 3 * pageSize;
// Determine participant ids to subscribe to, based on page. // Determine participant ids to subscribe to or stage, based on page
let subscribedIds = []; let renderedOrBufferedIds = [];
switch (page) { switch (page) {
// First page // First page
case 1: case 1:
subscribedIds = participants renderedOrBufferedIds = participants
.slice(0, Math.min(maxSubs, 2 * pageSize)) .slice(0, Math.min(maxSubs, 2 * pageSize))
.map((p) => p.id); .map((p) => p.id);
break; break;
// Last page // Last page
case Math.ceil(participants.length / pageSize): case Math.ceil(participants.length / pageSize):
subscribedIds = participants renderedOrBufferedIds = participants
.slice(-Math.min(maxSubs, 2 * pageSize)) .slice(-Math.min(maxSubs, 2 * pageSize))
.map((p) => p.id); .map((p) => p.id);
break; break;
@ -160,18 +165,29 @@ export const PaginatedVideoGrid = () => {
const buffer = (maxSubs - pageSize) / 2; const buffer = (maxSubs - pageSize) / 2;
const min = (page - 1) * pageSize - buffer; const min = (page - 1) * pageSize - buffer;
const max = page * pageSize + buffer; const max = page * pageSize + buffer;
subscribedIds = participants.slice(min, max).map((p) => p.id); renderedOrBufferedIds = participants.slice(min, max).map((p) => p.id);
} }
break; break;
} }
// Determine subscribed, but invisible (= paused) video tracks const subscribedIds = [];
const invisibleSubscribedIds = subscribedIds.filter( const stagedIds = [];
(id) => id !== 'local' && !visibleParticipants.some((vp) => vp.id === id)
); // Decide whether to subscribe to or stage participants'
// track based on isibility
renderedOrBufferedIds.forEach((id) => {
if (id !== isLocalId()) {
if (visibleParticipants.some((vp) => vp.id === id)) {
subscribedIds.push(id);
} else {
stagedIds.push(id);
}
}
});
return { return {
subscribedIds: subscribedIds.filter((id) => id !== 'local'), subscribedIds,
pausedIds: invisibleSubscribedIds, stagedIds,
}; };
}, [page, pageSize, participants, visibleParticipants]); }, [page, pageSize, participants, visibleParticipants]);
@ -180,8 +196,36 @@ export const PaginatedVideoGrid = () => {
camSubscriptions?.pausedIds camSubscriptions?.pausedIds
); );
// Set bandwidth layer based on amount of visible participants /**
usePreferredLayerByCount(visibleParticipants); * Set bandwidth layer based on amount of visible participants
*/
useEffect(() => {
if (!(callObject && callObject.meetingState() === 'joined-meeting')) return;
const count = visibleParticipants.length;
let layer;
if (count < 5) {
// highest quality layer
layer = 2;
} else if (count < 10) {
// mid quality layer
layer = 1;
} else {
// low qualtiy layer
layer = 0;
}
const receiveSettings = visibleParticipants.reduce(
(settings, participant) => {
if (isLocalId(participant.id)) return settings;
return { ...settings, [participant.id]: { video: { layer } } };
},
{}
);
callObject.updateReceiveSettings(receiveSettings);
}, [visibleParticipants, callObject]);
// -- Active speaker
/** /**
* Handle position updates based on active speaker events * Handle position updates based on active speaker events
@ -255,7 +299,9 @@ export const PaginatedVideoGrid = () => {
> >
<IconArrow /> <IconArrow />
</Button> </Button>
<div className="tiles">{tiles}</div> <div className="tiles">{tiles}</div>
<Button <Button
className="page-button next" className="page-button next"
disabled={!(pages > 1 && page < pages)} disabled={!(pages > 1 && page < pages)}
@ -264,6 +310,7 @@ export const PaginatedVideoGrid = () => {
> >
<IconArrow /> <IconArrow />
</Button> </Button>
<style jsx>{` <style jsx>{`
.grid { .grid {
align-items: center; align-items: center;

View File

@ -34,16 +34,23 @@ const CombinedAudioTrack = ({ tracks }) => {
} }
}); });
audio.load(); const playAudio = async () => {
try {
if (
stream
.getAudioTracks()
.some((t) => t.enabled && t.readyState === 'live') &&
audio.paused
) {
await audio.play();
}
} catch {
// ...
}
};
if ( audio.load();
stream playAudio();
.getAudioTracks()
.some((t) => t.enabled && t.readyState === 'live') &&
audio.paused
) {
audio.play();
}
}, [tracks, trackIds]); }, [tracks, trackIds]);
return ( return (

View File

@ -52,10 +52,6 @@ export const BasicTray = () => {
<IconPeople /> <IconPeople />
</TrayButton> </TrayButton>
<TrayButton label="Fake" onClick={() => callObject.addFakeParticipant()}>
+
</TrayButton>
{customTrayComponent} {customTrayComponent}
<span className="divider" /> <span className="divider" />

View File

@ -19,6 +19,7 @@ import {
ACCESS_STATE_UNKNOWN, ACCESS_STATE_UNKNOWN,
VIDEO_QUALITY_AUTO, VIDEO_QUALITY_AUTO,
} from '../constants'; } from '../constants';
import { useNetworkState } from '../hooks/useNetworkState';
import { useCallMachine } from './useCallMachine'; import { useCallMachine } from './useCallMachine';
export const CallContext = createContext(); export const CallContext = createContext();
@ -44,6 +45,7 @@ export const CallProvider = ({
token, token,
subscribeToTracksAutomatically, subscribeToTracksAutomatically,
}); });
const networkState = useNetworkState(daily, videoQuality);
// Feature detection taken from daily room object and client browser support // Feature detection taken from daily room object and client browser support
useEffect(() => { useEffect(() => {
@ -107,6 +109,7 @@ export const CallProvider = ({
addFakeParticipant, addFakeParticipant,
preJoinNonAuthorized, preJoinNonAuthorized,
leave, leave,
networkState,
showLocalVideo, showLocalVideo,
roomExp, roomExp,
videoQuality, videoQuality,
@ -115,8 +118,10 @@ export const CallProvider = ({
setBandwidth, setBandwidth,
setRedirectOnLeave, setRedirectOnLeave,
setShowLocalVideo, setShowLocalVideo,
setVideoQuality,
startCloudRecording, startCloudRecording,
subscribeToTracksAutomatically, subscribeToTracksAutomatically,
videoQuality,
}} }}
> >
{children} {children}

View File

@ -15,6 +15,12 @@ import {
} from '@dailyjs/shared/contexts/UIStateProvider'; } from '@dailyjs/shared/contexts/UIStateProvider';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import {
VIDEO_QUALITY_AUTO,
VIDEO_QUALITY_BANDWIDTH_SAVER,
VIDEO_QUALITY_LOW,
VIDEO_QUALITY_VERY_LOW,
} from '../constants';
import { sortByKey } from '../lib/sortByKey'; import { sortByKey } from '../lib/sortByKey';
import { useCallState } from './CallProvider'; import { useCallState } from './CallProvider';
@ -33,7 +39,7 @@ import {
export const ParticipantsContext = createContext(); export const ParticipantsContext = createContext();
export const ParticipantsProvider = ({ children }) => { export const ParticipantsProvider = ({ children }) => {
const { callObject } = useCallState(); const { callObject, videoQuality, networkState } = useCallState();
const [state, dispatch] = useReducer( const [state, dispatch] = useReducer(
participantsReducer, participantsReducer,
initialParticipantsState initialParticipantsState
@ -235,34 +241,50 @@ export const ParticipantsProvider = ({ children }) => {
}, [callObject, handleNewParticipantsState]); }, [callObject, handleNewParticipantsState]);
/** /**
* Adjust video quality from the 3 simulcast layers based * Change between the simulcast layers based on view / available bandwidth
* 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(() => { const setBandWidthControls = useCallback(() => {
if (typeof rtcpeers === 'undefined') return; if (!(callObject && callObject.meetingState() === 'joined-meeting')) return;
const sfu = rtcpeers?.soup;
const isSFU = rtcpeers?.currentlyPreferred?.typeName?.() === 'sfu';
if (!isSFU) return;
const ids = participantIds.split(','); const ids = participantIds.split(',');
const receiveSettings = {};
ids.forEach((id) => { ids.forEach((id) => {
if (isLocalId(id)) return; if (isLocalId(id)) return;
if (
// weak or bad network
([VIDEO_QUALITY_LOW, VIDEO_QUALITY_VERY_LOW].includes(networkState) &&
videoQuality === VIDEO_QUALITY_AUTO) ||
// Low quality or Bandwidth saver mode enabled
[VIDEO_QUALITY_BANDWIDTH_SAVER, VIDEO_QUALITY_LOW].includes(
videoQuality
)
) {
receiveSettings[id] = { video: { layer: 0 } };
return;
}
// Speaker view settings based on speaker status or pinned user // Speaker view settings based on speaker status or pinned user
if (viewMode === VIEW_MODE_SPEAKER) { if (viewMode === VIEW_MODE_SPEAKER) {
if (currentSpeaker?.id === id) { if (currentSpeaker?.id === id) {
sfu.setPreferredLayerForTrack(id, 'cam-video', 2); receiveSettings[id] = { video: { layer: 2 } };
} else { } else {
sfu.setPreferredLayerForTrack(id, 'cam-video', 0); receiveSettings[id] = { video: { layer: 0 } };
} }
} }
// Note: grid view settings are handled by the grid view component // Grid view settings are handled separately in GridView
}); });
}, [currentSpeaker?.id, participantIds, viewMode]); callObject.updateReceiveSettings(receiveSettings);
}, [
currentSpeaker?.id,
callObject,
networkState,
participantIds,
videoQuality,
viewMode,
]);
useEffect(() => { useEffect(() => {
setBandWidthControls(); setBandWidthControls();

View File

@ -32,7 +32,7 @@ const MAX_RECENT_SPEAKER_COUNT = 6;
* If the remote participant count passes this threshold, * If the remote participant count passes this threshold,
* cam subscriptions are defined by UI view modes. * cam subscriptions are defined by UI view modes.
*/ */
const SUBSCRIBE_ALL_VIDEO_THRESHOLD = 9; const SUBSCRIBE_OR_STAGE_ALL_VIDEO_THRESHOLD = 9;
const TracksContext = createContext(null); const TracksContext = createContext(null);
@ -52,135 +52,81 @@ export const TracksProvider = ({ children }) => {
[participants] [participants]
); );
const pauseVideoTrack = useCallback(
(id) => {
/**
* Ignore undefined, local or screenshare.
*/
if (
!id ||
subscribeToTracksAutomatically ||
isLocalId(id) ||
isScreenId(id) ||
rtcpeers.getCurrentType() !== 'sfu'
) {
return;
}
if (!rtcpeers.soup.implementationIsAcceptingCalls) {
return;
}
rtcpeers.soup.pauseTrack(id, 'cam-video');
},
[subscribeToTracksAutomatically]
);
const resumeVideoTrack = useCallback(
(id) => {
/**
* Ignore undefined, local or screenshare.
*/
if (
!id ||
subscribeToTracksAutomatically ||
isLocalId(id) ||
isScreenId(id)
)
return;
const videoTrack = callObject.participants()?.[id]?.tracks?.video;
const subscribe = () => {
if (videoTrack?.subscribed) return;
callObject.updateParticipant(id, {
setSubscribedTracks: true,
});
};
switch (rtcpeers.getCurrentType()) {
case 'peer-to-peer':
subscribe();
break;
case 'sfu': {
if (!rtcpeers.soup.implementationIsAcceptingCalls) return;
rtcpeers.soup.resumeTrack(id, 'cam-video');
break;
}
default:
break;
}
},
[callObject, subscribeToTracksAutomatically]
);
const remoteParticipantIds = useMemo( const remoteParticipantIds = useMemo(
() => participants.filter((p) => !p.isLocal).map((p) => p.id), () => participants.filter((p) => !p.isLocal).map((p) => p.id),
[participants] [participants]
); );
const subscribeToCam = useCallback(
(id) => {
// Ignore undefined, local or screenshare.
if (!id || isLocalId(id) || isScreenId(id)) return;
callObject.updateParticipant(id, {
setSubscribedTracks: { video: true },
});
},
[callObject]
);
/** /**
* Updates cam subscriptions based on passed ids. * Updates cam subscriptions based on passed subscribedIds and stagedIds.
* * For ids not provided, cam tracks will be unsubscribed from
* @param ids Array of ids to subscribe to, all others will be unsubscribed.
* @param pausedIds Array of ids that should be subscribed, but paused.
*/ */
const updateCamSubscriptions = useCallback( const updateCamSubscriptions = useCallback(
(ids, pausedIds = []) => { (subscribedIds, stagedIds = []) => {
if (!callObject || subscribeToTracksAutomatically) return; if (!callObject) return;
const subscribedIds =
remoteParticipantIds.length <= SUBSCRIBE_ALL_VIDEO_THRESHOLD
? [...remoteParticipantIds]
: [...ids, ...recentSpeakerIds];
// If total number of remote participants is less than a threshold, simply
// stage all remote cams that aren't already marked for subscription.
// Otherwise, honor the provided stagedIds, with recent speakers appended
// who aren't already marked for subscription.
const stagedIdsFiltered =
remoteParticipantIds.length <= SUBSCRIBE_OR_STAGE_ALL_VIDEO_THRESHOLD
? remoteParticipantIds.filter((id) => !subscribedIds.includes(id))
: [
...stagedIds,
...recentSpeakerIds.filter((id) => !subscribedIds.includes(id)),
];
// Assemble updates to get to desired cam subscriptions
const updates = remoteParticipantIds.reduce((u, id) => { const updates = remoteParticipantIds.reduce((u, id) => {
const shouldSubscribe = subscribedIds.includes(id); let desiredSubscription;
const shouldPause = pausedIds.includes(id); const currentSubscription =
const isSubscribed =
callObject.participants()?.[id]?.tracks?.video?.subscribed; callObject.participants()?.[id]?.tracks?.video?.subscribed;
/** // Ignore undefined, local or screenshare participant ids
* Pause already subscribed tracks. if (!id || isLocalId(id) || isScreenId(id)) return u;
*/
if (shouldSubscribe && shouldPause) { // Decide on desired cam subscription for this participant:
pauseVideoTrack(id); // subscribed, staged, or unsubscribed
if (subscribedIds.includes(id)) {
desiredSubscription = true;
} else if (stagedIdsFiltered.includes(id)) {
desiredSubscription = 'staged';
} else {
desiredSubscription = false;
} }
/** // Skip if we already have the desired subscription to this
* Fast resume tracks. // participant's cam
*/ if (desiredSubscription === currentSubscription) return u;
if (shouldSubscribe && !shouldPause) {
resumeVideoTrack(id);
}
if ( return {
isLocalId(id) || ...u,
isScreenId(id) || [id]: {
(shouldSubscribe && isSubscribed) setSubscribedTracks: {
) { audio: true,
return u; screenAudio: true,
} screenVideo: true,
video: desiredSubscription,
const result = { },
setSubscribedTracks: {
audio: true,
screenAudio: true,
screenVideo: true,
video: shouldSubscribe,
}, },
}; };
return { ...u, [id]: result };
}, {}); }, {});
callObject.updateParticipants(updates); callObject.updateParticipants(updates);
}, },
[ [callObject, remoteParticipantIds, recentSpeakerIds]
callObject,
subscribeToTracksAutomatically,
remoteParticipantIds,
recentSpeakerIds,
pauseVideoTrack,
resumeVideoTrack,
]
); );
useEffect(() => { useEffect(() => {
@ -273,15 +219,14 @@ export const TracksProvider = ({ children }) => {
callObject.off('participant-joined', handleParticipantJoined); callObject.off('participant-joined', handleParticipantJoined);
callObject.off('participant-left', handleParticipantLeft); callObject.off('participant-left', handleParticipantLeft);
}; };
}, [callObject, subscribeToTracksAutomatically, pauseVideoTrack]); }, [callObject, subscribeToTracksAutomatically]);
return ( return (
<TracksContext.Provider <TracksContext.Provider
value={{ value={{
audioTracks: state.audioTracks, audioTracks: state.audioTracks,
videoTracks: state.videoTracks, videoTracks: state.videoTracks,
pauseVideoTrack, subscribeToCam,
resumeVideoTrack,
updateCamSubscriptions, updateCamSubscriptions,
remoteParticipantIds, remoteParticipantIds,
recentSpeakerIds, recentSpeakerIds,

View File

@ -2,23 +2,25 @@ import { useDeepCompareEffect } from 'use-deep-compare';
import { useTracks } from '../contexts/TracksProvider'; import { useTracks } from '../contexts/TracksProvider';
/** /**
* Updates cam subscriptions based on passed ids and pausedIds. * Updates cam subscriptions based on passed subscribedIds and stagedIds.
* @param ids Participant ids which should be subscribed to. * @param subscribedIds Participant ids whose cam tracks should be subscribed to.
* @param pausedIds Participant ids which should be subscribed, but paused. * @param stagedIds Participant ids whose cam tracks should be staged.
* @param delay Throttle in milliseconds. Default: 50 * @param delay Throttle in milliseconds. Default: 50
*/ */
export const useCamSubscriptions = (ids, pausedIds = [], throttle = 50) => { export const useCamSubscriptions = (
subscribedIds,
stagedIds,
throttle = 50
) => {
const { updateCamSubscriptions } = useTracks(); const { updateCamSubscriptions } = useTracks();
useDeepCompareEffect(() => { useDeepCompareEffect(() => {
if (!ids || !pausedIds) return false; if (!subscribedIds || !stagedIds) return false;
const timeout = setTimeout(() => { const timeout = setTimeout(() => {
updateCamSubscriptions(ids, pausedIds); updateCamSubscriptions(subscribedIds, stagedIds);
}, throttle); }, throttle);
return () => { return () => clearTimeout(timeout);
clearTimeout(timeout); }, [subscribedIds, stagedIds, throttle, updateCamSubscriptions]);
};
}, [ids, pausedIds, throttle, updateCamSubscriptions]);
}; };
export default useCamSubscriptions; export default useCamSubscriptions;

View File

@ -0,0 +1,99 @@
/* global rtcpeers */
import { useCallback, useEffect, useState } from 'react';
import {
VIDEO_QUALITY_HIGH,
VIDEO_QUALITY_LOW,
VIDEO_QUALITY_BANDWIDTH_SAVER,
} from '../constants';
export const NETWORK_STATE_GOOD = 'good';
export const NETWORK_STATE_LOW = 'low';
export const NETWORK_STATE_VERY_LOW = 'very-low';
const STANDARD_HIGH_BITRATE_CAP = 980;
const STANDARD_LOW_BITRATE_CAP = 300;
export const useNetworkState = (
callObject = null,
quality = VIDEO_QUALITY_HIGH
) => {
const [threshold, setThreshold] = useState(NETWORK_STATE_GOOD);
const setQuality = useCallback(
(q) => {
if (!callObject || typeof rtcpeers === 'undefined') return;
const peers = Object.keys(callObject.participants()).length - 1;
const isSFU = rtcpeers?.currentlyPreferred?.typeName?.() === 'sfu';
const lowKbs = isSFU
? STANDARD_LOW_BITRATE_CAP
: STANDARD_LOW_BITRATE_CAP / Math.max(1, peers);
switch (q) {
case VIDEO_QUALITY_HIGH:
callObject.setBandwidth({ kbs: STANDARD_HIGH_BITRATE_CAP });
break;
case VIDEO_QUALITY_LOW:
callObject.setBandwidth({
kbs: lowKbs,
});
break;
case VIDEO_QUALITY_BANDWIDTH_SAVER:
callObject.setLocalVideo(false);
callObject.setBandwidth({
kbs: lowKbs,
});
break;
default:
break;
}
},
[callObject]
);
const handleNetworkQualityChange = useCallback(
(ev) => {
if (ev.threshold === threshold) return;
switch (ev.threshold) {
case NETWORK_STATE_VERY_LOW:
setQuality(VIDEO_QUALITY_BANDWIDTH_SAVER);
setThreshold(NETWORK_STATE_VERY_LOW);
break;
case NETWORK_STATE_LOW:
setQuality(
quality === VIDEO_QUALITY_BANDWIDTH_SAVER
? quality
: NETWORK_STATE_LOW
);
setThreshold(NETWORK_STATE_LOW);
break;
case NETWORK_STATE_GOOD:
setQuality(
[VIDEO_QUALITY_BANDWIDTH_SAVER, VIDEO_QUALITY_LOW].includes(quality)
? quality
: VIDEO_QUALITY_HIGH
);
setThreshold(NETWORK_STATE_GOOD);
break;
default:
break;
}
},
[setQuality, threshold, quality]
);
useEffect(() => {
if (!callObject) return false;
callObject.on('network-quality-change', handleNetworkQualityChange);
return () =>
callObject.off('network-quality-change', handleNetworkQualityChange);
}, [callObject, handleNetworkQualityChange]);
useEffect(() => {
setQuality(quality);
}, [quality, setQuality]);
return threshold;
};

View File

@ -1,43 +0,0 @@
/* global rtcpeers */
import { useEffect } from 'react';
/**
* This hook will switch between one of the 3 simulcast layers
* depending on the number of participants present on the call
* to optimise bandwidth / cpu usage
*
* Note: the API for this feature is currently work in progress
* and not documented. Momentarily we are using an internal
* method `setPreferredLayerForTrack` found on the global
* `rtcpeers` object.
*
* Note: this will have no effect when not in SFU mode
*/
export const usePreferredLayerByCount = (participants) => {
/**
* Set bandwidth layer based on amount of visible participants
*/
useEffect(() => {
if (typeof rtcpeers === 'undefined' || rtcpeers?.getCurrentType() !== 'sfu')
return;
const sfu = rtcpeers.soup;
const count = participants.length;
participants.forEach(({ id }) => {
if (count < 5) {
// High quality video for calls with < 5 people per page
sfu.setPreferredLayerForTrack(id, 'cam-video', 2);
} else if (count < 10) {
// Medium quality video for calls with < 10 people per page
sfu.setPreferredLayerForTrack(id, 'cam-video', 1);
} else {
// Low quality video for calls with 10 or more people per page
sfu.setPreferredLayerForTrack(id, 'cam-video', 0);
}
});
}, [participants]);
};
export default usePreferredLayerByCount;

View File

@ -4,7 +4,7 @@
"private": true, "private": true,
"main": "index.js", "main": "index.js",
"dependencies": { "dependencies": {
"@daily-co/daily-js": "^0.15.0", "@daily-co/daily-js": "^0.16.0",
"bowser": "^2.11.0", "bowser": "^2.11.0",
"classnames": "^2.3.1", "classnames": "^2.3.1",
"debounce": "^1.2.1", "debounce": "^1.2.1",

View File

@ -160,10 +160,10 @@
"@babel/helper-validator-identifier" "^7.12.11" "@babel/helper-validator-identifier" "^7.12.11"
to-fast-properties "^2.0.0" to-fast-properties "^2.0.0"
"@daily-co/daily-js@^0.15.0": "@daily-co/daily-js@^0.16.0":
version "0.15.0" version "0.16.0"
resolved "https://registry.yarnpkg.com/@daily-co/daily-js/-/daily-js-0.15.0.tgz#9dfd5c3ed8855df31c370d5b21a3b5098cce3c4f" resolved "https://registry.yarnpkg.com/@daily-co/daily-js/-/daily-js-0.16.0.tgz#9020104bb88de62dcc1966e713da65844243b9ab"
integrity sha512-rnivho7yx/yEOtqL81L4daPy9C/FDXf06k06df8vmyUXsE8y+cxSTD7ZvYIJDGJHN6IZRhVxxfbCyPI8CHfwCg== integrity sha512-DBWzbZs2IR7uYqfbABva1Ms3f/oX85dnQnCpVbGbexTN63LPIGknFSQp31ZYED88qcG+YJNydywBTb+ApNiNXA==
dependencies: dependencies:
"@babel/runtime" "^7.12.5" "@babel/runtime" "^7.12.5"
bowser "^2.8.1" bowser "^2.8.1"