Skip to content

Commit 490923a

Browse files
committed
feat: rework state and mimetype computed
1 parent eb51ce0 commit 490923a

File tree

4 files changed

+40
-27
lines changed

4 files changed

+40
-27
lines changed

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,18 @@ const { start, stop, pause, resume, data, state, isMimeTypeSupported, isSupporte
88
},
99
})
1010
function handleStop() {
11+
console.log('stop')
1112
stop()
1213
const blob = new Blob(data.value)
1314
const blobVideo = new Blob(data.value)
1415
audio.value.src = URL.createObjectURL(blob)
1516
video.value.src = URL.createObjectURL(blobVideo)
16-
data.value = []
1717
}
1818
</script>
1919

2020
<template>
2121
<div>
22-
<button @click="start()">
22+
<button @click="start">
2323
start
2424
</button>
2525
<button @click="pause">
@@ -37,5 +37,6 @@ function handleStop() {
3737
<pre>supported: {{ isSupported }}</pre>
3838
<pre>mime type: {{ mimeType }}</pre>
3939
<pre>mime supported: {{ isMimeTypeSupported }}</pre>
40+
<pre>data length: {{ data?.length}}</pre>
4041
</div>
4142
</template>

src/index.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
// Components
22
// export { default as Toggle } from './components/Toggle.vue'
33

4+
// Plugin
5+
export { default as MediaRecorderPlugin } from './plugin'
6+
47
// Composables
58
export { useMediaRecorder } from './useMediaRecorder'
69

710
// Types
8-
// export * from './types'
11+
export type {MediaRecorderPluginOptions} from './types'
12+
export type {UseMediaRecorderReturn} from './useMediaRecorder'

src/useMediaRecorder.ts

Lines changed: 24 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,7 @@ import { useSupported } from '@vueuse/core'
44
import { tryOnScopeDispose } from '@vueuse/shared'
55
import { defu } from 'defu'
66
import { computed, ref, shallowRef, toValue, watch } from 'vue'
7-
8-
export { MediaRecorderPlugin } from './plugin'
7+
import { computedWithControl } from '@vueuse/core'
98

109
interface UseMediaRecorderOptions extends ConfigurableNavigator {
1110
/**
@@ -20,13 +19,14 @@ interface UseMediaRecorderOptions extends ConfigurableNavigator {
2019

2120
const defaultOptions: UseMediaRecorderOptions = {
2221
constraints: { audio: false, video: false },
23-
mediaRecorderOptions: {},
22+
mediaRecorderOptions: {}
2423
}
2524

2625
export function useMediaRecorder(options: UseMediaRecorderOptions = {}) {
2726
const data = ref<Blob[]>([])
2827
const mediaRecorder = shallowRef<MediaRecorder>()
2928
const stream = shallowRef<MediaStream>()
29+
const result = shallowRef<Blob[]>([])
3030

3131
const isMimeTypeSupported = computed(() => {
3232
return toValue(options.mediaRecorderOptions)?.mimeType ? MediaRecorder.isTypeSupported(toValue(options.mediaRecorderOptions)?.mimeType ?? '') : true
@@ -35,66 +35,67 @@ export function useMediaRecorder(options: UseMediaRecorderOptions = {}) {
3535
return !!navigator?.mediaDevices?.getUserMedia && isMimeTypeSupported.value
3636
})
3737

38-
const state = shallowRef<RecordingState | undefined>(undefined)
38+
const state = computedWithControl<RecordingState | undefined>(()=>mediaRecorder.value,()=>{
39+
return mediaRecorder.value?.state
40+
})
3941

40-
const mimeType = shallowRef<string | undefined>(undefined)
42+
const mimeType = computedWithControl<string | undefined>(() => mediaRecorder.value, () => {
43+
return mediaRecorder.value?.mimeType
44+
})
4145

4246
const updateStates = () => {
43-
state.value = mediaRecorder.value?.state
47+
state.trigger()
4448
}
4549

4650
const {
4751
mediaRecorderOptions,
48-
constraints,
52+
constraints
4953
} = defu(options, defaultOptions)
5054

5155
const start = async (timeslice: number | undefined = undefined) => {
5256
if (state.value === 'recording')
5357
return // todo warning?
58+
data.value = []
5459
stream.value = await navigator!.mediaDevices.getUserMedia(toValue(constraints))
5560
mediaRecorder.value = new MediaRecorder(stream.value, toValue(mediaRecorderOptions))
56-
data.value = []
5761
mediaRecorder.value?.start(timeslice)
58-
updateStates()
59-
}
60-
61-
const reset = () => {
62-
stream.value?.getTracks().forEach(t => t.stop())
63-
stream.value = undefined
64-
mediaRecorder.value?.stop()
6562
}
6663

6764
const stop = () => {
6865
if (!state.value || state.value === 'inactive')
6966
return // todo warning?
70-
reset()
71-
updateStates()
67+
mediaRecorder.value?.stop()
7268
}
7369

7470
const pause = () => {
7571
if (state.value !== 'recording')
7672
return // todo warning?
7773
mediaRecorder.value?.pause()
78-
updateStates()
7974
}
8075

8176
const resume = () => {
8277
if (state.value !== 'paused')
8378
return // todo warning?
8479
mediaRecorder.value?.resume()
85-
updateStates()
8680
}
8781

8882
watch(() => mediaRecorder.value, (newMediaRecorder) => {
8983
if (!newMediaRecorder)
9084
return
9185
newMediaRecorder.ondataavailable = (e) => {
9286
const blob = e.data
93-
if (blob.type !== mimeType.value) {
94-
mimeType.value = blob.type ?? mediaRecorder.value?.mimeType
95-
}
87+
mimeType.trigger()
9688
data.value.push(e.data)
9789
}
90+
newMediaRecorder.onstop = () => {
91+
stream.value?.getTracks().forEach(t => t.stop())
92+
result.value = data.value
93+
updateStates()
94+
}
95+
newMediaRecorder.onpause = updateStates
96+
newMediaRecorder.onresume = updateStates
97+
newMediaRecorder.onstart = updateStates
98+
newMediaRecorder.onerror = updateStates
9899
}, { immediate: true })
99100

100101
tryOnScopeDispose(() => {
@@ -112,7 +113,7 @@ export function useMediaRecorder(options: UseMediaRecorderOptions = {}) {
112113
isSupported,
113114
isMimeTypeSupported,
114115
mimeType: computed(()=> mimeType.value),
115-
mediaRecorder: computed(() => mediaRecorder.value),
116+
mediaRecorder: computed(() => mediaRecorder.value)
116117
}
117118
}
118119

tests/index.spec.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,16 @@ describe('useMediaRecorder', () => {
1818
resume,
1919
} = useMediaRecorder({ constraints: { audio: true } })
2020
await start()
21+
await new Promise(resolve => setTimeout(resolve, 10))
2122
expect(state.value).toMatchInlineSnapshot(`"recording"`)
2223
await pause()
24+
await new Promise(resolve => setTimeout(resolve, 10))
2325
expect(state.value).toMatchInlineSnapshot(`"paused"`)
2426
await resume()
27+
await new Promise(resolve => setTimeout(resolve, 10))
2528
expect(state.value).toMatchInlineSnapshot(`"recording"`)
2629
await stop()
30+
await new Promise(resolve => setTimeout(resolve, 10))
2731
expect(state.value).toMatchInlineSnapshot(`"inactive"`)
2832
})
2933

@@ -57,7 +61,7 @@ describe('useMediaRecorder', () => {
5761
} = useMediaRecorder({ constraints: { audio: true } })
5862
await start()
5963
await stop()
60-
expect(stream.value).toBeUndefined()
64+
expect(stream.value.active).toMatchInlineSnapshot(`true`)
6165
})
6266

6367
it('stream should be undefined after pause', async () => {
@@ -120,10 +124,13 @@ describe('useMediaRecorder', () => {
120124
} = useMediaRecorder({ constraints: { audio: true } })
121125

122126
await start()
127+
await new Promise(resolve => setTimeout(resolve, 10))
123128
expect(state.value).toBe('recording')
124129
await pause()
130+
await new Promise(resolve => setTimeout(resolve, 10))
125131
expect(state.value).toBe('paused')
126132
await stop()
133+
await new Promise(resolve => setTimeout(resolve, 10))
127134
expect(state.value).toBe('inactive')
128135
})
129136

0 commit comments

Comments
 (0)