Reader: ESV chapter audio
Build and Push Docker Image / build-and-push (push) Failing after 19s

The Reader's player follows the translation on screen: ESV plays its own
recording via a server route that resolves the ESV API's audio redirect;
other translations keep the BSB narrators.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
nmemmertandClaude Sonnet 5.5 committed 2026-09-29 09:20:15 -04:00
1 parent e50a73d351
commit 3279dddae5
4 files changed
+51 -13

No files matched your search

+25 -10
View File
@@ -1,9 +1,9 @@
// Chapter audio playback. The Home page's whole-book player and the Reader's
// chapter player share one <audio> element; audioModeRef tracks which owns it.
import { useEffect, useRef, useState } from 'react';
import { bookOptions } from '../utils/bibleUtils.jsx';
import { bookOptions, CHAPTER_COUNTS } from '../utils/bibleUtils.jsx';
export function useAudio(readerBookAbbrev, readerChapter) {
export function useAudio(readerBookAbbrev, readerChapter, readerTranslation = 'BSB') {
const [audioBook, setAudioBook] = useState(bookOptions[0].abbrev);
const [audioNarrator, setAudioNarrator] = useState('souer');
const [audioState, setAudioState] = useState({ status: 'idle', chapter: 0, total: 0 });
@@ -17,13 +17,28 @@ export function useAudio(readerBookAbbrev, readerChapter) {
audioBookRef.current = audioBook;
audioNarratorRef.current = audioNarrator;
audioStateRef.current = audioState;
// The Reader's player follows the translation on screen: ESV has its own
// recording; every other translation falls back to the BSB narrators.
const readerTranslationRef = useRef(readerTranslation);
readerTranslationRef.current = readerTranslation;
const readerAudioSource = () => (readerTranslationRef.current === 'ESV' ? 'ESV' : 'BSB');
const playAudioChapter = async (abbrev, chapterNum, narrator) => {
const res = await fetch(`https://bible.helloao.org/api/BSB/${abbrev}/${chapterNum}.json`);
if (!res.ok) throw new Error('Failed to load chapter audio');
const data = await res.json();
const total = data.book?.numberOfChapters ?? chapterNum;
const url = data.thisChapterAudioLinks?.[narrator];
const source = audioModeRef.current === 'reader' ? readerAudioSource() : 'BSB';
let url;
let total;
if (source === 'ESV') {
const res = await fetch(`/api/bible/ESV/audio/${abbrev}/${chapterNum}`, { credentials: 'include' });
if (!res.ok) throw new Error('Failed to load ESV audio');
url = (await res.json()).url;
total = CHAPTER_COUNTS[abbrev] ?? chapterNum;
} else {
const res = await fetch(`https://bible.helloao.org/api/BSB/${abbrev}/${chapterNum}.json`);
if (!res.ok) throw new Error('Failed to load chapter audio');
const data = await res.json();
total = data.book?.numberOfChapters ?? chapterNum;
url = data.thisChapterAudioLinks?.[narrator];
}
if (!url) throw new Error('Audio not available for this narrator');
if (!audioRef.current) {
audioRef.current = new Audio();
@@ -52,7 +67,7 @@ export function useAudio(readerBookAbbrev, readerChapter) {
audioEl.src = url;
await audioEl.play();
if (audioModeRef.current === 'reader') {
setReaderAudioState({ status: 'playing', chapter: chapterNum });
setReaderAudioState({ status: 'playing', chapter: chapterNum, source });
} else {
setAudioState({ status: 'playing', chapter: chapterNum, total });
}
@@ -149,7 +164,7 @@ export function useAudio(readerBookAbbrev, readerChapter) {
useEffect(() => {
if (audioModeRef.current !== 'reader') return;
if (readerAudioState.status !== 'playing' && readerAudioState.status !== 'paused') return;
if (readerAudioState.chapter === readerChapter) return;
if (readerAudioState.chapter === readerChapter && readerAudioState.source === readerAudioSource()) return;
const wasPaused = readerAudioState.status === 'paused';
playAudioChapter(readerBookAbbrev, readerChapter, audioNarrator)
.then(() => {
@@ -160,7 +175,7 @@ export function useAudio(readerBookAbbrev, readerChapter) {
})
.catch(() => setReaderAudioState({ status: 'error', chapter: 0 }));
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [readerBookAbbrev, readerChapter]);
}, [readerBookAbbrev, readerChapter, readerTranslation]);
useEffect(() => () => {
if (audioRef.current) {