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:
1 parent
e50a73d351
commit
3279dddae5
4 files changed
+51
-13
No files matched your search
+1
-1
@@ -39,7 +39,7 @@ const App = () => {
|
||||
const [syncStatus, setSyncStatus] = useState(''); // '' | 'syncing' | 'synced' | 'error'
|
||||
const [studyBuildBusy, setStudyBuildBusy] = useState(false);
|
||||
const reader = useBibleReader(currentPage);
|
||||
const audio = useAudio(reader.readerBookAbbrev, reader.readerChapter);
|
||||
const audio = useAudio(reader.readerBookAbbrev, reader.readerChapter, reader.readerTranslation);
|
||||
const readingPlans = useReadingPlans();
|
||||
const { authUser, setAuthUser, authServerDown, signIn, logout } = useAuth({
|
||||
onSignIn: (user) => {
|
||||
|
||||
@@ -9,7 +9,9 @@ export default function AudioSheet({ open, onClose, translation, bookName, chapt
|
||||
title={`${bookName} ${chapter}`}
|
||||
subtitle={audio.state.status === 'error'
|
||||
? "Couldn't load audio for this chapter/narrator."
|
||||
: translation !== 'BSB'
|
||||
: translation === 'ESV'
|
||||
? 'ESV audio, read by David Cochran Heath. It follows the chapter as you browse.'
|
||||
: translation !== 'BSB'
|
||||
? `Audio is BSB only — no ${translation} audio is available, so it won't match the ${translation} text on screen.`
|
||||
: 'Audio follows the chapter as you browse.'}
|
||||
status={audio.state.status}
|
||||
@@ -23,7 +25,7 @@ export default function AudioSheet({ open, onClose, translation, bookName, chapt
|
||||
onNext={onNext}
|
||||
nextDisabled={nextDisabled}
|
||||
onStop={audio.stop}
|
||||
controls={(
|
||||
controls={translation === 'ESV' ? null : (
|
||||
<select value={narrator} onChange={(e) => onNarrator(e.target.value)}
|
||||
className="rounded-xl border border-[var(--r-line)] bg-[var(--r-bg)] px-2 py-1.5 text-sm text-[var(--r-text)]">
|
||||
<option value="david">David</option>
|
||||
|
||||
+25
-10
@@ -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) {
|
||||
|
||||
Reference in new issue
Block a user