Chapter by chapter auto-play

This commit is contained in:
Richard Roberson 2025-02-09 02:55:49 -07:00
parent fd6a370bce
commit 25f3375b2f
4 changed files with 73 additions and 57 deletions

View file

@ -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(() => {

View file

@ -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);

View file

@ -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,
]);

View file

@ -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, '');