Add live streaming option to the demo

This commit is contained in:
harshithpabbati 2021-12-30 14:29:39 +05:30
parent 99ea4f6504
commit 1358fd6142
6 changed files with 268 additions and 18 deletions

View File

@ -5,6 +5,7 @@ import { useCallUI } from '@custom/shared/hooks/useCallUI';
import PropTypes from 'prop-types';
import { ChatProvider } from '../../contexts/ChatProvider';
import { LiveStreamingProvider } from '../../contexts/LiveStreamingProvider';
import { RecordingProvider } from '../../contexts/RecordingProvider';
import Room from '../Call/Room';
import { Asides } from './Asides';
@ -25,23 +26,25 @@ export const App = ({ customComponentForState }) => {
<>
<ChatProvider>
<RecordingProvider>
{roomExp && <ExpiryTimer expiry={roomExp} />}
<div className="app">
{componentForState()}
<Modals />
<Asides />
<style jsx>{`
color: white;
height: 100vh;
display: flex;
align-items: center;
justify-content: center;
.loader {
margin: 0 auto;
}
`}</style>
</div>
<LiveStreamingProvider>
{roomExp && <ExpiryTimer expiry={roomExp} />}
<div className="app">
{componentForState()}
<Modals />
<Asides />
<style jsx>{`
color: white;
height: 100vh;
display: flex;
align-items: center;
justify-content: center;
.loader {
margin: 0 auto;
}
`}</style>
</div>
</LiveStreamingProvider>
</RecordingProvider>
</ChatProvider>
</>

View File

@ -0,0 +1,148 @@
import React, { useEffect, useState } from 'react';
import Button from '@custom/shared/components/Button';
import { CardBody } from '@custom/shared/components/Card';
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 { 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';
const LAYOUTS = [
{ label: 'Grid (default)', value: 'default' },
{ label: 'Single participant', value: 'single-participant' },
{ label: 'Active participant', value: 'active-participant' },
];
export const LiveStreamingModal = () => {
const { callObject } = useCallState();
const { allParticipants } = useParticipants();
const { currentModals, closeModal } = useUIState();
const { isStreaming, streamError } = useLiveStreaming();
const [pending, setPending] = useState(false);
const [rtmpUrl, setRtmpUrl] = useState('');
const [layout, setLayout] = useState(0);
const [maxCams, setMaxCams] = useState(9);
const [participant, setParticipant] = useState(0);
useEffect(() => {
// Reset pending state whenever stream state changes
setPending(false);
}, [isStreaming]);
function startLiveStream() {
setPending(true);
const opts =
layout === 'single-participant'
? { session_id: participant.id }
: { max_cam_streams: maxCams };
callObject.startLiveStreaming({ rtmpUrl, preset: layout, ...opts });
}
function stopLiveStreaming() {
setPending(true);
callObject.stopLiveStreaming();
}
return (
<Modal
title="Live stream"
isOpen={currentModals[LIVE_STREAMING_MODAL]}
onClose={() => closeModal(LIVE_STREAMING_MODAL)}
actions={[
<Button key="close" fullWidth variant="outline">
Close
</Button>,
!isStreaming ? (
<Button
fullWidth
disabled={!rtmpUrl || pending}
onClick={() => startLiveStream()}
>
{pending ? 'Starting stream...' : 'Start live streaming'}
</Button>
) : (
<Button
fullWidth
variant="warning"
onClick={() => stopLiveStreaming()}
>
Stop live streaming
</Button>
),
]}
>
{streamError && (
<Well variant="error">
Unable to start stream. Error message: {streamError}
</Well>
)}
<CardBody>
<Field label="Layout">
<SelectInput
onChange={(e) => setLayout(Number(e.target.value))}
value={layout}
>
{LAYOUTS.map((l, i) => (
<option value={i} key={l.value}>
{l.label}
</option>
))}
</SelectInput>
</Field>
{layout !==
LAYOUTS.findIndex((l) => l.value === 'single-participant') && (
<Field label="Additional cameras">
<SelectInput
onChange={(e) => setMaxCams(Number(e.target.value))}
value={maxCams}
>
<option value={9}>9 cameras</option>
<option value={8}>8 cameras</option>
<option value={7}>7 cameras</option>
<option value={6}>6 cameras</option>
<option value={5}>5 cameras</option>
<option value={4}>4 cameras</option>
<option value={3}>3 cameras</option>
<option value={2}>2 cameras</option>
<option value={1}>1 camera</option>
</SelectInput>
</Field>
)}
{layout ===
LAYOUTS.findIndex((l) => l.value === 'single-participant') && (
<Field label="Select participant">
<SelectInput
onChange={(e) => setParticipant(e.target.value)}
value={participant}
>
{allParticipants.map((p) => (
<option value={p.id} key={p.id}>
{p.name}
</option>
))}
</SelectInput>
</Field>
)}
<Field label="Enter RTMP endpoint">
<TextInput
type="text"
placeholder="RTMP URL"
required
onChange={(e) => setRtmpUrl(e.target.value)}
/>
</Field>
</CardBody>
</Modal>
);
};
export default LiveStreamingModal;

View File

@ -0,0 +1,25 @@
import React from 'react';
import { TrayButton } from '@custom/shared/components/Tray';
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 '../Modals/LiveStreamingModal';
export const Stream = () => {
const { openModal } = useUIState();
const { isStreaming } = useLiveStreaming();
return (
<TrayButton
label={isStreaming ? 'Live' : 'Stream'}
orange={isStreaming}
onClick={() => openModal(LIVE_STREAMING_MODAL)}
>
<IconStream />
</TrayButton>
);
};
export default Stream;

View File

@ -2,6 +2,7 @@ import React from 'react';
import ChatTray from './Chat';
import RecordTray from './Record';
import ScreenShareTray from './ScreenShare';
import StreamTray from './Stream';
export const Tray = () => {
return (
@ -9,6 +10,7 @@ export const Tray = () => {
<ChatTray />
<ScreenShareTray />
<RecordTray />
<StreamTray />
</>
);
};

View File

@ -0,0 +1,71 @@
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

@ -4,6 +4,7 @@ import Head from 'next/head';
import PropTypes from 'prop-types';
import { App as CustomApp } from '../components/App/App';
import ChatAside from '../components/Call/ChatAside';
import LiveStreamingModal from '../components/Modals/LiveStreamingModal';
import RecordingModal from '../components/Modals/RecordingModal';
import Tray from '../components/Tray';
@ -36,7 +37,7 @@ App.propTypes = {
};
App.asides = [ChatAside];
App.modals = [RecordingModal];
App.modals = [RecordingModal, LiveStreamingModal];
App.customTrayComponent = <Tray />;
App.customAppComponent = <CustomApp />;