|
import { useCallback, useEffect, useRef } from 'react'; |
|
|
|
export function useAudio() { |
|
const audioContextRef = useRef<AudioContext | null>(null); |
|
|
|
const stopAudio = useCallback(() => { |
|
audioContextRef.current?.close(); |
|
audioContextRef.current = null; |
|
}, []); |
|
|
|
|
|
async function base64ToArrayBuffer(base64: string): Promise<ArrayBuffer> { |
|
const response = await fetch(base64); |
|
return response.arrayBuffer(); |
|
} |
|
|
|
const playAudio = useCallback( |
|
async (base64Data?: string) => { |
|
stopAudio(); |
|
|
|
|
|
if (!base64Data) { |
|
return; |
|
} |
|
|
|
|
|
const audioContext = new AudioContext(); |
|
audioContextRef.current = audioContext; |
|
|
|
|
|
const formattedBase64 = |
|
base64Data.startsWith('data:audio/wav') || base64Data.startsWith('data:audio/wav;base64,') |
|
? base64Data |
|
: `data:audio/wav;base64,${base64Data}`; |
|
|
|
console.log(`formattedBase64: ${formattedBase64.slice(0, 50)} (len: ${formattedBase64.length})`); |
|
|
|
const arrayBuffer = await base64ToArrayBuffer(formattedBase64); |
|
|
|
return new Promise((resolve, reject) => { |
|
|
|
audioContext.decodeAudioData(arrayBuffer, (audioBuffer) => { |
|
|
|
const source = audioContext.createBufferSource(); |
|
const gainNode = audioContext.createGain(); |
|
|
|
|
|
source.buffer = audioBuffer; |
|
gainNode.gain.value = 1.0; |
|
|
|
|
|
source.connect(gainNode); |
|
gainNode.connect(audioContext.destination); |
|
|
|
|
|
source.start(); |
|
|
|
source.onended = () => { |
|
stopAudio(); |
|
resolve(true); |
|
}; |
|
}, (error) => { |
|
console.error('Error decoding audio data:', error); |
|
reject(error); |
|
}); |
|
}) |
|
}, |
|
[stopAudio] |
|
); |
|
|
|
|
|
useEffect(() => { |
|
return () => { |
|
stopAudio(); |
|
}; |
|
}, [stopAudio]); |
|
|
|
|
|
return playAudio; |
|
} |