feat: guided light meditation + Edge TTS neural voice audio
- Add continuous guided light-body-scan meditation (16 segments, 97% coverage) - Edge TTS API endpoint with Xiaoxiao neural voice at -40% rate - Pre-generated chime WAVs and guidance MP3s in public/audio/ - Mute toggle in practice page, sequenced non-overlapping audio - Fix middleware to serve /audio and /api/audio/tts without auth - All LLM output localized to Chinese; fix blank home/settings pages - DeepSeek API compat: json_object, increased max_tokens - Analytics, cron reminders, onboarding flow fixes Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
00db3f6c96
commit
6530b20172
@@ -0,0 +1,50 @@
|
||||
// Audio playback using pre-generated files + server-side TTS API.
|
||||
// All functions silently no-op when audio playback fails.
|
||||
|
||||
function playFile(path: string) {
|
||||
try {
|
||||
const audio = new Audio(path);
|
||||
audio.play().catch(() => {});
|
||||
} catch {}
|
||||
}
|
||||
|
||||
export function playStartChime() {
|
||||
playFile("/audio/start-chime.wav");
|
||||
}
|
||||
|
||||
export function playTick() {
|
||||
playFile("/audio/tick.wav");
|
||||
}
|
||||
|
||||
export function playEndChime() {
|
||||
playFile("/audio/end-chime.wav");
|
||||
}
|
||||
|
||||
export function playMidGuidance(lang: string) {
|
||||
const file = lang === "zh" ? "mid-guidance-zh.mp3" : "mid-guidance-en.mp3";
|
||||
playFile(`/audio/${file}`);
|
||||
}
|
||||
|
||||
export function playEndGuidance(lang: string) {
|
||||
const file = lang === "zh" ? "end-guidance-zh.mp3" : "end-guidance-en.mp3";
|
||||
playFile(`/audio/${file}`);
|
||||
}
|
||||
|
||||
// Dynamic TTS via server-side Edge TTS (neural voices) API.
|
||||
// Falls back silently on error (network issue, edge-tts not installed, etc.).
|
||||
export async function speakText(text: string, lang: string = "zh"): Promise<void> {
|
||||
try {
|
||||
const url = `/api/audio/tts?text=${encodeURIComponent(text)}&lang=${lang}&rate=-40%25`;
|
||||
const response = await fetch(url);
|
||||
if (!response.ok) return;
|
||||
const blob = await response.blob();
|
||||
const blobUrl = URL.createObjectURL(blob);
|
||||
|
||||
await new Promise<void>((resolve) => {
|
||||
const audio = new Audio(blobUrl);
|
||||
audio.onended = () => { URL.revokeObjectURL(blobUrl); resolve(); };
|
||||
audio.onerror = () => { URL.revokeObjectURL(blobUrl); resolve(); };
|
||||
audio.play().catch(() => { URL.revokeObjectURL(blobUrl); resolve(); });
|
||||
});
|
||||
} catch {}
|
||||
}
|
||||
Reference in New Issue
Block a user