Get hooks into parity with the current version of prebuilt
This commit is contained in:
parent
3d907d0168
commit
9a39dc2410
|
|
@ -2,7 +2,7 @@ import React, { useMemo } from 'react';
|
|||
import { Audio } from '@custom/shared/components/Audio';
|
||||
import { BasicTray } from '@custom/shared/components/Tray';
|
||||
import { useParticipants } from '@custom/shared/contexts/ParticipantsProvider';
|
||||
import useJoinSound from '@custom/shared/hooks/useJoinSound';
|
||||
import { useJoinSound } from '@custom/shared/hooks/useJoinSound';
|
||||
import PropTypes from 'prop-types';
|
||||
import { WaitingRoom } from './WaitingRoom';
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import React, { memo, useEffect, useState, useRef } from 'react';
|
||||
import useVideoTrack from '@custom/shared/hooks/useVideoTrack';
|
||||
import { useVideoTrack } from '@custom/shared/hooks/useVideoTrack';
|
||||
import { ReactComponent as IconMicMute } from '@custom/shared/icons/mic-off-sm.svg';
|
||||
import classNames from 'classnames';
|
||||
import PropTypes from 'prop-types';
|
||||
|
|
@ -21,7 +21,7 @@ export const Tile = memo(
|
|||
onVideoResize,
|
||||
...props
|
||||
}) => {
|
||||
const videoTrack = useVideoTrack(participant);
|
||||
const videoTrack = useVideoTrack(participant.id);
|
||||
const videoRef = useRef(null);
|
||||
const tileRef = useRef(null);
|
||||
const [tileWidth, setTileWidth] = useState(0);
|
||||
|
|
|
|||
|
|
@ -1,14 +1,11 @@
|
|||
import React from 'react';
|
||||
import { TrayButton } from '@custom/shared/components/Tray';
|
||||
import { useAudioLevel } from '@custom/shared/hooks/useAudioLevel';
|
||||
import { ReactComponent as IconMicOff } from '@custom/shared/icons/mic-off-md.svg';
|
||||
import { ReactComponent as IconMicOn } from '@custom/shared/icons/mic-on-md.svg';
|
||||
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
export const TrayMicButton = ({ isMuted, onClick }) => {
|
||||
const audioLevel = useAudioLevel('local');
|
||||
|
||||
return (
|
||||
<TrayButton label="Mic" onClick={onClick} orange={isMuted}>
|
||||
{isMuted ? <IconMicOff /> : <IconMicOn />}
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ export const CallProvider = ({
|
|||
const router = useRouter();
|
||||
const [roomInfo, setRoomInfo] = useState(null);
|
||||
const [enableScreenShare, setEnableScreenShare] = useState(false);
|
||||
const [enableJoinSound, setEnableJoinSound] = useState(true);
|
||||
const [videoQuality, setVideoQuality] = useState(VIDEO_QUALITY_AUTO);
|
||||
const [showLocalVideo, setShowLocalVideo] = useState(true);
|
||||
const [preJoinNonAuthorized, setPreJoinNonAuthorized] = useState(false);
|
||||
|
|
@ -144,6 +145,7 @@ export const CallProvider = ({
|
|||
roomExp,
|
||||
enableRecording,
|
||||
enableScreenShare,
|
||||
enableJoinSound,
|
||||
videoQuality,
|
||||
setVideoQuality,
|
||||
roomInfo,
|
||||
|
|
@ -154,6 +156,7 @@ export const CallProvider = ({
|
|||
setEnableScreenShare,
|
||||
startCloudRecording,
|
||||
subscribeToTracksAutomatically,
|
||||
setEnableJoinSound
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
|
|
|
|||
|
|
@ -78,10 +78,16 @@ export const useCallMachine = ({
|
|||
const join = useCallback(
|
||||
async (callObject) => {
|
||||
setState(CALL_STATE_JOINING);
|
||||
|
||||
// Force mute clients when joining a call with experimental_optimize_large_calls enabled.
|
||||
if (room?.config?.experimental_optimize_large_calls) {
|
||||
callObject.setLocalAudio(false);
|
||||
}
|
||||
|
||||
await callObject.join({ subscribeToTracksAutomatically, token, url });
|
||||
setState(CALL_STATE_JOINED);
|
||||
},
|
||||
[token, subscribeToTracksAutomatically, url]
|
||||
[room, token, subscribeToTracksAutomatically, url]
|
||||
);
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -6,12 +6,12 @@ import { useParticipants } from '../contexts/ParticipantsProvider';
|
|||
* (= the current one and only actively speaking person)
|
||||
*/
|
||||
export const useActiveSpeaker = () => {
|
||||
const { showLocalVideo } = useCallState();
|
||||
const { broadcastRole, showLocalVideo } = useCallState();
|
||||
const { activeParticipant, localParticipant, participantCount } =
|
||||
useParticipants();
|
||||
|
||||
// we don't show active speaker indicators EVER in a 1:1 call or when the user is alone in-call
|
||||
if (participantCount <= 2) return null;
|
||||
if (broadcastRole !== 'attendee' && participantCount <= 2) return null;
|
||||
|
||||
if (!activeParticipant?.isMicMuted) {
|
||||
return activeParticipant?.id;
|
||||
|
|
|
|||
|
|
@ -1,36 +1,54 @@
|
|||
import { useEffect, useState } from 'react';
|
||||
import getConfig from 'next/config';
|
||||
|
||||
export const useAudioLevel = (sessionId) => {
|
||||
const [audioLevel, setAudioLevel] = useState(0);
|
||||
export const useAudioLevel = (stream) => {
|
||||
const [micVolume, setMicVolume] = useState(0);
|
||||
const { assetPrefix } = getConfig().publicRuntimeConfig;
|
||||
|
||||
useEffect(() => {
|
||||
if (!sessionId) {
|
||||
return false;
|
||||
if (!stream) {
|
||||
setMicVolume(0);
|
||||
return;
|
||||
}
|
||||
const AudioCtx =
|
||||
typeof AudioContext !== 'undefined'
|
||||
? AudioContext
|
||||
: typeof webkitAudioContext !== 'undefined'
|
||||
? webkitAudioContext
|
||||
: null;
|
||||
if (!AudioCtx) return;
|
||||
const audioContext = new AudioCtx();
|
||||
const mediaStreamSource = audioContext.createMediaStreamSource(stream);
|
||||
let node;
|
||||
|
||||
const i = setInterval(async () => {
|
||||
const startProcessing = async () => {
|
||||
try {
|
||||
if (!(window.rtcpeers && window.rtcpeers.sfu)) {
|
||||
return;
|
||||
}
|
||||
const consumer =
|
||||
window.rtcpeers.sfu.consumers[`${sessionId}/cam-audio`];
|
||||
if (!(consumer && consumer.getStats)) {
|
||||
return;
|
||||
}
|
||||
const level = Array.from((await consumer.getStats()).values()).find(
|
||||
(s) => 'audioLevel' in s
|
||||
).audioLevel;
|
||||
setAudioLevel(level);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}, 2000);
|
||||
await audioContext.audioWorklet.addModule(
|
||||
`${assetPrefix}/audiolevel-processor.js`
|
||||
);
|
||||
|
||||
return () => clearInterval(i);
|
||||
}, [sessionId]);
|
||||
node = new AudioWorkletNode(audioContext, 'audiolevel');
|
||||
|
||||
return audioLevel;
|
||||
};
|
||||
node.port.onmessage = (event) => {
|
||||
let volume = 0;
|
||||
if (event.data.volume) volume = event.data.volume;
|
||||
if (!node) return;
|
||||
setMicVolume(volume);
|
||||
};
|
||||
|
||||
export default useAudioLevel;
|
||||
mediaStreamSource.connect(node).connect(audioContext.destination);
|
||||
} catch {}
|
||||
};
|
||||
|
||||
startProcessing();
|
||||
|
||||
return () => {
|
||||
node?.disconnect();
|
||||
node = null;
|
||||
mediaStreamSource?.disconnect();
|
||||
audioContext?.close();
|
||||
};
|
||||
}, [assetPrefix, stream]);
|
||||
|
||||
return micVolume;
|
||||
};
|
||||
|
|
@ -2,14 +2,13 @@ import { useDeepCompareMemo } from 'use-deep-compare';
|
|||
|
||||
import { useTracks } from '../contexts/TracksProvider';
|
||||
|
||||
export const useAudioTrack = (participant) => {
|
||||
export const useAudioTrack = (id) => {
|
||||
const { audioTracks } = useTracks();
|
||||
|
||||
return useDeepCompareMemo(() => {
|
||||
const audioTrack = audioTracks?.[participant?.id];
|
||||
// @ts-ignore
|
||||
const audioTrack = audioTracks?.[id];
|
||||
return audioTrack?.persistentTrack;
|
||||
}, [participant?.id, audioTracks]);
|
||||
}, [id, audioTracks]);
|
||||
};
|
||||
|
||||
export default useAudioTrack;
|
||||
export default useAudioTrack;
|
||||
|
|
@ -15,12 +15,10 @@ export const useCamSubscriptions = (
|
|||
const { updateCamSubscriptions } = useTracks();
|
||||
|
||||
useDeepCompareEffect(() => {
|
||||
if (!subscribedIds || !stagedIds) return false;
|
||||
if (!subscribedIds || !stagedIds) return;
|
||||
const timeout = setTimeout(() => {
|
||||
updateCamSubscriptions(subscribedIds, stagedIds);
|
||||
}, throttle);
|
||||
return () => clearTimeout(timeout);
|
||||
}, [subscribedIds, stagedIds, throttle, updateCamSubscriptions]);
|
||||
};
|
||||
|
||||
export default useCamSubscriptions;
|
||||
};
|
||||
|
|
@ -1,39 +1,42 @@
|
|||
import { useEffect, useMemo } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { debounce } from 'debounce';
|
||||
import { useCallState } from '../contexts/CallProvider';
|
||||
import { useSound } from './useSound';
|
||||
import { useSoundLoader } from './useSoundLoader';
|
||||
|
||||
/**
|
||||
* Convenience hook to play `join.mp3` when participants join the call
|
||||
* Convenience hook to play `join.mp3` when first other participants joins.
|
||||
*/
|
||||
export const useJoinSound = () => {
|
||||
const { callObject } = useCallState();
|
||||
const { load, play } = useSound('assets/join.mp3');
|
||||
const { callObject: daily } = useCallState();
|
||||
const { joinSound } = useSoundLoader();
|
||||
const [playJoinSound, setPlayJoinSound] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
load();
|
||||
}, [load]);
|
||||
|
||||
const debouncedPlay = useMemo(() => debounce(() => play(), 200), [play]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!callObject) return false;
|
||||
|
||||
const handleParticipantJoined = () => {
|
||||
debouncedPlay();
|
||||
};
|
||||
|
||||
callObject.on('participant-joined', handleParticipantJoined);
|
||||
|
||||
if (!daily) return;
|
||||
/**
|
||||
* We don't want to immediately play a joined sound, when the user joins the meeting:
|
||||
* Upon joining all other participants, that were already in-call, will emit a
|
||||
* participant-joined event.
|
||||
* In waiting 2 seconds we make sure, that the sound is only played when the user
|
||||
* is **really** the first participant.
|
||||
*/
|
||||
setTimeout(() => {
|
||||
handleParticipantJoined();
|
||||
setPlayJoinSound(true);
|
||||
}, 2000);
|
||||
}, [daily]);
|
||||
|
||||
return () => {
|
||||
callObject.off('participant-joined', handleParticipantJoined);
|
||||
useEffect(() => {
|
||||
if (!daily) return;
|
||||
const handleParticipantJoined = () => {
|
||||
// first other participant joined --> play sound
|
||||
if (!playJoinSound || Object.keys(daily.participants()).length !== 2)
|
||||
return;
|
||||
joinSound.play();
|
||||
};
|
||||
}, [callObject, debouncedPlay]);
|
||||
};
|
||||
|
||||
export default useJoinSound;
|
||||
daily.on('participant-joined', handleParticipantJoined);
|
||||
return () => {
|
||||
daily.off('participant-joined', handleParticipantJoined);
|
||||
};
|
||||
}, [daily, joinSound, playJoinSound]);
|
||||
};
|
||||
|
|
@ -1,4 +1,3 @@
|
|||
/* global rtcpeers */
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
|
||||
import {
|
||||
|
|
@ -21,10 +20,10 @@ export const useNetworkState = (
|
|||
|
||||
const setQuality = useCallback(
|
||||
(q) => {
|
||||
if (!callObject || typeof rtcpeers === 'undefined') return;
|
||||
if (!callObject) return;
|
||||
|
||||
const peers = Object.keys(callObject.participants()).length - 1;
|
||||
const isSFU = rtcpeers?.currentlyPreferred?.typeName?.() === 'sfu';
|
||||
const isSFU = callObject.getNetworkTopology().topology === 'sfu';
|
||||
|
||||
const lowKbs = isSFU
|
||||
? STANDARD_LOW_BITRATE_CAP
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
import { useCallback, useEffect, useRef } from 'react';
|
||||
|
||||
export const useSound = (src) => {
|
||||
const defaultNotMuted = () => false;
|
||||
|
||||
export const useSound = (src, isMuted = defaultNotMuted) => {
|
||||
const audio = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -22,17 +24,15 @@ export const useSound = (src) => {
|
|||
audio.current.load();
|
||||
}, [audio]);
|
||||
|
||||
const play = useCallback(() => {
|
||||
if (!audio.current) return;
|
||||
const play = useCallback(async () => {
|
||||
if (!audio.current || isMuted()) return;
|
||||
try {
|
||||
audio.current.currentTime = 0;
|
||||
audio.current.play();
|
||||
await audio.current.play();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}, [audio]);
|
||||
}, [audio, isMuted]);
|
||||
|
||||
return { load, play };
|
||||
};
|
||||
|
||||
export default useSound;
|
||||
};
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
import { useCallback, useMemo } from 'react';
|
||||
|
||||
import { useCallState } from '../contexts/CallProvider';
|
||||
import { useSound } from './useSound';
|
||||
|
||||
export const useSoundLoader = () => {
|
||||
const { enableJoinSound } = useCallState();
|
||||
|
||||
const isJoinSoundMuted = useCallback(
|
||||
() => !enableJoinSound,
|
||||
[enableJoinSound]
|
||||
);
|
||||
|
||||
const joinSound = useSound(`assets/join.mp3`, isJoinSoundMuted);
|
||||
|
||||
const load = useCallback(() => {
|
||||
joinSound.load();
|
||||
}, [joinSound]);
|
||||
|
||||
return useMemo(
|
||||
() => ({ joinSound, load }),
|
||||
[joinSound, load]
|
||||
);
|
||||
};
|
||||
|
|
@ -1,22 +1,28 @@
|
|||
import { useDeepCompareMemo } from 'use-deep-compare';
|
||||
import { useTracks } from '../contexts/TracksProvider';
|
||||
import { DEVICE_STATE_BLOCKED, DEVICE_STATE_OFF } from '../contexts/useDevices';
|
||||
|
||||
export const useVideoTrack = (participant) => {
|
||||
import { useTracks } from '../contexts/TracksProvider';
|
||||
import { isLocalId, isScreenId } from '../contexts/participantsState';
|
||||
|
||||
export const useVideoTrack = (id) => {
|
||||
const { videoTracks } = useTracks();
|
||||
|
||||
const videoTrack = useDeepCompareMemo(
|
||||
() => videoTracks?.[id],
|
||||
[id, videoTracks]
|
||||
);
|
||||
|
||||
/**
|
||||
* MediaStreamTrack's are difficult to compare.
|
||||
* Changes to a video track's id will likely need to be reflected in the UI / DOM.
|
||||
* This usually happens on P2P / SFU switches.
|
||||
*/
|
||||
return useDeepCompareMemo(() => {
|
||||
const videoTrack = videoTracks?.[participant?.id];
|
||||
if (
|
||||
videoTrack?.state === DEVICE_STATE_OFF ||
|
||||
videoTrack?.state === DEVICE_STATE_BLOCKED ||
|
||||
(!videoTrack?.subscribed &&
|
||||
participant?.id !== 'local' &&
|
||||
!participant.isScreenshare)
|
||||
videoTrack?.state === 'off' ||
|
||||
videoTrack?.state === 'blocked' ||
|
||||
(!videoTrack?.subscribed && !isLocalId(id) && !isScreenId(id))
|
||||
)
|
||||
return null;
|
||||
return videoTrack?.persistentTrack;
|
||||
}, [participant?.id, videoTracks]);
|
||||
};
|
||||
|
||||
export default useVideoTrack;
|
||||
}, [id, videoTrack]);
|
||||
};
|
||||
Loading…
Reference in New Issue