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 () => {
|
const handlePlayReaderAudio = async () => {
|
||||||
audioModeRef.current = 'reader';
|
audioModeRef.current = 'reader';
|
||||||
try {
|
try {
|
||||||
@@ -131,6 +147,8 @@ export function useAudio(readerBookAbbrev, readerChapter) {
|
|||||||
handlePlayBookAudio,
|
handlePlayBookAudio,
|
||||||
handleStopBookAudio,
|
handleStopBookAudio,
|
||||||
handleToggleBookAudioPause,
|
handleToggleBookAudioPause,
|
||||||
|
handleSkipBookChapter,
|
||||||
|
handleSeekAudio,
|
||||||
readerAudioState,
|
readerAudioState,
|
||||||
handlePlayReaderAudio,
|
handlePlayReaderAudio,
|
||||||
handleToggleReaderAudioPause,
|
handleToggleReaderAudioPause,
|
||||||
|
|||||||
@@ -31,6 +31,8 @@ export default function HomePage() {
|
|||||||
handlePlayBookAudio,
|
handlePlayBookAudio,
|
||||||
handleStopBookAudio,
|
handleStopBookAudio,
|
||||||
handleToggleBookAudioPause,
|
handleToggleBookAudioPause,
|
||||||
|
handleSkipBookChapter,
|
||||||
|
handleSeekAudio,
|
||||||
} = useApp();
|
} = useApp();
|
||||||
const [homeSearch, setHomeSearch] = useState('');
|
const [homeSearch, setHomeSearch] = useState('');
|
||||||
const [homeSort, setHomeSort] = useState(() => {
|
const [homeSort, setHomeSort] = useState(() => {
|
||||||
@@ -266,6 +268,22 @@ export default function HomePage() {
|
|||||||
</select>
|
</select>
|
||||||
{audioState.status === 'playing' || audioState.status === 'paused' ? (
|
{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
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={handleToggleBookAudioPause}
|
onClick={handleToggleBookAudioPause}
|
||||||
@@ -273,6 +291,15 @@ export default function HomePage() {
|
|||||||
>
|
>
|
||||||
{audioState.status === 'paused' ? 'Resume' : 'Pause'}
|
{audioState.status === 'paused' ? 'Resume' : 'Pause'}
|
||||||
</button>
|
</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
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={handleStopBookAudio}
|
onClick={handleStopBookAudio}
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ export default function ReaderPage() {
|
|||||||
handlePlayReaderAudio,
|
handlePlayReaderAudio,
|
||||||
handleToggleReaderAudioPause,
|
handleToggleReaderAudioPause,
|
||||||
handleStopBookAudio,
|
handleStopBookAudio,
|
||||||
|
handleSeekAudio,
|
||||||
setReaderCrossRefs,
|
setReaderCrossRefs,
|
||||||
readerInkByPage,
|
readerInkByPage,
|
||||||
updateReaderPageInk,
|
updateReaderPageInk,
|
||||||
@@ -520,10 +521,23 @@ export default function ReaderPage() {
|
|||||||
</select>
|
</select>
|
||||||
{readerAudioState.status === 'playing' || readerAudioState.status === 'paused' ? (
|
{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}
|
<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">
|
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'}
|
{readerAudioState.status === 'paused' ? 'Resume' : 'Pause'}
|
||||||
</button>
|
</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}
|
<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">
|
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
|
Stop
|
||||||
|
|||||||
Reference in new issue
Block a user