增加语音对话功能
This commit is contained in:
@@ -0,0 +1,124 @@
|
||||
import type { StoredBeat } from "../infrastructure/storage/lectureStore";
|
||||
import { pathToFileUrl } from "../infrastructure/platform/paths";
|
||||
|
||||
export interface PlayLectureHandle {
|
||||
play: () => Promise<void>;
|
||||
pause: () => void;
|
||||
next: () => Promise<void>;
|
||||
prev: () => Promise<void>;
|
||||
jumpToBeatId: (beatId: string) => Promise<void>;
|
||||
getCurrentBeatId: () => string | undefined;
|
||||
isPlaying: () => boolean;
|
||||
destroy: () => void;
|
||||
setBeats: (beats: StoredBeat[]) => void;
|
||||
}
|
||||
|
||||
export function createPlayLecture(options: {
|
||||
audio: HTMLAudioElement;
|
||||
beats: StoredBeat[];
|
||||
onBeatChange?: (beatId: string | undefined) => void;
|
||||
onPlayingChange?: (playing: boolean) => void;
|
||||
onComplete?: () => void;
|
||||
}): PlayLectureHandle {
|
||||
let beats = options.beats.filter((b) => b.ttsStatus === "ready" && b.audioPath);
|
||||
let index = 0;
|
||||
let playing = false;
|
||||
|
||||
const notifyBeat = () => {
|
||||
options.onBeatChange?.(beats[index]?.id);
|
||||
};
|
||||
|
||||
const loadIndex = (i: number): boolean => {
|
||||
if (i < 0 || i >= beats.length) return false;
|
||||
index = i;
|
||||
const beat = beats[i];
|
||||
if (!beat.audioPath) return false;
|
||||
options.audio.src = pathToFileUrl(beat.audioPath);
|
||||
options.audio.load();
|
||||
notifyBeat();
|
||||
return true;
|
||||
};
|
||||
|
||||
options.audio.onended = () => {
|
||||
void (async () => {
|
||||
if (index + 1 < beats.length) {
|
||||
index += 1;
|
||||
if (loadIndex(index)) {
|
||||
try {
|
||||
await options.audio.play();
|
||||
} catch {
|
||||
playing = false;
|
||||
options.onPlayingChange?.(false);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
playing = false;
|
||||
options.onPlayingChange?.(false);
|
||||
options.onComplete?.();
|
||||
}
|
||||
})();
|
||||
};
|
||||
|
||||
return {
|
||||
setBeats(next) {
|
||||
beats = next.filter((b) => b.ttsStatus === "ready" && b.audioPath);
|
||||
if (index >= beats.length) index = Math.max(0, beats.length - 1);
|
||||
notifyBeat();
|
||||
},
|
||||
|
||||
async play() {
|
||||
if (!beats.length) return;
|
||||
if (!options.audio.src || options.audio.paused) {
|
||||
loadIndex(index);
|
||||
}
|
||||
try {
|
||||
await options.audio.play();
|
||||
playing = true;
|
||||
options.onPlayingChange?.(true);
|
||||
} catch (e) {
|
||||
ztoolkit.log("[ChatPapers:Lecture] audio play failed", e);
|
||||
playing = false;
|
||||
options.onPlayingChange?.(false);
|
||||
}
|
||||
},
|
||||
|
||||
pause() {
|
||||
options.audio.pause();
|
||||
playing = false;
|
||||
options.onPlayingChange?.(false);
|
||||
},
|
||||
|
||||
async next() {
|
||||
if (index + 1 >= beats.length) return;
|
||||
index += 1;
|
||||
if (loadIndex(index)) await this.play();
|
||||
},
|
||||
|
||||
async prev() {
|
||||
if (index <= 0) return;
|
||||
index -= 1;
|
||||
if (loadIndex(index)) await this.play();
|
||||
},
|
||||
|
||||
async jumpToBeatId(beatId: string) {
|
||||
const i = beats.findIndex((b) => b.id === beatId);
|
||||
if (i < 0) return;
|
||||
index = i;
|
||||
if (loadIndex(index)) await this.play();
|
||||
},
|
||||
|
||||
getCurrentBeatId() {
|
||||
return beats[index]?.id;
|
||||
},
|
||||
|
||||
isPlaying() {
|
||||
return playing && !options.audio.paused;
|
||||
},
|
||||
|
||||
destroy() {
|
||||
options.audio.onended = null;
|
||||
options.audio.pause();
|
||||
playing = false;
|
||||
},
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user