import { useEffect, useRef, useState } from 'react' // Nur eine Stimme gleichzeitig im Feed let currentAudio = null // Reines Vorlesen wird gedrosselt (User-Wunsch). Pitch bleibt durch den // Browser-Default `preservesPitch` erhalten. const PLAYBACK_RATE = 0.7 export default function usePairAudio(url) { const audioRef = useRef(null) const rafRef = useRef(null) const failedRef = useRef(false) const [playing, setPlaying] = useState(false) const [currentTime, setCurrentTime] = useState(0) const [failed, setFailed] = useState(false) const markFailed = () => { failedRef.current = true; setFailed(true) } useEffect(() => { if (!url) { audioRef.current = null; return } failedRef.current = false setFailed(false) const audio = new Audio(url) audio.preload = 'auto' audio.playbackRate = PLAYBACK_RATE // currentTime ~30fps mitschreiben, solange das Audio läuft (für Karaoke-Sync). let lastTick = 0 const tick = (ts) => { if (ts - lastTick >= 33) { lastTick = ts; setCurrentTime(audio.currentTime) } rafRef.current = requestAnimationFrame(tick) } const stopLoop = () => { if (rafRef.current) { cancelAnimationFrame(rafRef.current); rafRef.current = null } } const onPlay = () => { setPlaying(true); stopLoop(); rafRef.current = requestAnimationFrame(tick) } const onStop = () => { setPlaying(false); stopLoop(); setCurrentTime(audio.currentTime) } const onEnded = () => { setPlaying(false); stopLoop(); setCurrentTime(0) } // Netz-/Decode-Fehler eines vorhandenen Files → File gilt als nicht abspielbar, // damit play() den TTS-Fallback der Karte greifen lässt. const onError = () => { setPlaying(false); stopLoop(); markFailed() } audio.addEventListener('play', onPlay) audio.addEventListener('pause', onStop) audio.addEventListener('ended', onEnded) audio.addEventListener('error', onError) audioRef.current = audio return () => { stopLoop() audio.pause() audio.removeEventListener('play', onPlay) audio.removeEventListener('pause', onStop) audio.removeEventListener('ended', onEnded) audio.removeEventListener('error', onError) if (currentAudio === audio) currentAudio = null audio.src = '' audioRef.current = null } }, [url]) function play() { const audio = audioRef.current if (!audio || failedRef.current) return false if (currentAudio && currentAudio !== audio) currentAudio.pause() currentAudio = audio audio.currentTime = 0 audio.playbackRate = PLAYBACK_RATE // AbortError = von einem pause() unterbrochen (harmlos); echte Quell-/Decode-Fehler // markieren das File als nicht abspielbar → Karte fällt auf TTS zurück. audio.play().catch((err) => { if (err?.name !== 'AbortError') markFailed() }) return true } return { play, playing, currentTime, failed } }