Add chapter skip and 10s seek controls to audio players
Build and Push Docker Image / build-and-push (push) Successful in 5m42s
Build and Push Docker Image / build-and-push (push) Successful in 5m42s
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
5287b471d0
commit
b9f9b97cba
3 files changed
+59
No files matched your search
@@ -80,6 +80,22 @@ export function useAudio(readerBookAbbrev, readerChapter) {
|
||||
}
|
||||
};
|
||||
|
||||
const handleSeekAudio = (deltaSeconds) => {
|
||||
if (!audioRef.current) return;
|
||||
const el = audioRef.current;
|
||||
const duration = Number.isFinite(el.duration) ? el.duration : Infinity;
|
||||
el.currentTime = Math.min(Math.max(el.currentTime + deltaSeconds, 0), duration);
|
||||
};
|
||||
|
||||
const handleSkipBookChapter = () => {
|
||||
const nextChapter = audioStateRef.current.chapter + 1;
|
||||
if (nextChapter > audioStateRef.current.total) return;
|
||||
audioModeRef.current = 'book';
|
||||
playAudioChapter(audioBookRef.current, nextChapter, audioNarratorRef.current).catch(() =>
|
||||
setAudioState({ status: 'error', chapter: 0, total: 0 }),
|
||||
);
|
||||
};
|
||||
|
||||
const handlePlayReaderAudio = async () => {
|
||||
audioModeRef.current = 'reader';
|
||||
try {
|
||||
@@ -131,6 +147,8 @@ export function useAudio(readerBookAbbrev, readerChapter) {
|
||||
handlePlayBookAudio,
|
||||
handleStopBookAudio,
|
||||
handleToggleBookAudioPause,
|
||||
handleSkipBookChapter,
|
||||
handleSeekAudio,
|
||||
readerAudioState,
|
||||
handlePlayReaderAudio,
|
||||
handleToggleReaderAudioPause,
|
||||
|
||||
@@ -31,6 +31,8 @@ export default function HomePage() {
|
||||
handlePlayBookAudio,
|
||||
handleStopBookAudio,
|
||||
handleToggleBookAudioPause,
|
||||
handleSkipBookChapter,
|
||||
handleSeekAudio,
|
||||
} = useApp();
|
||||
const [homeSearch, setHomeSearch] = useState('');
|
||||
const [homeSort, setHomeSort] = useState(() => {
|
||||
@@ -266,6 +268,22 @@ export default function HomePage() {
|
||||
</select>
|
||||
{audioState.status === 'playing' || audioState.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={handleToggleBookAudioPause}
|
||||
@@ -273,6 +291,15 @@ export default function HomePage() {
|
||||
>
|
||||
{audioState.status === 'paused' ? 'Resume' : 'Pause'}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleSkipBookChapter}
|
||||
disabled={audioState.chapter >= audioState.total}
|
||||
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}
|
||||
|
||||
@@ -47,6 +47,7 @@ export default function ReaderPage() {
|
||||
handlePlayReaderAudio,
|
||||
handleToggleReaderAudioPause,
|
||||
handleStopBookAudio,
|
||||
handleSeekAudio,
|
||||
setReaderCrossRefs,
|
||||
readerInkByPage,
|
||||
updateReaderPageInk,
|
||||
@@ -520,10 +521,23 @@ export default function ReaderPage() {
|
||||
</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
|
||||
|
||||
Reference in new issue
Block a user