mirror of
https://github.com/sakaljurgis/kodi-api-server.git
synced 2026-11-06 18:58:01 +00:00
add shared watch state, continue watching and new main menu
- watch_state table: resume point, watched, play count, last played per file, shared by all clients (Kodi plugin, Android TV app) - POST/GET /api/state/:fileId for players' progress reports (watched at 90% or on end; late resends carry `at` and lose to newer state) - listings carry watch state (files: playcount, resume, lastplayed; shows and seasons: watched/total episodes), episodes sorted by season/episode - all/continue: unfinished files and next episodes, latest activity first - context menus: mark (un)watched for a file, season or show; hide from continue watching - main menu: Continue watching, Movies, Shows, scans (Torr/LRT unlisted) - DATA_ROOT env overrides /srv/data for local runs outside docker Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
02522bbd04
commit
be7d0bc535
16 files changed
+819
-35
No files matched your search
@@ -0,0 +1,37 @@
|
||||
import { BadRequestException } from '@nestjs/common';
|
||||
|
||||
export type PlaybackEvent = 'start' | 'tick' | 'pause' | 'stop' | 'end';
|
||||
|
||||
const EVENTS: PlaybackEvent[] = ['start', 'tick', 'pause', 'stop', 'end'];
|
||||
|
||||
/** a player's progress report, positions in seconds */
|
||||
export class PlaybackReport {
|
||||
constructor(
|
||||
readonly position: number,
|
||||
readonly duration: number | null,
|
||||
readonly event: PlaybackEvent,
|
||||
readonly client: string | null,
|
||||
/**
|
||||
* when it happened (ms since epoch), only for reports sent late (offline,
|
||||
* imported history); live reports leave it out and get the server's time
|
||||
*/
|
||||
readonly at: number | null,
|
||||
) {}
|
||||
|
||||
static fromBody(body: Record<string, unknown>): PlaybackReport {
|
||||
const position = Number(body?.position);
|
||||
if (!Number.isFinite(position) || position < 0) {
|
||||
throw new BadRequestException('position (seconds) is required');
|
||||
}
|
||||
const duration = Number(body.duration);
|
||||
const at = Number(body.at);
|
||||
|
||||
return new PlaybackReport(
|
||||
position,
|
||||
Number.isFinite(duration) && duration > 0 ? duration : null,
|
||||
EVENTS.find((event) => event === body.event) ?? 'tick',
|
||||
typeof body.client === 'string' ? body.client.slice(0, 32) : null,
|
||||
Number.isFinite(at) && at > 0 ? at : null,
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user