fix(audio): seamless error recovery and smart preload delays

Improve error handling to maintain continuous playback:
- Remove setIsPlaying(false) from error handlers to keep playback going
- Replace error toasts with console warnings for non-intrusive experience
- Automatically skip problematic sentences without stopping playback
- User gets seamless reading experience even when some audio fails

Add progressive preload delays:
- Stagger preload requests with 50ms, 100ms, 150ms delays
- Prevents overwhelming the server with simultaneous requests
- Maintains smooth buffering without server congestion
- Better handling of rate limits and connection issues

Changes:
- onloaderror: Continue playback after max retries instead of stopping
- playSentenceWithHowl catch: Silent recovery without toast interruptions
- preloadNextAudio: Progressive setTimeout delays for each preload request
- All error messages downgraded from toast.error to console.warn
This commit is contained in:
Claude 2025-12-31 11:21:54 +00:00
parent 453765076c
commit 70ff7a4f68
No known key found for this signature in database

View file

@ -1057,20 +1057,16 @@ export function TTSProvider({ children }: { children: ReactNode }): ReactElement
retryHowl.play(); retryHowl.play();
} }
} else { } else {
console.error('Max retries reached, moving to next sentence'); console.warn('Max retries reached, skipping to next sentence');
setIsProcessing(false); setIsProcessing(false);
setActiveHowl(null); setActiveHowl(null);
this.unload(); this.unload();
setIsPlaying(false); // Don't stop playback - keep it seamless by continuing to next sentence
// setIsPlaying remains true so playback continues
toast.error('Audio loading failed after retries. Moving to next sentence...', { // Less intrusive notification - just log to console
id: 'audio-load-error', // User won't be interrupted by error toasts during reading
style: { console.warn('Skipping problematic sentence, continuing playback...');
background: 'var(--background)',
color: 'var(--accent)',
},
duration: 2000,
});
advance(); advance();
} }
@ -1119,18 +1115,13 @@ export function TTSProvider({ children }: { children: ReactNode }): ReactElement
throw new Error('Failed to create Howl instance'); throw new Error('Failed to create Howl instance');
} catch (error) { } catch (error) {
console.error('Error playing TTS:', error); console.warn('Error processing TTS, skipping to next sentence:', error);
setActiveHowl(null); setActiveHowl(null);
setIsProcessing(false); setIsProcessing(false);
toast.error('Failed to process audio. Skipping problematic sentence.', { // Keep playback going seamlessly - don't stop on errors
id: 'tts-processing-error', // Just log to console and move to next sentence
style: { console.warn('Skipping problematic sentence, continuing playback...');
background: 'var(--background)',
color: 'var(--accent)',
},
duration: 3000,
});
advance(); advance();
return null; return null;
@ -1211,11 +1202,13 @@ export function TTSProvider({ children }: { children: ReactNode }): ReactElement
/** /**
* Preloads the next 3 sentences' audio for smoother playback * Preloads the next 3 sentences' audio for smoother playback
* Uses progressive delays to avoid overwhelming the server
*/ */
const preloadNextAudio = useCallback(async () => { const preloadNextAudio = useCallback(async () => {
try { try {
// Preload the next 3 sentences to ensure smooth playback // Preload the next 3 sentences to ensure smooth playback
const PRELOAD_AHEAD_COUNT = 3; const PRELOAD_AHEAD_COUNT = 3;
const PRELOAD_DELAY_MS = 50; // Small delay between requests
for (let i = 1; i <= PRELOAD_AHEAD_COUNT; i++) { for (let i = 1; i <= PRELOAD_AHEAD_COUNT; i++) {
const nextSentence = sentences[currentIndex + i]; const nextSentence = sentences[currentIndex + i];
@ -1230,14 +1223,20 @@ export function TTSProvider({ children }: { children: ReactNode }): ReactElement
); );
if (!audioCache.has(nextKey) && !preloadRequests.current.has(nextSentence)) { if (!audioCache.has(nextKey) && !preloadRequests.current.has(nextSentence)) {
// Start preloading but don't wait for it to complete // Add progressive delay to avoid overwhelming the server
processSentence(nextSentence, true).catch(error => { // First request: 50ms, second: 100ms, third: 150ms
console.error(`Error preloading sentence ${i} ahead:`, error); const delay = PRELOAD_DELAY_MS * i;
});
setTimeout(() => {
// Start preloading but don't wait for it to complete
processSentence(nextSentence, true).catch(error => {
console.warn(`Error preloading sentence ${i} ahead:`, error);
});
}, delay);
} }
} }
} catch (error) { } catch (error) {
console.error('Error initiating preload:', error); console.warn('Error initiating preload:', error);
} }
}, [currentIndex, sentences, audioCache, processSentence, voice, speed, configTTSProvider, ttsModel]); }, [currentIndex, sentences, audioCache, processSentence, voice, speed, configTTSProvider, ttsModel]);