Merge pull request #51 from thepycoder/feature/remember-last-location
This commit is contained in:
commit
b4e538ca3b
2 changed files with 61 additions and 7 deletions
|
|
@ -84,7 +84,8 @@ export function PDFProvider({ children }: { children: ReactNode }) {
|
||||||
stop,
|
stop,
|
||||||
currDocPageNumber: currDocPage,
|
currDocPageNumber: currDocPage,
|
||||||
currDocPages,
|
currDocPages,
|
||||||
setCurrDocPages
|
setCurrDocPages,
|
||||||
|
setIsEPUB
|
||||||
} = useTTS();
|
} = useTTS();
|
||||||
const {
|
const {
|
||||||
headerMargin,
|
headerMargin,
|
||||||
|
|
@ -308,6 +309,13 @@ export function PDFProvider({ children }: { children: ReactNode }) {
|
||||||
}
|
}
|
||||||
}, [pdfDocument, headerMargin, footerMargin, leftMargin, rightMargin, apiKey, baseUrl, voice, voiceSpeed]);
|
}, [pdfDocument, headerMargin, footerMargin, leftMargin, rightMargin, apiKey, baseUrl, voice, voiceSpeed]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Effect hook to initialize TTS as non-EPUB mode
|
||||||
|
*/
|
||||||
|
useEffect(() => {
|
||||||
|
setIsEPUB(false);
|
||||||
|
}, [setIsEPUB]);
|
||||||
|
|
||||||
// Context value memoization
|
// Context value memoization
|
||||||
const contextValue = useMemo(
|
const contextValue = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ import { useAudioCache } from '@/hooks/audio/useAudioCache';
|
||||||
import { useVoiceManagement } from '@/hooks/audio/useVoiceManagement';
|
import { useVoiceManagement } from '@/hooks/audio/useVoiceManagement';
|
||||||
import { useMediaSession } from '@/hooks/audio/useMediaSession';
|
import { useMediaSession } from '@/hooks/audio/useMediaSession';
|
||||||
import { useAudioContext } from '@/hooks/audio/useAudioContext';
|
import { useAudioContext } from '@/hooks/audio/useAudioContext';
|
||||||
import { getLastDocumentLocation } from '@/utils/indexedDB';
|
import { getLastDocumentLocation, setLastDocumentLocation } from '@/utils/indexedDB';
|
||||||
import { useBackgroundState } from '@/hooks/audio/useBackgroundState';
|
import { useBackgroundState } from '@/hooks/audio/useBackgroundState';
|
||||||
import { withRetry } from '@/utils/audio';
|
import { withRetry } from '@/utils/audio';
|
||||||
import { processTextToSentences } from '@/utils/nlp';
|
import { processTextToSentences } from '@/utils/nlp';
|
||||||
|
|
@ -149,6 +149,8 @@ export function TTSProvider({ children }: { children: ReactNode }): ReactElement
|
||||||
const preloadRequests = useRef<Map<string, Promise<string>>>(new Map());
|
const preloadRequests = useRef<Map<string, Promise<string>>>(new Map());
|
||||||
// Track active abort controllers for TTS requests
|
// Track active abort controllers for TTS requests
|
||||||
const activeAbortControllers = useRef<Set<AbortController>>(new Set());
|
const activeAbortControllers = useRef<Set<AbortController>>(new Set());
|
||||||
|
// Track if we're restoring from a saved position
|
||||||
|
const [pendingRestoreIndex, setPendingRestoreIndex] = useState<number | null>(null);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Processes text into sentences using the shared NLP utility
|
* Processes text into sentences using the shared NLP utility
|
||||||
|
|
@ -309,7 +311,17 @@ export function TTSProvider({ children }: { children: ReactNode }): ReactElement
|
||||||
|
|
||||||
// Set all state updates in a predictable order
|
// Set all state updates in a predictable order
|
||||||
setSentences(newSentences);
|
setSentences(newSentences);
|
||||||
setCurrentIndex(0);
|
|
||||||
|
// Check if we have a pending restore index for PDF
|
||||||
|
if (pendingRestoreIndex !== null && !isEPUB) {
|
||||||
|
const restoreIndex = Math.min(pendingRestoreIndex, newSentences.length - 1);
|
||||||
|
console.log(`Restoring sentence index: ${restoreIndex}`);
|
||||||
|
setCurrentIndex(restoreIndex);
|
||||||
|
setPendingRestoreIndex(null); // Clear the pending restore
|
||||||
|
} else {
|
||||||
|
setCurrentIndex(0);
|
||||||
|
}
|
||||||
|
|
||||||
setIsProcessing(false);
|
setIsProcessing(false);
|
||||||
|
|
||||||
// Restore playback state if needed
|
// Restore playback state if needed
|
||||||
|
|
@ -328,7 +340,7 @@ export function TTSProvider({ children }: { children: ReactNode }): ReactElement
|
||||||
duration: 3000,
|
duration: 3000,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}, [isPlaying, handleBlankSection, abortAudio, processTextToSentencesLocal]);
|
}, [isPlaying, handleBlankSection, abortAudio, processTextToSentencesLocal, pendingRestoreIndex, isEPUB]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Toggles the playback state between playing and paused
|
* Toggles the playback state between playing and paused
|
||||||
|
|
@ -867,20 +879,54 @@ export function TTSProvider({ children }: { children: ReactNode }): ReactElement
|
||||||
skipBackward,
|
skipBackward,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Load last location on mount for EPUB only
|
// Load last location on mount for both EPUB and PDF
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (id && isEPUB) {
|
if (id) {
|
||||||
getLastDocumentLocation(id as string).then(lastLocation => {
|
getLastDocumentLocation(id as string).then(lastLocation => {
|
||||||
if (lastLocation) {
|
if (lastLocation) {
|
||||||
console.log('Setting last location:', lastLocation);
|
console.log('Setting last location:', lastLocation);
|
||||||
if (locationChangeHandlerRef.current) {
|
|
||||||
|
if (isEPUB && locationChangeHandlerRef.current) {
|
||||||
|
// For EPUB documents, use the location change handler
|
||||||
locationChangeHandlerRef.current(lastLocation);
|
locationChangeHandlerRef.current(lastLocation);
|
||||||
|
} else if (!isEPUB) {
|
||||||
|
// For PDF documents, parse the location as "page:sentence"
|
||||||
|
try {
|
||||||
|
const [pageStr, sentenceIndexStr] = lastLocation.split(':');
|
||||||
|
const page = parseInt(pageStr, 10);
|
||||||
|
const sentenceIndex = parseInt(sentenceIndexStr, 10);
|
||||||
|
|
||||||
|
if (!isNaN(page) && !isNaN(sentenceIndex)) {
|
||||||
|
console.log(`Restoring PDF position: page ${page}, sentence ${sentenceIndex}`);
|
||||||
|
// Skip to the page first, then the sentence index will be restored when setText is called
|
||||||
|
setCurrDocPage(page);
|
||||||
|
// Store the sentence index to be used when text is loaded
|
||||||
|
setPendingRestoreIndex(sentenceIndex);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('Error parsing PDF location:', error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [id, isEPUB]);
|
}, [id, isEPUB]);
|
||||||
|
|
||||||
|
// Save current position periodically for PDFs
|
||||||
|
useEffect(() => {
|
||||||
|
if (id && !isEPUB && sentences.length > 0) {
|
||||||
|
const location = `${currDocPageNumber}:${currentIndex}`;
|
||||||
|
const timeoutId = setTimeout(() => {
|
||||||
|
console.log(`Saving PDF position: ${location}`);
|
||||||
|
setLastDocumentLocation(id as string, location).catch(error => {
|
||||||
|
console.warn('Error saving PDF location:', error);
|
||||||
|
});
|
||||||
|
}, 1000); // Debounce saves by 1 second
|
||||||
|
|
||||||
|
return () => clearTimeout(timeoutId);
|
||||||
|
}
|
||||||
|
}, [id, isEPUB, currDocPageNumber, currentIndex, sentences.length]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Renders the TTS context provider with its children
|
* Renders the TTS context provider with its children
|
||||||
*
|
*
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue