Manual page turn - kind of working
This commit is contained in:
parent
2fa91a2448
commit
a3137ed88f
4 changed files with 101 additions and 28 deletions
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
]
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue