Fix overlapping audio when explaining multi-line PDF selections.

Use exclusive playback sessions so popup and pane do not play simultaneously, and stop standalone audio when the lecture pane is closed.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
yhy
2026-08-29 23:23:16 +08:00
co-authored by Cursor
parent 913e3675ae
commit 0fcc662464
2 changed files with 36 additions and 8 deletions
@@ -136,6 +136,14 @@ export async function playSelectionExplainStandalone(
await playSelectionExplainExclusive(result);
}
export function pauseStandalonePlayback(): void {
standaloneAudio?.pause();
if (standaloneAudio) {
standaloneAudio.onended = null;
standaloneAudio.removeAttribute("src");
}
}
export function stopSelectionExplainStandalone(): void {
stopAllLecturePlayback();
}
+28 -8
View File
@@ -50,9 +50,9 @@ import {
import {
beginSelectionExplainSession,
isSelectionExplainSessionActive,
playSelectionExplainAudio,
playSelectionExplainExclusive,
registerLecturePlaybackHandle,
pauseStandalonePlayback,
stopAllLecturePlayback,
} from "../application/playSelectionExplain";
import { getString } from "../../../utils/locale";
@@ -264,10 +264,23 @@ export class LecturePaneView {
this.updateActionButton();
this.bindEvents();
this.unregisterPlayback = registerLecturePlaybackHandle({
pause: () => {
this.paragraphPlayer?.pause();
this.player?.pause();
this.audioEl?.pause();
},
});
await this.refreshFromStore();
}
destroy(): void {
this.unregisterPlayback?.();
this.unregisterPlayback = undefined;
this.paragraphPlayer?.pause();
this.player?.pause();
this.audioEl?.pause();
pauseStandalonePlayback();
for (const off of this.unsubscribers) off();
this.unsubscribers = [];
this.player?.destroy();
@@ -313,7 +326,7 @@ export class LecturePaneView {
}),
lectureEvents.on("lecture:selection_explain", (d) => {
if (String(getRegularItem(this.item).id) !== d.itemId) return;
void this.applySelectionExplainResult(d.result);
void this.applySelectionExplainResult(d.result, { play: false });
}),
);
}
@@ -828,6 +841,8 @@ export class LecturePaneView {
this.explainingSelection = true;
this.explainSelectionBtn.disabled = true;
this.explainSessionId = beginSelectionExplainSession();
const sessionId = this.explainSessionId;
this.setStatus("summarizing", getString("lecture-selection-explaining"));
try {
@@ -836,7 +851,9 @@ export class LecturePaneView {
selectedText,
onProgress: (message) => this.setStatus("summarizing", message),
});
await this.applySelectionExplainResult(result);
if (!isSelectionExplainSessionActive(sessionId)) return;
await this.applySelectionExplainResult(result, { play: true });
if (!isSelectionExplainSessionActive(sessionId)) return;
this.setStatus("ready", getString("lecture-selection-done"));
} catch (e) {
const msg = e instanceof Error ? e.message : String(e);
@@ -849,12 +866,17 @@ export class LecturePaneView {
private async applySelectionExplainResult(
result: SelectionExplainResult,
options?: { play?: boolean },
): Promise<void> {
this.lastSelectionResult = result;
this.renderSelectionExplain(result);
await this.syncPdfHighlight(result.original, result.page);
if (options?.play === false) return;
stopAllLecturePlayback();
if (result.fromPrepared && result.matched) {
if (!this.paragraphPlayer) {
await this.refreshPlayer();
@@ -881,12 +903,10 @@ export class LecturePaneView {
}
if (!this.audioEl) return;
this.paragraphPlayer?.pause();
this.player?.pause();
this.playBtn.hidden = true;
this.pauseBtn.hidden = false;
try {
await playSelectionExplainAudio(this.audioEl, result);
await playSelectionExplainExclusive(result, this.audioEl);
} catch (e) {
ztoolkit.log("[ChatPapers:Lecture] selection audio failed", e);
} finally {
@@ -938,8 +958,8 @@ export class LecturePaneView {
this.doc.createTextNode(getString("lecture-selection-replay")),
);
replayBtn.addEventListener("click", () => {
if (!this.audioEl || !this.lastSelectionResult) return;
void playSelectionExplainAudio(this.audioEl, this.lastSelectionResult);
if (!this.lastSelectionResult) return;
void playSelectionExplainExclusive(this.lastSelectionResult, this.audioEl);
});
this.selectionExplainEl.append(replayBtn);
}