feat: add chapter announcing setting to audio player
All checks were successful
Release / Test backend (push) Successful in 43s
Release / Check ui (push) Successful in 1m51s
Release / Docker / caddy (push) Successful in 41s
Release / Docker / backend (push) Successful in 2m24s
Release / Docker / runner (push) Successful in 2m49s
Release / Upload source maps (push) Successful in 1m25s
Release / Docker / ui (push) Successful in 2m12s
Release / Gitea Release (push) Successful in 30s

When enabled, the Web Speech API speaks the upcoming chapter number and
title (e.g. 'Chapter 12 — The Final Battle') between auto-next chapters,
giving an audible cue before the next narration begins.

- AudioStore.announceChapter ( boolean, default false)
- PBUserSettings.announce_chapter persisted to PocketBase
- GET/PUT /api/settings includes announceChapter field
- +layout.server.ts loads + defaults the field
- +layout.svelte applies on load, saves in debounced PUT, and fires
  SpeechSynthesisUtterance in onended before navigating (falls back to
  immediate navigation if speechSynthesis is unavailable)
- ListeningMode: 'Announce' pill added to the Speed · Auto · Sleep row
This commit is contained in:
root
2026-04-06 21:12:10 +05:00
parent 93e9d88066
commit 1f83a7c05f
6 changed files with 66 additions and 12 deletions

View File

@@ -5,7 +5,7 @@ import { log } from '$lib/server/logger';
/**
* GET /api/settings
* Returns the current user's settings (auto_next, voice, speed, theme, locale, fontFamily, fontSize).
* Returns the current user's settings (auto_next, voice, speed, theme, locale, fontFamily, fontSize, announceChapter).
* Returns defaults if no settings record exists yet.
*/
export const GET: RequestHandler = async ({ locals }) => {
@@ -18,7 +18,8 @@ export const GET: RequestHandler = async ({ locals }) => {
theme: settings?.theme ?? 'amber',
locale: settings?.locale ?? 'en',
fontFamily: settings?.font_family ?? 'system',
fontSize: settings?.font_size || 1.0
fontSize: settings?.font_size || 1.0,
announceChapter: settings?.announce_chapter ?? false
});
} catch (e) {
log.error('settings', 'GET failed', { err: String(e) });
@@ -28,7 +29,7 @@ export const GET: RequestHandler = async ({ locals }) => {
/**
* PUT /api/settings
* Body: { autoNext: boolean, voice: string, speed: number, theme?: string, locale?: string, fontFamily?: string, fontSize?: number }
* Body: { autoNext: boolean, voice: string, speed: number, theme?: string, locale?: string, fontFamily?: string, fontSize?: number, announceChapter?: boolean }
* Saves user preferences.
*/
export const PUT: RequestHandler = async ({ request, locals }) => {
@@ -67,6 +68,11 @@ export const PUT: RequestHandler = async ({ request, locals }) => {
error(400, `Invalid fontSize — must be one of: ${validFontSizes.join(', ')}`);
}
// announceChapter is optional boolean
if (body.announceChapter !== undefined && typeof body.announceChapter !== 'boolean') {
error(400, 'Invalid announceChapter — must be boolean');
}
try {
await saveSettings(locals.sessionId, body, locals.user?.id);
} catch (e) {