import type { StoredBeat } from "../infrastructure/storage/lectureStore"; import { pathToFileUrl } from "../infrastructure/platform/paths"; export interface PlayLectureHandle { play: () => Promise; pause: () => void; next: () => Promise; prev: () => Promise; jumpToBeatId: (beatId: string) => Promise; 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; }, }; }