use useLiveStreaming hook

This commit is contained in:
harshithpabbati 2022-04-06 14:56:27 +05:30
parent ce280e2084
commit 1ddec5c968
8 changed files with 107 additions and 190 deletions

View File

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

View File

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

View File

@ -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 = () => (

View File

@ -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 = () => {
<Button
fullWidth
disabled={!rtmpUrl || pending}
onClick={() => startLiveStream()}
onClick={startLiveStream}
>
{pending ? 'Starting stream...' : 'Start live streaming'}
</Button>
@ -70,7 +78,7 @@ export const LiveStreamingModal = () => {
<Button
fullWidth
variant="warning"
onClick={() => stopLiveStreaming()}
onClick={stopLiveStream}
>
Stop live streaming
</Button>
@ -85,19 +93,18 @@ export const LiveStreamingModal = () => {
<CardBody>
<Field label="Layout">
<SelectInput
onChange={(e) => setLayout(Number(e.target.value))}
value={layout}
onChange={(e) => setLayoutType(e.target.value)}
value={layoutType}
>
{LAYOUTS.map((l, i) => (
<option value={i} key={l.value}>
{LAYOUTS.map((l) => (
<option value={l.value} key={l.value}>
{l.label}
</option>
))}
</SelectInput>
</Field>
{layout !==
LAYOUTS.findIndex((l) => l.value === 'single-participant') && (
{layoutType === 'default' && (
<Field label="Additional cameras">
<SelectInput
onChange={(e) => setMaxCams(Number(e.target.value))}
@ -116,15 +123,17 @@ export const LiveStreamingModal = () => {
</Field>
)}
{layout ===
LAYOUTS.findIndex((l) => l.value === 'single-participant') && (
{layoutType === 'single-participant' && (
<Field label="Select participant">
<SelectInput
onChange={(e) => setParticipant(e.target.value)}
value={participant}
onChange={(e) => setParticipantId(e.target.value)}
value={participantId}
>
{allParticipants.map((p) => (
<option value={p.id} key={p.id}>
<option value={0} disabled>
Select
</option>
{participants.map((p) => (
<option value={p.sessionId} key={p.sessionId}>
{p.name}
</option>
))}

View File

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

View File

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

View File

@ -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 (
<LiveStreamingContext.Provider
value={{
isStreaming: isLiveStreaming,
streamError: errorMsg,
layout,
startLiveStreaming,
updateLiveStreaming,
stopLiveStreaming,
}}>
{children}
</LiveStreamingContext.Provider>
);
};
LiveStreamingProvider.propTypes = {
children: PropTypes.node,
};
export const useLiveStreaming = () => useContext(LiveStreamingContext);

View File

@ -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();
}
}