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';
|
'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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue