Files
libnovel/ui/src/routes/api/announce/+server.ts
root 85492fae73
All checks were successful
Release / Test backend (push) Successful in 41s
Release / Check ui (push) Successful in 1m48s
Release / Docker (push) Successful in 13m20s
Release / Gitea Release (push) Successful in 40s
fix: replace speechSynthesis announce with real audio clip via /api/tts-announce
speechSynthesis is silently muted on iOS Safari and Chrome Android after
the audio session ends (onended), so chapter announcements never played.

Fix:
- Add GET /api/tts-announce backend endpoint: streams a short TTS clip
  for arbitrary text without MinIO caching (backend/internal/backend/)
- Add GET /api/announce SvelteKit proxy route (no paywall)
- Add announceNavigatePending/announcePendingSlug/announcePendingChapter
  to AudioStore
- Rewrite onended announce branch: sets audioStore.audioUrl to the
  announcement clip URL so the persistent <audio> element plays it;
  the next onended detects announceNavigatePending and navigates
- 10s safety timeout in case the clip fails to load/end
2026-04-08 11:57:04 +05:00

40 lines
1.1 KiB
TypeScript

import { error } from '@sveltejs/kit';
import type { RequestHandler } from './$types';
import { backendFetch } from '$lib/server/scraper';
/**
* GET /api/announce?text=...&voice=...&format=...
*
* Thin proxy to backend GET /api/tts-announce.
* No paywall — this is a short announcement clip (a few words), not chapter audio.
* No MinIO caching — the backend streams the clip directly.
*/
export const GET: RequestHandler = async ({ url }) => {
const text = url.searchParams.get('text') ?? '';
if (!text) error(400, 'text is required');
const qs = new URLSearchParams();
qs.set('text', text);
const voice = url.searchParams.get('voice');
if (voice) qs.set('voice', voice);
const format = url.searchParams.get('format') ?? 'mp3';
qs.set('format', format);
const backendRes = await backendFetch(`/api/tts-announce?${qs}`);
if (!backendRes.ok) {
error(backendRes.status as Parameters<typeof error>[0], 'TTS announce failed');
}
return new Response(backendRes.body, {
status: 200,
headers: {
'Content-Type': backendRes.headers.get('Content-Type') ?? 'audio/mpeg',
'Cache-Control': 'no-store',
'X-Accel-Buffering': 'no'
}
});
};