import { state } from '../state'; import { SkipSegment } from '../types'; import { displayTimeFromAbsolute } from '../timeline'; import { resolveActiveSegments, renderSegments } from '../skip/segments'; import { updateSubtitleOptions } from '../subtitles'; import { updateQualityOptions } from '../quality'; import { updateModeButtons } from '../mode'; import { updateOverlay, isAutoplayEnabled, updateEpisodeHighlight, switchEpisodeRange } from './ui'; import { markEpisodeTransition } from '../progress'; export const goToNextEpisode = async (): Promise => { const currentEp = Number.parseInt(state.currentEpisode, 10); if (!currentEp) return; if (state.totalEpisodes > 0 && currentEp >= state.totalEpisodes) { import('./complete').then(m => m.completeAnime(currentEp)); return; } if (!isAutoplayEnabled()) return; const nextEp = currentEp + 1; markEpisodeTransition(nextEp); try { const res = await fetch(`/api/watch/episode/${state.malID}/${nextEp}`); if (!res.ok) { sessionStorage.setItem('mal:autoplay-next', 'true'); const url = new URL(window.location.href); url.searchParams.set('ep', String(nextEp)); window.location.href = url.toString(); return; } const data = await res.json(); state.modeSources = data.mode_sources ?? {}; state.availableModes = data.available_modes ?? []; const fallback = state.availableModes.find(m => state.modeSources[m]?.token); if (!fallback) { sessionStorage.setItem('mal:autoplay-next', 'true'); const url = new URL(window.location.href); url.searchParams.set('ep', String(nextEp)); window.location.href = url.toString(); return; } state.video.src = `${state.streamURL}?mode=${encodeURIComponent(fallback)}&token=${encodeURIComponent(state.modeSources[fallback].token)}`; state.video.load(); if (!state.video.paused) state.video.play().catch(() => {}); state.currentEpisode = String(nextEp); state.pendingSeekTime = null; state.completionSent = false; state.completionAttempts = 0; state.activeSubtitles = []; updateSubtitleOptions(); updateQualityOptions(); updateModeButtons(); updateOverlay(state.currentEpisode, data.episode_title ?? ''); if (data.segments?.length) { state.parsedSegments = data.segments .map((s: SkipSegment) => ({ ...s, start: Number(s.start) || 0, end: Number(s.end) || 0 })) .filter((s: SkipSegment) => s.end > s.start); resolveActiveSegments(); renderSegments(); } state.episodeList ?.querySelectorAll('[data-episode-id]') .forEach(el => el.classList.remove('bg-accent/20')); const newListEl = state.episodeList?.querySelector(`[data-episode-id="${nextEp}"]`); newListEl?.classList.add('bg-accent/20'); if (state.episodeGrid) { state.episodeGrid.querySelectorAll('[data-episode-id]').forEach(el => { el.classList.remove('bg-accent/20', 'ring-2', 'ring-accent', 'text-accent'); }); switchEpisodeRange(Math.floor((nextEp - 1) / 100)); const newGridEl = state.episodeGrid.querySelector(`[data-episode-id="${nextEp}"]`); newGridEl?.classList.add('bg-accent/20', 'ring-2', 'ring-accent', 'text-accent'); } const url = new URL(window.location.href); url.searchParams.set('ep', String(nextEp)); history.pushState(null, '', url.toString()); state.transitionEpisode = null; } catch { sessionStorage.setItem('mal:autoplay-next', 'true'); const url = new URL(window.location.href); url.searchParams.set('ep', String(nextEp)); window.location.href = url.toString(); } };