Add PDF_PARSER_VERSION constant and propagate parser versioning throughout the PDF parsing, job, and API layers. Implement normalization of parse state to ensure compatibility with the current parser version, and enable reuse of parsed PDF results when possible. Add isPlaybackReady state to document hooks and TTS player, improving playback UX by disabling controls until content is ready. Update tests to reflect new playback readiness logic. BREAKING CHANGE: PDF parse state and job logic now require explicit parserVersion; older parse states may be treated as pending until reprocessed.
87 lines
2.7 KiB
TypeScript
87 lines
2.7 KiB
TypeScript
'use client';
|
|
|
|
import { useTTS } from '@/contexts/TTSContext';
|
|
import {
|
|
PlayIcon,
|
|
PauseIcon,
|
|
SkipForwardIcon,
|
|
SkipBackwardIcon,
|
|
} from '@/components/icons/Icons';
|
|
import { LoadingSpinner } from '@/components/Spinner';
|
|
import { VoicesControl } from '@/components/player/VoicesControl';
|
|
import { SpeedControl } from '@/components/player/SpeedControl';
|
|
import { Navigator } from '@/components/player/Navigator';
|
|
import { IconButton } from '@/components/ui';
|
|
|
|
export default function TTSPlayer({ currentPage, numPages, isPlaybackReady = true }: {
|
|
currentPage?: number;
|
|
numPages?: number | undefined;
|
|
isPlaybackReady?: boolean;
|
|
}) {
|
|
const {
|
|
isPlaying,
|
|
togglePlay,
|
|
skipForward,
|
|
skipBackward,
|
|
isProcessing,
|
|
setSpeedAndRestart,
|
|
setAudioPlayerSpeedAndRestart,
|
|
setVoiceAndRestart,
|
|
availableVoices,
|
|
skipToLocation,
|
|
} = useTTS();
|
|
|
|
return (
|
|
<div className="sticky bottom-0 z-30 w-full border-t border-line-soft bg-surface" data-app-ttsbar>
|
|
<div className="px-2 md:px-3 pt-1 pb-[max(0.375rem,env(safe-area-inset-bottom))] flex items-center justify-center gap-1 min-h-10">
|
|
{/* Speed control */}
|
|
<SpeedControl
|
|
setSpeedAndRestart={setSpeedAndRestart}
|
|
setAudioPlayerSpeedAndRestart={setAudioPlayerSpeedAndRestart}
|
|
/>
|
|
|
|
{/* Page Navigation */}
|
|
{currentPage && numPages && (
|
|
<Navigator
|
|
currentPage={currentPage}
|
|
numPages={numPages}
|
|
skipToLocation={skipToLocation}
|
|
/>
|
|
)}
|
|
|
|
{/* Playback Controls */}
|
|
<IconButton
|
|
onClick={skipBackward}
|
|
aria-label="Skip backward"
|
|
disabled={isProcessing || !isPlaybackReady}
|
|
className="relative"
|
|
>
|
|
{isProcessing ? <LoadingSpinner /> : <SkipBackwardIcon className="w-5 h-5" />}
|
|
</IconButton>
|
|
|
|
<IconButton
|
|
onClick={togglePlay}
|
|
aria-label={isPlaying ? 'Pause' : 'Play'}
|
|
disabled={!isPlaying && (!isPlaybackReady || isProcessing)}
|
|
className="relative"
|
|
>
|
|
{!isPlaying && !isPlaybackReady
|
|
? <LoadingSpinner />
|
|
: (isPlaying ? <PauseIcon className="w-5 h-5" /> : <PlayIcon className="w-5 h-5" />)}
|
|
</IconButton>
|
|
|
|
<IconButton
|
|
onClick={skipForward}
|
|
aria-label="Skip forward"
|
|
disabled={isProcessing || !isPlaybackReady}
|
|
className="relative"
|
|
>
|
|
{isProcessing ? <LoadingSpinner /> : <SkipForwardIcon className="w-5 h-5" />}
|
|
</IconButton>
|
|
|
|
{/* Voice control */}
|
|
<VoicesControl availableVoices={availableVoices} setVoiceAndRestart={setVoiceAndRestart} />
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|