@@ -174,6 +174,7 @@ export function TTSProvider({ children }: { children: ReactNode }) {
174174 const abortAudio = useCallback ( ( clearPending = false ) => {
175175 if ( activeHowl ) {
176176 activeHowl . stop ( ) ;
177+ activeHowl . unload ( ) ; // Ensure Howl instance is fully cleaned up
177178 setActiveHowl ( null ) ;
178179 }
179180 if ( clearPending ) {
@@ -274,27 +275,34 @@ export function TTSProvider({ children }: { children: ReactNode }) {
274275 // Check for blank section first
275276 if ( handleBlankSection ( text ) ) return ;
276277
277- // Keep track of previous state
278+ // Keep track of previous state and pause playback
278279 const wasPlaying = isPlaying ;
280+ setIsPlaying ( false ) ;
281+ abortAudio ( true ) ; // Clear pending requests since text is changing
282+ setIsProcessing ( true ) ; // Set processing state before text processing starts
279283
280284 console . log ( 'Setting text:' , text ) ;
281285 processTextToSentences ( text )
282286 . then ( newSentences => {
283287 if ( newSentences . length === 0 ) {
284288 console . warn ( 'No sentences found in text' ) ;
289+ setIsProcessing ( false ) ;
285290 return ;
286291 }
287292
293+ // Set all state updates in a predictable order
288294 setSentences ( newSentences ) ;
289295 setCurrentIndex ( 0 ) ;
296+ setIsProcessing ( false ) ;
290297
291- // Only restore previous playback state if we shouldn't pause
292- if ( shouldPause ) setIsPlaying ( false ) ;
293- else if ( wasPlaying ) setIsPlaying ( true ) ;
294-
298+ // Restore playback state if needed
299+ if ( ! shouldPause && wasPlaying ) {
300+ setIsPlaying ( true ) ;
301+ }
295302 } )
296303 . catch ( error => {
297304 console . warn ( 'Error processing text:' , error ) ;
305+ setIsProcessing ( false ) ;
298306 toast . error ( 'Failed to process text' , {
299307 style : {
300308 background : 'var(--background)' ,
@@ -303,7 +311,7 @@ export function TTSProvider({ children }: { children: ReactNode }) {
303311 duration : 3000 ,
304312 } ) ;
305313 } ) ;
306- } , [ processTextToSentences , handleBlankSection , isPlaying ] ) ;
314+ } , [ isPlaying , handleBlankSection , abortAudio , processTextToSentences ] ) ;
307315
308316 /**
309317 * Toggles the playback state between playing and paused
@@ -493,12 +501,17 @@ export function TTSProvider({ children }: { children: ReactNode }) {
493501 throw new Error ( 'No audio URL generated' ) ;
494502 }
495503
504+ // Force unload any previous Howl instance to free up resources
505+ if ( activeHowl ) {
506+ activeHowl . unload ( ) ;
507+ }
508+
496509 const howl = new Howl ( {
497510 src : [ audioUrl ] ,
498511 format : [ 'mp3' ] ,
499512 html5 : true ,
500513 preload : true ,
501- pool : 1 ,
514+ pool : 5 , // Reduced pool size for iOS compatibility
502515 onplay : ( ) => {
503516 setIsProcessing ( false ) ;
504517 if ( 'mediaSession' in navigator ) {
@@ -512,6 +525,7 @@ export function TTSProvider({ children }: { children: ReactNode }) {
512525 } ,
513526 onend : ( ) => {
514527 URL . revokeObjectURL ( audioUrl ) ;
528+ howl . unload ( ) ; // Explicitly unload when done
515529 setActiveHowl ( null ) ;
516530 if ( isPlaying ) {
517531 advance ( ) ;
@@ -522,12 +536,14 @@ export function TTSProvider({ children }: { children: ReactNode }) {
522536 setIsProcessing ( false ) ;
523537 setActiveHowl ( null ) ;
524538 URL . revokeObjectURL ( audioUrl ) ;
539+ howl . unload ( ) ; // Ensure cleanup on error
525540 // Don't auto-advance on load error
526541 setIsPlaying ( false ) ;
527542 } ,
528543 onstop : ( ) => {
529544 setIsProcessing ( false ) ;
530545 URL . revokeObjectURL ( audioUrl ) ;
546+ howl . unload ( ) ; // Ensure cleanup on stop
531547 }
532548 } ) ;
533549
@@ -550,7 +566,7 @@ export function TTSProvider({ children }: { children: ReactNode }) {
550566
551567 advance ( ) ; // Skip problematic sentence
552568 }
553- } , [ isPlaying , processSentence , advance ] ) ;
569+ } , [ isPlaying , processSentence , advance , activeHowl ] ) ;
554570
555571 /**
556572 * Preloads the next sentence's audio
0 commit comments