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:
2026-08-20 19:29:59 +02:00
parent 4c82a0a025
commit d54d8cc7c4
63 changed files with 686 additions and 490 deletions
+9 -6
View File
@@ -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);
+4 -1
View File
@@ -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) {
+6 -3
View File
@@ -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);
}
}
}
+16 -13
View File
@@ -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) {
+4 -1
View File
@@ -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 {
+51 -46
View File
@@ -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);
}
}
+10 -7
View File
@@ -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);
}
}