feat(ui): persist user settings and audio position across sessions
- Replace double inline player controls with compact 'now playing' indicator when mini-bar is active - Add user_settings PocketBase collection (auto_next, voice, speed) and audio_time field on progress - Add getSettings/saveSettings/setAudioTime/getAudioTime server functions - Add GET/PUT /api/settings and GET/PATCH /api/progress/audio-time API routes - Load settings server-side in layout and apply to audioStore on mount - Debounced 800ms effect persists settings changes to DB - Save audio currentTime on pause/end; restore position when replaying a chapter
This commit is contained in:
56
ui/src/routes/api/progress/audio-time/+server.ts
Normal file
56
ui/src/routes/api/progress/audio-time/+server.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import { json, error } from '@sveltejs/kit';
|
||||
import type { RequestHandler } from './$types';
|
||||
import { setAudioTime, getAudioTime } from '$lib/server/pocketbase';
|
||||
import { log } from '$lib/server/logger';
|
||||
|
||||
/**
|
||||
* GET /api/progress/audio-time?slug=&chapter=
|
||||
* Returns the last saved audio position for a chapter, or null.
|
||||
*/
|
||||
export const GET: RequestHandler = async ({ url, locals }) => {
|
||||
const slug = url.searchParams.get('slug');
|
||||
const chapterParam = url.searchParams.get('chapter');
|
||||
|
||||
if (!slug || !chapterParam) {
|
||||
error(400, 'Missing slug or chapter query params');
|
||||
}
|
||||
|
||||
const chapter = parseInt(chapterParam, 10);
|
||||
if (isNaN(chapter)) {
|
||||
error(400, 'chapter must be a number');
|
||||
}
|
||||
|
||||
try {
|
||||
const audioTime = await getAudioTime(locals.sessionId, slug, chapter, locals.user?.id);
|
||||
return json({ audioTime });
|
||||
} catch (e) {
|
||||
log.error('audio-time', 'GET failed', { slug, chapter, err: String(e) });
|
||||
error(500, 'Failed to load audio time');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* PATCH /api/progress/audio-time
|
||||
* Body: { slug: string, chapter: number, audioTime: number }
|
||||
* Saves the current audio playback position.
|
||||
*/
|
||||
export const PATCH: RequestHandler = async ({ request, locals }) => {
|
||||
const body = await request.json().catch(() => null);
|
||||
|
||||
if (
|
||||
!body ||
|
||||
typeof body.slug !== 'string' ||
|
||||
typeof body.chapter !== 'number' ||
|
||||
typeof body.audioTime !== 'number'
|
||||
) {
|
||||
error(400, 'Invalid body — expected { slug, chapter, audioTime }');
|
||||
}
|
||||
|
||||
try {
|
||||
await setAudioTime(locals.sessionId, body.slug, body.chapter, body.audioTime, locals.user?.id);
|
||||
} catch (e) {
|
||||
log.error('audio-time', 'PATCH failed', { slug: body.slug, chapter: body.chapter, err: String(e) });
|
||||
error(500, 'Failed to save audio time');
|
||||
}
|
||||
return json({ ok: true });
|
||||
};
|
||||
49
ui/src/routes/api/settings/+server.ts
Normal file
49
ui/src/routes/api/settings/+server.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import { json, error } from '@sveltejs/kit';
|
||||
import type { RequestHandler } from './$types';
|
||||
import { getSettings, saveSettings } from '$lib/server/pocketbase';
|
||||
import { log } from '$lib/server/logger';
|
||||
|
||||
/**
|
||||
* GET /api/settings
|
||||
* Returns the current user's settings (auto_next, voice, speed).
|
||||
* Returns defaults if no settings record exists yet.
|
||||
*/
|
||||
export const GET: RequestHandler = async ({ locals }) => {
|
||||
try {
|
||||
const settings = await getSettings(locals.sessionId, locals.user?.id);
|
||||
return json({
|
||||
autoNext: settings?.auto_next ?? false,
|
||||
voice: settings?.voice ?? 'af_bella',
|
||||
speed: settings?.speed ?? 1.0
|
||||
});
|
||||
} catch (e) {
|
||||
log.error('settings', 'GET failed', { err: String(e) });
|
||||
error(500, 'Failed to load settings');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* PUT /api/settings
|
||||
* Body: { autoNext: boolean, voice: string, speed: number }
|
||||
* Saves user preferences.
|
||||
*/
|
||||
export const PUT: RequestHandler = async ({ request, locals }) => {
|
||||
const body = await request.json().catch(() => null);
|
||||
|
||||
if (
|
||||
!body ||
|
||||
typeof body.autoNext !== 'boolean' ||
|
||||
typeof body.voice !== 'string' ||
|
||||
typeof body.speed !== 'number'
|
||||
) {
|
||||
error(400, 'Invalid body — expected { autoNext, voice, speed }');
|
||||
}
|
||||
|
||||
try {
|
||||
await saveSettings(locals.sessionId, body, locals.user?.id);
|
||||
} catch (e) {
|
||||
log.error('settings', 'PUT failed', { err: String(e) });
|
||||
error(500, 'Failed to save settings');
|
||||
}
|
||||
return json({ ok: true });
|
||||
};
|
||||
Reference in New Issue
Block a user