@@ -150,7 +150,7 @@ export function TTSProvider({ children }: { children: ReactNode }) {
150150 * @returns {Promise<string[]> } Array of processed sentences
151151 */
152152 const processTextToSentences = useCallback ( async ( text : string ) : Promise < string [ ] > => {
153- if ( text . length === 0 ) {
153+ if ( text . length < 1 ) {
154154 return [ ] ;
155155 }
156156
@@ -524,58 +524,107 @@ export function TTSProvider({ children }: { children: ReactNode }) {
524524 return ;
525525 }
526526
527- try {
528- // Get the processed audio data URI directly from processSentence
529- const audioDataUri = await processSentence ( sentence ) ;
530- if ( ! audioDataUri ) {
531- throw new Error ( 'No audio data generated' ) ;
532- }
527+ const MAX_RETRIES = 3 ;
528+ const INITIAL_RETRY_DELAY = 1000 ; // 1 second
533529
534- // Force unload any previous Howl instance to free up resources
535- if ( activeHowl ) {
536- activeHowl . unload ( ) ;
537- }
530+ const createHowl = async ( retryCount = 0 ) : Promise < Howl | null > => {
531+ try {
532+ // Get the processed audio data URI directly from processSentence
533+ const audioDataUri = await processSentence ( sentence ) ;
534+ if ( ! audioDataUri ) {
535+ throw new Error ( 'No audio data generated' ) ;
536+ }
538537
539- const howl = new Howl ( {
540- src : [ audioDataUri ] ,
541- format : [ 'mp3' ] ,
542- html5 : true ,
543- preload : true ,
544- pool : 5 ,
545- onplay : ( ) => {
546- setIsProcessing ( false ) ;
547- if ( 'mediaSession' in navigator ) {
548- navigator . mediaSession . playbackState = 'playing' ;
549- }
550- } ,
551- onpause : ( ) => {
552- if ( 'mediaSession' in navigator ) {
553- navigator . mediaSession . playbackState = 'paused' ;
554- }
555- } ,
556- onend : ( ) => {
557- howl . unload ( ) ;
558- setActiveHowl ( null ) ;
559- if ( isPlaying ) {
560- advance ( ) ;
561- }
562- } ,
563- onloaderror : ( id , error ) => {
564- console . warn ( 'Error loading audio:' , error ) ;
565- setIsProcessing ( false ) ;
566- setActiveHowl ( null ) ;
567- howl . unload ( ) ;
568- setIsPlaying ( false ) ;
569- } ,
570- onstop : ( ) => {
571- setIsProcessing ( false ) ;
572- howl . unload ( ) ;
538+ // Force unload any previous Howl instance to free up resources
539+ if ( activeHowl ) {
540+ activeHowl . unload ( ) ;
573541 }
574- } ) ;
575542
576- setActiveHowl ( howl ) ;
577- return howl ;
543+ return new Howl ( {
544+ src : [ audioDataUri ] ,
545+ format : [ 'mp3' , 'mpeg' ] ,
546+ html5 : true ,
547+ preload : true ,
548+ pool : 5 ,
549+ onplay : ( ) => {
550+ setIsProcessing ( false ) ;
551+ if ( 'mediaSession' in navigator ) {
552+ navigator . mediaSession . playbackState = 'playing' ;
553+ }
554+ } ,
555+ onplayerror : function ( this : Howl , error ) {
556+ console . warn ( 'Howl playback error:' , error ) ;
557+ // Try to recover by forcing HTML5 audio mode
558+ if ( this . state ( ) === 'loaded' ) {
559+ this . unload ( ) ;
560+ this . once ( 'load' , ( ) => {
561+ this . play ( ) ;
562+ } ) ;
563+ this . load ( ) ;
564+ }
565+ } ,
566+ onloaderror : async function ( this : Howl , error ) {
567+ console . warn ( `Error loading audio (attempt ${ retryCount + 1 } /${ MAX_RETRIES } ):` , error ) ;
568+
569+ if ( retryCount < MAX_RETRIES ) {
570+ // Calculate exponential backoff delay
571+ const delay = INITIAL_RETRY_DELAY * Math . pow ( 2 , retryCount ) ;
572+ console . log ( `Retrying in ${ delay } ms...` ) ;
573+
574+ // Wait for the delay
575+ await new Promise ( resolve => setTimeout ( resolve , delay ) ) ;
576+
577+ // Try to create a new Howl instance
578+ const retryHowl = await createHowl ( retryCount + 1 ) ;
579+ if ( retryHowl ) {
580+ setActiveHowl ( retryHowl ) ;
581+ retryHowl . play ( ) ;
582+ }
583+ } else {
584+ console . error ( 'Max retries reached, moving to next sentence' ) ;
585+ setIsProcessing ( false ) ;
586+ setActiveHowl ( null ) ;
587+ this . unload ( ) ;
588+ setIsPlaying ( false ) ;
589+
590+ toast . error ( 'Audio loading failed after retries. Moving to next sentence...' , {
591+ id : 'audio-load-error' ,
592+ style : {
593+ background : 'var(--background)' ,
594+ color : 'var(--accent)' ,
595+ } ,
596+ duration : 2000 ,
597+ } ) ;
598+
599+ advance ( ) ;
600+ }
601+ } ,
602+ onend : function ( this : Howl ) {
603+ this . unload ( ) ;
604+ setActiveHowl ( null ) ;
605+ if ( isPlaying ) {
606+ advance ( ) ;
607+ }
608+ } ,
609+ onstop : function ( this : Howl ) {
610+ setIsProcessing ( false ) ;
611+ this . unload ( ) ;
612+ }
613+ } ) ;
614+ } catch ( error ) {
615+ console . error ( 'Error creating Howl instance:' , error ) ;
616+ return null ;
617+ }
618+ } ;
619+
620+ try {
621+ const howl = await createHowl ( ) ;
622+ if ( howl ) {
623+ setActiveHowl ( howl ) ;
624+ return howl ;
625+ }
578626
627+ throw new Error ( 'Failed to create Howl instance' ) ;
579628 } catch ( error ) {
580629 console . error ( 'Error playing TTS:' , error ) ;
581630 setActiveHowl ( null ) ;
0 commit comments