Manual page turn - kind of working

This commit is contained in:
Richard Roberson 2025-02-08 20:35:38 -07:00
parent 2fa91a2448
commit a3137ed88f
4 changed files with 101 additions and 28 deletions

View file

@ -1,9 +1,12 @@
'use client'; 'use client';
import { useState } from 'react'; import { useState, useEffect, useRef } from 'react';
import dynamic from 'next/dynamic'; import dynamic from 'next/dynamic';
import { useEPUB } from '@/contexts/EPUBContext'; import { useEPUB } from '@/contexts/EPUBContext';
import { useTTS } from '@/contexts/TTSContext';
import { DocumentSkeleton } from '@/components/DocumentSkeleton'; import { DocumentSkeleton } from '@/components/DocumentSkeleton';
import TTSPlayer from '@/components/player/TTSPlayer';
import type { Rendition, Book, NavItem } from 'epubjs';
const ReactReader = dynamic(() => import('react-reader').then(mod => mod.ReactReader), { const ReactReader = dynamic(() => import('react-reader').then(mod => mod.ReactReader), {
ssr: false, ssr: false,
@ -15,22 +18,53 @@ interface EPUBViewerProps {
} }
export function EPUBViewer({ className = '' }: EPUBViewerProps) { export function EPUBViewer({ className = '' }: EPUBViewerProps) {
const { currDocData, currDocName } = useEPUB(); // Changed from currDocURL const { currDocData, currDocName, currDocPage, currDocPages, extractPageText } = useEPUB();
const [location, setLocation] = useState<string | number>(0); const { skipToLocation } = useTTS();
const bookRef = useRef<Book | null>(null);
const rendition = useRef<Rendition | undefined>(undefined);
const toc = useRef<NavItem[]>([]);
const locationRef = useRef<string | number>(currDocPage);
const handleLocationChanged = async (location: string | number) => {
if (bookRef.current && rendition.current) {
const { displayed, href } = rendition.current.location.start
const chapter = toc.current.find((item) => item.href === href)
console.log('Displayed:', displayed, 'Chapter:', chapter);
// Update the current location
locationRef.current = location;
// Skip to the current location in the TTS
skipToLocation(displayed.page, displayed.total);
// Extract text using the current rendition
await extractPageText(bookRef.current, rendition.current);
}
};
if (!currDocData) { if (!currDocData) {
return <DocumentSkeleton />; return <DocumentSkeleton />;
} }
return ( return (
<div style={{ height: '100vh' }} className={className}> <div className={`h-screen flex flex-col ${className}`}>
<ReactReader <div className="z-10">
location={location} <TTSPlayer />
locationChanged={setLocation} </div>
url={currDocData} // ReactReader can accept ArrayBuffer directly <div className="flex-1 -mt-16 pt-16">
title={currDocName} <ReactReader
showToc={true} location={locationRef.current}
/> locationChanged={handleLocationChanged}
url={currDocData}
title={currDocName}
tocChanged={(_toc) => (toc.current = _toc)}
showToc={true}
getRendition={(_rendition: Rendition) => {
bookRef.current = _rendition.book;
rendition.current = _rendition;
}}
/>
</div>
</div> </div>
); );
} }

View file

@ -14,8 +14,8 @@ import { SpeedControl } from '@/components/player/SpeedControl';
import { Navigator } from '@/components/player/Navigator'; import { Navigator } from '@/components/player/Navigator';
export default function TTSPlayer({ currentPage, numPages }: { export default function TTSPlayer({ currentPage, numPages }: {
currentPage: number; currentPage?: number;
numPages: number | undefined; numPages?: number | undefined;
}) { }) {
const { const {
isPlaying, isPlaying,
@ -36,7 +36,8 @@ export default function TTSPlayer({ currentPage, numPages }: {
<SpeedControl setSpeedAndRestart={setSpeedAndRestart} /> <SpeedControl setSpeedAndRestart={setSpeedAndRestart} />
{/* Page Navigation */} {/* Page Navigation */}
<Navigator currentPage={currentPage} numPages={numPages} skipToPage={skipToPage} /> {currentPage && numPages
&& <Navigator currentPage={currentPage} numPages={numPages} skipToPage={skipToPage} />}
{/* Playback Controls */} {/* Playback Controls */}
<Button <Button

View file

@ -10,35 +10,27 @@ import {
} from 'react'; } from 'react';
import { indexedDBService } from '@/utils/indexedDB'; import { indexedDBService } from '@/utils/indexedDB';
import { useTTS } from '@/contexts/TTSContext'; import { useTTS } from '@/contexts/TTSContext';
import { Book, Contents, Rendition } from 'epubjs';
interface EPUBContextType { interface EPUBContextType {
// Current document state currDocData: ArrayBuffer | undefined;
currDocData: ArrayBuffer | undefined; // Changed back to currDocData
currDocName: string | undefined; currDocName: string | undefined;
currDocPages: number | undefined; currDocPages: number | undefined;
currDocPage: number; currDocPage: number;
currDocText: string | undefined; currDocText: string | undefined;
setCurrentDocument: (id: string) => Promise<void>; setCurrentDocument: (id: string) => Promise<void>;
clearCurrDoc: () => void; clearCurrDoc: () => void;
// EPUB functionality
onDocumentLoadSuccess: ({ numPages }: { numPages: number }) => void; onDocumentLoadSuccess: ({ numPages }: { numPages: number }) => void;
extractPageText: (book: Book, rendition: Rendition) => Promise<string>;
} }
// Create the context
const EPUBContext = createContext<EPUBContextType | undefined>(undefined); const EPUBContext = createContext<EPUBContextType | undefined>(undefined);
/**
* EPUBProvider Component
*
* Main provider component that manages EPUB state and functionality.
* Handles document loading, text processing, and integration with TTS.
*/
export function EPUBProvider({ children }: { children: ReactNode }) { export function EPUBProvider({ children }: { children: ReactNode }) {
const { setText: setTTSText, currDocPage, currDocPages, setCurrDocPages } = useTTS(); const { setText: setTTSText, currDocPage, currDocPages, setCurrDocPages } = useTTS();
// Current document state // Current document state
const [currDocData, setCurrDocData] = useState<ArrayBuffer>(); // Changed back to currDocData const [currDocData, setCurrDocData] = useState<ArrayBuffer>();
const [currDocName, setCurrDocName] = useState<string>(); const [currDocName, setCurrDocName] = useState<string>();
const [currDocText, setCurrDocText] = useState<string>(); const [currDocText, setCurrDocText] = useState<string>();
@ -87,28 +79,55 @@ export function EPUBProvider({ children }: { children: ReactNode }) {
} }
}, [clearCurrDoc]); }, [clearCurrDoc]);
/**
* Extracts text content from the current EPUB page/location
*/
const extractPageText = useCallback(async (book: Book, rendition: Rendition): Promise<string> => {
try {
console.log('Extracting EPUB text from current location');
const { start } = rendition.location;
if (!start) return '';
const section = await book.spine.get(start.cfi);
if (!section) return '';
const content = await section.load();
const textContent = content.textContent || '';
setTTSText(textContent);
setCurrDocText(textContent);
return textContent;
} catch (error) {
console.error('Error extracting EPUB text:', error);
return '';
}
}, [setTTSText]);
// Context value memoization // Context value memoization
const contextValue = useMemo( const contextValue = useMemo(
() => ({ () => ({
onDocumentLoadSuccess, onDocumentLoadSuccess,
setCurrentDocument, setCurrentDocument,
currDocData, // Changed back to currDocData currDocData,
currDocName, currDocName,
currDocPages, currDocPages,
currDocPage, currDocPage,
currDocText, currDocText,
clearCurrDoc, clearCurrDoc,
extractPageText,
}), }),
[ [
onDocumentLoadSuccess, onDocumentLoadSuccess,
setCurrentDocument, setCurrentDocument,
currDocData, // Changed back to currDocData currDocData,
currDocName, currDocName,
currDocPages, currDocPages,
currDocPage, currDocPage,
currDocText, currDocText,
clearCurrDoc, clearCurrDoc,
extractPageText,
] ]
); );

View file

@ -69,6 +69,7 @@ interface TTSContextType {
setSpeedAndRestart: (speed: number) => void; setSpeedAndRestart: (speed: number) => void;
setVoiceAndRestart: (voice: string) => void; setVoiceAndRestart: (voice: string) => void;
skipToPage: (page: number) => void; skipToPage: (page: number) => void;
skipToLocation: (page: string | number, total: number) => void;
} }
// Create the context // Create the context
@ -114,6 +115,8 @@ export function TTSProvider({ children }: { children: React.ReactNode }) {
const [currDocPages, setCurrDocPages] = useState<number>(); const [currDocPages, setCurrDocPages] = useState<number>();
const [nextPageLoading, setNextPageLoading] = useState(false); const [nextPageLoading, setNextPageLoading] = useState(false);
console.log('page:', currDocPage, 'pages:', currDocPages);
/** /**
* Changes the current page by a specified amount * Changes the current page by a specified amount
* *
@ -201,6 +204,20 @@ export function TTSProvider({ children }: { children: React.ReactNode }) {
setCurrDocPage(page); setCurrDocPage(page);
}, [abortAudio]); }, [abortAudio]);
/**
* Navigates to a specific location in the EPUB document
* Similar to skipToPage but for EPUB locations
*/
const skipToLocation = useCallback((page: string | number, total: number) => {
setCurrDocPages(total);
setCurrDocPage(Number(page));
abortAudio();
setIsPlaying(false);
setNextPageLoading(true);
setCurrentIndex(0);
}, [abortAudio]);
/** /**
* Moves to the next or previous sentence * Moves to the next or previous sentence
* *
@ -539,6 +556,7 @@ export function TTSProvider({ children }: { children: React.ReactNode }) {
setSpeedAndRestart, setSpeedAndRestart,
setVoiceAndRestart, setVoiceAndRestart,
skipToPage, skipToPage,
skipToLocation, // Add this line
}), [ }), [
isPlaying, isPlaying,
isProcessing, isProcessing,
@ -557,6 +575,7 @@ export function TTSProvider({ children }: { children: React.ReactNode }) {
setSpeedAndRestart, setSpeedAndRestart,
setVoiceAndRestart, setVoiceAndRestart,
skipToPage, skipToPage,
skipToLocation, // Add this line
]); ]);
// Use media session hook // Use media session hook