Skip to content

Commit 606aae2

Browse files
committed
feat(stage-web): added audit recorder demo as devtools
1 parent 7a1bc00 commit 606aae2

File tree

6 files changed

+608
-47
lines changed

6 files changed

+608
-47
lines changed

apps/stage-tamagotchi/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@
6060
"drizzle-orm": "^0.44.5",
6161
"jszip": "^3.10.1",
6262
"localforage": "^1.10.0",
63+
"mediabunny": "^1.11.2",
6364
"node-vibrant": "^4.0.3",
6465
"nprogress": "^0.2.0",
6566
"ofetch": "^1.4.1",

apps/stage-web/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@
6161
"html2canvas": "^1.4.1",
6262
"jszip": "^3.10.1",
6363
"localforage": "^1.10.0",
64+
"mediabunny": "^1.11.2",
6465
"nanoid": "^5.1.5",
6566
"node-vibrant": "^4.0.3",
6667
"nprogress": "^0.2.0",
Lines changed: 63 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,73 @@
1-
<script lang="ts" setup>
2-
import { Button } from '@proj-airi/stage-ui/components'
3-
import { Option, Select } from '@proj-airi/ui'
4-
import { onMounted, onUnmounted } from 'vue'
1+
<script setup lang="ts">
2+
import { useDevicesList, useObjectUrl } from '@vueuse/core'
3+
import { BufferTarget, MediaStreamAudioTrackSource, Output, QUALITY_MEDIUM, WavOutputFormat } from 'mediabunny'
4+
import { computed, ref } from 'vue'
55
6-
import { useAudioInput } from '../../composables/audio-input'
7-
import { useAudioRecord } from '../../composables/audio-record'
6+
const { audioInputs } = useDevicesList({ constraints: { audio: true }, requestPermissions: true })
7+
const constraintId = ref('')
88
9-
const { audioInputs, selectedAudioInputId, start, stop, media, request } = useAudioInput()
10-
const { startRecord, stopRecord } = useAudioRecord(media.stream, start)
9+
async function getMediaStreamTrack(constraint: ConstrainDOMString) {
10+
const stream = await navigator.mediaDevices.getUserMedia({ audio: { deviceId: constraint } })
11+
return stream.getAudioTracks()[0]
12+
}
1113
12-
onMounted(() => request())
13-
onUnmounted(() => stop())
14+
let output: Output | undefined
15+
let audioInputTrack: MediaStreamAudioTrack | undefined
16+
let format: string | undefined
17+
18+
const recorded = ref<ArrayBuffer[]>([])
19+
const recordedUrls = computed(() => recorded.value.map(rec => useObjectUrl(new Blob([rec], { type: format })).value))
20+
21+
async function handleStart() {
22+
audioInputTrack = await getMediaStreamTrack(constraintId.value)
23+
output = new Output({ format: new WavOutputFormat(), target: new BufferTarget() })
24+
25+
const audioSource = new MediaStreamAudioTrackSource(audioInputTrack, { codec: 'pcm-f32', bitrate: QUALITY_MEDIUM })
26+
audioSource.errorPromise.catch(console.error)
27+
output.addAudioTrack(audioSource)
28+
29+
format = await output.getMimeType()
30+
await output.start()
31+
}
32+
33+
async function handleStop() {
34+
await output?.finalize()
35+
const bufferTarget = output?.target as BufferTarget | undefined
36+
bufferTarget?.buffer && recorded.value.push(bufferTarget?.buffer)
37+
}
38+
39+
function handleCancel() {
40+
output?.cancel()
41+
}
1442
</script>
1543

1644
<template>
1745
<div>
18-
<Select v-model="selectedAudioInputId" @change="() => start()">
19-
<template #default="{ value }">
20-
<div>
21-
{{ value ? audioInputs.find(device => device.deviceId === value)?.label : 'Select Audio Input' }}
22-
</div>
23-
</template>
24-
<template #options="{ hide }">
25-
<Option
26-
v-for="device in audioInputs"
27-
:key="device.deviceId"
28-
:value="device.deviceId"
29-
:active="device.deviceId === selectedAudioInputId"
30-
@click="hide()"
31-
>
32-
{{ device.label }}
33-
</Option>
34-
</template>
35-
</Select>
36-
<div class="mt-4 w-full flex justify-center gap-2">
37-
<Button @click="startRecord">
38-
Start Recording
39-
</Button>
40-
<Button @click="stopRecord">
41-
Stop Recording
42-
</Button>
46+
<div>
47+
<select v-model="constraintId">
48+
<option value="">
49+
Select
50+
</option>
51+
<option v-for="(item, index) of audioInputs" :key="index" :value="item.deviceId">
52+
{{ item.label }}
53+
</option>
54+
</select>
55+
</div>
56+
<div space-x-2>
57+
<button @click="handleStart">
58+
Start
59+
</button>
60+
<button @click="handleCancel">
61+
Cancel
62+
</button>
63+
<button @click="handleStop">
64+
Stop
65+
</button>
66+
</div>
67+
<div>
68+
<audio v-for="(url, index) in recordedUrls" :key="index" controls>
69+
<source :src="url" type="audio/wav">
70+
</audio>
4371
</div>
4472
</div>
4573
</template>

cspell.config.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ words:
1717
- astrojs
1818
- Attributify
1919
- attw
20+
- audia
2021
- audioworklet
2122
- autoplay
2223
- awilix
@@ -138,6 +139,7 @@ words:
138139
- Maru
139140
- matchall
140141
- mdit
142+
- mediabunny
141143
- micvad
142144
- mineflayer
143145
- mingcute

packages/stage-ui/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@
9595
"gpuu": "^1.0.4",
9696
"jszip": "^3.10.1",
9797
"localforage": "^1.10.0",
98+
"mediabunny": "^1.11.2",
9899
"nanoid": "^5.1.5",
99100
"pinia": "^3.0.3",
100101
"pixi-filters": "4",

0 commit comments

Comments
 (0)