Add recording option to the demo
This commit is contained in:
parent
22b4afbb34
commit
99ea4f6504
|
|
@ -5,6 +5,7 @@ import { useCallUI } from '@custom/shared/hooks/useCallUI';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import { ChatProvider } from '../../contexts/ChatProvider';
|
import { ChatProvider } from '../../contexts/ChatProvider';
|
||||||
|
import { RecordingProvider } from '../../contexts/RecordingProvider';
|
||||||
import Room from '../Call/Room';
|
import Room from '../Call/Room';
|
||||||
import { Asides } from './Asides';
|
import { Asides } from './Asides';
|
||||||
import { Modals } from './Modals';
|
import { Modals } from './Modals';
|
||||||
|
|
@ -23,23 +24,25 @@ export const App = ({ customComponentForState }) => {
|
||||||
() => (
|
() => (
|
||||||
<>
|
<>
|
||||||
<ChatProvider>
|
<ChatProvider>
|
||||||
{roomExp && <ExpiryTimer expiry={roomExp} />}
|
<RecordingProvider>
|
||||||
<div className="app">
|
{roomExp && <ExpiryTimer expiry={roomExp} />}
|
||||||
{componentForState()}
|
<div className="app">
|
||||||
<Modals />
|
{componentForState()}
|
||||||
<Asides />
|
<Modals />
|
||||||
<style jsx>{`
|
<Asides />
|
||||||
color: white;
|
<style jsx>{`
|
||||||
height: 100vh;
|
color: white;
|
||||||
display: flex;
|
height: 100vh;
|
||||||
align-items: center;
|
display: flex;
|
||||||
justify-content: center;
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
.loader {
|
.loader {
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
}
|
}
|
||||||
`}</style>
|
`}</style>
|
||||||
</div>
|
</div>
|
||||||
|
</RecordingProvider>
|
||||||
</ChatProvider>
|
</ChatProvider>
|
||||||
</>
|
</>
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,145 @@
|
||||||
|
import React, { useEffect } from 'react';
|
||||||
|
import Button from '@custom/shared/components/Button';
|
||||||
|
import { CardBody } from '@custom/shared/components/Card';
|
||||||
|
import Modal from '@custom/shared/components/Modal';
|
||||||
|
import Well from '@custom/shared/components/Well';
|
||||||
|
import { useCallState } from '@custom/shared/contexts/CallProvider';
|
||||||
|
import { useUIState } from '@custom/shared/contexts/UIStateProvider';
|
||||||
|
import {
|
||||||
|
RECORDING_COUNTDOWN_1,
|
||||||
|
RECORDING_COUNTDOWN_2,
|
||||||
|
RECORDING_COUNTDOWN_3,
|
||||||
|
RECORDING_IDLE,
|
||||||
|
RECORDING_RECORDING,
|
||||||
|
RECORDING_SAVED,
|
||||||
|
RECORDING_TYPE_CLOUD,
|
||||||
|
RECORDING_TYPE_CLOUD_BETA,
|
||||||
|
RECORDING_TYPE_RTP_TRACKS,
|
||||||
|
RECORDING_UPLOADING,
|
||||||
|
useRecording,
|
||||||
|
} from '../../contexts/RecordingProvider';
|
||||||
|
|
||||||
|
export const RECORDING_MODAL = 'recording';
|
||||||
|
|
||||||
|
export const RecordingModal = () => {
|
||||||
|
const { currentModals, closeModal } = useUIState();
|
||||||
|
const { enableRecording } = useCallState();
|
||||||
|
const {
|
||||||
|
recordingStartedDate,
|
||||||
|
recordingState,
|
||||||
|
startRecordingWithCountdown,
|
||||||
|
stopRecording,
|
||||||
|
} = useRecording();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (recordingState === RECORDING_RECORDING) {
|
||||||
|
closeModal(RECORDING_MODAL);
|
||||||
|
}
|
||||||
|
}, [recordingState, closeModal]);
|
||||||
|
|
||||||
|
const disabled =
|
||||||
|
enableRecording &&
|
||||||
|
[RECORDING_IDLE, RECORDING_RECORDING].includes(recordingState);
|
||||||
|
|
||||||
|
function renderButtonLabel() {
|
||||||
|
if (!enableRecording) {
|
||||||
|
return 'Recording disabled';
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (recordingState) {
|
||||||
|
case RECORDING_COUNTDOWN_3:
|
||||||
|
return '3...';
|
||||||
|
case RECORDING_COUNTDOWN_2:
|
||||||
|
return '2...';
|
||||||
|
case RECORDING_COUNTDOWN_1:
|
||||||
|
return '1...';
|
||||||
|
case RECORDING_RECORDING:
|
||||||
|
return 'Stop recording';
|
||||||
|
case RECORDING_UPLOADING:
|
||||||
|
case RECORDING_SAVED:
|
||||||
|
return 'Stopping recording...';
|
||||||
|
default:
|
||||||
|
return 'Start recording';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleRecordingClick() {
|
||||||
|
if (recordingState === RECORDING_IDLE) {
|
||||||
|
startRecordingWithCountdown();
|
||||||
|
} else {
|
||||||
|
stopRecording();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
title="Recording"
|
||||||
|
isOpen={currentModals[RECORDING_MODAL]}
|
||||||
|
onClose={() => closeModal(RECORDING_MODAL)}
|
||||||
|
actions={[
|
||||||
|
<Button key="close" fullWidth variant="outline">
|
||||||
|
Close
|
||||||
|
</Button>,
|
||||||
|
<Button
|
||||||
|
fullWidth
|
||||||
|
disabled={!disabled}
|
||||||
|
key="record"
|
||||||
|
onClick={() => handleRecordingClick()}
|
||||||
|
>
|
||||||
|
{renderButtonLabel()}
|
||||||
|
</Button>,
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<CardBody>
|
||||||
|
{!enableRecording ? (
|
||||||
|
<Well variant="error">
|
||||||
|
Recording is not enabled for this room (or your browser does not
|
||||||
|
support it.) Please enable recording when creating the room or via
|
||||||
|
the Daily dashboard.
|
||||||
|
</Well>
|
||||||
|
) : (
|
||||||
|
<p>
|
||||||
|
Recording type enabled: <strong>{enableRecording}</strong>
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{recordingStartedDate && (
|
||||||
|
<p>Recording started: {recordingStartedDate.toString()}</p>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{[RECORDING_TYPE_CLOUD, RECORDING_TYPE_CLOUD_BETA].includes(
|
||||||
|
enableRecording
|
||||||
|
) && (
|
||||||
|
<>
|
||||||
|
<hr />
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Cloud recordings can be accessed via the Daily dashboard under the
|
||||||
|
"Recordings" section.
|
||||||
|
</p>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{enableRecording === RECORDING_TYPE_RTP_TRACKS && (
|
||||||
|
<>
|
||||||
|
<hr />
|
||||||
|
|
||||||
|
<p>
|
||||||
|
rtp-tracks recordings can be accessed via the Daily API. See the{' '}
|
||||||
|
<a
|
||||||
|
href="https://docs.daily.co/guides/recording-calls-with-the-daily-api#retrieve-individual-tracks-from-rtp-tracks-recordings"
|
||||||
|
noreferrer
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
>
|
||||||
|
Daily recording guide
|
||||||
|
</a>{' '}
|
||||||
|
for details.
|
||||||
|
</p>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</CardBody>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default RecordingModal;
|
||||||
|
|
@ -0,0 +1,45 @@
|
||||||
|
import React, { useEffect } from 'react';
|
||||||
|
|
||||||
|
import { TrayButton } from '@custom/shared/components/Tray';
|
||||||
|
import { useUIState } from '@custom/shared/contexts/UIStateProvider';
|
||||||
|
import { ReactComponent as IconRecord } from '@custom/shared/icons/record-md.svg';
|
||||||
|
|
||||||
|
import {
|
||||||
|
RECORDING_ERROR,
|
||||||
|
RECORDING_RECORDING,
|
||||||
|
RECORDING_SAVED,
|
||||||
|
RECORDING_UPLOADING,
|
||||||
|
useRecording,
|
||||||
|
} from '../../contexts/RecordingProvider';
|
||||||
|
import { RECORDING_MODAL } from '../Modals/RecordingModal';
|
||||||
|
|
||||||
|
export const Tray = () => {
|
||||||
|
const { openModal } = useUIState();
|
||||||
|
const { recordingState } = useRecording();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
console.log(`⏺️ Recording state: ${recordingState}`);
|
||||||
|
|
||||||
|
if (recordingState === RECORDING_ERROR) {
|
||||||
|
// show error modal here
|
||||||
|
}
|
||||||
|
}, [recordingState]);
|
||||||
|
|
||||||
|
const isRecording = [
|
||||||
|
RECORDING_RECORDING,
|
||||||
|
RECORDING_UPLOADING,
|
||||||
|
RECORDING_SAVED,
|
||||||
|
].includes(recordingState);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TrayButton
|
||||||
|
label={isRecording ? 'Stop' : 'Record'}
|
||||||
|
orange={isRecording}
|
||||||
|
onClick={() => openModal(RECORDING_MODAL)}
|
||||||
|
>
|
||||||
|
<IconRecord />
|
||||||
|
</TrayButton>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Tray;
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ChatTray from './Chat';
|
import ChatTray from './Chat';
|
||||||
|
import RecordTray from './Record';
|
||||||
import ScreenShareTray from './ScreenShare';
|
import ScreenShareTray from './ScreenShare';
|
||||||
|
|
||||||
export const Tray = () => {
|
export const Tray = () => {
|
||||||
|
|
@ -7,6 +8,7 @@ export const Tray = () => {
|
||||||
<>
|
<>
|
||||||
<ChatTray />
|
<ChatTray />
|
||||||
<ScreenShareTray />
|
<ScreenShareTray />
|
||||||
|
<RecordTray />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,337 @@
|
||||||
|
import React, {
|
||||||
|
createContext,
|
||||||
|
useCallback,
|
||||||
|
useContext,
|
||||||
|
useEffect,
|
||||||
|
useState,
|
||||||
|
} from 'react';
|
||||||
|
|
||||||
|
import { useCallState } from '@custom/shared/contexts/CallProvider';
|
||||||
|
import { useParticipants } from '@custom/shared/contexts/ParticipantsProvider';
|
||||||
|
import { useUIState } from '@custom/shared/contexts/UIStateProvider';
|
||||||
|
import {
|
||||||
|
CALL_STATE_REDIRECTING,
|
||||||
|
CALL_STATE_JOINED,
|
||||||
|
} from '@custom/shared/contexts/useCallMachine';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { useDeepCompareEffect } from 'use-deep-compare';
|
||||||
|
|
||||||
|
export const RECORDING_ERROR = 'error';
|
||||||
|
export const RECORDING_SAVED = 'saved';
|
||||||
|
export const RECORDING_RECORDING = 'recording';
|
||||||
|
export const RECORDING_UPLOADING = 'uploading';
|
||||||
|
export const RECORDING_COUNTDOWN_1 = 'starting1';
|
||||||
|
export const RECORDING_COUNTDOWN_2 = 'starting2';
|
||||||
|
export const RECORDING_COUNTDOWN_3 = 'starting3';
|
||||||
|
export const RECORDING_IDLE = 'idle';
|
||||||
|
|
||||||
|
export const RECORDING_TYPE_CLOUD = 'cloud';
|
||||||
|
export const RECORDING_TYPE_CLOUD_BETA = 'cloud-beta';
|
||||||
|
export const RECORDING_TYPE_LOCAL = 'local';
|
||||||
|
export const RECORDING_TYPE_OUTPUT_BYTE_STREAM = 'output-byte-stream';
|
||||||
|
export const RECORDING_TYPE_RTP_TRACKS = 'rtp-tracks';
|
||||||
|
|
||||||
|
const RecordingContext = createContext({
|
||||||
|
isRecordingLocally: false,
|
||||||
|
recordingStartedDate: null,
|
||||||
|
recordingState: RECORDING_IDLE,
|
||||||
|
startRecording: null,
|
||||||
|
stopRecording: null,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const RecordingProvider = ({ children }) => {
|
||||||
|
const {
|
||||||
|
callObject,
|
||||||
|
enableRecording,
|
||||||
|
startCloudRecording,
|
||||||
|
state,
|
||||||
|
} = useCallState();
|
||||||
|
const { participants } = useParticipants();
|
||||||
|
const [recordingStartedDate, setRecordingStartedDate] = useState(null);
|
||||||
|
const [recordingState, setRecordingState] = useState(RECORDING_IDLE);
|
||||||
|
const [isRecordingLocally, setIsRecordingLocally] = useState(false);
|
||||||
|
const [hasRecordingStarted, setHasRecordingStarted] = useState(false);
|
||||||
|
const { setCustomCapsule } = useUIState();
|
||||||
|
|
||||||
|
const handleOnUnload = useCallback(
|
||||||
|
() => 'Unsaved recording in progress. Do you really want to leave?',
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (
|
||||||
|
!enableRecording ||
|
||||||
|
!isRecordingLocally ||
|
||||||
|
recordingState !== RECORDING_RECORDING ||
|
||||||
|
state === CALL_STATE_REDIRECTING
|
||||||
|
)
|
||||||
|
return false;
|
||||||
|
const prev = window.onbeforeunload;
|
||||||
|
window.onbeforeunload = handleOnUnload;
|
||||||
|
return () => {
|
||||||
|
window.onbeforeunload = prev;
|
||||||
|
};
|
||||||
|
}, [
|
||||||
|
enableRecording,
|
||||||
|
handleOnUnload,
|
||||||
|
recordingState,
|
||||||
|
isRecordingLocally,
|
||||||
|
state,
|
||||||
|
]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!callObject || !enableRecording) return false;
|
||||||
|
|
||||||
|
const handleAppMessage = (ev) => {
|
||||||
|
switch (ev?.data?.event) {
|
||||||
|
case 'recording-starting':
|
||||||
|
setRecordingState(RECORDING_COUNTDOWN_3);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// The 'recording-data' event is emitted when an output-byte-stream recording has started
|
||||||
|
// When the event emits, start writing data to the stream created in handleRecordingStarted()
|
||||||
|
const handleRecordingData = async (ev) => {
|
||||||
|
try {
|
||||||
|
console.log('got data', ev);
|
||||||
|
await window.writer.write(ev.data);
|
||||||
|
if (ev.finished) {
|
||||||
|
console.log('closing!');
|
||||||
|
window.writer.close();
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
callObject.on('app-message', handleAppMessage);
|
||||||
|
callObject.on('recording-data', handleRecordingData);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
callObject.off('app-message', handleAppMessage);
|
||||||
|
callObject.off('recording-data', handleRecordingData);
|
||||||
|
};
|
||||||
|
}, [callObject, enableRecording]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Automatically start cloud recording, if startCloudRecording is set.
|
||||||
|
*/
|
||||||
|
useEffect(() => {
|
||||||
|
if (
|
||||||
|
hasRecordingStarted ||
|
||||||
|
!callObject ||
|
||||||
|
!startCloudRecording ||
|
||||||
|
enableRecording !== 'cloud' ||
|
||||||
|
state !== CALL_STATE_JOINED
|
||||||
|
)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Small timeout, in case other participants are already in-call.
|
||||||
|
const timeout = setTimeout(() => {
|
||||||
|
const isSomebodyRecording = participants.some((p) => p.isRecording);
|
||||||
|
if (!isSomebodyRecording) {
|
||||||
|
callObject.startRecording();
|
||||||
|
setIsRecordingLocally(true);
|
||||||
|
setHasRecordingStarted(true);
|
||||||
|
} else {
|
||||||
|
setHasRecordingStarted(true);
|
||||||
|
}
|
||||||
|
}, 500);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
clearTimeout(timeout);
|
||||||
|
};
|
||||||
|
}, [
|
||||||
|
callObject,
|
||||||
|
enableRecording,
|
||||||
|
hasRecordingStarted,
|
||||||
|
participants,
|
||||||
|
startCloudRecording,
|
||||||
|
state,
|
||||||
|
]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle participant updates to sync recording state.
|
||||||
|
*/
|
||||||
|
useDeepCompareEffect(() => {
|
||||||
|
if (isRecordingLocally || recordingState === RECORDING_SAVED) return;
|
||||||
|
if (participants.some(({ isRecording }) => isRecording)) {
|
||||||
|
setRecordingState(RECORDING_RECORDING);
|
||||||
|
} else {
|
||||||
|
setRecordingState(RECORDING_IDLE);
|
||||||
|
}
|
||||||
|
}, [isRecordingLocally, participants, recordingState]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle recording started.
|
||||||
|
*/
|
||||||
|
const handleRecordingStarted = useCallback(
|
||||||
|
(event) => {
|
||||||
|
console.log('RECORDING');
|
||||||
|
console.log(event);
|
||||||
|
|
||||||
|
if (recordingState === RECORDING_RECORDING) return;
|
||||||
|
setRecordingState(RECORDING_RECORDING);
|
||||||
|
if (event.local) {
|
||||||
|
// Recording started locally, either through UI or programmatically
|
||||||
|
setIsRecordingLocally(true);
|
||||||
|
if (!recordingStartedDate) setRecordingStartedDate(new Date());
|
||||||
|
// If an output-byte-stream recording has started, create a new data stream that can be piped to a third-party (in this case a file)
|
||||||
|
if (event.type === 'output-byte-stream') {
|
||||||
|
const { readable, writable } = new TransformStream({
|
||||||
|
transform: (chunk, ctrl) => {
|
||||||
|
chunk.arrayBuffer().then((b) => ctrl.enqueue(new Uint8Array(b)));
|
||||||
|
},
|
||||||
|
});
|
||||||
|
window.writer = writable.getWriter();
|
||||||
|
readable.pipeTo(window.streamSaver.createWriteStream('test-vid.mp4'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[recordingState, recordingStartedDate]
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!callObject || !enableRecording) return false;
|
||||||
|
|
||||||
|
callObject.on('recording-started', handleRecordingStarted);
|
||||||
|
return () => callObject.off('recording-started', handleRecordingStarted);
|
||||||
|
}, [callObject, enableRecording, handleRecordingStarted]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle recording stopped.
|
||||||
|
*/
|
||||||
|
useEffect(() => {
|
||||||
|
if (!callObject || !enableRecording) return false;
|
||||||
|
|
||||||
|
const handleRecordingStopped = (event) => {
|
||||||
|
console.log(event);
|
||||||
|
if (isRecordingLocally) return;
|
||||||
|
setRecordingState(RECORDING_IDLE);
|
||||||
|
setRecordingStartedDate(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
callObject.on('recording-stopped', handleRecordingStopped);
|
||||||
|
return () => callObject.off('recording-stopped', handleRecordingStopped);
|
||||||
|
}, [callObject, enableRecording, isRecordingLocally]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle recording error.
|
||||||
|
*/
|
||||||
|
const handleRecordingError = useCallback(() => {
|
||||||
|
if (isRecordingLocally) setRecordingState(RECORDING_ERROR);
|
||||||
|
setIsRecordingLocally(false);
|
||||||
|
}, [isRecordingLocally]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!callObject || !enableRecording) return false;
|
||||||
|
|
||||||
|
callObject.on('recording-error', handleRecordingError);
|
||||||
|
return () => callObject.off('recording-error', handleRecordingError);
|
||||||
|
}, [callObject, enableRecording, handleRecordingError]);
|
||||||
|
|
||||||
|
const startRecording = useCallback(() => {
|
||||||
|
if (!callObject || !isRecordingLocally) return;
|
||||||
|
callObject.startRecording();
|
||||||
|
}, [callObject, isRecordingLocally]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let timeout;
|
||||||
|
switch (recordingState) {
|
||||||
|
case RECORDING_COUNTDOWN_3:
|
||||||
|
timeout = setTimeout(() => {
|
||||||
|
setRecordingState(RECORDING_COUNTDOWN_2);
|
||||||
|
}, 1000);
|
||||||
|
break;
|
||||||
|
case RECORDING_COUNTDOWN_2:
|
||||||
|
timeout = setTimeout(() => {
|
||||||
|
setRecordingState(RECORDING_COUNTDOWN_1);
|
||||||
|
}, 1000);
|
||||||
|
break;
|
||||||
|
case RECORDING_COUNTDOWN_1:
|
||||||
|
startRecording();
|
||||||
|
break;
|
||||||
|
case RECORDING_ERROR:
|
||||||
|
case RECORDING_SAVED:
|
||||||
|
timeout = setTimeout(() => {
|
||||||
|
setRecordingState(RECORDING_IDLE);
|
||||||
|
setIsRecordingLocally(false);
|
||||||
|
}, 5000);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return () => {
|
||||||
|
clearTimeout(timeout);
|
||||||
|
};
|
||||||
|
}, [recordingState, startRecording]);
|
||||||
|
|
||||||
|
// Show a custom capsule when recording in progress
|
||||||
|
useEffect(() => {
|
||||||
|
if (recordingState !== RECORDING_RECORDING) {
|
||||||
|
setCustomCapsule(null);
|
||||||
|
} else {
|
||||||
|
setCustomCapsule({ variant: 'recording', label: 'Recording' });
|
||||||
|
}
|
||||||
|
}, [recordingState, setCustomCapsule]);
|
||||||
|
|
||||||
|
const startRecordingWithCountdown = useCallback(() => {
|
||||||
|
if (!callObject || !enableRecording) return;
|
||||||
|
setIsRecordingLocally(true);
|
||||||
|
setRecordingState(RECORDING_COUNTDOWN_3);
|
||||||
|
callObject?.sendAppMessage({
|
||||||
|
event: 'recording-starting',
|
||||||
|
});
|
||||||
|
}, [callObject, enableRecording]);
|
||||||
|
|
||||||
|
const stopRecording = useCallback(() => {
|
||||||
|
if (!callObject || !enableRecording || !isRecordingLocally) return;
|
||||||
|
if (recordingState === RECORDING_RECORDING) {
|
||||||
|
switch (enableRecording) {
|
||||||
|
case RECORDING_TYPE_LOCAL:
|
||||||
|
case RECORDING_TYPE_OUTPUT_BYTE_STREAM:
|
||||||
|
setRecordingState(RECORDING_SAVED);
|
||||||
|
setIsRecordingLocally(false);
|
||||||
|
break;
|
||||||
|
case RECORDING_TYPE_CLOUD:
|
||||||
|
case RECORDING_TYPE_CLOUD_BETA:
|
||||||
|
case RECORDING_TYPE_RTP_TRACKS:
|
||||||
|
setRecordingState(RECORDING_UPLOADING);
|
||||||
|
setRecordingState(RECORDING_SAVED);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else if (recordingState === RECORDING_IDLE) {
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
setIsRecordingLocally(false);
|
||||||
|
setRecordingState(RECORDING_IDLE);
|
||||||
|
}
|
||||||
|
setRecordingStartedDate(null);
|
||||||
|
callObject.stopRecording();
|
||||||
|
}, [callObject, enableRecording, isRecordingLocally, recordingState]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<RecordingContext.Provider
|
||||||
|
value={{
|
||||||
|
isRecordingLocally,
|
||||||
|
recordingStartedDate,
|
||||||
|
recordingState,
|
||||||
|
startRecordingWithCountdown,
|
||||||
|
stopRecording,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</RecordingContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
RecordingProvider.propTypes = {
|
||||||
|
children: PropTypes.node,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useRecording = () => useContext(RecordingContext);
|
||||||
|
|
@ -4,6 +4,7 @@ import Head from 'next/head';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { App as CustomApp } from '../components/App/App';
|
import { App as CustomApp } from '../components/App/App';
|
||||||
import ChatAside from '../components/Call/ChatAside';
|
import ChatAside from '../components/Call/ChatAside';
|
||||||
|
import RecordingModal from '../components/Modals/RecordingModal';
|
||||||
import Tray from '../components/Tray';
|
import Tray from '../components/Tray';
|
||||||
|
|
||||||
function App({ Component, pageProps }) {
|
function App({ Component, pageProps }) {
|
||||||
|
|
@ -35,7 +36,7 @@ App.propTypes = {
|
||||||
};
|
};
|
||||||
|
|
||||||
App.asides = [ChatAside];
|
App.asides = [ChatAside];
|
||||||
App.modals = [];
|
App.modals = [RecordingModal];
|
||||||
App.customTrayComponent = <Tray />;
|
App.customTrayComponent = <Tray />;
|
||||||
App.customAppComponent = <CustomApp />;
|
App.customAppComponent = <CustomApp />;
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue