Chapter by chapter auto-play
This commit is contained in:
parent
fd6a370bce
commit
25f3375b2f
4 changed files with 73 additions and 57 deletions
|
|
@ -1,6 +1,6 @@
|
|||
'use client';
|
||||
|
||||
import { useState, useEffect, useRef, useCallback } from 'react';
|
||||
import { useEffect, useRef, useCallback } from 'react';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { useEPUB } from '@/contexts/EPUBContext';
|
||||
import { useTTS } from '@/contexts/TTSContext';
|
||||
|
|
@ -18,8 +18,8 @@ interface EPUBViewerProps {
|
|||
}
|
||||
|
||||
export function EPUBViewer({ className = '' }: EPUBViewerProps) {
|
||||
const { currDocData, currDocName, currDocPage, currDocPages, extractPageText } = useEPUB();
|
||||
const { skipToLocation, registerLocationChangeHandler } = useTTS();
|
||||
const { currDocData, currDocName, currDocPage, extractPageText } = useEPUB();
|
||||
const { setEPUBPageInChapter, registerLocationChangeHandler } = useTTS();
|
||||
const bookRef = useRef<Book | null>(null);
|
||||
const rendition = useRef<Rendition | undefined>(undefined);
|
||||
const toc = useRef<NavItem[]>([]);
|
||||
|
|
@ -43,11 +43,11 @@ export function EPUBViewer({ className = '' }: EPUBViewerProps) {
|
|||
console.log('Displayed:', displayed, 'Chapter:', chapter);
|
||||
|
||||
locationRef.current = location;
|
||||
skipToLocation(displayed.page, displayed.total);
|
||||
setEPUBPageInChapter(displayed.page, displayed.total, chapter?.label || '');
|
||||
|
||||
extractPageText(bookRef.current, rendition.current);
|
||||
}
|
||||
}, [skipToLocation, extractPageText]);
|
||||
}, [setEPUBPageInChapter, extractPageText]);
|
||||
|
||||
// Register the location change handler
|
||||
useEffect(() => {
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import {
|
|||
} from 'react';
|
||||
import { indexedDBService } from '@/utils/indexedDB';
|
||||
import { useTTS } from '@/contexts/TTSContext';
|
||||
import { Book, Contents, Rendition } from 'epubjs';
|
||||
import { Book, Rendition } from 'epubjs';
|
||||
import { createRangeCfi } from '@/utils/epub';
|
||||
|
||||
interface EPUBContextType {
|
||||
|
|
@ -28,7 +28,7 @@ interface EPUBContextType {
|
|||
const EPUBContext = createContext<EPUBContextType | undefined>(undefined);
|
||||
|
||||
export function EPUBProvider({ children }: { children: ReactNode }) {
|
||||
const { setText: setTTSText, currDocPage, currDocPages, setCurrDocPages } = useTTS();
|
||||
const { setText: setTTSText, stop, currDocPage, currDocPages, setCurrDocPages } = useTTS();
|
||||
|
||||
// Current document state
|
||||
const [currDocData, setCurrDocData] = useState<ArrayBuffer>();
|
||||
|
|
@ -51,8 +51,8 @@ export function EPUBProvider({ children }: { children: ReactNode }) {
|
|||
setCurrDocName(undefined);
|
||||
setCurrDocText(undefined);
|
||||
setCurrDocPages(undefined);
|
||||
setTTSText('');
|
||||
}, [setCurrDocPages, setTTSText]);
|
||||
stop();
|
||||
}, [setCurrDocPages, stop]);
|
||||
|
||||
/**
|
||||
* Sets the current document based on its ID
|
||||
|
|
@ -90,7 +90,6 @@ export function EPUBProvider({ children }: { children: ReactNode }) {
|
|||
const { start, end } = rendition.location;
|
||||
if (!start?.cfi || !end?.cfi) return '';
|
||||
|
||||
|
||||
const rangeCfi = createRangeCfi(start.cfi, end.cfi);
|
||||
|
||||
const range = await book.getRange(rangeCfi);
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ interface TTSContextType {
|
|||
setSpeedAndRestart: (speed: number) => void;
|
||||
setVoiceAndRestart: (voice: string) => void;
|
||||
skipToPage: (page: number) => void;
|
||||
skipToLocation: (page: string | number, total: number) => void;
|
||||
setEPUBPageInChapter: (page: string | number, total: number, chapter: string | number) => void; // Add this line
|
||||
registerLocationChangeHandler: (handler: (location: string | number) => void) => void;
|
||||
}
|
||||
|
||||
|
|
@ -120,7 +120,7 @@ export function TTSProvider({ children }: { children: React.ReactNode }) {
|
|||
const [isProcessing, setIsProcessing] = useState(false);
|
||||
const [speed, setSpeed] = useState(voiceSpeed);
|
||||
const [voice, setVoice] = useState(configVoice);
|
||||
const [currDocPage, setCurrDocPage] = useState<number>(0);
|
||||
const [currDocPage, setCurrDocPage] = useState<number>(1);
|
||||
const [currDocPages, setCurrDocPages] = useState<number>();
|
||||
const [nextPageLoading, setNextPageLoading] = useState(false);
|
||||
|
||||
|
|
@ -217,19 +217,25 @@ export function TTSProvider({ children }: { children: React.ReactNode }) {
|
|||
* Navigates to a specific location in the EPUB document
|
||||
* Similar to skipToPage but for EPUB locations
|
||||
*/
|
||||
const skipToLocation = useCallback((page: string | number, total: number) => {
|
||||
const [currChapter, setCurrChapter] = useState<string | number>('');
|
||||
const setEPUBPageInChapter = useCallback((page: string | number, total: number, chapter: string | number) => {
|
||||
const alreadyPlaying = isPlaying;
|
||||
if (chapter !== currChapter) {
|
||||
setCurrDocPages(total);
|
||||
setCurrChapter(chapter);
|
||||
}
|
||||
|
||||
abortAudio();
|
||||
setIsPlaying(false);
|
||||
setNextPageLoading(true);
|
||||
setCurrentIndex(0);
|
||||
setSentences([]);
|
||||
|
||||
setCurrDocPage(Number(page));
|
||||
setCurrDocPages(total);
|
||||
|
||||
// if (locationChangeHandlerRef.current) {
|
||||
// setIsPlaying(true);
|
||||
// }
|
||||
}, [abortAudio]);
|
||||
setIsPlaying(alreadyPlaying);
|
||||
|
||||
}, [abortAudio, currChapter]);
|
||||
|
||||
/**
|
||||
* Moves to the next or previous sentence
|
||||
|
|
@ -238,41 +244,49 @@ export function TTSProvider({ children }: { children: React.ReactNode }) {
|
|||
* @returns {Promise<void>}
|
||||
*/
|
||||
const advance = useCallback(async (backwards = false) => {
|
||||
setCurrentIndex((prev) => {
|
||||
const nextIndex = prev + (backwards ? -1 : 1);
|
||||
if (nextIndex < sentences.length && nextIndex >= 0) {
|
||||
console.log('Advancing to next sentence:', sentences[nextIndex]);
|
||||
return nextIndex;
|
||||
} else if (nextIndex >= sentences.length && currDocPage < currDocPages!) {
|
||||
console.log('Advancing to next page:', currDocPage + 1);
|
||||
|
||||
// Trigger page turn in React Reader
|
||||
if (locationChangeHandlerRef.current) {
|
||||
locationChangeHandlerRef.current('next');
|
||||
} else {
|
||||
incrementPage();
|
||||
}
|
||||
|
||||
return 0;
|
||||
} else if (nextIndex < 0 && currDocPage > 1) {
|
||||
console.log('Advancing to previous page:', currDocPage - 1);
|
||||
|
||||
// Trigger page turn in React Reader
|
||||
if (locationChangeHandlerRef.current) {
|
||||
locationChangeHandlerRef.current('prev');
|
||||
} else {
|
||||
incrementPage(-1);
|
||||
}
|
||||
|
||||
return 0;
|
||||
} else if (nextIndex >= sentences.length && currDocPage >= currDocPages!) {
|
||||
console.log('Reached end of document');
|
||||
setIsPlaying(false);
|
||||
return prev;
|
||||
const nextIndex = currentIndex + (backwards ? -1 : 1);
|
||||
|
||||
// Handle within current page bounds
|
||||
if (nextIndex < sentences.length && nextIndex >= 0) {
|
||||
console.log('Advancing to next sentence:', sentences[nextIndex]);
|
||||
setCurrentIndex(nextIndex);
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle next page
|
||||
if (nextIndex >= sentences.length && currDocPage < currDocPages!) {
|
||||
console.log('Advancing to next page:', currDocPage + 1);
|
||||
setCurrentIndex(0);
|
||||
|
||||
// Trigger page turn in React Reader
|
||||
if (locationChangeHandlerRef.current) {
|
||||
locationChangeHandlerRef.current('next');
|
||||
} else {
|
||||
incrementPage();
|
||||
}
|
||||
return prev;
|
||||
});
|
||||
}, [sentences, currDocPage, currDocPages]);
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle previous page
|
||||
if (nextIndex < 0 && currDocPage > 1) {
|
||||
console.log('Advancing to previous page:', currDocPage - 1);
|
||||
setCurrentIndex(0);
|
||||
|
||||
// Trigger page turn in React Reader
|
||||
if (locationChangeHandlerRef.current) {
|
||||
locationChangeHandlerRef.current('prev');
|
||||
} else {
|
||||
incrementPage(-1);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle end of document
|
||||
if (nextIndex >= sentences.length && currDocPage >= currDocPages!) {
|
||||
console.log('Reached end of document');
|
||||
setIsPlaying(false);
|
||||
}
|
||||
}, [currentIndex, incrementPage, sentences, currDocPage, currDocPages]);
|
||||
|
||||
/**
|
||||
* Moves forward one sentence in the text
|
||||
|
|
@ -502,9 +516,14 @@ export function TTSProvider({ children }: { children: React.ReactNode }) {
|
|||
const stop = useCallback(() => {
|
||||
// Cancel any ongoing request
|
||||
abortAudio();
|
||||
|
||||
locationChangeHandlerRef.current = null;
|
||||
setIsPlaying(false);
|
||||
setCurrentIndex(0);
|
||||
setSentences([]);
|
||||
setCurrChapter('');
|
||||
setCurrDocPage(1);
|
||||
setCurrDocPages(undefined);
|
||||
setNextPageLoading(false);
|
||||
setIsProcessing(false);
|
||||
}, [abortAudio]);
|
||||
|
||||
|
|
@ -579,7 +598,7 @@ export function TTSProvider({ children }: { children: React.ReactNode }) {
|
|||
setSpeedAndRestart,
|
||||
setVoiceAndRestart,
|
||||
skipToPage,
|
||||
skipToLocation, // Add this line
|
||||
setEPUBPageInChapter, // Add this line
|
||||
registerLocationChangeHandler,
|
||||
}), [
|
||||
isPlaying,
|
||||
|
|
@ -599,7 +618,7 @@ export function TTSProvider({ children }: { children: React.ReactNode }) {
|
|||
setSpeedAndRestart,
|
||||
setVoiceAndRestart,
|
||||
skipToPage,
|
||||
skipToLocation, // Add this line
|
||||
setEPUBPageInChapter, // Add this line
|
||||
registerLocationChangeHandler,
|
||||
]);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
import { EpubCFI } from 'epubjs';
|
||||
|
||||
export function findCommonBase(startCfi: string, endCfi: string): string {
|
||||
// Remove 'epubcfi(' prefix and ')' suffix
|
||||
const start = startCfi.replace(/^epubcfi\(|\)$/g, '');
|
||||
|
|
|
|||
Loading…
Reference in a new issue