| import { ref, onBeforeUnmount } from 'vue'; |
| import axios from 'axios'; |
|
|
| interface VADOptions { |
| onSpeechStart?: () => void; |
| onSpeechRealStart?: () => void; |
| onSpeechEnd: (audio: Float32Array) => void; |
| onVADMisfire?: () => void; |
| onFrameProcessed?: (probabilities: { isSpeech: number; notSpeech: number }, frame: Float32Array) => void; |
| positiveSpeechThreshold?: number; |
| negativeSpeechThreshold?: number; |
| redemptionMs?: number; |
| preSpeechPadMs?: number; |
| minSpeechMs?: number; |
| submitUserSpeechOnPause?: boolean; |
| model?: 'v5' | 'legacy'; |
| baseAssetPath?: string; |
| onnxWASMBasePath?: string; |
| } |
|
|
| interface VADInstance { |
| start(): void; |
| pause(): void; |
| listening: boolean; |
| } |
|
|
| |
| declare global { |
| interface Window { |
| vad: { |
| MicVAD: { |
| new(options: VADOptions): Promise<VADInstance>; |
| }; |
| }; |
| } |
| } |
|
|
| |
| |
| |
| |
| export function useVADRecording() { |
| const isRecording = ref(false); |
| const isSpeaking = ref(false); |
| const audioEnergy = ref(0); |
| const vadInstance = ref<VADInstance | null>(null); |
| const isInitialized = ref(false); |
| const onSpeechStartCallback = ref<(() => void) | null>(null); |
| const onSpeechEndCallback = ref<((audio: Float32Array) => void) | null>(null); |
|
|
| |
|
|
| |
| async function initVAD() { |
| if (!window.vad) { |
| console.error('VAD library not loaded. Please ensure the scripts are included in index.html'); |
| return; |
| } |
|
|
| try { |
| vadInstance.value = await (window.vad.MicVAD as any).new({ |
| onSpeechStart: () => { |
| console.log('[VAD] Speech started'); |
| isSpeaking.value = true; |
| |
| if (onSpeechStartCallback.value) { |
| onSpeechStartCallback.value(); |
| } |
| }, |
| onSpeechRealStart: () => { |
| console.log('[VAD] Real speech started'); |
| }, |
| onSpeechEnd: (audio: Float32Array) => { |
| console.log('[VAD] Speech ended, audio length:', audio.length); |
| isSpeaking.value = false; |
| |
| if (onSpeechEndCallback.value) { |
| onSpeechEndCallback.value(audio); |
| } |
| }, |
| onVADMisfire: () => { |
| console.log('[VAD] VAD misfire - speech segment too short'); |
| isSpeaking.value = false; |
| }, |
| onFrameProcessed: (probabilities: { isSpeech: number; notSpeech: number }, frame: Float32Array) => { |
| |
| let sum = 0; |
| for (let i = 0; i < frame.length; i++) { |
| sum += frame[i] * frame[i]; |
| } |
| const rms = Math.sqrt(sum / frame.length); |
| |
| |
| const targetEnergy = Math.min(rms * 5, 1); |
| audioEnergy.value = audioEnergy.value * 0.8 + targetEnergy * 0.2; |
| }, |
| |
| positiveSpeechThreshold: 0.3, |
| negativeSpeechThreshold: 0.25, |
| redemptionMs: 1400, |
| preSpeechPadMs: 800, |
| minSpeechMs: 400, |
| submitUserSpeechOnPause: false, |
| model: 'v5', |
| baseAssetPath: 'https://cdn.jsdelivr.net/npm/@ricky0123/vad-web@0.0.29/dist/', |
| onnxWASMBasePath: 'https://cdn.jsdelivr.net/npm/onnxruntime-web@1.22.0/dist/' |
| }); |
|
|
| isInitialized.value = true; |
| console.log('VAD initialized successfully'); |
| } catch (error) { |
| console.error('Failed to initialize VAD:', error); |
| isInitialized.value = false; |
| } |
| } |
|
|
| |
| async function startRecording( |
| onSpeechStart: () => void, |
| onSpeechEnd: (audio: Float32Array) => void |
| ) { |
| |
| onSpeechStartCallback.value = onSpeechStart; |
| onSpeechEndCallback.value = onSpeechEnd; |
|
|
| if (!isInitialized.value) { |
| await initVAD(); |
| } |
|
|
| if (vadInstance.value) { |
| vadInstance.value.start(); |
| isRecording.value = true; |
| console.log('[VAD] Started'); |
| } |
| } |
|
|
| |
| function stopRecording() { |
| if (vadInstance.value) { |
| vadInstance.value.pause(); |
| isRecording.value = false; |
| isSpeaking.value = false; |
| onSpeechStartCallback.value = null; |
| onSpeechEndCallback.value = null; |
| console.log('[VAD] Stopped'); |
| } |
| } |
|
|
| |
| onBeforeUnmount(() => { |
| if (vadInstance.value && isRecording.value) { |
| stopRecording(); |
| } |
| }); |
|
|
| return { |
| isRecording, |
| isSpeaking, |
| audioEnergy, |
| startRecording, |
| stopRecording |
| }; |
| } |
|
|