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
@@ -8,6 +8,7 @@ export function useAudio(readerBookAbbrev, readerChapter) {
|
||||
const [audioNarrator, setAudioNarrator] = useState('souer');
|
||||
const [audioState, setAudioState] = useState({ status: 'idle', chapter: 0, total: 0 });
|
||||
const [readerAudioState, setReaderAudioState] = useState({ status: 'idle', chapter: 0 });
|
||||
const [audioProgress, setAudioProgress] = useState({ currentTime: 0, duration: 0 });
|
||||
const audioRef = useRef(null);
|
||||
const audioModeRef = useRef('book'); // 'book' | 'reader' — which player owns audioRef
|
||||
const audioBookRef = useRef(audioBook);
|
||||
@@ -26,6 +27,12 @@ export function useAudio(readerBookAbbrev, readerChapter) {
|
||||
if (!url) throw new Error('Audio not available for this narrator');
|
||||
if (!audioRef.current) {
|
||||
audioRef.current = new Audio();
|
||||
audioRef.current.addEventListener('timeupdate', () => {
|
||||
setAudioProgress({ currentTime: audioRef.current.currentTime, duration: audioRef.current.duration || 0 });
|
||||
});
|
||||
audioRef.current.addEventListener('loadedmetadata', () => {
|
||||
setAudioProgress((prev) => ({ ...prev, duration: audioRef.current.duration || 0 }));
|
||||
});
|
||||
audioRef.current.addEventListener('ended', () => {
|
||||
if (audioModeRef.current === 'reader') {
|
||||
setReaderAudioState({ status: 'idle', chapter: 0 });
|
||||
@@ -67,6 +74,7 @@ export function useAudio(readerBookAbbrev, readerChapter) {
|
||||
}
|
||||
setAudioState({ status: 'idle', chapter: 0, total: 0 });
|
||||
setReaderAudioState({ status: 'idle', chapter: 0 });
|
||||
setAudioProgress({ currentTime: 0, duration: 0 });
|
||||
};
|
||||
|
||||
const handleToggleBookAudioPause = () => {
|
||||
@@ -87,6 +95,13 @@ export function useAudio(readerBookAbbrev, readerChapter) {
|
||||
el.currentTime = Math.min(Math.max(el.currentTime + deltaSeconds, 0), duration);
|
||||
};
|
||||
|
||||
const handleSeekAudioTo = (seconds) => {
|
||||
if (!audioRef.current) return;
|
||||
const el = audioRef.current;
|
||||
const duration = Number.isFinite(el.duration) ? el.duration : 0;
|
||||
el.currentTime = Math.min(Math.max(seconds, 0), duration);
|
||||
};
|
||||
|
||||
const handleSkipBookChapter = () => {
|
||||
const nextChapter = audioStateRef.current.chapter + 1;
|
||||
if (nextChapter > audioStateRef.current.total) return;
|
||||
@@ -144,11 +159,13 @@ export function useAudio(readerBookAbbrev, readerChapter) {
|
||||
audioBook, setAudioBook,
|
||||
audioNarrator, setAudioNarrator,
|
||||
audioState,
|
||||
audioProgress,
|
||||
handlePlayBookAudio,
|
||||
handleStopBookAudio,
|
||||
handleToggleBookAudioPause,
|
||||
handleSkipBookChapter,
|
||||
handleSeekAudio,
|
||||
handleSeekAudioTo,
|
||||
readerAudioState,
|
||||
handlePlayReaderAudio,
|
||||
handleToggleReaderAudioPause,
|
||||
|
||||
Reference in new issue
Block a user