Skip to content

Commit 0e58bf0

Browse files
committed
type: update callback types
1 parent af859fa commit 0e58bf0

File tree

4 files changed

+97
-66
lines changed

4 files changed

+97
-66
lines changed

changelog.config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@ import type { ChangelogConfig } from 'changelogen'
22

33
export default {
44
excludeAuthors: ['[email protected]'],
5-
} satisfies ChangelogConfig
5+
} satisfies Partial<ChangelogConfig>

playgrounds/nuxt/app/components/recorder-demo.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ function handleStop() {
2424

2525
<template>
2626
<div>
27-
<button @click="start">
27+
<button @click="()=>start()">
2828
start
2929
</button>
3030
<button @click="pause">

src/useMediaRecorder.ts

Lines changed: 37 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -18,28 +18,38 @@ interface UseMediaRecorderOptions extends ConfigurableNavigator {
1818
/**
1919
* Callback when recording starts.
2020
*/
21-
onStart?: () => any
21+
onStart?: (ev: Event)=>any
2222
/**
2323
* Callback when recording pauses.
2424
*/
25-
onPause?: () => any
25+
onPause?: (ev: Event)=>any
2626
/**
2727
* Callback when recording resumes.
2828
*/
29-
onResume?: () => any
29+
onResume?: (ev: Event)=>any
3030
/**
3131
* Callback when recording stops.
3232
*/
33-
onStop?: () => any
33+
onStop?: (ev: Event)=>any
34+
/**
35+
* Callback when an error occurs.
36+
*/
37+
onError?: (ev: Event)=>any
3438
}
3539

3640
const defaultOptions: UseMediaRecorderOptions = {
3741
constraints: { audio: false, video: false },
3842
mediaRecorderOptions: {},
39-
onStart: () => {},
40-
onPause: ()=> {},
41-
onResume: ()=> {},
42-
onStop: ()=> {},
43+
onStart: () => {
44+
},
45+
onPause: () => {
46+
},
47+
onResume: () => {
48+
},
49+
onStop: () => {
50+
},
51+
onError: () => {
52+
}
4353
}
4454

4555
export function useMediaRecorder(options: UseMediaRecorderOptions = {}) {
@@ -55,11 +65,11 @@ export function useMediaRecorder(options: UseMediaRecorderOptions = {}) {
5565
return !!navigator?.mediaDevices?.getUserMedia && isMimeTypeSupported.value
5666
})
5767

58-
const state = computedWithControl<RecordingState | undefined>(()=>mediaRecorder.value,()=>{
68+
const state = computedWithControl<RecordingState | undefined, MediaRecorder | undefined>(() => mediaRecorder.value, () => {
5969
return mediaRecorder.value?.state
6070
})
6171

62-
const mimeType = computedWithControl<string | undefined>(() => mediaRecorder.value, () => {
72+
const mimeType = computedWithControl<string | undefined, MediaRecorder | undefined>(() => mediaRecorder.value, () => {
6373
return mediaRecorder.value?.mimeType
6474
})
6575

@@ -73,7 +83,9 @@ export function useMediaRecorder(options: UseMediaRecorderOptions = {}) {
7383
return // todo warning?
7484
data.value = []
7585
stream.value = await navigator!.mediaDevices.getUserMedia(toValue(constraints))
76-
mediaRecorder.value = new MediaRecorder(stream.value, toValue(mediaRecorderOptions))
86+
const mr = new MediaRecorder(stream.value, toValue(mediaRecorderOptions))
87+
setupMediaRecorder(mr)
88+
mediaRecorder.value = mr
7789
mediaRecorder.value?.start(timeslice)
7890
}
7991

@@ -95,45 +107,44 @@ export function useMediaRecorder(options: UseMediaRecorderOptions = {}) {
95107
mediaRecorder.value?.resume()
96108
}
97109

98-
watch(() => mediaRecorder.value, (newMediaRecorder) => {
110+
const setupMediaRecorder = (newMediaRecorder: MediaRecorder) => {
99111
if (!newMediaRecorder)
100112
return
101113
newMediaRecorder.ondataavailable = (e) => {
102-
const blob = e.data
103114
mimeType.trigger()
104115
data.value.push(e.data)
105116
}
106-
newMediaRecorder.onstop = () => {
117+
newMediaRecorder.onstop = (...args) => {
107118
stream.value?.getTracks().forEach(t => t.stop())
108119
result.value = data.value
109120
state.trigger()
110121
mimeType.trigger()
111-
options.onStop?.()
122+
options.onStop?.(...args)
112123
}
113-
newMediaRecorder.onpause = ()=>{
124+
newMediaRecorder.onpause = (...args) => {
114125
state.trigger()
115126
mimeType.trigger()
116-
options.onPause?.()
127+
options.onPause?.(...args)
117128
}
118-
newMediaRecorder.onresume = ()=>{
129+
newMediaRecorder.onresume = (...args) => {
119130
state.trigger()
120131
mimeType.trigger()
121-
options.onResume?.()
132+
options.onResume?.(...args)
122133
}
123-
newMediaRecorder.onstart = ()=>{
134+
newMediaRecorder.onstart = (...args) => {
124135
state.trigger()
125136
mimeType.trigger()
126-
options.onStart?.()
137+
options.onStart?.(...args)
127138
}
128-
newMediaRecorder.onerror = ()=>{
139+
newMediaRecorder.onerror = (...args) => {
129140
state.trigger()
130141
mimeType.trigger()
131-
// TODO: error handling
142+
options.onError?.(...args)
132143
}
133-
}, { immediate: true })
144+
}
134145

135146
tryOnScopeDispose(() => {
136-
reset()
147+
mediaRecorder.value?.stop()
137148
})
138149

139150
return {
@@ -146,7 +157,7 @@ export function useMediaRecorder(options: UseMediaRecorderOptions = {}) {
146157
state,
147158
isSupported,
148159
isMimeTypeSupported,
149-
mimeType: computed(()=> mimeType.value),
160+
mimeType: computed(() => mimeType.value),
150161
mediaRecorder: computed(() => mediaRecorder.value)
151162
}
152163
}

tests/index.spec.ts

Lines changed: 58 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -18,17 +18,21 @@ describe('useMediaRecorder', () => {
1818
resume,
1919
} = useMediaRecorder({ constraints: { audio: true } })
2020
await start()
21-
await new Promise(resolve => setTimeout(resolve, 10))
22-
expect(state.value).toMatchInlineSnapshot(`"recording"`)
23-
await pause()
24-
await new Promise(resolve => setTimeout(resolve, 10))
25-
expect(state.value).toMatchInlineSnapshot(`"paused"`)
26-
await resume()
27-
await new Promise(resolve => setTimeout(resolve, 10))
28-
expect(state.value).toMatchInlineSnapshot(`"recording"`)
29-
await stop()
30-
await new Promise(resolve => setTimeout(resolve, 10))
31-
expect(state.value).toMatchInlineSnapshot(`"inactive"`)
21+
await vi.waitFor(() => {
22+
expect(state.value).toBe("recording")
23+
})
24+
pause()
25+
await vi.waitFor(() => {
26+
expect(state.value).toBe("paused")
27+
})
28+
resume()
29+
await vi.waitFor(() => {
30+
expect(state.value).toBe("recording")
31+
})
32+
stop()
33+
await vi.waitFor(() => {
34+
expect(state.value).toBe("inactive")
35+
})
3236
})
3337

3438
it('data should update when recording', async () => {
@@ -43,28 +47,33 @@ describe('useMediaRecorder', () => {
4347
expect(data.value?.length).toBeGreaterThan(0)
4448
})
4549

46-
it('stream should be defined after start', async () => {
50+
it('stream should be defined and active after start', async () => {
4751
const {
4852
stream,
4953
start,
5054
} = useMediaRecorder({ constraints: { audio: true } })
5155
expect(stream.value).toBeUndefined()
56+
expect(stream.value?.active).toBeUndefined()
5257
await start()
5358
expect(stream.value).toBeDefined()
59+
expect(stream.value?.active).toBe(true)
5460
})
5561

56-
it('stream should be undefined after stop', async () => {
62+
it('stream should not be active after stop', async () => {
5763
const {
5864
stream,
5965
start,
6066
stop,
6167
} = useMediaRecorder({ constraints: { audio: true } })
6268
await start()
63-
await stop()
64-
expect(stream.value.active).toMatchInlineSnapshot(`true`)
69+
expect(stream.value?.active).toMatchInlineSnapshot(`true`)
70+
stop()
71+
await vi.waitFor(() => {
72+
expect(stream.value?.active).toBe(false)
73+
})
6574
})
6675

67-
it('stream should be undefined after pause', async () => {
76+
it.skip('stream should be undefined after pause', async () => {
6877
const {
6978
stream,
7079
start,
@@ -83,8 +92,8 @@ describe('useMediaRecorder', () => {
8392
resume,
8493
} = useMediaRecorder({ constraints: { audio: true } })
8594
await start()
86-
await pause()
87-
await resume()
95+
pause()
96+
resume()
8897
expect(stream.value).toBeDefined()
8998
})
9099

@@ -94,9 +103,10 @@ describe('useMediaRecorder', () => {
94103
mimeType,
95104
} = useMediaRecorder({ constraints: { audio: true } })
96105
expect(mimeType.value).toBeUndefined()
97-
await start(10)
98-
await new Promise(resolve => setTimeout(resolve, 200))
99-
expect(mimeType.value).toBeDefined()
106+
await start()
107+
await vi.waitFor(() => {
108+
expect(mimeType.value).toBeDefined()
109+
})
100110
})
101111

102112
it('should be supported', () => {
@@ -121,17 +131,22 @@ describe('useMediaRecorder', () => {
121131
pause,
122132
stop,
123133
state,
134+
stream
124135
} = useMediaRecorder({ constraints: { audio: true } })
125136

126137
await start()
127-
await new Promise(resolve => setTimeout(resolve, 10))
128-
expect(state.value).toBe('recording')
129-
await pause()
130-
await new Promise(resolve => setTimeout(resolve, 10))
131-
expect(state.value).toBe('paused')
138+
await vi.waitFor(() => {
139+
expect(state.value).toBe('recording')
140+
})
141+
pause()
142+
await vi.waitFor(() => {
143+
expect(state.value).toBe('paused')
144+
})
132145
await stop()
133-
await new Promise(resolve => setTimeout(resolve, 10))
134-
expect(state.value).toBe('inactive')
146+
await vi.waitFor(() => {
147+
expect(state.value).toBe('inactive')
148+
expect(stream.value?.active).toBe(false)
149+
})
135150
})
136151

137152
it('data should exist when stopping from pause', async () => {
@@ -143,11 +158,12 @@ describe('useMediaRecorder', () => {
143158
} = useMediaRecorder({ constraints: { audio: true } })
144159

145160
expect(data.value).toHaveLength(0)
146-
await start(10)
147-
await new Promise(resolve => setTimeout(resolve, 100))
148-
await pause()
149-
await stop()
150-
expect(data.value.length).toBeGreaterThan(0)
161+
await start()
162+
pause()
163+
stop()
164+
await vi.waitFor(() => {
165+
expect(data.value.length).toBeGreaterThan(0)
166+
})
151167
})
152168

153169
it('should call all lifecycle hooks', async () => {
@@ -164,15 +180,19 @@ describe('useMediaRecorder', () => {
164180
} = useMediaRecorder({ constraints: { audio: true }, onStop, onStart, onPause, onResume })
165181

166182
await start()
183+
await vi.waitFor(() => {
184+
expect(onStart).toHaveBeenCalledTimes(1)
185+
})
167186
pause()
168-
await new Promise(resolve => setTimeout(resolve, 10))
187+
await vi.waitFor(() => {
188+
expect(onPause).toHaveBeenCalledTimes(1)
189+
})
169190
resume()
191+
await vi.waitFor(() => {
192+
expect(onResume).toHaveBeenCalledTimes(1)
193+
})
170194
stop()
171-
172195
await vi.waitFor( () => {
173-
expect(onStart).toHaveBeenCalledTimes(1)
174-
expect(onPause).toHaveBeenCalledTimes(1)
175-
expect(onResume).toHaveBeenCalledTimes(1)
176196
expect(onStop).toHaveBeenCalledTimes(1)
177197
})
178198
})

0 commit comments

Comments
 (0)