Redesign audio controls into a real media player
Build and Push Docker Image / build-and-push (push) Successful in 5m5s
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:
1 parent
b9f9b97cba
commit
f1026dc194
4 files changed
+265
-124
No files matched your search
+29
-45
@@ -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 */}
|
||||
|
||||
Reference in new issue
Block a user