jellytau/src/lib/services/playbackReporting.ts
Duncan Tourolle d01c2aab9f
Some checks failed
🏗️ Build and Test JellyTau / Run Tests (pull_request) Successful in 4m39s
Traceability Validation / Check Requirement Traces (pull_request) Failing after 36s
🏗️ Build and Test JellyTau / Build Android APK (pull_request) Failing after 1m57s
Migrate all IPC call sites to typed tauri-specta commands.*
Replace the remaining ~155 untyped invoke() calls across stores, services,
components, and routes with the generated commands.* wrappers from
$lib/api/bindings, so every IPC call is compile-time-checked against the
command signatures.

- Register repository_get_subtitle_url and repository_get_video_download_url
  in specta_builder() and the invoke_handler; regenerate bindings.ts.
- Source duplicated wire types (AutoplaySettings, CacheConfig, Session,
  ConnectivityStatus, audio/video settings, etc.) from bindings.
- Fix two bugs surfaced by the typed wrappers:
  - VideoDownloadButton passed an un-awaited Promise as the stream URL.
  - setAutoplaySettings omitted the required userId argument.
- Update unit tests asserting the old invoke(name, args) shape.
- Remove the five param-naming guard tests; the compiler and codegen now
  enforce what they checked.

svelte-check: 0 errors. vitest: green. cargo test --lib: green.
2026-06-21 08:47:04 +02:00

150 lines
4.4 KiB
TypeScript

// Playback reporting service
//
// Simplified service that delegates all logic to the Rust backend.
// The backend handles:
// - Local DB updates
// - Jellyfin server reporting
// - Offline queueing (via sync queue)
// - Connectivity checks
//
// TRACES: UR-005, UR-019, UR-025 | DR-028, DR-047
import { commands } from "$lib/api/bindings";
import { auth } from "$lib/stores/auth";
/**
* Report playback start to Jellyfin (or queue if offline)
*
* The Rust backend handles both local DB updates and server reporting,
* automatically queueing for sync if the server is unreachable.
*
* TRACES: UR-005, UR-025 | DR-028
*/
export async function reportPlaybackStart(
itemId: string,
positionSeconds: number,
contextType: "container" | "single" = "single",
contextId: string | null = null
): Promise<void> {
const positionTicks = Math.floor(positionSeconds * 10000000);
const userId = auth.getUserId();
console.log(
"[PlaybackReporting] reportPlaybackStart - itemId:",
itemId,
"positionSeconds:",
positionSeconds,
"context:",
contextType,
contextId
);
// Update local DB with context (always works, even offline)
if (userId) {
try {
await commands.storageUpdatePlaybackContext(userId, itemId, positionTicks, contextType, contextId);
} catch (e) {
console.error("[PlaybackReporting] Failed to update playback context:", e);
}
}
}
/**
* Report playback progress to Jellyfin (or queue if offline)
*
* Note: Progress reports are frequent and are not queued for sync.
* The final position is captured by reportPlaybackStopped.
*
* TRACES: UR-005 | DR-028
*/
export async function reportPlaybackProgress(
itemId: string,
positionSeconds: number,
_isPaused = false
): Promise<void> {
const positionTicks = Math.floor(positionSeconds * 10000000);
const userId = auth.getUserId();
// Reduce logging for frequent progress updates
if (Math.floor(positionSeconds) % 30 === 0) {
console.log("[PlaybackReporting] reportPlaybackProgress - itemId:", itemId, "position:", positionSeconds);
}
// Update local DB only (progress updates are frequent, don't report to server)
if (userId) {
try {
await commands.storageUpdatePlaybackProgress(userId, itemId, positionTicks);
} catch (e) {
console.error("[PlaybackReporting] Failed to update local progress:", e);
}
}
}
/**
* Report playback stopped to Jellyfin (or queue if offline)
*
* The Rust backend handles both local DB updates and server reporting,
* automatically queuing for sync if the server is unreachable.
*
* TRACES: UR-005, UR-025 | DR-028
*/
export async function reportPlaybackStopped(itemId: string, positionSeconds: number): Promise<void> {
const positionTicks = Math.floor(positionSeconds * 10000000);
const userId = auth.getUserId();
console.log("[PlaybackReporting] reportPlaybackStopped - itemId:", itemId, "positionSeconds:", positionSeconds);
// Update local DB first (always works, even offline)
if (userId) {
try {
await commands.storageUpdatePlaybackProgress(userId, itemId, positionTicks);
} catch (e) {
console.error("[PlaybackReporting] Failed to update local progress:", e);
}
}
// Queue for sync to server (the sync service will handle retry logic)
if (userId && positionSeconds > 0) {
try {
// Get the repository to check if we should queue
const repo = auth.getRepository();
await repo.reportPlaybackStopped(itemId, positionTicks);
} catch (e) {
console.error("[PlaybackReporting] Failed to report to server:", e);
// Server error - could queue, but for now just log
}
}
}
/**
* Mark an item as played (100% progress)
*
* TRACES: UR-025 | DR-028
*/
export async function markAsPlayed(itemId: string): Promise<void> {
const userId = auth.getUserId();
console.log("[PlaybackReporting] markAsPlayed - itemId:", itemId);
// Update local DB first
if (userId) {
try {
await commands.storageMarkPlayed(userId, itemId);
} catch (e) {
console.error("[PlaybackReporting] Failed to mark as played in local DB:", e);
}
}
// Try to report to server via repository (handles queuing internally)
try {
const repo = auth.getRepository();
const item = await repo.getItem(itemId);
if (item.runTimeTicks) {
await repo.reportPlaybackStopped(itemId, item.runTimeTicks);
}
} catch (e) {
console.error("[PlaybackReporting] Failed to report as played:", e);
}
}