refactor(logging): route frontend console calls through the logger
TRACES: | DR-204 484 ungated `console.*` calls across 63 non-test frontend files shipped to end users with no way to turn them off. Mechanical substitution, no control flow, error handling or message semantics changed: console.log / console.debug -> log.debug console.info -> log.info console.warn -> log.warn console.error -> log.error Hand-written `"[Scope] …"` prefixes are dropped where the logger's scope now carries them; scope names that already existed are preserved verbatim (`[Auth]`, `[VideoPlayer]`, `[PiP]`, …) and inferred from the filename where a file had none. `src/routes/player/[id]/+page.svelte` keeps its `NextEpisode` and `AutoPlay` sub-scopes as separate loggers rather than flattening them into the page scope. `grep -rn 'console\.' src/` now matches nothing outside the tests and the facade itself.
This commit is contained in:
@@ -34,6 +34,9 @@
|
||||
import { useScrollRestore } from "$lib/utils/scrollContainer";
|
||||
import { startNetworkReporting } from "$lib/services/networkType";
|
||||
import { initSafeArea } from "$lib/utils/safeArea";
|
||||
import { createLogger } from "$lib/utils/logger";
|
||||
|
||||
const log = createLogger("Layout");
|
||||
|
||||
let { children } = $props();
|
||||
|
||||
@@ -105,7 +108,7 @@
|
||||
const platformName = platform();
|
||||
isAndroid.set(platformName === "android");
|
||||
} catch (err) {
|
||||
console.error("Platform detection failed:", err);
|
||||
log.error("Platform detection failed:", err);
|
||||
}
|
||||
|
||||
// Prime the safe-area custom properties from the native WindowInsets bridge
|
||||
@@ -154,7 +157,7 @@
|
||||
const userId = get(auth).user?.id;
|
||||
if (userId) {
|
||||
downloads.refresh(userId).catch((err) =>
|
||||
console.error("Initial downloads refresh failed:", err)
|
||||
log.error("Initial downloads refresh failed:", err)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -168,7 +171,7 @@
|
||||
// without spending their retry budget (DR-131).
|
||||
if (get(auth).user?.id) {
|
||||
commands.syncProcessPending().catch((err) =>
|
||||
console.debug("[Layout] Startup sync drain skipped:", err)
|
||||
log.debug("Startup sync drain skipped:", err)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -206,7 +209,7 @@
|
||||
if (session?.serverUrl) {
|
||||
connectivity.forceCheck().catch((error) => {
|
||||
// If check fails, monitoring might not be started yet, so start it
|
||||
console.debug("[Layout] Queue status check failed, starting monitoring:", error);
|
||||
log.debug("Queue status check failed, starting monitoring:", error);
|
||||
connectivity.startMonitoring(session.serverUrl, {
|
||||
onServerReconnected: () => {
|
||||
// Retry session verification when server becomes reachable
|
||||
@@ -215,7 +218,7 @@
|
||||
void onCatalogReconnected();
|
||||
},
|
||||
}).catch((monitorError) => {
|
||||
console.error("[Layout] Failed to start connectivity monitoring:", monitorError);
|
||||
log.error("Failed to start connectivity monitoring:", monitorError);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -242,7 +245,7 @@
|
||||
.then((unlisten) => {
|
||||
unlistenDrain = unlisten;
|
||||
})
|
||||
.catch((err) => console.debug("[Layout] sync-queue-changed listen failed:", err));
|
||||
.catch((err) => log.debug("sync-queue-changed listen failed:", err));
|
||||
|
||||
return () => {
|
||||
clearInterval(interval);
|
||||
|
||||
@@ -14,6 +14,9 @@
|
||||
import { assumedLibraryRatio } from "$lib/components/library/libraryMosaic";
|
||||
import { useScrollRestore } from "$lib/utils/scrollContainer";
|
||||
import type { MediaItem, Library } from "$lib/api/types";
|
||||
import { createLogger } from "$lib/utils/logger";
|
||||
|
||||
const log = createLogger("HomePage");
|
||||
|
||||
// Home scrolls in its own box rather than the shell's, and is destroyed on
|
||||
// every navigation away — so its offsets live in the module-level memory,
|
||||
@@ -40,7 +43,7 @@
|
||||
const platformName = await platform();
|
||||
isAndroid = platformName === "android";
|
||||
} catch (err) {
|
||||
console.error("Platform detection failed:", err);
|
||||
log.error("Platform detection failed:", err);
|
||||
}
|
||||
|
||||
if ($isAuthenticated) {
|
||||
|
||||
@@ -21,6 +21,9 @@
|
||||
import { auth } from "$lib/stores/auth";
|
||||
import DownloadItem from "$lib/components/downloads/DownloadItem.svelte";
|
||||
import DownloadedBrowse from "$lib/components/downloads/DownloadedBrowse.svelte";
|
||||
import { createLogger } from "$lib/utils/logger";
|
||||
|
||||
const log = createLogger("DownloadsPage");
|
||||
|
||||
type ViewType = "downloaded" | "transfers";
|
||||
let view = $state<ViewType>("downloaded");
|
||||
@@ -42,7 +45,7 @@
|
||||
await downloads.refresh(userId);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to load downloads:", error);
|
||||
log.error("Failed to load downloads:", error);
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
@@ -60,7 +63,7 @@
|
||||
try {
|
||||
await downloads.pause(download.id);
|
||||
} catch (error) {
|
||||
console.error(`Failed to pause download ${download.id}:`, error);
|
||||
log.error(`Failed to pause download ${download.id}:`, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -74,7 +77,7 @@
|
||||
try {
|
||||
await downloads.resume(download.id);
|
||||
} catch (error) {
|
||||
console.error(`Failed to resume download ${download.id}:`, error);
|
||||
log.error(`Failed to resume download ${download.id}:`, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,6 +43,9 @@
|
||||
initialExpandedSeasons,
|
||||
type SeasonData,
|
||||
} from "$lib/components/library/seriesNavigation";
|
||||
import { createLogger } from "$lib/utils/logger";
|
||||
|
||||
const log = createLogger("LibraryDetail");
|
||||
|
||||
let item = $state<MediaItem | null>(null);
|
||||
let loading = $state(true);
|
||||
@@ -136,11 +139,11 @@
|
||||
}
|
||||
// Series-less episode: rendered by the Focus View below, series and all.
|
||||
}
|
||||
console.log(`[LibraryDetail] ✓ Loaded item: ${item?.name} (${item?.kind})`);
|
||||
console.log(`[LibraryDetail] - Has people? ${item?.people ? `YES (${item.people.length})` : 'NO'}`);
|
||||
log.debug(`✓ Loaded item: ${item?.name} (${item?.kind})`);
|
||||
log.debug(`- Has people? ${item?.people ? `YES (${item.people.length})` : 'NO'}`);
|
||||
if (item?.people) {
|
||||
item.people.forEach((p, i) => {
|
||||
console.log(`[LibraryDetail] [${i}] ${p.name} (${p.type})`);
|
||||
log.debug(` [${i}] ${p.name} (${p.type})`);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -154,7 +157,7 @@
|
||||
const musicLibrary = $libraries.find(lib => lib.collectionType === "music");
|
||||
if (musicLibrary) {
|
||||
library.setCurrentLibrary(musicLibrary);
|
||||
console.log("[LibraryDetail] Set current library to music library for music item");
|
||||
log.debug("Set current library to music library for music item");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -163,20 +166,20 @@
|
||||
// Ensure cast/crew data is loaded for Movies, Series, and Episodes
|
||||
// Some APIs/caches may not include people data on first load
|
||||
if ((item?.kind === "movie" || item?.kind === "series" || item?.kind === "episode") && (!item.people || item.people.length === 0)) {
|
||||
console.log(`[LibraryDetail] ⚠ People data missing, reloading ${item?.kind}...`);
|
||||
log.debug(`⚠ People data missing, reloading ${item?.kind}...`);
|
||||
try {
|
||||
const repo = auth.getRepository();
|
||||
const fullItem = await repo.getItem(itemId);
|
||||
console.log(`[LibraryDetail] - Reloaded has people? ${fullItem.people ? `YES (${fullItem.people.length})` : 'NO'}`);
|
||||
log.debug(`- Reloaded has people? ${fullItem.people ? `YES (${fullItem.people.length})` : 'NO'}`);
|
||||
if (fullItem.people && fullItem.people.length > 0) {
|
||||
item = fullItem;
|
||||
console.log(`[LibraryDetail] ✓ Updated item with ${fullItem.people.length} people`);
|
||||
log.debug(`✓ Updated item with ${fullItem.people.length} people`);
|
||||
fullItem.people.forEach((p, i) => {
|
||||
console.log(`[LibraryDetail] [${i}] ${p.name} (${p.type})`);
|
||||
log.debug(` [${i}] ${p.name} (${p.type})`);
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn(`Could not reload ${item?.kind} with full cast data:`, e);
|
||||
log.warn(`Could not reload ${item?.kind} with full cast data:`, e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -194,7 +197,7 @@
|
||||
repo.getSeriesEpisodes(itemId),
|
||||
// Best-effort: a series still renders if the anchor cannot be resolved.
|
||||
repo.getSeriesCurrentEpisode(itemId).catch((e) => {
|
||||
console.warn("Could not resolve the current episode:", e);
|
||||
log.warn("Could not resolve the current episode:", e);
|
||||
return null;
|
||||
}),
|
||||
]);
|
||||
@@ -220,7 +223,7 @@
|
||||
directFetchedEpisode = await repo.getItem(episodeIdParam);
|
||||
} catch {
|
||||
// Best-effort: the list entry still renders a usable hero.
|
||||
console.warn("Could not fetch focused episode directly:", episodeIdParam);
|
||||
log.warn("Could not fetch focused episode directly:", episodeIdParam);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -322,7 +325,7 @@
|
||||
shuffle: false,
|
||||
});
|
||||
} catch (e) {
|
||||
console.error("Failed to play album:", e);
|
||||
log.error("Failed to play album:", e);
|
||||
alert(`Failed to play album: ${e instanceof Error ? e.message : 'Unknown error'}`);
|
||||
}
|
||||
} else if ($libraryItems.length > 0) {
|
||||
@@ -346,7 +349,7 @@
|
||||
shuffle: true,
|
||||
});
|
||||
} catch (e) {
|
||||
console.error("Failed to shuffle play album:", e);
|
||||
log.error("Failed to shuffle play album:", e);
|
||||
alert(`Failed to shuffle play: ${e instanceof Error ? e.message : 'Unknown error'}`);
|
||||
}
|
||||
} else if (item?.kind === "series" && allEpisodes.length > 0) {
|
||||
|
||||
@@ -29,6 +29,9 @@
|
||||
favoritesRouteUrl,
|
||||
emptyStateMessage,
|
||||
} from "$lib/utils/favoritesView";
|
||||
import { createLogger } from "$lib/utils/logger";
|
||||
|
||||
const log = createLogger("FavoritesPage");
|
||||
|
||||
const scope = $derived(resolveFavoritesScope($page.url.searchParams.get("scope")));
|
||||
|
||||
@@ -49,7 +52,7 @@
|
||||
const result = await repo.getFavorites(currentScope, { limit: 500 });
|
||||
items = result.items;
|
||||
} catch (error) {
|
||||
console.error("Failed to load favorites:", error);
|
||||
log.error("Failed to load favorites:", error);
|
||||
loadError = "Could not load your favourites.";
|
||||
items = [];
|
||||
} finally {
|
||||
|
||||
@@ -24,6 +24,11 @@
|
||||
import { reportSkippedEpisode, shouldSuppressStopReport } from "$lib/services/skipReporting";
|
||||
import { cleanup as cleanupNextEpisode } from "$lib/services/nextEpisodeService";
|
||||
import * as html5Adapter from "$lib/player/html5Adapter";
|
||||
import { createLogger } from "$lib/utils/logger";
|
||||
|
||||
const log = createLogger("PlayerPage");
|
||||
const nextEpisodeLog = createLogger("NextEpisode");
|
||||
const autoPlayLog = createLogger("AutoPlay");
|
||||
|
||||
const itemId = $derived($page.params.id);
|
||||
const queueParam = $derived($page.url.searchParams.get("queue"));
|
||||
@@ -95,7 +100,7 @@
|
||||
const id = itemId;
|
||||
const restart = restartParam;
|
||||
if (id && id !== loadedItemId) {
|
||||
console.log("[AutoPlay] $effect triggered: loading new item", id, "(was:", loadedItemId, ") restart:", restart);
|
||||
autoPlayLog.debug("$effect triggered: loading new item", id, "(was:", loadedItemId, ") restart:", restart);
|
||||
// restart=true (advancing to next episode) forces start-from-beginning,
|
||||
// bypassing the resume-progress check.
|
||||
loadAndPlay(id, restart ? 0 : undefined, restart);
|
||||
@@ -128,16 +133,16 @@
|
||||
let retrievedProgressSeconds: number | null = null;
|
||||
|
||||
try {
|
||||
console.log("loadAndPlay: Loading item", id);
|
||||
log.debug("loadAndPlay: Loading item", id);
|
||||
// Load item details
|
||||
const item = await library.loadItem(id);
|
||||
console.log("loadAndPlay: Loaded item", item.name, "kind:", item.kind);
|
||||
log.debug("loadAndPlay: Loaded item", item.name, "kind:", item.kind);
|
||||
currentMedia = item;
|
||||
|
||||
// Check if this is a non-playable collection type that should be viewed in library instead
|
||||
const collectionKinds: MediaKind[] = ["album", "artist", "series", "season", "folder", "playlist", "channel"];
|
||||
if (item.kind && collectionKinds.includes(item.kind)) {
|
||||
console.log("loadAndPlay: Redirecting collection type to library:", item.kind);
|
||||
log.debug("loadAndPlay: Redirecting collection type to library:", item.kind);
|
||||
goto(`/library/${id}`);
|
||||
return;
|
||||
}
|
||||
@@ -162,7 +167,7 @@
|
||||
forceRestart,
|
||||
})
|
||||
) {
|
||||
console.log("loadAndPlay: Track already playing, showing UI without restarting");
|
||||
log.debug("loadAndPlay: Track already playing, showing UI without restarting");
|
||||
isPlaying = true;
|
||||
loading = false;
|
||||
// hasNext/hasPrevious come from the event-driven queue store.
|
||||
@@ -175,7 +180,7 @@
|
||||
try {
|
||||
await commands.playerStop();
|
||||
queue.clear();
|
||||
console.log("loadAndPlay: Stopped audio backend for video playback");
|
||||
log.debug("loadAndPlay: Stopped audio backend for video playback");
|
||||
} catch (e) {
|
||||
// Ignore - player may not have been playing
|
||||
}
|
||||
@@ -185,43 +190,43 @@
|
||||
// When forceRestart is set (advancing to a next episode) we always start
|
||||
// from the beginning, skipping the resume check and resume dialog.
|
||||
const userId = auth.getUserId();
|
||||
console.log("Resume check - userId:", userId, "itemId:", id, "startPosition:", startPosition, "forceRestart:", forceRestart);
|
||||
log.debug("Resume check - userId:", userId, "itemId:", id, "startPosition:", startPosition, "forceRestart:", forceRestart);
|
||||
|
||||
// Live streams have no fixed position - never resume.
|
||||
if (!startPosition && !forceRestart && userId && !isLive) {
|
||||
try {
|
||||
const progress = await commands.storageGetPlaybackProgress(userId, id);
|
||||
console.log("Resume check - retrieved progress:", progress);
|
||||
log.debug("Resume check - retrieved progress:", progress);
|
||||
|
||||
if (progress && progress.positionMs > 0 && item.durationMs) {
|
||||
const positionSeconds = progress.positionMs / 1000;
|
||||
const totalSeconds = item.durationMs / 1000;
|
||||
const progressPercent = (positionSeconds / totalSeconds) * 100;
|
||||
|
||||
console.log("Resume check - positionSeconds:", positionSeconds, "totalSeconds:", totalSeconds, "progressPercent:", progressPercent);
|
||||
log.debug("Resume check - positionSeconds:", positionSeconds, "totalSeconds:", totalSeconds, "progressPercent:", progressPercent);
|
||||
|
||||
// Store for later use regardless of whether dialog is shown
|
||||
retrievedProgressSeconds = positionSeconds;
|
||||
|
||||
// Show resume dialog if watched > 30 seconds and < 90% complete
|
||||
if (positionSeconds > 30 && progressPercent < 90) {
|
||||
console.log("Resume check - SHOWING RESUME DIALOG");
|
||||
log.debug("Resume check - SHOWING RESUME DIALOG");
|
||||
savedProgress = { positionSeconds, progressPercent };
|
||||
showResumeDialog = true;
|
||||
loading = false;
|
||||
return; // Wait for user decision
|
||||
} else {
|
||||
console.log("Resume check - NOT showing dialog. Position > 30?", positionSeconds > 30, "Progress < 90?", progressPercent < 90);
|
||||
log.debug("Resume check - NOT showing dialog. Position > 30?", positionSeconds > 30, "Progress < 90?", progressPercent < 90);
|
||||
}
|
||||
} else {
|
||||
console.log("Resume check - No valid progress found. Has progress?", !!progress, "Has position?", progress?.positionMs, "Has runtime?", !!item.durationMs);
|
||||
log.debug("Resume check - No valid progress found. Has progress?", !!progress, "Has position?", progress?.positionMs, "Has runtime?", !!item.durationMs);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Failed to check saved progress:", e);
|
||||
log.error("Failed to check saved progress:", e);
|
||||
// Continue with normal playback
|
||||
}
|
||||
} else {
|
||||
console.log("Resume check - Skipped. Reason:", !userId ? "No userId" : "Has startPosition");
|
||||
log.debug("Resume check - Skipped. Reason:", !userId ? "No userId" : "Has startPosition");
|
||||
}
|
||||
|
||||
// Check if this item is downloaded locally
|
||||
@@ -232,7 +237,7 @@
|
||||
|
||||
if (localDownload) {
|
||||
// Use local file for playback
|
||||
console.log("loadAndPlay: Found local download, using offline playback:", localDownload.filePath);
|
||||
log.debug("loadAndPlay: Found local download, using offline playback:", localDownload.filePath);
|
||||
isOfflinePlayback = true;
|
||||
|
||||
// Get the storage path and resolve the file's location. A completed
|
||||
@@ -241,7 +246,7 @@
|
||||
// TRACES: UR-071 | DR-133
|
||||
const storagePath = await commands.storageGetPath();
|
||||
const fullPath = downloadedFilePath(storagePath, localDownload.filePath);
|
||||
console.log("loadAndPlay: Full local path:", fullPath);
|
||||
log.debug("loadAndPlay: Full local path:", fullPath);
|
||||
|
||||
// Serve the file over the loopback media server rather than the asset
|
||||
// protocol: the asset protocol answers a range-less request with the
|
||||
@@ -249,7 +254,7 @@
|
||||
// the URL (it holds the port and the per-session token).
|
||||
// TRACES: UR-071 | DR-137
|
||||
const localUrl = await commands.mediaLocalUrl(fullPath);
|
||||
console.log("loadAndPlay: Local media URL resolved");
|
||||
log.debug("loadAndPlay: Local media URL resolved");
|
||||
|
||||
if (isVideo) {
|
||||
// Local video files don't need transcoding and support native seeking
|
||||
@@ -260,7 +265,7 @@
|
||||
videoInitialPosition = effectivePosition;
|
||||
} else {
|
||||
// Local audio playback via MPV backend
|
||||
console.log("loadAndPlay: Using MPV backend for offline audio");
|
||||
log.debug("loadAndPlay: Using MPV backend for offline audio");
|
||||
// Use player_play_tracks - backend fetches all metadata from single ID
|
||||
const repo = auth.getRepository();
|
||||
const repositoryHandle = repo.getHandle();
|
||||
@@ -286,9 +291,9 @@
|
||||
if (isLive) {
|
||||
// Live TV channels must be "opened" before streaming; the server returns
|
||||
// a ready-to-play HLS transcoding URL. No resume, no seek, no progress.
|
||||
console.log("loadAndPlay: Opening live stream for channel:", id);
|
||||
log.debug("loadAndPlay: Opening live stream for channel:", id);
|
||||
const liveInfo = await repo.openLiveStream(id);
|
||||
console.log("loadAndPlay: Live stream URL:", liveInfo.streamUrl);
|
||||
log.debug("loadAndPlay: Live stream URL:", liveInfo.streamUrl);
|
||||
mediaSourceId = liveInfo.mediaSourceId;
|
||||
streamUrl = liveInfo.streamUrl;
|
||||
videoNeedsTranscoding = true;
|
||||
@@ -298,13 +303,13 @@
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("loadAndPlay: Getting playback info");
|
||||
log.debug("loadAndPlay: Getting playback info");
|
||||
const playbackInfo = await repo.getPlaybackInfo(id);
|
||||
console.log("loadAndPlay: Got playback info, mediaSourceId:", playbackInfo.mediaSourceId);
|
||||
log.debug("loadAndPlay: Got playback info, mediaSourceId:", playbackInfo.mediaSourceId);
|
||||
|
||||
if (isVideo) {
|
||||
// Playback API now detects HEVC/10-bit and returns transcoded URL when needed
|
||||
console.log("loadAndPlay: Using video stream, directPlay:", playbackInfo.directPlay, "needsTranscoding:", playbackInfo.needsTranscoding);
|
||||
log.debug("loadAndPlay: Using video stream, directPlay:", playbackInfo.directPlay, "needsTranscoding:", playbackInfo.needsTranscoding);
|
||||
mediaSourceId = playbackInfo.mediaSourceId;
|
||||
|
||||
// Prefer a completed download over streaming. Audio has done this
|
||||
@@ -328,7 +333,7 @@
|
||||
|
||||
streamUrl = source.url;
|
||||
videoNeedsTranscoding = source.needsTranscoding;
|
||||
console.log(
|
||||
log.debug(
|
||||
source.isLocal
|
||||
? "loadAndPlay: Playing downloaded file from disk"
|
||||
: `loadAndPlay: Using stream URL: ${streamUrl}`
|
||||
@@ -347,17 +352,17 @@
|
||||
const effectivePosition = startPosition ?? retrievedProgressSeconds ?? 0;
|
||||
videoInitialPosition = effectivePosition > 0 ? effectivePosition : 0;
|
||||
if (videoInitialPosition > 0) {
|
||||
console.log("loadAndPlay: Will seek to position after load:", videoInitialPosition);
|
||||
log.debug("loadAndPlay: Will seek to position after load:", videoInitialPosition);
|
||||
}
|
||||
} else {
|
||||
// For audio, use MPV backend
|
||||
console.log("loadAndPlay: Using MPV backend for audio");
|
||||
log.debug("loadAndPlay: Using MPV backend for audio");
|
||||
|
||||
// Check if we have a queue parameter (e.g., queue=parent:albumId)
|
||||
const queueParamValue = queueParam;
|
||||
if (queueParamValue?.startsWith("parent:")) {
|
||||
const parentId = queueParamValue.substring(7); // Remove "parent:" prefix
|
||||
console.log("loadAndPlay: Loading queue from parent:", parentId);
|
||||
log.debug("loadAndPlay: Loading queue from parent:", parentId);
|
||||
|
||||
// Fetch all tracks from the parent (album/playlist)
|
||||
const result = await repo.getItems(parentId, {
|
||||
@@ -372,16 +377,16 @@
|
||||
const startIndex = audioTracks.findIndex(t => t.id === id);
|
||||
const actualStartIndex = startIndex >= 0 ? startIndex : 0;
|
||||
|
||||
console.log("loadAndPlay: Building queue with", audioTracks.length, "tracks, startIndex:", actualStartIndex);
|
||||
log.debug("loadAndPlay: Building queue with", audioTracks.length, "tracks, startIndex:", actualStartIndex);
|
||||
|
||||
// Build queue items with stream URLs
|
||||
// Add error handling and logging for each track
|
||||
const queueItems = await Promise.all(audioTracks.map(async (t, idx) => {
|
||||
try {
|
||||
console.log(`loadAndPlay: Fetching stream URL for track ${idx + 1}/${audioTracks.length}: ${t.name}`);
|
||||
log.debug(`loadAndPlay: Fetching stream URL for track ${idx + 1}/${audioTracks.length}: ${t.name}`);
|
||||
const trackStreamUrl = await repo.getAudioStreamUrl(t.id);
|
||||
if (!trackStreamUrl) {
|
||||
console.error(`loadAndPlay: Empty stream URL for track: ${t.name}`);
|
||||
log.error(`loadAndPlay: Empty stream URL for track: ${t.name}`);
|
||||
throw new Error(`Failed to get stream URL for ${t.name}`);
|
||||
}
|
||||
return {
|
||||
@@ -398,7 +403,7 @@
|
||||
jellyfinItemId: t.id,
|
||||
};
|
||||
} catch (e) {
|
||||
console.error(`loadAndPlay: Failed to build queue item for track ${t.name}:`, e);
|
||||
log.error(`loadAndPlay: Failed to build queue item for track ${t.name}:`, e);
|
||||
throw e; // Re-throw to fail fast and show error to user
|
||||
}
|
||||
}));
|
||||
@@ -411,10 +416,10 @@
|
||||
} as unknown as PlayQueueRequest);
|
||||
|
||||
// Queue will auto-update from Rust backend event
|
||||
console.log("loadAndPlay: Successfully set up queue with", audioTracks.length, "tracks");
|
||||
log.debug("loadAndPlay: Successfully set up queue with", audioTracks.length, "tracks");
|
||||
} else {
|
||||
// Fallback to single item playback
|
||||
console.log("loadAndPlay: No audio tracks found in parent, falling back to single item");
|
||||
log.debug("loadAndPlay: No audio tracks found in parent, falling back to single item");
|
||||
// Use player_play_tracks - backend fetches all metadata from single ID
|
||||
const repo = auth.getRepository();
|
||||
const repositoryHandle = repo.getHandle();
|
||||
@@ -430,7 +435,7 @@
|
||||
});
|
||||
|
||||
// Queue will auto-update from Rust backend event
|
||||
console.log("loadAndPlay: Set queue with single item:", item.name);
|
||||
log.debug("loadAndPlay: Set queue with single item:", item.name);
|
||||
}
|
||||
} else {
|
||||
// No queue parameter - single item playback
|
||||
@@ -449,7 +454,7 @@
|
||||
});
|
||||
|
||||
// Queue will auto-update from Rust backend event
|
||||
console.log("loadAndPlay: Set queue with single item:", item.name);
|
||||
log.debug("loadAndPlay: Set queue with single item:", item.name);
|
||||
}
|
||||
|
||||
// Seek to start position if provided
|
||||
@@ -463,14 +468,14 @@
|
||||
loading = false;
|
||||
|
||||
// Fetch next episode for video episodes (for skip button)
|
||||
console.log("[NextEpisode] Post-load check: isVideo=", isVideo, "currentMedia=", currentMedia?.kind, currentMedia?.name);
|
||||
nextEpisodeLog.debug("Post-load check: isVideo=", isVideo, "currentMedia=", currentMedia?.kind, currentMedia?.name);
|
||||
if (isVideo && currentMedia) {
|
||||
fetchNextEpisode(currentMedia);
|
||||
} else {
|
||||
console.log("[NextEpisode] Skipped fetchNextEpisode - isVideo:", isVideo, "currentMedia:", !!currentMedia);
|
||||
nextEpisodeLog.debug("Skipped fetchNextEpisode - isVideo:", isVideo, "currentMedia:", !!currentMedia);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("loadAndPlay error:", e);
|
||||
log.error("loadAndPlay error:", e);
|
||||
// Show detailed error including the full error object
|
||||
if (e instanceof Error) {
|
||||
error = `${e.name}: ${e.message}`;
|
||||
@@ -599,21 +604,21 @@
|
||||
// and check for next episodes. HTML5 video plays independently of the Rust
|
||||
// backend queue, so the backend needs these to know what just finished.
|
||||
const mediaId = currentMedia?.id ?? null;
|
||||
console.log("[AutoPlay] Video ended. currentMedia:", mediaId, currentMedia?.name, "itemId (URL):", itemId);
|
||||
autoPlayLog.debug("Video ended. currentMedia:", mediaId, currentMedia?.name, "itemId (URL):", itemId);
|
||||
try {
|
||||
const repo = auth.getRepository();
|
||||
const repoHandle = repo.getHandle();
|
||||
await commands.playerOnPlaybackEnded(mediaId, repoHandle);
|
||||
} catch (e) {
|
||||
console.error("[AutoPlay] Failed to handle playback ended:", e);
|
||||
autoPlayLog.error("Failed to handle playback ended:", e);
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchNextEpisode(media: MediaItem) {
|
||||
nextEpisode = null;
|
||||
console.log("[NextEpisode] fetchNextEpisode called:", { kind: media.kind, seriesId: media.seriesId, seasonId: media.seasonId, indexNumber: media.indexNumber, id: media.id, name: media.name });
|
||||
nextEpisodeLog.debug("fetchNextEpisode called:", { kind: media.kind, seriesId: media.seriesId, seasonId: media.seasonId, indexNumber: media.indexNumber, id: media.id, name: media.name });
|
||||
if (media.kind !== "episode" || !media.seasonId || media.indexNumber == null) {
|
||||
console.log("[NextEpisode] Skipping - not an episode or missing seasonId/indexNumber");
|
||||
nextEpisodeLog.debug("Skipping - not an episode or missing seasonId/indexNumber");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
@@ -621,18 +626,18 @@
|
||||
// Fetch all episodes in the season sorted by episode number
|
||||
const result = await repo.getItems(media.seasonId, { sortBy: "IndexNumber", sortOrder: "Ascending", limit: 500 });
|
||||
const episodes = result.items.filter(e => e.kind === "episode");
|
||||
console.log("[NextEpisode] Season has", episodes.length, "episodes, current index:", media.indexNumber);
|
||||
nextEpisodeLog.debug("Season has", episodes.length, "episodes, current index:", media.indexNumber);
|
||||
|
||||
// Find the episode after the current one by index number
|
||||
const currentIdx = episodes.findIndex(e => e.id === media.id);
|
||||
if (currentIdx >= 0 && currentIdx < episodes.length - 1) {
|
||||
nextEpisode = episodes[currentIdx + 1];
|
||||
console.log("[NextEpisode] Set nextEpisode:", nextEpisode.name, "index:", nextEpisode.indexNumber);
|
||||
nextEpisodeLog.debug("Set nextEpisode:", nextEpisode.name, "index:", nextEpisode.indexNumber);
|
||||
} else {
|
||||
console.log("[NextEpisode] No next episode in season (current position:", currentIdx, "of", episodes.length, ")");
|
||||
nextEpisodeLog.debug("No next episode in season (current position:", currentIdx, "of", episodes.length, ")");
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("[NextEpisode] Failed to fetch next episode:", e);
|
||||
nextEpisodeLog.error("Failed to fetch next episode:", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -29,6 +29,9 @@
|
||||
} from "$lib/services/networkType";
|
||||
import { experimentalNativeVideo } from "$lib/stores/nativeVideo";
|
||||
import { getPlaybackCapabilities } from "$lib/services/playbackCapabilities";
|
||||
import { createLogger } from "$lib/utils/logger";
|
||||
|
||||
const log = createLogger("SettingsPage");
|
||||
|
||||
const episodeLimitOptions = [
|
||||
{ value: 0, label: "Unlimited" },
|
||||
@@ -153,7 +156,7 @@
|
||||
// Load cache stats in parallel but don't block on it
|
||||
loadCacheStats();
|
||||
} catch (e) {
|
||||
console.error("Failed to load settings:", e);
|
||||
log.error("Failed to load settings:", e);
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
@@ -164,7 +167,7 @@
|
||||
cacheLoading = true;
|
||||
cacheStats = await getCacheStats();
|
||||
} catch (e) {
|
||||
console.error("Failed to load cache stats:", e);
|
||||
log.error("Failed to load cache stats:", e);
|
||||
} finally {
|
||||
cacheLoading = false;
|
||||
}
|
||||
@@ -176,7 +179,7 @@
|
||||
// Reload stats to reflect new limit
|
||||
await loadCacheStats();
|
||||
} catch (e) {
|
||||
console.error("Failed to set cache limit:", e);
|
||||
log.error("Failed to set cache limit:", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -186,7 +189,7 @@
|
||||
await clearCache();
|
||||
await loadCacheStats();
|
||||
} catch (e) {
|
||||
console.error("Failed to clear cache:", e);
|
||||
log.error("Failed to clear cache:", e);
|
||||
} finally {
|
||||
clearingCache = false;
|
||||
}
|
||||
@@ -214,7 +217,7 @@
|
||||
try {
|
||||
await commands.playerSetAudioSettings(settings);
|
||||
} catch (e) {
|
||||
console.error("Failed to save audio settings:", e);
|
||||
log.error("Failed to save audio settings:", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -222,7 +225,7 @@
|
||||
try {
|
||||
await commands.playerSetVideoSettings(videoSettings);
|
||||
} catch (e) {
|
||||
console.error("Failed to save video settings:", e);
|
||||
log.error("Failed to save video settings:", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -234,7 +237,7 @@
|
||||
// rather than at the next network change.
|
||||
await reportNetworkState();
|
||||
} catch (e) {
|
||||
console.error("Failed to save download settings:", e);
|
||||
log.error("Failed to save download settings:", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user