Redesign audio controls into a real media player
Build and Push Docker Image / build-and-push (push) Successful in 5m5s

Adds a shared AudioPlayer component with a seekable progress bar,
big play/pause button, and icon transport controls, replacing the
row of text buttons on both the book and chapter audio players.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
nmemmertandClaude Sonnet 5 committed 2026-09-23 11:59:42 -04:00
1 parent b9f9b97cba
commit f1026dc194
4 files changed
+265 -124

No files matched your search

+29 -45
View File
@@ -2,6 +2,7 @@ import { useState, useEffect, useRef, useMemo, useDeferredValue } from 'react';
import { useApp } from '../context/AppContext.js';
import { formatCrossRef, speakOriginalWord } from '../utils/wordStudy.js';
import AuthStatus from '../components/AuthStatus.jsx';
import AudioPlayer from '../components/AudioPlayer.jsx';
import { bookOptions, BookmarkIcon, CopyIcon } from '../utils/bibleUtils.jsx';
import DrawCanvas from './DrawCanvas.jsx';
@@ -33,6 +34,7 @@ export default function ReaderPage() {
readerSearchActive, setReaderSearchActive,
readerSearchScope, setReaderSearchScope,
readerAudioState,
audioProgress,
bibleIndexStatus,
audioNarrator, setAudioNarrator,
_bibleIndexCacheRef,
@@ -48,6 +50,7 @@ export default function ReaderPage() {
handleToggleReaderAudioPause,
handleStopBookAudio,
handleSeekAudio,
handleSeekAudioTo,
setReaderCrossRefs,
readerInkByPage,
updateReaderPageInk,
@@ -503,53 +506,34 @@ export default function ReaderPage() {
)}
{/* Audio player */}
<div className="mb-4 flex flex-wrap items-center gap-3 rounded-3xl border border-slate-200 bg-white p-4 shadow-panel">
<div className="flex-1">
<h3 className="text-sm font-semibold text-slate-900">Listen to this chapter</h3>
<p className="mt-1 text-xs text-slate-500">
{readerAudioState.status === 'error'
<div className="mb-4">
<AudioPlayer
title="Listen to this chapter"
subtitle={
readerAudioState.status === 'error'
? "Couldn't load audio for this chapter/narrator."
: `Audio follows ${readerBook?.name} ${readerChapter} as you browse.`}
</p>
</div>
<div className="flex flex-wrap items-center gap-2">
<select value={audioNarrator} onChange={(e) => setAudioNarrator(e.target.value)}
className="rounded-xl border border-slate-300 bg-white px-2 py-1.5 text-sm text-slate-900 shadow-sm focus:border-sky-500 focus:outline-none focus:ring-2 focus:ring-sky-200">
<option value="david">David</option>
<option value="hays">Hays</option>
<option value="souer">Souer</option>
</select>
{readerAudioState.status === 'playing' || readerAudioState.status === 'paused' ? (
<>
<button type="button" onClick={() => handleSeekAudio(-10)} title="Rewind 10 seconds"
className="rounded-xl border border-slate-300 bg-white px-3 py-1.5 text-sm font-medium text-slate-700 shadow-sm hover:bg-slate-50">
-10s
</button>
<button type="button" onClick={() => handleSeekAudio(10)} title="Fast-forward 10 seconds"
className="rounded-xl border border-slate-300 bg-white px-3 py-1.5 text-sm font-medium text-slate-700 shadow-sm hover:bg-slate-50">
+10s
</button>
<button type="button" onClick={handleToggleReaderAudioPause}
className="rounded-xl bg-sky-600 px-3 py-1.5 text-sm font-medium text-white shadow-sm hover:bg-sky-500">
{readerAudioState.status === 'paused' ? 'Resume' : 'Pause'}
</button>
<button type="button" onClick={readerGoToNextChapter} disabled={readerChapter >= readerTotalChapters}
title="Skip to next chapter"
className="rounded-xl border border-slate-300 bg-white px-3 py-1.5 text-sm font-medium text-slate-700 shadow-sm hover:bg-slate-50 disabled:opacity-60">
Next chapter
</button>
<button type="button" onClick={handleStopBookAudio}
className="rounded-xl border border-slate-300 bg-white px-3 py-1.5 text-sm font-medium text-slate-700 shadow-sm hover:bg-slate-50">
Stop
</button>
</>
) : (
<button type="button" onClick={handlePlayReaderAudio}
className="rounded-xl bg-sky-600 px-3 py-1.5 text-sm font-medium text-white shadow-sm hover:bg-sky-500">
Play
</button>
: `Audio follows ${readerBook?.name} ${readerChapter} as you browse.`
}
status={readerAudioState.status}
currentTime={audioProgress.currentTime}
duration={audioProgress.duration}
onPlay={handlePlayReaderAudio}
onTogglePause={handleToggleReaderAudioPause}
onSeekBack={() => handleSeekAudio(-10)}
onSeekForward={() => handleSeekAudio(10)}
onSeekTo={handleSeekAudioTo}
onNext={readerGoToNextChapter}
nextDisabled={readerChapter >= readerTotalChapters}
onStop={handleStopBookAudio}
controls={(
<select value={audioNarrator} onChange={(e) => setAudioNarrator(e.target.value)}
className="rounded-xl border border-slate-300 bg-white px-2 py-1.5 text-sm text-slate-900 shadow-sm focus:border-sky-500 focus:outline-none focus:ring-2 focus:ring-sky-200">
<option value="david">David</option>
<option value="hays">Hays</option>
<option value="souer">Souer</option>
</select>
)}
</div>
/>
</div>
{/* Verses — normal scroll mode */}