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.
79 lines
2.3 KiB
TypeScript
79 lines
2.3 KiB
TypeScript
/**
|
|
* Next Episode Service
|
|
*
|
|
* Handles user interactions with the next episode popup.
|
|
* Backend manages countdown logic and autoplay decisions.
|
|
*
|
|
* Navigation uses goto() directly to load the next episode.
|
|
* The player page's $effect detects the URL param change and
|
|
* calls loadAndPlay for the new episode.
|
|
*
|
|
* TRACES: UR-023 | DR-047, DR-048
|
|
*/
|
|
|
|
import { goto } from "$app/navigation";
|
|
import { cancelAutoplayCountdown } from "$lib/api/autoplay";
|
|
import { nextEpisode } from "$lib/stores/nextEpisode";
|
|
import type { MediaItem } from "$lib/api/types";
|
|
import { createLogger } from "$lib/utils/logger";
|
|
|
|
const log = createLogger("NextEpisode");
|
|
|
|
/** Guard against double-navigation */
|
|
let isNavigating = false;
|
|
|
|
/**
|
|
* Cleanup next episode state (called on unmount/destroy)
|
|
*/
|
|
export function cleanup() {
|
|
nextEpisode.reset();
|
|
isNavigating = false;
|
|
}
|
|
|
|
/**
|
|
* Cancel the autoplay countdown
|
|
* Called when user clicks "Cancel" button on next episode popup
|
|
*/
|
|
export async function cancelAutoPlay() {
|
|
await cancelAutoplayCountdown();
|
|
nextEpisode.hidePopup();
|
|
}
|
|
|
|
/**
|
|
* Navigate to the next episode via goto().
|
|
* Uses replaceState to prevent history buildup when auto-advancing.
|
|
*
|
|
* Advancing to a next episode always starts that episode from the
|
|
* beginning, even if it was previously started or watched. The `restart`
|
|
* query param signals the player page to skip the resume-progress check.
|
|
*/
|
|
function navigateToEpisode(episode: MediaItem) {
|
|
if (isNavigating) {
|
|
log.warn("Already navigating, skipping duplicate navigation to", episode.id);
|
|
return;
|
|
}
|
|
isNavigating = true;
|
|
log.debug("Navigating to next episode:", episode.id, episode.name);
|
|
nextEpisode.hidePopup();
|
|
goto(`/player/${episode.id}?restart=true`, { replaceState: true }).finally(() => {
|
|
isNavigating = false;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Manually play the next episode
|
|
* Called when user clicks "Play Now" button on next episode popup
|
|
*/
|
|
export async function watchNextManually(nextEpisodeItem: MediaItem) {
|
|
await cancelAutoplayCountdown();
|
|
navigateToEpisode(nextEpisodeItem);
|
|
}
|
|
|
|
/**
|
|
* Auto-play the next episode when countdown reaches 0
|
|
* Called by playerEvents when countdown_tick event has remaining_seconds: 0
|
|
*/
|
|
export function autoPlayNext(nextEpisodeItem: MediaItem) {
|
|
navigateToEpisode(nextEpisodeItem);
|
|
}
|