|
|
@@ -1735,6 +1735,9 @@
|
|
|
|
|
|
pdfViewer.update();
|
|
|
renderingQueue?.renderHighestPriority();
|
|
|
+ if (playbackHighlightContext?.activeSentenceText || playbackHighlightContext?.activeSentenceIndex !== null) {
|
|
|
+ restoreActiveSentenceHighlight();
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
function getFullscreenElement() {
|
|
|
@@ -2307,13 +2310,43 @@
|
|
|
return {
|
|
|
textLayer,
|
|
|
textLayerIndex,
|
|
|
+ fullText,
|
|
|
sentences: splitTextForHighlight(fullText),
|
|
|
searchOffset: 0,
|
|
|
lastWindowKey: '',
|
|
|
- currentRange: null
|
|
|
+ currentRange: null,
|
|
|
+ activeSentenceIndex: null,
|
|
|
+ activeSentenceText: ''
|
|
|
};
|
|
|
}
|
|
|
|
|
|
+ function rebuildPlaybackHighlightContext() {
|
|
|
+ const fullText = playbackHighlightContext?.fullText;
|
|
|
+ if (!fullText) return false;
|
|
|
+ const nextContext = buildPlaybackHighlightContext(fullText);
|
|
|
+ if (!nextContext) return false;
|
|
|
+ nextContext.searchOffset = playbackHighlightContext?.searchOffset || 0;
|
|
|
+ nextContext.activeSentenceIndex = playbackHighlightContext?.activeSentenceIndex ?? null;
|
|
|
+ nextContext.activeSentenceText = playbackHighlightContext?.activeSentenceText || '';
|
|
|
+ playbackHighlightContext = nextContext;
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ function restoreActiveSentenceHighlight(retryCount = 0) {
|
|
|
+ const activeSentenceIndex = playbackHighlightContext?.activeSentenceIndex;
|
|
|
+ const activeSentenceText = playbackHighlightContext?.activeSentenceText || '';
|
|
|
+ if (activeSentenceIndex === null && !activeSentenceText) return;
|
|
|
+
|
|
|
+ clearActiveTtsHighlight();
|
|
|
+ if (rebuildPlaybackHighlightContext()) {
|
|
|
+ highlightSentenceAtIndex(activeSentenceIndex ?? 0, activeSentenceText, true);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (retryCount >= 12) return;
|
|
|
+ setTimeout(() => restoreActiveSentenceHighlight(retryCount + 1), 120);
|
|
|
+ }
|
|
|
+
|
|
|
function updateHighlightWindow(rangeStart, rangeEnd) {
|
|
|
const ctx = playbackHighlightContext;
|
|
|
if (!ctx?.textLayerIndex) return;
|
|
|
@@ -2362,7 +2395,7 @@
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- function highlightSentenceAtIndex(sentenceIndex, sentenceText = '', audio = null) {
|
|
|
+ function highlightSentenceAtIndex(sentenceIndex, sentenceText = '', preserveState = false) {
|
|
|
clearActiveTtsHighlight();
|
|
|
const ctx = playbackHighlightContext;
|
|
|
if (!ctx?.textLayerIndex) return;
|
|
|
@@ -2379,53 +2412,14 @@
|
|
|
if (matchStart === -1) return;
|
|
|
|
|
|
const matchEnd = matchStart + targetSentence.length;
|
|
|
- ctx.searchOffset = matchEnd;
|
|
|
+ if (!preserveState) {
|
|
|
+ ctx.searchOffset = matchEnd;
|
|
|
+ }
|
|
|
ctx.currentRange = { start: matchStart, end: matchEnd };
|
|
|
ctx.lastWindowKey = '';
|
|
|
-
|
|
|
- const words = targetSentence.match(/\S+/g) || [];
|
|
|
- const totalWords = words.length;
|
|
|
- const windowWordCount = Math.min(5, Math.max(1, totalWords));
|
|
|
- const wordRanges = [];
|
|
|
- let searchFrom = matchStart;
|
|
|
- for (const word of words) {
|
|
|
- const wordStart = layerText.indexOf(word, searchFrom);
|
|
|
- if (wordStart === -1) continue;
|
|
|
- const wordEnd = wordStart + word.length;
|
|
|
- wordRanges.push({ start: wordStart, end: wordEnd });
|
|
|
- searchFrom = wordEnd;
|
|
|
- }
|
|
|
-
|
|
|
- if (wordRanges.length === 0) {
|
|
|
- updateHighlightWindow(matchStart, Math.min(matchEnd, matchStart + 24));
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- const applyProgressWindow = () => {
|
|
|
- if (!audio || !Number.isFinite(audio.duration) || audio.duration <= 0) {
|
|
|
- updateHighlightWindow(wordRanges[0].start, wordRanges[Math.min(windowWordCount - 1, wordRanges.length - 1)].end);
|
|
|
- return;
|
|
|
- }
|
|
|
- const progress = Math.min(1, Math.max(0, audio.currentTime / audio.duration));
|
|
|
- const maxOffset = Math.max(0, wordRanges.length - windowWordCount);
|
|
|
- const offset = Math.min(maxOffset, Math.round(progress * maxOffset));
|
|
|
- const startWord = wordRanges[offset];
|
|
|
- const endWord = wordRanges[Math.min(wordRanges.length - 1, offset + windowWordCount - 1)];
|
|
|
- updateHighlightWindow(startWord.start, endWord.end);
|
|
|
- };
|
|
|
-
|
|
|
- applyProgressWindow();
|
|
|
- const runHighlightFrame = () => {
|
|
|
- if (!audio || audio.ended) {
|
|
|
- activeHighlightFrame = null;
|
|
|
- return;
|
|
|
- }
|
|
|
- if (!audio.paused) {
|
|
|
- applyProgressWindow();
|
|
|
- }
|
|
|
- activeHighlightFrame = requestAnimationFrame(runHighlightFrame);
|
|
|
- };
|
|
|
- activeHighlightFrame = requestAnimationFrame(runHighlightFrame);
|
|
|
+ ctx.activeSentenceIndex = sentenceIndex;
|
|
|
+ ctx.activeSentenceText = targetSentence;
|
|
|
+ updateHighlightWindow(matchStart, matchEnd);
|
|
|
}
|
|
|
|
|
|
// 仅识别 PDF 页面文字层里的选择,并记录是该页第几次出现
|
|
|
@@ -2499,7 +2493,7 @@
|
|
|
|
|
|
try {
|
|
|
if (signal.aborted) break;
|
|
|
- highlightSentenceAtIndex(chunk.index, chunk.text || chunk.sentence || chunk.chunk || '', audio);
|
|
|
+ highlightSentenceAtIndex(chunk.index, chunk.text || chunk.sentence || chunk.chunk || '');
|
|
|
loadingIndicator.textContent = `正在播放第 ${chunk.index + 1} 句`;
|
|
|
loadingIndicator.style.display = 'block';
|
|
|
await playAudio(audio);
|