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';
import { useState } from 'react';
import { useState, useEffect, useRef } from 'react';
import dynamic from 'next/dynamic';
import { useEPUB } from '@/contexts/EPUBContext';
import { useTTS } from '@/contexts/TTSContext';
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), {
ssr: false,
@ -15,22 +18,53 @@ interface EPUBViewerProps {
}
export function EPUBViewer({ className = '' }: EPUBViewerProps) {
const { currDocData, currDocName } = useEPUB(); // Changed from currDocURL
const [location, setLocation] = useState<string | number>(0);
const { currDocData, currDocName, currDocPage, currDocPages, extractPageText } = useEPUB();
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) {
return <DocumentSkeleton />;
}
return (
<div style={{ height: '100vh' }} className={className}>
<ReactReader
location={location}
locationChanged={setLocation}
url={currDocData} // ReactReader can accept ArrayBuffer directly
title={currDocName}
showToc={true}
/>
<div className={`h-screen flex flex-col ${className}`}>
<div className="z-10">
<TTSPlayer />
</div>
<div className="flex-1 -mt-16 pt-16">
<ReactReader
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>
);
}

View file

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

View file

@ -10,35 +10,27 @@ import {
} from 'react';
import { indexedDBService } from '@/utils/indexedDB';
import { useTTS } from '@/contexts/TTSContext';
import { Book, Contents, Rendition } from 'epubjs';
interface EPUBContextType {
// Current document state
currDocData: ArrayBuffer | undefined; // Changed back to currDocData
currDocData: ArrayBuffer | undefined;
currDocName: string | undefined;
currDocPages: number | undefined;
currDocPage: number;
currDocText: string | undefined;
setCurrentDocument: (id: string) => Promise<void>;
clearCurrDoc: () => void;
// EPUB functionality
onDocumentLoadSuccess: ({ numPages }: { numPages: number }) => void;
extractPageText: (book: Book, rendition: Rendition) => Promise<string>;
}
// Create the context
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 }) {
const { setText: setTTSText, currDocPage, currDocPages, setCurrDocPages } = useTTS();
// Current document state
const [currDocData, setCurrDocData] = useState<ArrayBuffer>(); // Changed back to currDocData
const [currDocData, setCurrDocData] = useState<ArrayBuffer>();
const [currDocName, setCurrDocName] = useState<string>();
const [currDocText, setCurrDocText] = useState<string>();
@ -87,28 +79,55 @@ export function EPUBProvider({ children }: { children: ReactNode }) {
}
}, [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
const contextValue = useMemo(
() => ({
onDocumentLoadSuccess,
setCurrentDocument,
currDocData, // Changed back to currDocData
currDocData,
currDocName,
currDocPages,
currDocPage,
currDocText,
clearCurrDoc,
extractPageText,
}),
[
onDocumentLoadSuccess,
setCurrentDocument,
currDocData, // Changed back to currDocData
currDocData,
currDocName,
currDocPages,
currDocPage,
currDocText,
clearCurrDoc,
extractPageText,
]
);

View file

@ -69,6 +69,7 @@ interface TTSContextType {
setSpeedAndRestart: (speed: number) => void;
setVoiceAndRestart: (voice: string) => void;
skipToPage: (page: number) => void;
skipToLocation: (page: string | number, total: number) => void;
}
// Create the context
@ -114,6 +115,8 @@ export function TTSProvider({ children }: { children: React.ReactNode }) {
const [currDocPages, setCurrDocPages] = useState<number>();
const [nextPageLoading, setNextPageLoading] = useState(false);
console.log('page:', currDocPage, 'pages:', currDocPages);
/**
* Changes the current page by a specified amount
*
@ -201,6 +204,20 @@ export function TTSProvider({ children }: { children: React.ReactNode }) {
setCurrDocPage(page);
}, [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
*
@ -539,6 +556,7 @@ export function TTSProvider({ children }: { children: React.ReactNode }) {
setSpeedAndRestart,
setVoiceAndRestart,
skipToPage,
skipToLocation, // Add this line
}), [
isPlaying,
isProcessing,
@ -557,6 +575,7 @@ export function TTSProvider({ children }: { children: React.ReactNode }) {
setSpeedAndRestart,
setVoiceAndRestart,
skipToPage,
skipToLocation, // Add this line
]);
// Use media session hook