4
4
useCallback ,
5
5
useSyncExternalStore ,
6
6
useEffect ,
7
+ useRef ,
7
8
} from "react" ;
8
9
import { ShallowShapeOf } from "../types" ;
9
10
@@ -160,9 +161,15 @@ export type RecorderState =
160
161
* @returns See {@link RecorderState} for more information.
161
162
*/
162
163
export function useMediaRecorder ( ) : RecorderState {
163
- const [ recorder , setRecorder ] = useState < MediaRecorder | undefined > (
164
- undefined ,
165
- ) ;
164
+ // we need _both_ a referentially stable version of MediaRecorder and a mutable version
165
+ // so that we can have stable start/stop functions, but also dynamic state updates
166
+ const recorderRef = useRef < MediaRecorder | null > ( null ) ;
167
+ const [ isRecorderDirty , setIsRecorderDirty ] = useState < boolean > ( false ) ;
168
+ const recorder = useMemo ( ( ) => {
169
+ if ( isRecorderDirty ) setIsRecorderDirty ( false ) ;
170
+
171
+ return recorderRef . current ?? undefined ;
172
+ } , [ recorderRef , isRecorderDirty ] ) ;
166
173
167
174
const recorderState = useMediaRecorderState ( recorder ) ;
168
175
const isRecording = useMemo (
@@ -182,60 +189,45 @@ export function useMediaRecorder(): RecorderState {
182
189
const [ startTime , setStartTime ] = useState < DOMHighResTimeStamp | null > ( null ) ;
183
190
const [ endTime , setEndTime ] = useState < DOMHighResTimeStamp | null > ( null ) ;
184
191
185
- const startRecording = useCallback (
186
- function startRecordingMedia (
187
- media : MediaStream ,
188
- options ?: RecorderOptions ,
189
- ) {
190
- // don't allow multiple recordings at once
191
- if ( isRecording ) {
192
- return ;
193
- }
194
-
195
- const { timeslice, dataAvailableHandler, ...recorderOptions } = {
196
- timeslice : 30 * 1000 /* 30s */ ,
197
- dataAvailableHandler : (
198
- ev : BlobEvent ,
199
- callback : ( value : React . SetStateAction < Blob [ ] > ) => void ,
200
- ) => {
201
- callback ( ( current ) => current . concat ( ev . data ) ) ;
202
- } ,
203
- ...options ,
204
- } ;
192
+ const startRecording = useCallback ( function startRecordingMedia (
193
+ media : MediaStream ,
194
+ options ?: RecorderOptions ,
195
+ ) {
196
+ const { timeslice, dataAvailableHandler, ...recorderOptions } = {
197
+ timeslice : 30 * 1000 /* 30s */ ,
198
+ dataAvailableHandler : (
199
+ ev : BlobEvent ,
200
+ callback : ( value : React . SetStateAction < Blob [ ] > ) => void ,
201
+ ) => {
202
+ callback ( ( current ) => current . concat ( ev . data ) ) ;
203
+ } ,
204
+ ...options ,
205
+ } ;
205
206
206
- const recorder = new MediaRecorder ( media , recorderOptions ) ;
207
+ const recorder = new MediaRecorder ( media , recorderOptions ) ;
207
208
208
- recorder . addEventListener ( "dataavailable" , function onDataAvailable ( ev ) {
209
- dataAvailableHandler ( ev , setSegments ) ;
210
- } ) ;
209
+ recorder . addEventListener ( "dataavailable" , function onDataAvailable ( ev ) {
210
+ dataAvailableHandler ( ev , setSegments ) ;
211
+ } ) ;
211
212
212
- setSegments ( [ ] ) ;
213
+ setSegments ( [ ] ) ;
213
214
214
- const startTime = performance . now ( ) ;
215
- recorder . start ( timeslice ) ;
215
+ const startTime = performance . now ( ) ;
216
+ recorder . start ( timeslice ) ;
216
217
217
- setStartTime ( startTime ) ;
218
- setRecorder ( recorder ) ;
219
- } ,
220
- [ isRecording ] ,
221
- ) ;
218
+ setStartTime ( startTime ) ;
222
219
223
- const stopRecording = useCallback (
224
- function stopRecordingMedia ( ) {
225
- // don't allow stopping of "nothing"
226
- if ( ! isRecording ) {
227
- console . log ( "not recording" ) ;
228
- return ;
229
- }
220
+ recorderRef . current = recorder ;
221
+ setIsRecorderDirty ( true ) ;
222
+ } , [ ] ) ;
230
223
231
- const endTime = performance . now ( ) ;
224
+ const stopRecording = useCallback ( function stopRecordingMedia ( ) {
225
+ const endTime = performance . now ( ) ;
232
226
233
- recorder ?. stop ( ) ;
227
+ recorderRef . current ?. stop ( ) ;
234
228
235
- setEndTime ( endTime ) ;
236
- } ,
237
- [ isRecording , recorder ] ,
238
- ) ;
229
+ setEndTime ( endTime ) ;
230
+ } , [ ] ) ;
239
231
240
232
const state = {
241
233
isError,
0 commit comments