From 4c2caf14fe2a6fd15f58f1dea3aff4913fd55301 Mon Sep 17 00:00:00 2001 From: harshithpabbati Date: Wed, 6 Apr 2022 10:53:50 +0530 Subject: [PATCH 1/7] use useDevices hook --- custom/basic-call/README.md | 3 - custom/fitness-demo/README.md | 3 - .../shared/components/HairCheck/HairCheck.js | 49 ++-- custom/shared/components/Tray/BasicTray.js | 17 +- custom/shared/contexts/MediaDeviceProvider.js | 107 +++---- custom/shared/contexts/useDevices.js | 262 ------------------ 6 files changed, 85 insertions(+), 356 deletions(-) delete mode 100644 custom/shared/contexts/useDevices.js diff --git a/custom/basic-call/README.md b/custom/basic-call/README.md index 648f221..7d4b1e6 100644 --- a/custom/basic-call/README.md +++ b/custom/basic-call/README.md @@ -38,9 +38,6 @@ This demo puts to work the following [shared libraries](../shared): **[MediaDeviceProvider.js](../shared/contexts/MediaDeviceProvider.js)** Convenience context that provides an interface to media devices throughout app -**[useDevices.js](../shared/contexts/useDevices.js)** -Hook for managing the enumeration and status of client media devices) - **[CallProvider.js](../shared/contexts/CallProvider.js)** Primary call context that manages Daily call state, participant state and call object interaction diff --git a/custom/fitness-demo/README.md b/custom/fitness-demo/README.md index 1bec2c5..833f476 100644 --- a/custom/fitness-demo/README.md +++ b/custom/fitness-demo/README.md @@ -38,9 +38,6 @@ This demo puts to work the following [shared libraries](../shared): **[MediaDeviceProvider.js](../shared/contexts/MediaDeviceProvider.js)** Convenience context that provides an interface to media devices throughout app -**[useDevices.js](../shared/contexts/useDevices.js)** -Hook for managing the enumeration and status of client media devices) - **[CallProvider.js](../shared/contexts/CallProvider.js)** Primary call context that manages Daily call state, participant state and call object interaction diff --git a/custom/shared/components/HairCheck/HairCheck.js b/custom/shared/components/HairCheck/HairCheck.js index 4cf0659..3a0e378 100644 --- a/custom/shared/components/HairCheck/HairCheck.js +++ b/custom/shared/components/HairCheck/HairCheck.js @@ -7,17 +7,15 @@ import MuteButton from '@custom/shared/components/MuteButton'; import Tile from '@custom/shared/components/Tile'; import { ACCESS_STATE_LOBBY } from '@custom/shared/constants'; import { useCallState } from '@custom/shared/contexts/CallProvider'; -import { useMediaDevices } from '@custom/shared/contexts/MediaDeviceProvider'; -import { useParticipants } from '@custom/shared/contexts/ParticipantsProvider'; -import { useUIState } from '@custom/shared/contexts/UIStateProvider'; import { DEVICE_STATE_BLOCKED, DEVICE_STATE_NOT_FOUND, DEVICE_STATE_IN_USE, DEVICE_STATE_PENDING, - DEVICE_STATE_LOADING, - DEVICE_STATE_GRANTED, -} from '@custom/shared/contexts/useDevices'; + useMediaDevices, +} from '@custom/shared/contexts/MediaDeviceProvider'; +import { useParticipants } from '@custom/shared/contexts/ParticipantsProvider'; +import { useUIState } from '@custom/shared/contexts/UIStateProvider'; import IconSettings from '@custom/shared/icons/settings-sm.svg'; import { useDeepCompareMemo } from 'use-deep-compare'; @@ -33,7 +31,8 @@ export const HairCheck = () => { const { callObject } = useCallState(); const { localParticipant } = useParticipants(); const { - deviceState, + camState, + micState, camError, micError, isCamMuted, @@ -100,21 +99,14 @@ export const HairCheck = () => { [localParticipant] ); - const isLoading = useMemo(() => deviceState === DEVICE_STATE_LOADING, [ - deviceState, + const isLoading = useMemo(() => camState === DEVICE_STATE_PENDING || micState === DEVICE_STATE_PENDING, [ + camState, micState, ]); - const hasError = useMemo(() => { - return !(!deviceState || - [ - DEVICE_STATE_LOADING, - DEVICE_STATE_PENDING, - DEVICE_STATE_GRANTED, - ].includes(deviceState)); - }, [deviceState]); + const hasError = useMemo(() => camError || micError, [camError, micError]); const camErrorVerbose = useMemo(() => { - switch (camError) { + switch (camState) { case DEVICE_STATE_BLOCKED: return 'Camera blocked by user'; case DEVICE_STATE_NOT_FOUND: @@ -124,7 +116,20 @@ export const HairCheck = () => { default: return 'unknown'; } - }, [camError]); + }, [camState]); + + const micErrorVerbose = useMemo(() => { + switch (micState) { + case DEVICE_STATE_BLOCKED: + return 'Microphone blocked by user'; + case DEVICE_STATE_NOT_FOUND: + return 'Microphone not found'; + case DEVICE_STATE_IN_USE: + return 'Microphone in use'; + default: + return 'unknown'; + } + }, [micState]); const showWaitingMessage = useMemo(() => { return ( @@ -206,14 +211,14 @@ export const HairCheck = () => {
{camErrorVerbose}
)} {micError && ( -
{micError}
+
{micErrorVerbose}
)} )}
- - + +
{tileMemo} diff --git a/custom/shared/components/Tray/BasicTray.js b/custom/shared/components/Tray/BasicTray.js index 4ad4b41..8bf3e77 100644 --- a/custom/shared/components/Tray/BasicTray.js +++ b/custom/shared/components/Tray/BasicTray.js @@ -1,10 +1,9 @@ -import React, { useRef, useState, useEffect } from 'react'; +import React, { useRef, useState, useEffect, useMemo } from 'react'; import { NETWORK_ASIDE } from '@custom/shared/components/Aside/NetworkAside'; import { PEOPLE_ASIDE } from '@custom/shared/components/Aside/PeopleAside'; import Button from '@custom/shared/components/Button'; import { DEVICE_MODAL } from '@custom/shared/components/DeviceSelectModal'; import { useCallState } from '@custom/shared/contexts/CallProvider'; -import { useMediaDevices } from '@custom/shared/contexts/MediaDeviceProvider'; import { useUIState } from '@custom/shared/contexts/UIStateProvider'; import { useResponsive } from '@custom/shared/hooks/useResponsive'; import { ReactComponent as IconCameraOff } from '@custom/shared/icons/camera-off-md.svg'; @@ -16,6 +15,7 @@ import { ReactComponent as IconMore } from '@custom/shared/icons/more-md.svg'; import { ReactComponent as IconNetwork } from '@custom/shared/icons/network-md.svg'; import { ReactComponent as IconPeople } from '@custom/shared/icons/people-md.svg'; import { ReactComponent as IconSettings } from '@custom/shared/icons/settings-md.svg'; +import { useLocalParticipant, useDevices } from '@daily-co/daily-react-hooks'; import { Tray, TrayButton } from './Tray'; export const BasicTray = () => { @@ -24,7 +24,18 @@ export const BasicTray = () => { const [showMore, setShowMore] = useState(false); const { callObject, leave } = useCallState(); const { customTrayComponent, openModal, toggleAside } = useUIState(); - const { isCamMuted, isMicMuted } = useMediaDevices(); + const localParticipant = useLocalParticipant(); + const { hasCamError, hasMicError } = useDevices(); + + const isCamMuted = useMemo(() => { + const videoState = localParticipant?.tracks?.video?.state; + return videoState === 'off' || videoState === 'blocked' || hasCamError; + }, [hasCamError, localParticipant?.tracks?.video?.state]); + + const isMicMuted = useMemo(() => { + const audioState = localParticipant?.tracks?.audio?.state; + return audioState === 'off' || audioState === 'blocked' || hasMicError; + }, [hasMicError, localParticipant?.tracks?.audio?.state]); const toggleCamera = (newState) => { if (!callObject) return false; diff --git a/custom/shared/contexts/MediaDeviceProvider.js b/custom/shared/contexts/MediaDeviceProvider.js index b0b4349..afe5a82 100644 --- a/custom/shared/contexts/MediaDeviceProvider.js +++ b/custom/shared/contexts/MediaDeviceProvider.js @@ -1,82 +1,63 @@ -import React, { createContext, useContext, useCallback } from 'react'; +import React, { createContext, useContext, useMemo } from 'react'; +import { useDevices, useLocalParticipant } from '@daily-co/daily-react-hooks'; import PropTypes from 'prop-types'; -import { useCallState } from './CallProvider'; -import { useParticipants } from './ParticipantsProvider'; -import { useDevices } from './useDevices'; +export const DEVICE_STATE_LOADING = 'loading'; +export const DEVICE_STATE_PENDING = 'pending'; +export const DEVICE_STATE_ERROR = 'error'; +export const DEVICE_STATE_GRANTED = 'granted'; +export const DEVICE_STATE_NOT_FOUND = 'not-found'; +export const DEVICE_STATE_NOT_SUPPORTED = 'not-supported'; +export const DEVICE_STATE_BLOCKED = 'blocked'; +export const DEVICE_STATE_IN_USE = 'in-use'; +export const DEVICE_STATE_OFF = 'off'; +export const DEVICE_STATE_PLAYABLE = 'playable'; +export const DEVICE_STATE_SENDABLE = 'sendable'; export const MediaDeviceContext = createContext(); export const MediaDeviceProvider = ({ children }) => { - const { callObject } = useCallState(); - const { localParticipant } = useParticipants(); - const { - camError, - cams, - currentCam, - currentMic, - currentSpeaker, - deviceState, - micError, - mics, - refreshDevices, - setCurrentCam, - setCurrentMic, - setCurrentSpeaker, + hasCamError, + cameras, + camState, + setCamera, + hasMicError, + microphones, + micState, + setMicrophone, speakers, - } = useDevices(callObject); + setSpeaker, + refreshDevices, + } = useDevices(); - const selectCamera = useCallback( - async (newCam) => { - if (!callObject || newCam.deviceId === currentCam?.deviceId) return; - const { camera } = await callObject.setInputDevicesAsync({ - videoDeviceId: newCam.deviceId, - }); - setCurrentCam(camera); - }, - [callObject, currentCam, setCurrentCam] - ); + const localParticipant = useLocalParticipant(); - const selectMic = useCallback( - async (newMic) => { - if (!callObject || newMic.deviceId === currentMic?.deviceId) return; - const { mic } = await callObject.setInputDevicesAsync({ - audioDeviceId: newMic.deviceId, - }); - setCurrentMic(mic); - }, - [callObject, currentMic, setCurrentMic] - ); + const isCamMuted = useMemo(() => { + const videoState = localParticipant?.tracks?.video?.state; + return videoState === DEVICE_STATE_OFF || videoState === DEVICE_STATE_BLOCKED || hasCamError; + }, [hasCamError, localParticipant?.tracks?.video?.state]); - const selectSpeaker = useCallback( - (newSpeaker) => { - if (!callObject || newSpeaker.deviceId === currentSpeaker?.deviceId) return; - callObject.setOutputDevice({ - outputDeviceId: newSpeaker.deviceId, - }); - setCurrentSpeaker(newSpeaker); - }, - [callObject, currentSpeaker, setCurrentSpeaker] - ); + const isMicMuted = useMemo(() => { + const audioState = localParticipant?.tracks?.audio?.state; + return audioState === DEVICE_STATE_OFF || audioState === DEVICE_STATE_BLOCKED || hasMicError; + }, [hasMicError, localParticipant?.tracks?.audio?.state]); return ( diff --git a/custom/shared/contexts/useDevices.js b/custom/shared/contexts/useDevices.js deleted file mode 100644 index ee4362a..0000000 --- a/custom/shared/contexts/useDevices.js +++ /dev/null @@ -1,262 +0,0 @@ -import { useState, useCallback, useEffect } from 'react'; -import { sortByKey } from '../lib/sortByKey'; - -export const DEVICE_STATE_LOADING = 'loading'; -export const DEVICE_STATE_PENDING = 'pending'; -export const DEVICE_STATE_ERROR = 'error'; -export const DEVICE_STATE_GRANTED = 'granted'; -export const DEVICE_STATE_NOT_FOUND = 'not-found'; -export const DEVICE_STATE_NOT_SUPPORTED = 'not-supported'; -export const DEVICE_STATE_BLOCKED = 'blocked'; -export const DEVICE_STATE_IN_USE = 'in-use'; -export const DEVICE_STATE_OFF = 'off'; -export const DEVICE_STATE_PLAYABLE = 'playable'; -export const DEVICE_STATE_SENDABLE = 'sendable'; - -export const useDevices = (callObject) => { - const [deviceState, setDeviceState] = useState(DEVICE_STATE_LOADING); - const [currentCam, setCurrentCam] = useState(null); - const [currentMic, setCurrentMic] = useState(null); - const [currentSpeaker, setCurrentSpeaker] = useState(null); - - const [cams, setCams] = useState([]); - const [mics, setMics] = useState([]); - const [speakers, setSpeakers] = useState([]); - - const [camError, setCamError] = useState(null); - const [micError, setMicError] = useState(null); - - const updateDeviceState = useCallback(async () => { - if ( - typeof navigator?.mediaDevices?.getUserMedia === 'undefined' || - typeof navigator?.mediaDevices?.enumerateDevices === 'undefined' - ) { - setDeviceState(DEVICE_STATE_NOT_SUPPORTED); - return; - } - - try { - const { devices } = await callObject.enumerateDevices(); - - const { camera, mic, speaker } = await callObject.getInputDevices(); - - setCurrentCam(camera ?? null); - setCurrentMic(mic ?? null); - setCurrentSpeaker(speaker ?? null); - - const [defaultCam, ...videoDevices] = devices.filter( - (d) => d.kind === 'videoinput' && d.deviceId !== '' - ); - setCams( - [ - defaultCam, - ...videoDevices.sort((a, b) => sortByKey(a, b, 'label', false)), - ].filter(Boolean) - ); - const [defaultMic, ...micDevices] = devices.filter( - (d) => d.kind === 'audioinput' && d.deviceId !== '' - ); - setMics( - [ - defaultMic, - ...micDevices.sort((a, b) => sortByKey(a, b, 'label', false)), - ].filter(Boolean) - ); - const [defaultSpeaker, ...speakerDevices] = devices.filter( - (d) => d.kind === 'audiooutput' && d.deviceId !== '' - ); - setSpeakers( - [ - defaultSpeaker, - ...speakerDevices.sort((a, b) => sortByKey(a, b, 'label', false)), - ].filter(Boolean) - ); - - console.log(`Current cam: ${camera.label}`); - console.log(`Current mic: ${mic.label}`); - console.log(`Current speakers: ${speaker.label}`); - } catch (e) { - setDeviceState(DEVICE_STATE_NOT_SUPPORTED); - } - }, [callObject]); - - const updateDeviceErrors = useCallback(() => { - if (!callObject) return; - const { tracks } = callObject.participants().local; - - if (tracks.video?.blocked?.byPermissions) { - setCamError(DEVICE_STATE_BLOCKED); - } else if (tracks.video?.blocked?.byDeviceMissing) { - setCamError(DEVICE_STATE_NOT_FOUND); - } else if (tracks.video?.blocked?.byDeviceInUse) { - setCamError(DEVICE_STATE_IN_USE); - } - - if ( - [ - DEVICE_STATE_LOADING, - DEVICE_STATE_OFF, - DEVICE_STATE_PLAYABLE, - DEVICE_STATE_SENDABLE, - ].includes(tracks.video.state) - ) { - setCamError(null); - } - - if (tracks.audio?.blocked?.byPermissions) { - setMicError(DEVICE_STATE_BLOCKED); - } else if (tracks.audio?.blocked?.byDeviceMissing) { - setMicError(DEVICE_STATE_NOT_FOUND); - } else if (tracks.audio?.blocked?.byDeviceInUse) { - setMicError(DEVICE_STATE_IN_USE); - } - - if ( - [ - DEVICE_STATE_LOADING, - DEVICE_STATE_OFF, - DEVICE_STATE_PLAYABLE, - DEVICE_STATE_SENDABLE, - ].includes(tracks.audio.state) - ) { - setMicError(null); - } - }, [callObject]); - - const handleParticipantUpdated = useCallback( - ({ participant }) => { - if (!callObject || deviceState === 'not-supported' || !participant.local) return; - - switch (participant?.tracks.video.state) { - case DEVICE_STATE_BLOCKED: - setDeviceState(DEVICE_STATE_ERROR); - break; - case DEVICE_STATE_OFF: - case DEVICE_STATE_PLAYABLE: - updateDeviceState(); - setDeviceState(DEVICE_STATE_GRANTED); - break; - } - - updateDeviceErrors(); - }, - [callObject, deviceState, updateDeviceErrors, updateDeviceState] - ); - - useEffect(() => { - if (!callObject) return; - - /** - If the user is slow to allow access, we'll update the device state - so our app can show a prompt requesting access - */ - let pendingAccessTimeout; - - const handleJoiningMeeting = () => { - pendingAccessTimeout = setTimeout(() => { - setDeviceState(DEVICE_STATE_PENDING); - }, 2000); - }; - - const handleJoinedMeeting = () => { - clearTimeout(pendingAccessTimeout); - // Note: setOutputDevice() is not honored before join() so we must enumerate again - updateDeviceState(); - }; - - updateDeviceState(); - callObject.on('joining-meeting', handleJoiningMeeting); - callObject.on('joined-meeting', handleJoinedMeeting); - callObject.on('participant-updated', handleParticipantUpdated); - return () => { - clearTimeout(pendingAccessTimeout); - callObject.off('joining-meeting', handleJoiningMeeting); - callObject.off('joined-meeting', handleJoinedMeeting); - callObject.off('participant-updated', handleParticipantUpdated); - }; - }, [callObject, handleParticipantUpdated, updateDeviceState]); - - useEffect(() => { - if (!callObject) return; - - console.log('💻 Device provider events bound'); - - const handleCameraError = ({ - errorMsg: { errorMsg, audioOk, videoOk }, - error, - }) => { - switch (error?.type) { - case 'cam-in-use': - setDeviceState(DEVICE_STATE_ERROR); - setCamError(DEVICE_STATE_IN_USE); - break; - case 'mic-in-use': - setDeviceState(DEVICE_STATE_ERROR); - setMicError(DEVICE_STATE_IN_USE); - break; - case 'cam-mic-in-use': - setDeviceState(DEVICE_STATE_ERROR); - setCamError(DEVICE_STATE_IN_USE); - setMicError(DEVICE_STATE_IN_USE); - break; - default: - switch (errorMsg) { - case 'devices error': - setDeviceState(DEVICE_STATE_ERROR); - setCamError(videoOk ? null : DEVICE_STATE_NOT_FOUND); - setMicError(audioOk ? null : DEVICE_STATE_NOT_FOUND); - break; - case 'not allowed': - setDeviceState(DEVICE_STATE_ERROR); - updateDeviceErrors(); - break; - default: - break; - } - break; - } - }; - - const handleError = ({ errorMsg }) => { - switch (errorMsg) { - case 'not allowed': - setDeviceState(DEVICE_STATE_ERROR); - updateDeviceErrors(); - break; - default: - break; - } - }; - - const handleStartedCamera = () => { - updateDeviceErrors(); - }; - - callObject.on('camera-error', handleCameraError); - callObject.on('error', handleError); - callObject.on('started-camera', handleStartedCamera); - return () => { - callObject.off('camera-error', handleCameraError); - callObject.off('error', handleError); - callObject.off('started-camera', handleStartedCamera); - }; - }, [callObject, updateDeviceErrors]); - - return { - camError, - cams, - currentCam, - currentMic, - currentSpeaker, - deviceState, - micError, - mics, - refreshDevices: updateDeviceState, - setCurrentCam, - setCurrentMic, - setCurrentSpeaker, - speakers, - }; -}; - -export default useDevices; From 1ddec5c968ff35082c30c1f7d21cd4fd0f968c4c Mon Sep 17 00:00:00 2001 From: harshithpabbati Date: Wed, 6 Apr 2022 14:56:27 +0530 Subject: [PATCH 2/7] use useLiveStreaming hook --- custom/fitness-demo/components/App/App.js | 2 +- custom/fitness-demo/components/Tray/Stream.js | 2 +- custom/live-streaming/components/App.js | 2 +- .../components/LiveStreamingModal.js | 67 ++++++++------- custom/live-streaming/components/Tray.js | 2 +- .../contexts/LiveStreamingProvider.js | 71 --------------- .../shared/contexts/LiveStreamingProvider.js | 65 ++++++++++++++ custom/shared/contexts/callState.js | 86 ------------------- 8 files changed, 107 insertions(+), 190 deletions(-) delete mode 100644 custom/live-streaming/contexts/LiveStreamingProvider.js create mode 100644 custom/shared/contexts/LiveStreamingProvider.js delete mode 100644 custom/shared/contexts/callState.js diff --git a/custom/fitness-demo/components/App/App.js b/custom/fitness-demo/components/App/App.js index a86b453..4c3170d 100644 --- a/custom/fitness-demo/components/App/App.js +++ b/custom/fitness-demo/components/App/App.js @@ -1,5 +1,5 @@ import React, { useMemo } from 'react'; -import { LiveStreamingProvider } from '@custom/live-streaming/contexts/LiveStreamingProvider'; +import { LiveStreamingProvider } from '@custom/shared/contexts/LiveStreamingProvider'; import { RecordingProvider } from '@custom/recording/contexts/RecordingProvider'; import ExpiryTimer from '@custom/shared/components/ExpiryTimer'; import { useCallState } from '@custom/shared/contexts/CallProvider'; diff --git a/custom/fitness-demo/components/Tray/Stream.js b/custom/fitness-demo/components/Tray/Stream.js index a03885e..379a49e 100644 --- a/custom/fitness-demo/components/Tray/Stream.js +++ b/custom/fitness-demo/components/Tray/Stream.js @@ -1,8 +1,8 @@ import React from 'react'; import { LIVE_STREAMING_MODAL } from '@custom/live-streaming/components/LiveStreamingModal'; -import { useLiveStreaming } from '@custom/live-streaming/contexts/LiveStreamingProvider'; import { TrayButton } from '@custom/shared/components/Tray'; +import { useLiveStreaming } from '@custom/shared/contexts/LiveStreamingProvider'; import { useParticipants } from '@custom/shared/contexts/ParticipantsProvider'; import { useUIState } from '@custom/shared/contexts/UIStateProvider'; import { ReactComponent as IconStream } from '@custom/shared/icons/streaming-md.svg'; diff --git a/custom/live-streaming/components/App.js b/custom/live-streaming/components/App.js index cd47217..888fa3e 100644 --- a/custom/live-streaming/components/App.js +++ b/custom/live-streaming/components/App.js @@ -1,7 +1,7 @@ import React from 'react'; import App from '@custom/basic-call/components/App'; -import { LiveStreamingProvider } from '../contexts/LiveStreamingProvider'; +import { LiveStreamingProvider } from '@custom/shared/contexts/LiveStreamingProvider'; // Extend our basic call app component with the live streaming context export const AppWithLiveStreaming = () => ( diff --git a/custom/live-streaming/components/LiveStreamingModal.js b/custom/live-streaming/components/LiveStreamingModal.js index 97896c8..f4cdee1 100644 --- a/custom/live-streaming/components/LiveStreamingModal.js +++ b/custom/live-streaming/components/LiveStreamingModal.js @@ -5,10 +5,9 @@ import Field from '@custom/shared/components/Field'; import { TextInput, SelectInput } from '@custom/shared/components/Input'; import Modal from '@custom/shared/components/Modal'; import Well from '@custom/shared/components/Well'; -import { useCallState } from '@custom/shared/contexts/CallProvider'; +import { useLiveStreaming } from '@custom/shared/contexts/LiveStreamingProvider'; import { useParticipants } from '@custom/shared/contexts/ParticipantsProvider'; import { useUIState } from '@custom/shared/contexts/UIStateProvider'; -import { useLiveStreaming } from '../contexts/LiveStreamingProvider'; export const LIVE_STREAMING_MODAL = 'live-streaming'; @@ -19,15 +18,19 @@ const LAYOUTS = [ ]; export const LiveStreamingModal = () => { - const { callObject } = useCallState(); - const { allParticipants } = useParticipants(); + const { participants } = useParticipants(); const { currentModals, closeModal } = useUIState(); - const { isStreaming, streamError } = useLiveStreaming(); + const { + isStreaming, + streamError, + startLiveStreaming, + stopLiveStreaming, + } = useLiveStreaming(); const [pending, setPending] = useState(false); const [rtmpUrl, setRtmpUrl] = useState(''); - const [layout, setLayout] = useState(0); + const [layoutType, setLayoutType] = useState('default'); const [maxCams, setMaxCams] = useState(9); - const [participant, setParticipant] = useState(0); + const [participantId, setParticipantId] = useState(0); useEffect(() => { // Reset pending state whenever stream state changes @@ -35,18 +38,23 @@ export const LiveStreamingModal = () => { }, [isStreaming]); function startLiveStream() { - setPending(true); + const config = { + rtmpUrl, + layout: { + preset: layoutType, + }, + }; - const opts = - layout === 'single-participant' - ? { session_id: participant.id } - : { max_cam_streams: maxCams }; - callObject.startLiveStreaming({ rtmpUrl, preset: layout, ...opts }); + if (layoutType === 'single-participant') + config.layout.session_id = participantId; + else if (layoutType === 'default') config.layout.max_cam_streams = maxCams; + + startLiveStreaming(config); } - function stopLiveStreaming() { + function stopLiveStream() { setPending(true); - callObject.stopLiveStreaming(); + stopLiveStreaming(); } return ( @@ -62,7 +70,7 @@ export const LiveStreamingModal = () => { @@ -70,7 +78,7 @@ export const LiveStreamingModal = () => { @@ -85,19 +93,18 @@ export const LiveStreamingModal = () => { setLayout(Number(e.target.value))} - value={layout} + onChange={(e) => setLayoutType(e.target.value)} + value={layoutType} > - {LAYOUTS.map((l, i) => ( - ))} - {layout !== - LAYOUTS.findIndex((l) => l.value === 'single-participant') && ( + {layoutType === 'default' && ( setMaxCams(Number(e.target.value))} @@ -116,15 +123,17 @@ export const LiveStreamingModal = () => { )} - {layout === - LAYOUTS.findIndex((l) => l.value === 'single-participant') && ( + {layoutType === 'single-participant' && ( setParticipant(e.target.value)} - value={participant} + onChange={(e) => setParticipantId(e.target.value)} + value={participantId} > - {allParticipants.map((p) => ( - + {participants.map((p) => ( + ))} diff --git a/custom/live-streaming/components/Tray.js b/custom/live-streaming/components/Tray.js index baee790..2943187 100644 --- a/custom/live-streaming/components/Tray.js +++ b/custom/live-streaming/components/Tray.js @@ -1,10 +1,10 @@ import React from 'react'; import { TrayButton } from '@custom/shared/components/Tray'; +import { useLiveStreaming } from '@custom/shared/contexts/LiveStreamingProvider'; import { useUIState } from '@custom/shared/contexts/UIStateProvider'; import { ReactComponent as IconStream } from '@custom/shared/icons/streaming-md.svg'; -import { useLiveStreaming } from '../contexts/LiveStreamingProvider'; import { LIVE_STREAMING_MODAL } from './LiveStreamingModal'; export const Tray = () => { diff --git a/custom/live-streaming/contexts/LiveStreamingProvider.js b/custom/live-streaming/contexts/LiveStreamingProvider.js deleted file mode 100644 index 6e940c8..0000000 --- a/custom/live-streaming/contexts/LiveStreamingProvider.js +++ /dev/null @@ -1,71 +0,0 @@ -import React, { - useState, - createContext, - useContext, - useEffect, - useCallback, -} from 'react'; -import { useCallState } from '@custom/shared/contexts/CallProvider'; -import { useUIState } from '@custom/shared/contexts/UIStateProvider'; -import PropTypes from 'prop-types'; - -export const LiveStreamingContext = createContext(); - -export const LiveStreamingProvider = ({ children }) => { - const [isStreaming, setIsStreaming] = useState(false); - const [streamError, setStreamError] = useState(); - const { setCustomCapsule } = useUIState(); - const { callObject } = useCallState(); - - const handleStreamStarted = useCallback(() => { - console.log('📺 Live stream started'); - setIsStreaming(true); - setStreamError(null); - setCustomCapsule({ variant: 'recording', label: 'Live streaming' }); - }, [setCustomCapsule]); - - const handleStreamStopped = useCallback(() => { - console.log('📺 Live stream stopped'); - setIsStreaming(false); - setCustomCapsule(null); - }, [setCustomCapsule]); - - const handleStreamError = useCallback( - (e) => { - setIsStreaming(false); - setCustomCapsule(null); - setStreamError(e.errorMsg); - }, - [setCustomCapsule] - ); - - useEffect(() => { - if (!callObject) { - return false; - } - - console.log('📺 Live streaming provider listening for stream events'); - - callObject.on('live-streaming-started', handleStreamStarted); - callObject.on('live-streaming-stopped', handleStreamStopped); - callObject.on('live-streaming-error', handleStreamError); - - return () => { - callObject.off('live-streaming-started', handleStreamStarted); - callObject.off('live-streaming-stopped', handleStreamStopped); - callObject.on('live-streaming-error', handleStreamError); - }; - }, [callObject, handleStreamStarted, handleStreamStopped, handleStreamError]); - - return ( - - {children} - - ); -}; - -LiveStreamingProvider.propTypes = { - children: PropTypes.node, -}; - -export const useLiveStreaming = () => useContext(LiveStreamingContext); diff --git a/custom/shared/contexts/LiveStreamingProvider.js b/custom/shared/contexts/LiveStreamingProvider.js new file mode 100644 index 0000000..8e877a9 --- /dev/null +++ b/custom/shared/contexts/LiveStreamingProvider.js @@ -0,0 +1,65 @@ +import React, { + createContext, + useContext, + useCallback, +} from 'react'; +import { useLiveStreaming as useDailyLiveStreaming } from '@daily-co/daily-react-hooks'; +import PropTypes from 'prop-types'; +import { useUIState } from './UIStateProvider'; + +export const LiveStreamingContext = createContext(); + +export const LiveStreamingProvider = ({ children }) => { + const { setCustomCapsule } = useUIState(); + + const handleStreamStarted = useCallback(() => { + console.log('📺 Live stream started'); + setCustomCapsule({ variant: 'recording', label: 'Live streaming' }); + }, [setCustomCapsule]); + + const handleStreamStopped = useCallback(() => { + console.log('📺 Live stream stopped'); + setCustomCapsule(null); + }, [setCustomCapsule]); + + const handleStreamError = useCallback( + (e) => { + console.log('📺 Live stream error ' + e.errorMsg); + setCustomCapsule(null); + }, + [setCustomCapsule] + ); + + const { + isLiveStreaming, + layout, + errorMsg, + startLiveStreaming, + updateLiveStreaming, + stopLiveStreaming + } = useDailyLiveStreaming({ + onLiveStreamingStarted: handleStreamStarted, + onLiveStreamingStopped: handleStreamStopped, + onLiveStreamingError: handleStreamError, + }); + + return ( + + {children} + + ); +}; + +LiveStreamingProvider.propTypes = { + children: PropTypes.node, +}; + +export const useLiveStreaming = () => useContext(LiveStreamingContext); diff --git a/custom/shared/contexts/callState.js b/custom/shared/contexts/callState.js deleted file mode 100644 index 2ff9112..0000000 --- a/custom/shared/contexts/callState.js +++ /dev/null @@ -1,86 +0,0 @@ -/** - * Call State - * --- - * Duck file that keeps state of call participants - */ - -export const ACTION_PARTICIPANT_JOINED = 'ACTION_PARTICIPANT_JOINED'; -export const ACTION_PARTICIPANT_UPDATED = 'ACTION_PARTICIPANT_UPDATED'; -export const ACTION_PARTICIPANTED_LEFT = 'ACTION_PARTICIPANT_LEFT'; - -export const initialCallState = { - audioTracks: {}, - videoTracks: {}, - callItems: {}, - fatalError: false, -}; - -export function isLocal(id) { - return id === 'local'; -} - -function getCallItems(newParticipants, prevCallItems) { - const callItems = {}; - const entries = Object.entries(newParticipants); - entries.forEach(([id, participant]) => { - const prevState = prevCallItems[id]; - const hasLoaded = !prevState?.isLoading; - const missingTracks = !(participant.audioTrack || participant.videoTrack); - const joined = prevState?.joined || new Date().getTime() / 1000; - const local = isLocal(id); - - callItems[id] = { - id, - name: participant.user_name || 'Guest', - audioTrack: participant.audioTrack, - videoTrack: participant.videoTrack, - hasNameSet: !!participant.user_name, - isActiveSpeaker: !!prevState?.isActiveSpeaker, - isCamMuted: !participant.video, - isLoading: !hasLoaded && missingTracks, - isLocal: local, - isMicMuted: !participant.audio, - isOwner: !!participant.owner, - isRecording: !!participant.record, - lastActiveDate: prevState?.lastActiveDate ?? null, - mutedByHost: participant?.tracks?.audio?.off?.byRemoteRequest, - isScreenshare: false, - joined, - }; - - if (participant.screenVideoTrack || participant.screenAudioTrack) { - callItems[`${id}-screen`] = { - audioTrack: participant.tracks.screenAudio.persistentTrack, - hasNameSet: null, - id: `${id}-screen`, - isLoading: false, - isLocal: local, - isScreenshare: true, - lastActiveDate: prevState?.lastActiveDate ?? null, - name: participant.user_name, - videoTrack: participant.screenVideoTrack, - }; - } - }); - return callItems; -} - -export function isScreenShare(id) { - return id.endsWith('-screen'); -} - -export function containsScreenShare(participants) { - return Object.keys(participants).some((id) => isScreenShare(id)); -} - -export function callReducer(state, action) { - switch (action.type) { - case ACTION_PARTICIPANT_UPDATED: - return { - ...state, - callItems: getCallItems(action.participants, state.callItems), - }; - default: - throw new Error(); - } -} From 93a45bdf648dbbf44f8bf3b53ef913535d9d29a2 Mon Sep 17 00:00:00 2001 From: harshithpabbati Date: Wed, 6 Apr 2022 15:07:13 +0530 Subject: [PATCH 3/7] fix lint issue --- custom/fitness-demo/components/App/App.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom/fitness-demo/components/App/App.js b/custom/fitness-demo/components/App/App.js index 4c3170d..8bf5709 100644 --- a/custom/fitness-demo/components/App/App.js +++ b/custom/fitness-demo/components/App/App.js @@ -1,8 +1,8 @@ import React, { useMemo } from 'react'; -import { LiveStreamingProvider } from '@custom/shared/contexts/LiveStreamingProvider'; import { RecordingProvider } from '@custom/recording/contexts/RecordingProvider'; import ExpiryTimer from '@custom/shared/components/ExpiryTimer'; import { useCallState } from '@custom/shared/contexts/CallProvider'; +import { LiveStreamingProvider } from '@custom/shared/contexts/LiveStreamingProvider'; import { useCallUI } from '@custom/shared/hooks/useCallUI'; import PropTypes from 'prop-types'; From 4184c1fa094c797e94535b510da265ad8d63aded Mon Sep 17 00:00:00 2001 From: harshithpabbati Date: Wed, 6 Apr 2022 15:38:20 +0530 Subject: [PATCH 4/7] use useScreenShare hook --- custom/basic-call/pages/index.js | 5 ++- .../components/Tray/ScreenShare.js | 31 ++++++-------- custom/fitness-demo/pages/[room].js | 5 ++- custom/shared/contexts/ScreenShareProvider.js | 40 +++++++++++++++++++ 4 files changed, 60 insertions(+), 21 deletions(-) create mode 100644 custom/shared/contexts/ScreenShareProvider.js diff --git a/custom/basic-call/pages/index.js b/custom/basic-call/pages/index.js index 6ba92ec..9f64b27 100644 --- a/custom/basic-call/pages/index.js +++ b/custom/basic-call/pages/index.js @@ -2,6 +2,7 @@ import React, { useState, useCallback } from 'react'; import { CallProvider } from '@custom/shared/contexts/CallProvider'; import { MediaDeviceProvider } from '@custom/shared/contexts/MediaDeviceProvider'; import { ParticipantsProvider } from '@custom/shared/contexts/ParticipantsProvider'; +import { ScreenShareProvider } from '@custom/shared/contexts/ScreenShareProvider'; import { TracksProvider } from '@custom/shared/contexts/TracksProvider'; import { UIStateProvider } from '@custom/shared/contexts/UIStateProvider'; import { WaitingRoomProvider } from '@custom/shared/contexts/WaitingRoomProvider'; @@ -125,7 +126,9 @@ export default function Index({ - {customAppComponent || } + + {customAppComponent || } + diff --git a/custom/fitness-demo/components/Tray/ScreenShare.js b/custom/fitness-demo/components/Tray/ScreenShare.js index 6321f82..7079314 100644 --- a/custom/fitness-demo/components/Tray/ScreenShare.js +++ b/custom/fitness-demo/components/Tray/ScreenShare.js @@ -1,30 +1,23 @@ -import React, { useMemo } from 'react'; +import React from 'react'; import { TrayButton } from '@custom/shared/components/Tray'; import { useCallState } from '@custom/shared/contexts/CallProvider'; import { useParticipants } from '@custom/shared/contexts/ParticipantsProvider'; +import { useScreenShare } from '@custom/shared/contexts/ScreenShareProvider'; import { ReactComponent as IconShare } from '@custom/shared/icons/share-sm.svg'; -const MAX_SCREEN_SHARES = 2; - export const ScreenShareTray = () => { - const { callObject, enableScreenShare } = useCallState(); - const { screens, participants, localParticipant } = useParticipants(); - - const isSharingScreen = useMemo( - () => screens.some((s) => s.isLocal), - [screens] - ); - - const screensLength = useMemo(() => screens.length, [screens]); + const { enableScreenShare } = useCallState(); + const { localParticipant } = useParticipants(); + const { + isSharingScreen, + isDisabled, + startScreenShare, + stopScreenShare + } = useScreenShare(); const toggleScreenShare = () => - isSharingScreen ? callObject.stopScreenShare() : callObject.startScreenShare(); - - const disabled = - participants.length && - screensLength >= MAX_SCREEN_SHARES && - !isSharingScreen; + isSharingScreen ? stopScreenShare() : startScreenShare(); if (!enableScreenShare) return null; if (!localParticipant.isOwner) return null; @@ -33,7 +26,7 @@ export const ScreenShareTray = () => { diff --git a/custom/fitness-demo/pages/[room].js b/custom/fitness-demo/pages/[room].js index cc035bb..dfb0a8b 100644 --- a/custom/fitness-demo/pages/[room].js +++ b/custom/fitness-demo/pages/[room].js @@ -2,6 +2,7 @@ import React from 'react'; import { CallProvider } from '@custom/shared/contexts/CallProvider'; import { MediaDeviceProvider } from '@custom/shared/contexts/MediaDeviceProvider'; import { ParticipantsProvider } from '@custom/shared/contexts/ParticipantsProvider'; +import { ScreenShareProvider } from '@custom/shared/contexts/ScreenShareProvider'; import { TracksProvider } from '@custom/shared/contexts/TracksProvider'; import { UIStateProvider } from '@custom/shared/contexts/UIStateProvider'; import { WaitingRoomProvider } from '@custom/shared/contexts/WaitingRoomProvider'; @@ -40,7 +41,9 @@ const Room = ({ - {customAppComponent || } + + {customAppComponent || } + diff --git a/custom/shared/contexts/ScreenShareProvider.js b/custom/shared/contexts/ScreenShareProvider.js new file mode 100644 index 0000000..9b4e68c --- /dev/null +++ b/custom/shared/contexts/ScreenShareProvider.js @@ -0,0 +1,40 @@ +import React, { createContext, useContext, useMemo } from 'react'; +import { useScreenShare as useDailyScreenShare } from '@daily-co/daily-react-hooks'; +import PropTypes from 'prop-types'; + +export const MAX_SCREEN_SHARES = 2; + +const ScreenShareContext = createContext(null); + +export const ScreenShareProvider = ({ children }) => { + const { + isSharingScreen, + screens, + startScreenShare, + stopScreenShare + } = useDailyScreenShare(); + + const isDisabled = useMemo(() => screens.length >= MAX_SCREEN_SHARES && !isSharingScreen, + [isSharingScreen, screens.length] + ); + + return ( + + {children} + + ); +}; + +ScreenShareProvider.propTypes = { + children: PropTypes.node, +}; + +export const useScreenShare = () => useContext(ScreenShareContext); From 0d0a86fd60a4f935d7c83ca90ce80cec25e2c08f Mon Sep 17 00:00:00 2001 From: harshithpabbati Date: Thu, 7 Apr 2022 16:42:41 +0530 Subject: [PATCH 5/7] get local participant from the useDaily hook --- custom/shared/components/Tray/BasicTray.js | 17 +++-------------- custom/shared/contexts/MediaDeviceProvider.js | 5 +++-- 2 files changed, 6 insertions(+), 16 deletions(-) diff --git a/custom/shared/components/Tray/BasicTray.js b/custom/shared/components/Tray/BasicTray.js index 8bf3e77..fa89a20 100644 --- a/custom/shared/components/Tray/BasicTray.js +++ b/custom/shared/components/Tray/BasicTray.js @@ -1,4 +1,4 @@ -import React, { useRef, useState, useEffect, useMemo } from 'react'; +import React, { useRef, useState, useEffect } from 'react'; import { NETWORK_ASIDE } from '@custom/shared/components/Aside/NetworkAside'; import { PEOPLE_ASIDE } from '@custom/shared/components/Aside/PeopleAside'; import Button from '@custom/shared/components/Button'; @@ -15,7 +15,7 @@ import { ReactComponent as IconMore } from '@custom/shared/icons/more-md.svg'; import { ReactComponent as IconNetwork } from '@custom/shared/icons/network-md.svg'; import { ReactComponent as IconPeople } from '@custom/shared/icons/people-md.svg'; import { ReactComponent as IconSettings } from '@custom/shared/icons/settings-md.svg'; -import { useLocalParticipant, useDevices } from '@daily-co/daily-react-hooks'; +import { useMediaDevices } from '../../contexts/MediaDeviceProvider'; import { Tray, TrayButton } from './Tray'; export const BasicTray = () => { @@ -24,18 +24,7 @@ export const BasicTray = () => { const [showMore, setShowMore] = useState(false); const { callObject, leave } = useCallState(); const { customTrayComponent, openModal, toggleAside } = useUIState(); - const localParticipant = useLocalParticipant(); - const { hasCamError, hasMicError } = useDevices(); - - const isCamMuted = useMemo(() => { - const videoState = localParticipant?.tracks?.video?.state; - return videoState === 'off' || videoState === 'blocked' || hasCamError; - }, [hasCamError, localParticipant?.tracks?.video?.state]); - - const isMicMuted = useMemo(() => { - const audioState = localParticipant?.tracks?.audio?.state; - return audioState === 'off' || audioState === 'blocked' || hasMicError; - }, [hasMicError, localParticipant?.tracks?.audio?.state]); + const { isMicMuted, isCamMuted } = useMediaDevices(); const toggleCamera = (newState) => { if (!callObject) return false; diff --git a/custom/shared/contexts/MediaDeviceProvider.js b/custom/shared/contexts/MediaDeviceProvider.js index afe5a82..6230429 100644 --- a/custom/shared/contexts/MediaDeviceProvider.js +++ b/custom/shared/contexts/MediaDeviceProvider.js @@ -1,5 +1,5 @@ import React, { createContext, useContext, useMemo } from 'react'; -import { useDevices, useLocalParticipant } from '@daily-co/daily-react-hooks'; +import { useDaily, useDevices } from '@daily-co/daily-react-hooks'; import PropTypes from 'prop-types'; export const DEVICE_STATE_LOADING = 'loading'; @@ -31,7 +31,8 @@ export const MediaDeviceProvider = ({ children }) => { refreshDevices, } = useDevices(); - const localParticipant = useLocalParticipant(); + const daily = useDaily(); + const localParticipant = daily?.participants().local; const isCamMuted = useMemo(() => { const videoState = localParticipant?.tracks?.video?.state; From bc0911d2473f4fa7ab8d36f512724098c44d4d05 Mon Sep 17 00:00:00 2001 From: harshithpabbati Date: Fri, 8 Apr 2022 12:52:46 +0530 Subject: [PATCH 6/7] fix track subscription error --- custom/shared/contexts/TracksProvider.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/custom/shared/contexts/TracksProvider.js b/custom/shared/contexts/TracksProvider.js index d4f17e5..83b2068 100644 --- a/custom/shared/contexts/TracksProvider.js +++ b/custom/shared/contexts/TracksProvider.js @@ -32,7 +32,7 @@ const SUBSCRIBE_OR_STAGE_ALL_VIDEO_THRESHOLD = 9; const TracksContext = createContext(null); export const TracksProvider = ({ children }) => { - const { callObject: daily, optimizeLargeCalls } = useCallState(); + const { callObject: daily, optimizeLargeCalls, subscribeToTracksAutomatically } = useCallState(); const { participants } = useParticipants(); const { viewMode } = useUIState(); const [state, dispatch] = useReducer(tracksReducer, initialTracksState); @@ -327,7 +327,8 @@ export const TracksProvider = ({ children }) => { const joinedSubscriptionQueue = useRef([]); useEffect(() => { - if (!daily) return; + if (!daily || subscribeToTracksAutomatically) return; + const joinBatchInterval = setInterval(async () => { if (!joinedSubscriptionQueue.current.length) return; const ids = joinedSubscriptionQueue.current.splice(0); @@ -356,7 +357,7 @@ export const TracksProvider = ({ children }) => { return () => { clearInterval(joinBatchInterval); }; - }, [daily]); + }, [daily, subscribeToTracksAutomatically]); useEffect(() => { if (optimizeLargeCalls) { From 43f69601f29008340f45cd334a267102c637ab07 Mon Sep 17 00:00:00 2001 From: harshithpabbati Date: Fri, 8 Apr 2022 13:15:25 +0530 Subject: [PATCH 7/7] address suggestions --- custom/basic-call/components/App/App.js | 2 +- .../basic-call/components/Call/VideoGrid.js | 2 +- custom/fitness-demo/components/App/App.js | 2 +- custom/live-streaming/README.md | 8 +++++++- .../components/LiveStreamingModal.js | 19 +++++++++++++++---- .../shared/components/HairCheck/HairCheck.js | 2 +- .../shared/contexts/LiveStreamingProvider.js | 2 ++ 7 files changed, 28 insertions(+), 9 deletions(-) diff --git a/custom/basic-call/components/App/App.js b/custom/basic-call/components/App/App.js index 81b43d9..f648f33 100644 --- a/custom/basic-call/components/App/App.js +++ b/custom/basic-call/components/App/App.js @@ -17,7 +17,7 @@ export const App = ({ customComponentForState }) => { ...customComponentForState, }); - // Memoize children to avoid unnecassary renders from HOC + // Memoize children to avoid unnecessary renders from HOC return useMemo( () => ( <> diff --git a/custom/basic-call/components/Call/VideoGrid.js b/custom/basic-call/components/Call/VideoGrid.js index e71ff6d..a8ba799 100644 --- a/custom/basic-call/components/Call/VideoGrid.js +++ b/custom/basic-call/components/Call/VideoGrid.js @@ -89,7 +89,7 @@ export const VideoGrid = React.memo( return bestLayout; }, [dimensions, participants]); - // Memoize our tile list to avoid unnecassary re-renders + // Memoize our tile list to avoid unnecessary re-renders const tiles = useDeepCompareMemo( () => participants.map((p) => ( diff --git a/custom/fitness-demo/components/App/App.js b/custom/fitness-demo/components/App/App.js index 8bf5709..4f978b9 100644 --- a/custom/fitness-demo/components/App/App.js +++ b/custom/fitness-demo/components/App/App.js @@ -21,7 +21,7 @@ export const App = ({ customComponentForState }) => { ...customComponentForState, }); - // Memoize children to avoid unnecassary renders from HOC + // Memoize children to avoid unnecessary renders from HOC return useMemo( () => ( <> diff --git a/custom/live-streaming/README.md b/custom/live-streaming/README.md index e8da111..86031e7 100644 --- a/custom/live-streaming/README.md +++ b/custom/live-streaming/README.md @@ -18,7 +18,13 @@ Please note: this demo is not currently mobile optimised -### Getting started +## Pre-requisites + +To use this demo, you will need to create a [Daily account](https://dashboard.daily.co/signup) and a [Daily room](https://dashboard.daily.co/rooms/create). + +You will also need to enter an RTMP URL in the demo UI to start a live stream. To learn more about where to find this value, please read Daily's [live streaming guide](https://docs.daily.co/guides/paid-features/live-streaming-with-daily). You may also find the [live streaming with AWS's IVS tutorial](https://www.daily.co/blog/live-stream-daily-calls-with-only-3-second-latency/) helpful. + +## Getting started ``` # set both DAILY_API_KEY and DAILY_DOMAIN diff --git a/custom/live-streaming/components/LiveStreamingModal.js b/custom/live-streaming/components/LiveStreamingModal.js index f4cdee1..4047f0a 100644 --- a/custom/live-streaming/components/LiveStreamingModal.js +++ b/custom/live-streaming/components/LiveStreamingModal.js @@ -57,6 +57,11 @@ export const LiveStreamingModal = () => { stopLiveStreaming(); } + const handleRMTPURLChange = (e) => setRtmpUrl(e.target.value); + const handleSelectLayoutInputChange = (e) => setLayoutType(e.target.value); + const handleSelectParticipantInputChange = (e) => setParticipantId(e.target.value); + const handleSelectMaxCamsInputChange = (e) => setMaxCams(e.target.valueAsNumber); + return ( { setLayoutType(e.target.value)} + onChange={handleSelectLayoutInputChange} value={layoutType} > {LAYOUTS.map((l) => ( @@ -107,7 +112,7 @@ export const LiveStreamingModal = () => { {layoutType === 'default' && ( setMaxCams(Number(e.target.value))} + onChange={handleSelectMaxCamsInputChange} value={maxCams} > @@ -126,7 +131,7 @@ export const LiveStreamingModal = () => { {layoutType === 'single-participant' && ( setParticipantId(e.target.value)} + onChange={handleSelectParticipantInputChange} value={participantId} > diff --git a/custom/shared/components/HairCheck/HairCheck.js b/custom/shared/components/HairCheck/HairCheck.js index 4cf0659..8899c34 100644 --- a/custom/shared/components/HairCheck/HairCheck.js +++ b/custom/shared/components/HairCheck/HairCheck.js @@ -87,7 +87,7 @@ export const HairCheck = () => { } }; - // Memoize the to prevent unnecassary re-renders + // Memoize the to prevent unnecessary re-renders const tileMemo = useDeepCompareMemo( () => ( { + // setCustomCapsule allows us to set the recording capsule on the header + // to indicate that the recording is going on. const { setCustomCapsule } = useUIState(); const handleStreamStarted = useCallback(() => {