@@ -1526,12 +1526,69 @@ export function TTSProvider({ children }: { children: ReactNode }): ReactElement
15261526 const PRELOAD_LOOKAHEAD = 3 ;
15271527 if ( isAtLimit ) return ;
15281528
1529+ const preloadBoundarySentence = ( ) => {
1530+ // Only add next-location candidates when we're close to the end of the current page/range.
1531+ const remainingInCurrent = Math . max ( 0 , sentences . length - ( currentIndex + 1 ) ) ;
1532+ if ( remainingInCurrent > PRELOAD_LOOKAHEAD ) return ;
1533+
1534+ const targetLocation = pendingNextLocationRef . current ;
1535+ if ( targetLocation === undefined ) return ;
1536+
1537+ const bufferKey = normalizeLocationKey ( targetLocation ) ;
1538+ const prefetchedText = prefetchedLocationTextRef . current . get ( bufferKey ) ;
1539+ if ( ! prefetchedText ?. trim ( ) ) return ;
1540+
1541+ void splitTextToTtsBlocksLocal ( prefetchedText )
1542+ . then ( ( nextSentences ) => {
1543+ const firstSentence = nextSentences [ 0 ] ;
1544+ if ( ! firstSentence ) return ;
1545+
1546+ const firstKey = buildCacheKey (
1547+ firstSentence ,
1548+ voice ,
1549+ speed ,
1550+ configTTSProvider ,
1551+ ttsModel ,
1552+ ) ;
1553+
1554+ if ( audioCache . has ( firstKey ) || preloadRequests . current . has ( firstSentence ) ) return ;
1555+
1556+ return processSentence ( firstSentence , true ) ;
1557+ } )
1558+ . catch ( ( error ) => {
1559+ const status = ( ( ) => {
1560+ if ( typeof error === 'object' && error !== null && 'status' in error ) {
1561+ const maybe = ( error as { status ?: unknown } ) . status ;
1562+ return typeof maybe === 'number' ? maybe : undefined ;
1563+ }
1564+ return undefined ;
1565+ } ) ( ) ;
1566+ const code = ( ( ) => {
1567+ if ( typeof error === 'object' && error !== null && 'code' in error ) {
1568+ const maybe = ( error as { code ?: unknown } ) . code ;
1569+ return typeof maybe === 'string' ? maybe : undefined ;
1570+ }
1571+ return undefined ;
1572+ } ) ( ) ;
1573+ // Ignore quota errors during preload.
1574+ if ( ! ( status === 429 && code === 'USER_DAILY_QUOTA_EXCEEDED' ) ) {
1575+ console . error ( 'Error preloading first sentence from buffered next location:' , error ) ;
1576+ }
1577+ } ) ;
1578+ } ;
1579+
15291580 const preloadFromOffset = ( offset : number ) => {
1530- if ( offset > PRELOAD_LOOKAHEAD ) return ;
1581+ if ( offset > PRELOAD_LOOKAHEAD ) {
1582+ preloadBoundarySentence ( ) ;
1583+ return ;
1584+ }
15311585
15321586 const sentenceIndex = currentIndex + offset ;
15331587 const nextSentence = sentences [ sentenceIndex ] ;
1534- if ( ! nextSentence ) return ;
1588+ if ( ! nextSentence ) {
1589+ preloadBoundarySentence ( ) ;
1590+ return ;
1591+ }
15351592
15361593 const nextKey = buildCacheKey (
15371594 nextSentence ,
@@ -1589,7 +1646,7 @@ export function TTSProvider({ children }: { children: ReactNode }): ReactElement
15891646 } catch ( error ) {
15901647 console . error ( 'Error initiating preload:' , error ) ;
15911648 }
1592- } , [ isAtLimit , currentIndex , sentences , audioCache , processSentence , voice , speed , configTTSProvider , ttsModel ] ) ;
1649+ } , [ isAtLimit , currentIndex , sentences , audioCache , processSentence , splitTextToTtsBlocksLocal , voice , speed , configTTSProvider , ttsModel ] ) ;
15931650
15941651 /**
15951652 * Main Playback Driver
0 commit comments