From 16d259ccc4c33a54241be9c3eb28bdbba4dbddbd Mon Sep 17 00:00:00 2001 From: Kimberlee Johnson Date: Mon, 22 Nov 2021 16:57:27 -0800 Subject: [PATCH 1/5] First pass at more recording types, updated README --- custom/recording/README.md | 6 ++-- custom/recording/components/RecordingModal.js | 24 ++++++++++++++- .../recording/contexts/RecordingProvider.js | 29 +++++++++++++++++-- custom/shared/contexts/CallProvider.js | 27 +++++++++-------- 4 files changed, 68 insertions(+), 18 deletions(-) diff --git a/custom/recording/README.md b/custom/recording/README.md index 523abe6..ebabce6 100644 --- a/custom/recording/README.md +++ b/custom/recording/README.md @@ -6,14 +6,14 @@ ## What does this demo do? -- Use [startRecording](https://docs.daily.co/reference#%EF%B8%8F-startrecording) to create a video and audio recording of your call. You can read more about Daily call recording (and the different modes and types) [here](https://docs.daily.co/reference#recordings) -- Supports both `cloud` and `local` recording modes (specified when creating the room or managed using the Daily dashboard) +- Use [startRecording](https://docs.daily.co/reference#%EF%B8%8F-startrecording) to create a video and audio recording of your call. You can read more about Daily call recording (and the different modes and types) [here](https://docs.daily.co/guides/recording-calls-with-the-daily-api) +- Supports `cloud`, `cloud-beta`, `local`, and `rtp-tracks` recording modes (specified when creating the room or managed using the Daily dashboard). _Heads up: if recording on mobile devices or Safari, then `cloud-beta` must be the specified mode._ - Coming soon: support different recording layouts / composites - Coming soon: use the Daily REST API to retrieve a list of cloud recordings for the currently active room **To turn on recording, you need to be on the Scale plan. There is also a per minute recording fee for cloud recording.** -Please note: this demo is not currently mobile optimised +Please note: this demo is not currently mobile optimised. ### Getting started diff --git a/custom/recording/components/RecordingModal.js b/custom/recording/components/RecordingModal.js index e89efa4..1725f4d 100644 --- a/custom/recording/components/RecordingModal.js +++ b/custom/recording/components/RecordingModal.js @@ -13,6 +13,8 @@ import { RECORDING_RECORDING, RECORDING_SAVED, RECORDING_TYPE_CLOUD, + RECORDING_TYPE_CLOUD_BETA, + RECORDING_TYPE_RTP_TRACKS, RECORDING_UPLOADING, useRecording, } from '../contexts/RecordingProvider'; @@ -105,7 +107,9 @@ export const RecordingModal = () => {

Recording started: {recordingStartedDate.toString()}

)} - {enableRecording === RECORDING_TYPE_CLOUD && ( + {[RECORDING_TYPE_CLOUD, RECORDING_TYPE_CLOUD_BETA].includes( + enableRecording + ) && ( <>
@@ -115,6 +119,24 @@ export const RecordingModal = () => {

)} + {enableRecording === RECORDING_TYPE_RTP_TRACKS && ( + <> +
+ +

+ rtp-tracks recordings can be accessed via the Daily API. See the{' '} + + Daily recording guide + {' '} + for details. +

+ + )} ); diff --git a/custom/recording/contexts/RecordingProvider.js b/custom/recording/contexts/RecordingProvider.js index 326bb37..a56a0e3 100644 --- a/custom/recording/contexts/RecordingProvider.js +++ b/custom/recording/contexts/RecordingProvider.js @@ -26,7 +26,10 @@ 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, @@ -37,8 +40,12 @@ const RecordingContext = createContext({ }); export const RecordingProvider = ({ children }) => { - const { callObject, enableRecording, startCloudRecording, state } = - useCallState(); + const { + callObject, + enableRecording, + startCloudRecording, + state, + } = useCallState(); const { participants } = useParticipants(); const [recordingStartedDate, setRecordingStartedDate] = useState(null); const [recordingState, setRecordingState] = useState(RECORDING_IDLE); @@ -85,11 +92,26 @@ export const RecordingProvider = ({ children }) => { } }; + // The 'recording-data' event is emitted when an output-byte-stream recording has started + 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); + } + }; + const handleRecordingUploadCompleted = () => { setRecordingState(RECORDING_SAVED); }; callObject.on('app-message', handleAppMessage); + callObject.on('recording-data', handleRecordingData); callObject.on('recording-upload-completed', handleRecordingUploadCompleted); return () => { @@ -263,10 +285,13 @@ export const RecordingProvider = ({ children }) => { 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); break; default: diff --git a/custom/shared/contexts/CallProvider.js b/custom/shared/contexts/CallProvider.js index eb133c8..62b6919 100644 --- a/custom/shared/contexts/CallProvider.js +++ b/custom/shared/contexts/CallProvider.js @@ -3,7 +3,7 @@ * --- * Configures the general state of a Daily call, such as which features * to enable, as well as instantiate the 'call machine' hook responsible - * fir the overaching call loop (joining, leaving, etc) + * for the overaching call loop (joining, leaving, etc) */ import React, { createContext, @@ -60,19 +60,22 @@ export const CallProvider = ({ ); } const browser = Bowser.parse(window.navigator.userAgent); + const recordingType = + roomConfig?.tokenConfig?.enable_recording ?? + roomConfig?.config?.enable_recording; + + // Mobile and Safari recordings are only supported under the 'cloud-beta' type const supportsRecording = - browser.platform.type === 'desktop' && browser.engine.name === 'Blink'; - // recording and screen sharing is hidden in owner_only_broadcast for non-owners + ((browser.platform.type !== 'desktop' || + browser.engine.name !== 'Blink') && + recordingType === 'cloud-beta') || + (browser.platform.type === 'desktop' && + browser.engine.name === 'Blink'); if (supportsRecording) { - const recordingType = - roomConfig?.tokenConfig?.enable_recording ?? - roomConfig?.config?.enable_recording; - if (['local', 'cloud'].includes(recordingType)) { - setEnableRecording(recordingType); - setStartCloudRecording( - roomConfig?.tokenConfig?.start_cloud_recording ?? false - ); - } + setEnableRecording(recordingType); + setStartCloudRecording( + roomConfig?.tokenConfig?.start_cloud_recording ?? false + ); } }; updateRoomConfigState(); From d270fd3d34bd5a7bf02302fba1e92a4be000f8c9 Mon Sep 17 00:00:00 2001 From: Kimberlee Johnson Date: Mon, 29 Nov 2021 16:01:00 -0800 Subject: [PATCH 2/5] Updated handleRecordingStarted and fixed typo --- custom/recording/components/RecordingModal.js | 2 +- custom/recording/contexts/RecordingProvider.js | 14 +++++++++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/custom/recording/components/RecordingModal.js b/custom/recording/components/RecordingModal.js index 1725f4d..11ae3fa 100644 --- a/custom/recording/components/RecordingModal.js +++ b/custom/recording/components/RecordingModal.js @@ -94,7 +94,7 @@ export const RecordingModal = () => { {!enableRecording ? ( Recording is not enabled for this room (or your browser does not - support it.) Please enabled recording when creating the room or via + support it.) Please enable recording when creating the room or via the Daily dashboard. ) : ( diff --git a/custom/recording/contexts/RecordingProvider.js b/custom/recording/contexts/RecordingProvider.js index a56a0e3..c34f56c 100644 --- a/custom/recording/contexts/RecordingProvider.js +++ b/custom/recording/contexts/RecordingProvider.js @@ -177,13 +177,25 @@ export const RecordingProvider = ({ children }) => { */ 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 (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')); + } } - setRecordingState(RECORDING_RECORDING); }, [recordingState, recordingStartedDate] ); From e03dcdaaef9894e3ddd3ad3a16cbeaa80e8ae906 Mon Sep 17 00:00:00 2001 From: Kimberlee Johnson Date: Mon, 29 Nov 2021 16:21:51 -0800 Subject: [PATCH 3/5] Removed recording-upload-complete event handler in favor of recording-stopped --- custom/recording/contexts/RecordingProvider.js | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/custom/recording/contexts/RecordingProvider.js b/custom/recording/contexts/RecordingProvider.js index c34f56c..620568e 100644 --- a/custom/recording/contexts/RecordingProvider.js +++ b/custom/recording/contexts/RecordingProvider.js @@ -106,20 +106,11 @@ export const RecordingProvider = ({ children }) => { } }; - const handleRecordingUploadCompleted = () => { - setRecordingState(RECORDING_SAVED); - }; - callObject.on('app-message', handleAppMessage); callObject.on('recording-data', handleRecordingData); - callObject.on('recording-upload-completed', handleRecordingUploadCompleted); return () => { callObject.off('app-message', handleAppMessage); - callObject.off( - 'recording-upload-completed', - handleRecordingUploadCompleted - ); }; }, [callObject, enableRecording]); @@ -213,7 +204,8 @@ export const RecordingProvider = ({ children }) => { useEffect(() => { if (!callObject || !enableRecording) return false; - const handleRecordingStopped = () => { + const handleRecordingStopped = (event) => { + console.log(event); if (isRecordingLocally) return; setRecordingState(RECORDING_IDLE); setRecordingStartedDate(null); @@ -305,6 +297,7 @@ export const RecordingProvider = ({ children }) => { case RECORDING_TYPE_CLOUD_BETA: case RECORDING_TYPE_RTP_TRACKS: setRecordingState(RECORDING_UPLOADING); + setRecordingState(RECORDING_SAVED); break; default: break; From 4e17e71fa9abacce93e09a34517ae256a3d51d53 Mon Sep 17 00:00:00 2001 From: Kimberlee Johnson Date: Tue, 30 Nov 2021 16:49:52 -0800 Subject: [PATCH 4/5] Added cleanup for handleRecordingData --- custom/recording/contexts/RecordingProvider.js | 1 + 1 file changed, 1 insertion(+) diff --git a/custom/recording/contexts/RecordingProvider.js b/custom/recording/contexts/RecordingProvider.js index 620568e..d7ad1f5 100644 --- a/custom/recording/contexts/RecordingProvider.js +++ b/custom/recording/contexts/RecordingProvider.js @@ -111,6 +111,7 @@ export const RecordingProvider = ({ children }) => { return () => { callObject.off('app-message', handleAppMessage); + callObject.off('recording-data', handleRecordingData); }; }, [callObject, enableRecording]); From d03b9ad6314b59b33e0ccad6adb38022821ea194 Mon Sep 17 00:00:00 2001 From: Kimberlee Johnson Date: Wed, 1 Dec 2021 12:25:22 -0800 Subject: [PATCH 5/5] Added comments to clarify output-byte-stream --- custom/recording/contexts/RecordingProvider.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/custom/recording/contexts/RecordingProvider.js b/custom/recording/contexts/RecordingProvider.js index d7ad1f5..c63d0b8 100644 --- a/custom/recording/contexts/RecordingProvider.js +++ b/custom/recording/contexts/RecordingProvider.js @@ -93,6 +93,7 @@ export const RecordingProvider = ({ children }) => { }; // 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); @@ -178,6 +179,7 @@ export const RecordingProvider = ({ children }) => { // 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) => {