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
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:
@@ -101,6 +101,13 @@ class AudioStore {
|
||||
*/
|
||||
autoNext = $state(false);
|
||||
|
||||
/**
|
||||
* When true, announces the upcoming chapter number and title via the
|
||||
* Web Speech API before auto-next navigation fires.
|
||||
* e.g. "Chapter 12 — The Final Battle"
|
||||
*/
|
||||
announceChapter = $state(false);
|
||||
|
||||
/**
|
||||
* The next chapter number for the currently playing chapter, or null if
|
||||
* there is no next chapter. Written by the chapter page's AudioPlayer.
|
||||
|
||||
@@ -618,8 +618,8 @@
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- Secondary controls: unified single row — Speed · Auto · Sleep -->
|
||||
<div class="flex items-center justify-center gap-2 shrink-0">
|
||||
<!-- Secondary controls: unified single row — Speed · Auto · Announce · Sleep -->
|
||||
<div class="flex items-center justify-center gap-2 shrink-0 flex-wrap">
|
||||
<!-- Speed — segmented pill -->
|
||||
<div class="flex items-center gap-0.5 bg-(--color-surface-2) rounded-full px-1.5 py-1 border border-(--color-border)">
|
||||
{#each SPEED_OPTIONS as s}
|
||||
@@ -661,6 +661,25 @@
|
||||
{/if}
|
||||
</button>
|
||||
|
||||
<!-- Announce chapter pill (only meaningful when auto-next is on) -->
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => (audioStore.announceChapter = !audioStore.announceChapter)}
|
||||
class={cn(
|
||||
'flex items-center gap-1.5 px-3 py-1.5 rounded-full text-xs font-semibold border transition-colors',
|
||||
audioStore.announceChapter
|
||||
? 'border-(--color-brand) bg-(--color-brand)/15 text-(--color-brand)'
|
||||
: 'border-(--color-border) bg-(--color-surface-2) text-(--color-muted) hover:text-(--color-text)'
|
||||
)}
|
||||
aria-pressed={audioStore.announceChapter}
|
||||
title={audioStore.announceChapter ? 'Chapter announcing on' : 'Chapter announcing off'}
|
||||
>
|
||||
<svg class="w-3.5 h-3.5" fill="currentColor" viewBox="0 0 24 24">
|
||||
<path d="M3 9v6h4l5 5V4L7 9H3zm13.5 3c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02z"/>
|
||||
</svg>
|
||||
Announce
|
||||
</button>
|
||||
|
||||
<!-- Sleep timer pill -->
|
||||
<button
|
||||
type="button"
|
||||
|
||||
@@ -75,6 +75,7 @@ export interface PBUserSettings {
|
||||
locale?: string;
|
||||
font_family?: string;
|
||||
font_size?: number;
|
||||
announce_chapter?: boolean;
|
||||
updated?: string;
|
||||
}
|
||||
|
||||
@@ -998,7 +999,7 @@ export async function getSettings(
|
||||
|
||||
export async function saveSettings(
|
||||
sessionId: string,
|
||||
settings: { autoNext: boolean; voice: string; speed: number; theme?: string; locale?: string; fontFamily?: string; fontSize?: number },
|
||||
settings: { autoNext: boolean; voice: string; speed: number; theme?: string; locale?: string; fontFamily?: string; fontSize?: number; announceChapter?: boolean },
|
||||
userId?: string
|
||||
): Promise<void> {
|
||||
const existing = await listOne<PBUserSettings & { id: string }>(
|
||||
@@ -1017,6 +1018,7 @@ export async function saveSettings(
|
||||
if (settings.locale !== undefined) payload.locale = settings.locale;
|
||||
if (settings.fontFamily !== undefined) payload.font_family = settings.fontFamily;
|
||||
if (settings.fontSize !== undefined) payload.font_size = settings.fontSize;
|
||||
if (settings.announceChapter !== undefined) payload.announce_chapter = settings.announceChapter;
|
||||
if (userId) payload.user_id = userId;
|
||||
|
||||
if (existing) {
|
||||
|
||||
Reference in New Issue
Block a user