// Playback reporting service - syncs to both Jellyfin server and local DB // // This service handles: // - Updating local DB (always works, even offline) // - Reporting to Jellyfin server when online // - Queueing operations for sync when offline import { invoke } from "@tauri-apps/api/core"; import { get } from "svelte/store"; import { auth } from "$lib/stores/auth"; import { isServerReachable } from "$lib/stores/connectivity"; import { syncService } from "./syncService"; import { secondsToTicks } from "$lib/utils/playbackUnits"; /** * Report playback start to Jellyfin and local DB */ export async function reportPlaybackStart( itemId: string, positionSeconds: number, contextType: "container" | "single" = "single", contextId: string | null = null ): Promise { const positionTicks = secondsToTicks(positionSeconds); const userId = auth.getUserId(); console.log("reportPlaybackStart - itemId:", itemId, "positionSeconds:", positionSeconds, "context:", contextType, contextId, "userId:", userId); // Update local DB with context (always works, even offline) if (userId) { try { await invoke("storage_update_playback_context", { userId, itemId, positionTicks, contextType, contextId, }); console.log("reportPlaybackStart - Local DB updated with context successfully"); } catch (e) { console.error("Failed to update playback context:", e); } } // Check connectivity before trying server if (!get(isServerReachable)) { console.log("reportPlaybackStart - Server not reachable, queueing for sync"); if (userId) { await syncService.queueMutation("report_playback_start", itemId, { positionTicks }); } return; } // Report to Jellyfin server try { const repo = auth.getRepository(); await repo.reportPlaybackStart(itemId, positionTicks); console.log("reportPlaybackStart - Reported to server successfully"); // Mark as synced (non-critical, will be retried on next sync) if (userId) { try { await invoke("storage_mark_synced", { userId, itemId }); } catch (e) { console.debug("Failed to mark sync status (will retry):", e); } } } catch (e) { console.error("Failed to report playback start to server:", e); // Queue for sync later if (userId) { await syncService.queueMutation("report_playback_start", itemId, { positionTicks }); } } } /** * Report playback progress to Jellyfin and local DB * * Note: Progress reports are frequent, so we don't queue them for sync. * The final position is captured by reportPlaybackStopped. */ export async function reportPlaybackProgress( itemId: string, positionSeconds: number, isPaused = false ): Promise { const positionTicks = secondsToTicks(positionSeconds); const userId = auth.getUserId(); // Reduce logging for frequent progress updates if (Math.floor(positionSeconds) % 30 === 0) { console.log("reportPlaybackProgress - itemId:", itemId, "positionSeconds:", positionSeconds, "isPaused:", isPaused); } // Update local DB first (always works, even offline) if (userId) { try { await invoke("storage_update_playback_progress", { userId, itemId, positionTicks, }); } catch (e) { console.error("Failed to update local playback progress:", e); } } // Check connectivity before trying server if (!get(isServerReachable)) { // Don't queue progress updates - too frequent. Just store locally. return; } // Report to Jellyfin server (silent failure - progress reports are non-critical) try { const repo = auth.getRepository(); await repo.reportPlaybackProgress(itemId, positionTicks); } catch { // Silent failure for progress reports - they're frequent and non-critical // The final position is captured by reportPlaybackStopped } } /** * Report playback stopped to Jellyfin and local DB */ export async function reportPlaybackStopped( itemId: string, positionSeconds: number ): Promise { const positionTicks = secondsToTicks(positionSeconds); const userId = auth.getUserId(); console.log("reportPlaybackStopped - itemId:", itemId, "positionSeconds:", positionSeconds, "userId:", userId); // Update local DB first (always works, even offline) if (userId) { try { await invoke("storage_update_playback_progress", { userId, itemId, positionTicks, }); console.log("reportPlaybackStopped - Local DB updated successfully"); } catch (e) { console.error("Failed to update local playback progress:", e); } } // Check connectivity before trying server if (!get(isServerReachable)) { console.log("reportPlaybackStopped - Server not reachable, queueing for sync"); if (userId) { await syncService.queueMutation("report_playback_stopped", itemId, { positionTicks }); } return; } // Report to Jellyfin server try { const repo = auth.getRepository(); await repo.reportPlaybackStopped(itemId, positionTicks); console.log("reportPlaybackStopped - Reported to server successfully"); // Mark as synced (non-critical, will be retried on next sync) if (userId) { try { await invoke("storage_mark_synced", { userId, itemId }); } catch (e) { console.debug("Failed to mark sync status (will retry):", e); } } } catch (e) { console.error("Failed to report playback stopped to server:", e); // Queue for sync later if (userId) { await syncService.queueMutation("report_playback_stopped", itemId, { positionTicks }); } } } /** * Mark an item as played (100% progress) */ export async function markAsPlayed(itemId: string): Promise { const userId = auth.getUserId(); console.log("markAsPlayed - itemId:", itemId, "userId:", userId); // Update local DB first if (userId) { try { await invoke("storage_mark_played", { userId, itemId }); console.log("markAsPlayed - Local DB updated successfully"); } catch (e) { console.error("Failed to mark as played in local DB:", e); } } // Check connectivity before trying server if (!get(isServerReachable)) { console.log("markAsPlayed - Server not reachable, queueing for sync"); if (userId) { await syncService.queueMutation("mark_played", itemId); } return; } // For Jellyfin, we need to get the item's runtime and report stopped at 100% try { const repo = auth.getRepository(); const item = await repo.getItem(itemId); if (item.runTimeTicks) { await repo.reportPlaybackStopped(itemId, item.runTimeTicks); console.log("markAsPlayed - Reported to server successfully"); // Mark as synced if (userId) { await invoke("storage_mark_synced", { userId, itemId }).catch(() => {}); } } } catch (e) { console.error("Failed to mark as played on server:", e); // Queue for sync later if (userId) { await syncService.queueMutation("mark_played", itemId); } } }