Skip to content

Commit 1e5993a

Browse files
committed
feat: Add boundary sentence preloading for the next content location when nearing the end of the current content.
1 parent 49c5b79 commit 1e5993a

1 file changed

Lines changed: 60 additions & 3 deletions

File tree

src/contexts/TTSContext.tsx

Lines changed: 60 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)