diff --git a/src/App.jsx b/src/App.jsx index 50142a4..b7725a5 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -904,7 +904,9 @@ const App = () => { const [audioBook, setAudioBook] = useState(bookOptions[0].abbrev); const [audioNarrator, setAudioNarrator] = useState('souer'); const [audioState, setAudioState] = useState({ status: 'idle', chapter: 0, total: 0 }); + const [readerAudioState, setReaderAudioState] = useState({ status: 'idle', chapter: 0 }); const audioRef = useRef(null); + const audioModeRef = useRef('book'); // 'book' | 'reader' — which player owns audioRef const audioBookRef = useRef(audioBook); const audioNarratorRef = useRef(audioNarrator); const audioStateRef = useRef(audioState); @@ -922,6 +924,10 @@ const App = () => { if (!audioRef.current) { audioRef.current = new Audio(); audioRef.current.addEventListener('ended', () => { + if (audioModeRef.current === 'reader') { + setReaderAudioState({ status: 'idle', chapter: 0 }); + return; + } const nextChapter = audioStateRef.current.chapter + 1; if (nextChapter > audioStateRef.current.total) { setAudioState({ status: 'idle', chapter: 0, total: 0 }); @@ -935,10 +941,15 @@ const App = () => { const audioEl = audioRef.current; audioEl.src = url; await audioEl.play(); - setAudioState({ status: 'playing', chapter: chapterNum, total }); + if (audioModeRef.current === 'reader') { + setReaderAudioState({ status: 'playing', chapter: chapterNum }); + } else { + setAudioState({ status: 'playing', chapter: chapterNum, total }); + } }; const handlePlayBookAudio = async () => { + audioModeRef.current = 'book'; try { await playAudioChapter(audioBook, 1, audioNarrator); } catch { @@ -952,6 +963,7 @@ const App = () => { audioRef.current.src = ''; } setAudioState({ status: 'idle', chapter: 0, total: 0 }); + setReaderAudioState({ status: 'idle', chapter: 0 }); }; const handleToggleBookAudioPause = () => { @@ -965,6 +977,43 @@ const App = () => { } }; + const handlePlayReaderAudio = async () => { + audioModeRef.current = 'reader'; + try { + await playAudioChapter(readerBookAbbrev, readerChapter, audioNarrator); + } catch { + setReaderAudioState({ status: 'error', chapter: 0 }); + } + }; + + const handleToggleReaderAudioPause = () => { + if (!audioRef.current) return; + if (audioRef.current.paused) { + audioRef.current.play(); + setReaderAudioState((prev) => ({ ...prev, status: 'playing' })); + } else { + audioRef.current.pause(); + setReaderAudioState((prev) => ({ ...prev, status: 'paused' })); + } + }; + + // Auto-switch the reader's audio to match the book/chapter currently being read. + useEffect(() => { + if (audioModeRef.current !== 'reader') return; + if (readerAudioState.status !== 'playing' && readerAudioState.status !== 'paused') return; + if (readerAudioState.chapter === readerChapter) return; + const wasPaused = readerAudioState.status === 'paused'; + playAudioChapter(readerBookAbbrev, readerChapter, audioNarrator) + .then(() => { + if (wasPaused && audioRef.current) { + audioRef.current.pause(); + setReaderAudioState((prev) => ({ ...prev, status: 'paused' })); + } + }) + .catch(() => setReaderAudioState({ status: 'error', chapter: 0 })); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [readerBookAbbrev, readerChapter]); + useEffect(() => () => { if (audioRef.current) { audioRef.current.pause(); @@ -3099,6 +3148,54 @@ const restoreRemoteProject = async (id) => { +
+ {readerAudioState.status === 'error' + ? "Couldn't load audio for this chapter/narrator." + : `Audio follows ${readerBook?.name} ${readerChapter} as you browse.`} +
+