Merge pull request #78 from daily-demos/use-live-streaming
use useLiveStreaming hook - Daily react hooks
This commit is contained in:
commit
3e1b2638fb
|
|
@ -17,7 +17,7 @@ export const App = ({ customComponentForState }) => {
|
||||||
...customComponentForState,
|
...customComponentForState,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Memoize children to avoid unnecassary renders from HOC
|
// Memoize children to avoid unnecessary renders from HOC
|
||||||
return useMemo(
|
return useMemo(
|
||||||
() => (
|
() => (
|
||||||
<>
|
<>
|
||||||
|
|
|
||||||
|
|
@ -89,7 +89,7 @@ export const VideoGrid = React.memo(
|
||||||
return bestLayout;
|
return bestLayout;
|
||||||
}, [dimensions, participants]);
|
}, [dimensions, participants]);
|
||||||
|
|
||||||
// Memoize our tile list to avoid unnecassary re-renders
|
// Memoize our tile list to avoid unnecessary re-renders
|
||||||
const tiles = useDeepCompareMemo(
|
const tiles = useDeepCompareMemo(
|
||||||
() =>
|
() =>
|
||||||
participants.map((p) => (
|
participants.map((p) => (
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
import React, { useMemo } from 'react';
|
import React, { useMemo } from 'react';
|
||||||
import { LiveStreamingProvider } from '@custom/live-streaming/contexts/LiveStreamingProvider';
|
|
||||||
import { RecordingProvider } from '@custom/recording/contexts/RecordingProvider';
|
import { RecordingProvider } from '@custom/recording/contexts/RecordingProvider';
|
||||||
import ExpiryTimer from '@custom/shared/components/ExpiryTimer';
|
import ExpiryTimer from '@custom/shared/components/ExpiryTimer';
|
||||||
import { useCallState } from '@custom/shared/contexts/CallProvider';
|
import { useCallState } from '@custom/shared/contexts/CallProvider';
|
||||||
|
import { LiveStreamingProvider } from '@custom/shared/contexts/LiveStreamingProvider';
|
||||||
import { useCallUI } from '@custom/shared/hooks/useCallUI';
|
import { useCallUI } from '@custom/shared/hooks/useCallUI';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
|
@ -21,7 +21,7 @@ export const App = ({ customComponentForState }) => {
|
||||||
...customComponentForState,
|
...customComponentForState,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Memoize children to avoid unnecassary renders from HOC
|
// Memoize children to avoid unnecessary renders from HOC
|
||||||
return useMemo(
|
return useMemo(
|
||||||
() => (
|
() => (
|
||||||
<>
|
<>
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { LIVE_STREAMING_MODAL } from '@custom/live-streaming/components/LiveStreamingModal';
|
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 { TrayButton } from '@custom/shared/components/Tray';
|
||||||
|
import { useLiveStreaming } from '@custom/shared/contexts/LiveStreamingProvider';
|
||||||
import { useParticipants } from '@custom/shared/contexts/ParticipantsProvider';
|
import { useParticipants } from '@custom/shared/contexts/ParticipantsProvider';
|
||||||
import { useUIState } from '@custom/shared/contexts/UIStateProvider';
|
import { useUIState } from '@custom/shared/contexts/UIStateProvider';
|
||||||
import { ReactComponent as IconStream } from '@custom/shared/icons/streaming-md.svg';
|
import { ReactComponent as IconStream } from '@custom/shared/icons/streaming-md.svg';
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,13 @@
|
||||||
|
|
||||||
Please note: this demo is not currently mobile optimised
|
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
|
# set both DAILY_API_KEY and DAILY_DOMAIN
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import App from '@custom/basic-call/components/App';
|
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
|
// Extend our basic call app component with the live streaming context
|
||||||
export const AppWithLiveStreaming = () => (
|
export const AppWithLiveStreaming = () => (
|
||||||
|
|
|
||||||
|
|
@ -5,10 +5,9 @@ import Field from '@custom/shared/components/Field';
|
||||||
import { TextInput, SelectInput } from '@custom/shared/components/Input';
|
import { TextInput, SelectInput } from '@custom/shared/components/Input';
|
||||||
import Modal from '@custom/shared/components/Modal';
|
import Modal from '@custom/shared/components/Modal';
|
||||||
import Well from '@custom/shared/components/Well';
|
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 { useParticipants } from '@custom/shared/contexts/ParticipantsProvider';
|
||||||
import { useUIState } from '@custom/shared/contexts/UIStateProvider';
|
import { useUIState } from '@custom/shared/contexts/UIStateProvider';
|
||||||
import { useLiveStreaming } from '../contexts/LiveStreamingProvider';
|
|
||||||
|
|
||||||
export const LIVE_STREAMING_MODAL = 'live-streaming';
|
export const LIVE_STREAMING_MODAL = 'live-streaming';
|
||||||
|
|
||||||
|
|
@ -19,15 +18,19 @@ const LAYOUTS = [
|
||||||
];
|
];
|
||||||
|
|
||||||
export const LiveStreamingModal = () => {
|
export const LiveStreamingModal = () => {
|
||||||
const { callObject } = useCallState();
|
const { participants } = useParticipants();
|
||||||
const { allParticipants } = useParticipants();
|
|
||||||
const { currentModals, closeModal } = useUIState();
|
const { currentModals, closeModal } = useUIState();
|
||||||
const { isStreaming, streamError } = useLiveStreaming();
|
const {
|
||||||
|
isStreaming,
|
||||||
|
streamError,
|
||||||
|
startLiveStreaming,
|
||||||
|
stopLiveStreaming,
|
||||||
|
} = useLiveStreaming();
|
||||||
const [pending, setPending] = useState(false);
|
const [pending, setPending] = useState(false);
|
||||||
const [rtmpUrl, setRtmpUrl] = useState('');
|
const [rtmpUrl, setRtmpUrl] = useState('');
|
||||||
const [layout, setLayout] = useState(0);
|
const [layoutType, setLayoutType] = useState('default');
|
||||||
const [maxCams, setMaxCams] = useState(9);
|
const [maxCams, setMaxCams] = useState(9);
|
||||||
const [participant, setParticipant] = useState(0);
|
const [participantId, setParticipantId] = useState(0);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Reset pending state whenever stream state changes
|
// Reset pending state whenever stream state changes
|
||||||
|
|
@ -35,20 +38,30 @@ export const LiveStreamingModal = () => {
|
||||||
}, [isStreaming]);
|
}, [isStreaming]);
|
||||||
|
|
||||||
function startLiveStream() {
|
function startLiveStream() {
|
||||||
setPending(true);
|
const config = {
|
||||||
|
rtmpUrl,
|
||||||
|
layout: {
|
||||||
|
preset: layoutType,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
const opts =
|
if (layoutType === 'single-participant')
|
||||||
layout === 'single-participant'
|
config.layout.session_id = participantId;
|
||||||
? { session_id: participant.id }
|
else if (layoutType === 'default') config.layout.max_cam_streams = maxCams;
|
||||||
: { max_cam_streams: maxCams };
|
|
||||||
callObject.startLiveStreaming({ rtmpUrl, preset: layout, ...opts });
|
startLiveStreaming(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
function stopLiveStreaming() {
|
function stopLiveStream() {
|
||||||
setPending(true);
|
setPending(true);
|
||||||
callObject.stopLiveStreaming();
|
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 (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
title="Live stream"
|
title="Live stream"
|
||||||
|
|
@ -62,7 +75,7 @@ export const LiveStreamingModal = () => {
|
||||||
<Button
|
<Button
|
||||||
fullWidth
|
fullWidth
|
||||||
disabled={!rtmpUrl || pending}
|
disabled={!rtmpUrl || pending}
|
||||||
onClick={() => startLiveStream()}
|
onClick={startLiveStream}
|
||||||
>
|
>
|
||||||
{pending ? 'Starting stream...' : 'Start live streaming'}
|
{pending ? 'Starting stream...' : 'Start live streaming'}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
@ -70,7 +83,7 @@ export const LiveStreamingModal = () => {
|
||||||
<Button
|
<Button
|
||||||
fullWidth
|
fullWidth
|
||||||
variant="warning"
|
variant="warning"
|
||||||
onClick={() => stopLiveStreaming()}
|
onClick={stopLiveStream}
|
||||||
>
|
>
|
||||||
Stop live streaming
|
Stop live streaming
|
||||||
</Button>
|
</Button>
|
||||||
|
|
@ -85,22 +98,21 @@ export const LiveStreamingModal = () => {
|
||||||
<CardBody>
|
<CardBody>
|
||||||
<Field label="Layout">
|
<Field label="Layout">
|
||||||
<SelectInput
|
<SelectInput
|
||||||
onChange={(e) => setLayout(Number(e.target.value))}
|
onChange={handleSelectLayoutInputChange}
|
||||||
value={layout}
|
value={layoutType}
|
||||||
>
|
>
|
||||||
{LAYOUTS.map((l, i) => (
|
{LAYOUTS.map((l) => (
|
||||||
<option value={i} key={l.value}>
|
<option value={l.value} key={l.value}>
|
||||||
{l.label}
|
{l.label}
|
||||||
</option>
|
</option>
|
||||||
))}
|
))}
|
||||||
</SelectInput>
|
</SelectInput>
|
||||||
</Field>
|
</Field>
|
||||||
|
|
||||||
{layout !==
|
{layoutType === 'default' && (
|
||||||
LAYOUTS.findIndex((l) => l.value === 'single-participant') && (
|
|
||||||
<Field label="Additional cameras">
|
<Field label="Additional cameras">
|
||||||
<SelectInput
|
<SelectInput
|
||||||
onChange={(e) => setMaxCams(Number(e.target.value))}
|
onChange={handleSelectMaxCamsInputChange}
|
||||||
value={maxCams}
|
value={maxCams}
|
||||||
>
|
>
|
||||||
<option value={9}>9 cameras</option>
|
<option value={9}>9 cameras</option>
|
||||||
|
|
@ -116,15 +128,17 @@ export const LiveStreamingModal = () => {
|
||||||
</Field>
|
</Field>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{layout ===
|
{layoutType === 'single-participant' && (
|
||||||
LAYOUTS.findIndex((l) => l.value === 'single-participant') && (
|
|
||||||
<Field label="Select participant">
|
<Field label="Select participant">
|
||||||
<SelectInput
|
<SelectInput
|
||||||
onChange={(e) => setParticipant(e.target.value)}
|
onChange={handleSelectParticipantInputChange}
|
||||||
value={participant}
|
value={participantId}
|
||||||
>
|
>
|
||||||
{allParticipants.map((p) => (
|
<option value={0} disabled>
|
||||||
<option value={p.id} key={p.id}>
|
Select
|
||||||
|
</option>
|
||||||
|
{participants.map((p) => (
|
||||||
|
<option value={p.sessionId} key={p.sessionId}>
|
||||||
{p.name}
|
{p.name}
|
||||||
</option>
|
</option>
|
||||||
))}
|
))}
|
||||||
|
|
@ -137,8 +151,14 @@ export const LiveStreamingModal = () => {
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="RTMP URL"
|
placeholder="RTMP URL"
|
||||||
required
|
required
|
||||||
onChange={(e) => setRtmpUrl(e.target.value)}
|
onChange={handleRMTPURLChange}
|
||||||
/>
|
/>
|
||||||
|
<a
|
||||||
|
className="learn-more"
|
||||||
|
href="https://docs.daily.co/guides/paid-features/live-streaming-with-daily"
|
||||||
|
>
|
||||||
|
Want to learn more about RTMP url?
|
||||||
|
</a>
|
||||||
</Field>
|
</Field>
|
||||||
</CardBody>
|
</CardBody>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { TrayButton } from '@custom/shared/components/Tray';
|
import { TrayButton } from '@custom/shared/components/Tray';
|
||||||
|
import { useLiveStreaming } from '@custom/shared/contexts/LiveStreamingProvider';
|
||||||
import { useUIState } from '@custom/shared/contexts/UIStateProvider';
|
import { useUIState } from '@custom/shared/contexts/UIStateProvider';
|
||||||
import { ReactComponent as IconStream } from '@custom/shared/icons/streaming-md.svg';
|
import { ReactComponent as IconStream } from '@custom/shared/icons/streaming-md.svg';
|
||||||
|
|
||||||
import { useLiveStreaming } from '../contexts/LiveStreamingProvider';
|
|
||||||
import { LIVE_STREAMING_MODAL } from './LiveStreamingModal';
|
import { LIVE_STREAMING_MODAL } from './LiveStreamingModal';
|
||||||
|
|
||||||
export const Tray = () => {
|
export const Tray = () => {
|
||||||
|
|
|
||||||
|
|
@ -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 (
|
|
||||||
<LiveStreamingContext.Provider value={{ isStreaming, streamError }}>
|
|
||||||
{children}
|
|
||||||
</LiveStreamingContext.Provider>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
LiveStreamingProvider.propTypes = {
|
|
||||||
children: PropTypes.node,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useLiveStreaming = () => useContext(LiveStreamingContext);
|
|
||||||
|
|
@ -86,7 +86,7 @@ export const HairCheck = () => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Memoize the to prevent unnecassary re-renders
|
// Memoize the to prevent unnecessary re-renders
|
||||||
const tileMemo = useDeepCompareMemo(
|
const tileMemo = useDeepCompareMemo(
|
||||||
() => (
|
() => (
|
||||||
<Tile
|
<Tile
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,67 @@
|
||||||
|
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 }) => {
|
||||||
|
// 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(() => {
|
||||||
|
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 (
|
||||||
|
<LiveStreamingContext.Provider
|
||||||
|
value={{
|
||||||
|
isStreaming: isLiveStreaming,
|
||||||
|
streamError: errorMsg,
|
||||||
|
layout,
|
||||||
|
startLiveStreaming,
|
||||||
|
updateLiveStreaming,
|
||||||
|
stopLiveStreaming,
|
||||||
|
}}>
|
||||||
|
{children}
|
||||||
|
</LiveStreamingContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
LiveStreamingProvider.propTypes = {
|
||||||
|
children: PropTypes.node,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useLiveStreaming = () => useContext(LiveStreamingContext);
|
||||||
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loading…
Reference in New Issue