Skip to content

Commit c7d8ea4

Browse files
committed
mic-tester: pull the media api stuff into a service.
1 parent e8f1766 commit c7d8ea4

File tree

2 files changed

+106
-96
lines changed

2 files changed

+106
-96
lines changed
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import { ref, onBeforeUnmount } from 'vue';
2+
3+
export function useMicrophoneService() {
4+
let audioContext: AudioContext | null = null;
5+
let delayNode: DelayNode | null = null;
6+
let sourceNode: MediaStreamAudioSourceNode | null = null;
7+
let analyserNode: AnalyserNode | null = null;
8+
let stream: MediaStream | null = null;
9+
10+
const isPlaying = ref(false);
11+
const loudnessLevel = ref(0); // Observable for loudness
12+
13+
const startMicReplay = async () => {
14+
if (!audioContext) {
15+
audioContext = new (window.AudioContext || window.webkitAudioContext)();
16+
}
17+
18+
try {
19+
stream = await navigator.mediaDevices.getUserMedia({ audio: true });
20+
} catch (err) {
21+
console.error('Microphone access denied:', err);
22+
alert('Microphone access denied (the error is also in the console):', err);
23+
return;
24+
}
25+
26+
sourceNode = audioContext.createMediaStreamSource(stream);
27+
delayNode = audioContext.createDelay(1.0);
28+
delayNode.delayTime.value = 1.0;
29+
30+
analyserNode = audioContext.createAnalyser();
31+
analyserNode.fftSize = 256;
32+
33+
// Connect nodes: mic -> delay -> speakers
34+
sourceNode.connect(delayNode);
35+
delayNode.connect(audioContext.destination);
36+
sourceNode.connect(analyserNode);
37+
38+
isPlaying.value = true;
39+
measureLoudness();
40+
};
41+
42+
const stopMicReplay = () => {
43+
if (audioContext && stream) {
44+
const tracks = stream.getTracks();
45+
tracks.forEach(track => track.stop());
46+
audioContext.close();
47+
audioContext = null;
48+
isPlaying.value = false;
49+
loudnessLevel.value = 0;
50+
}
51+
};
52+
53+
// Measure loudness and update loudness bar
54+
const measureLoudness = () => {
55+
const dataArray = new Uint8Array(analyserNode!.frequencyBinCount);
56+
57+
const updateLoudness = () => {
58+
analyserNode!.getByteFrequencyData(dataArray);
59+
60+
// Calculate average loudness
61+
let sum = 0;
62+
dataArray.forEach(value => sum += value);
63+
const average = sum / dataArray.length;
64+
65+
// Update the observable loudness level
66+
loudnessLevel.value = average;
67+
68+
if (isPlaying.value) {
69+
requestAnimationFrame(updateLoudness);
70+
}
71+
};
72+
73+
updateLoudness();
74+
};
75+
76+
// Cleanup on service destruction
77+
onBeforeUnmount(() => {
78+
stopMicReplay();
79+
});
80+
81+
return {
82+
startMicReplay,
83+
stopMicReplay,
84+
loudnessLevel,
85+
isPlaying
86+
};
87+
}

src/tools/mic-tester/mic-tester.vue

Lines changed: 19 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -1,121 +1,44 @@
11
<script setup lang="ts">
2-
import { ref, onBeforeUnmount } from 'vue';
2+
import { useMicrophoneService } from './mic-tester.service';
33
import { useI18n } from 'vue-i18n';
4-
import { Microphone } from '@vicons/tabler';
5-
const { t } = useI18n();
6-
7-
// Audio-related variables
8-
let audioContext: AudioContext | null = null;
9-
let delayNode: DelayNode | null = null;
10-
let sourceNode: MediaStreamAudioSourceNode | null = null;
11-
let analyserNode: AnalyserNode | null = null;
12-
let stream: MediaStream | null = null;
13-
14-
const isPlaying = ref(false);
15-
const loudnessBarWidth = ref(0);
16-
17-
const startMicReplayAndLoudMeter = async () => {
18-
if (!audioContext) {
19-
audioContext = new (window.AudioContext || window.webkitAudioContext)();
20-
}
21-
22-
try {
23-
stream = await navigator.mediaDevices.getUserMedia({ audio: true });
24-
} catch (err) {
25-
console.error('Microphone access denied:', err);
26-
alert('Microphone access denied (the error is also in the console):', err);
27-
return;
28-
}
29-
30-
sourceNode = audioContext.createMediaStreamSource(stream);
31-
delayNode = audioContext.createDelay(1.0);
32-
delayNode.delayTime.value = 1.0;
33-
34-
analyserNode = audioContext.createAnalyser();
35-
analyserNode.fftSize = 256;
36-
37-
// Connect nodes: mic -> delay -> speakers
38-
sourceNode.connect(delayNode);
39-
delayNode.connect(audioContext.destination);
40-
sourceNode.connect(analyserNode);
41-
42-
isPlaying.value = true;
43-
measureLoudness();
44-
};
454
46-
const stopMicReplayAndLoudMeter = () => {
47-
if (audioContext && stream) {
48-
const tracks = stream.getTracks();
49-
tracks.forEach(track => track.stop());
50-
audioContext.close();
51-
audioContext = null;
52-
isPlaying.value = false;
53-
loudnessBarWidth.value = 0;
54-
}
55-
};
56-
57-
// Measure loudness and update loudness bar
58-
const measureLoudness = () => {
59-
const dataArray = new Uint8Array(analyserNode!.frequencyBinCount);
60-
61-
const updateLoudness = () => {
62-
analyserNode!.getByteFrequencyData(dataArray);
63-
64-
// Calculate average loudness
65-
let sum = 0;
66-
dataArray.forEach(value => sum += value);
67-
const average = sum / dataArray.length;
68-
69-
// Update loudness bar width based on loudness level
70-
loudnessBarWidth.value = average;
71-
72-
if (isPlaying.value) {
73-
requestAnimationFrame(updateLoudness);
74-
}
75-
};
76-
77-
updateLoudness();
78-
};
79-
80-
// Cleanup on component unmount
81-
onBeforeUnmount(() => {
82-
stopMicReplay();
83-
});
5+
const { t } = useI18n();
6+
const { startMicReplay, stopMicReplay, loudnessLevel, isPlaying } = useMicrophoneService();
847
</script>
858

869
<template>
8710
<div>
8811
<c-card>
8912
<div class="control-buttons">
90-
<c-button @click="startMicReplayAndLoudMeter" :disabled="isPlaying">{{ t('tools.mic-tester.start-button-text') }}</c-button>
91-
<c-button @click="stopMicReplayAndLoudMeter" :disabled="!isPlaying">{{ t('tools.mic-tester.stop-button-text') }}</c-button>
13+
<c-button @click="startMicReplay" :disabled="isPlaying">{{ t('tools.mic-tester.start-button-text') }}</c-button>
14+
<c-button @click="stopMicReplay" :disabled="!isPlaying">{{ t('tools.mic-tester.stop-button-text') }}</c-button>
9215
</div>
9316

9417
<!-- Loudness Meter -->
95-
<div id="loudnessMeter">
96-
<div id="loudnessBar" :style="{ width: loudnessBarWidth + '%' }"></div>
18+
<div id="loudnessMeter">
19+
<div id="loudnessBar" :style="{ width: loudnessLevel + '%' }">
20+
</div>
9721
</div>
9822
</c-card>
9923
</div>
10024
</template>
10125

10226
<style scoped>
10327
#loudnessMeter {
104-
width: 100%;
105-
height: 30px;
106-
background-color: rgba(46, 51, 56, 0.05);
107-
margin-top: 20px;
108-
position: relative;
28+
width: 100%;
29+
height: 30px;
30+
background-color: rgba(46, 51, 56, 0.05);
31+
margin-top: 20px;
32+
position: relative;
10933
}
11034
#loudnessBar {
111-
height: 100%;
112-
background: linear-gradient(48deg, rgba(37, 99, 108, 1) 0%, rgba(59, 149, 111, 1) 60%, rgba(20, 160, 88, 1) 100%);
35+
height: 100%;
36+
background: linear-gradient(48deg, rgba(37, 99, 108, 1) 0%, rgba(59, 149, 111, 1) 60%, rgba(20, 160, 88, 1) 100%);
11337
}
11438
.control-buttons {
115-
display: flex;
116-
gap: 10px;
117-
margin-bottom: 20px;
118-
justify-content: space-between;
39+
display: flex;
40+
gap: 10px;
41+
margin-bottom: 20px;
42+
justify-content: space-between;
11943
}
12044
</style>
121-

0 commit comments

Comments
 (0)