feat: preload alternate mode source on episode load
This commit is contained in:
@@ -3,7 +3,7 @@ import type { SkipSegment } from "../types";
|
|||||||
import { resolveActiveSegments, renderSegments } from "../skip/segments";
|
import { resolveActiveSegments, renderSegments } from "../skip/segments";
|
||||||
import { updateSubtitleOptions } from "../subtitles";
|
import { updateSubtitleOptions } from "../subtitles";
|
||||||
import { updateQualityOptions } from "../quality";
|
import { updateQualityOptions } from "../quality";
|
||||||
import { updateModeButtons } from "../mode";
|
import { hydrateAlternateMode, updateModeButtons } from "../mode";
|
||||||
import { updateOverlay, isAutoplayEnabled, switchEpisodeRange } from "./ui";
|
import { updateOverlay, isAutoplayEnabled, switchEpisodeRange } from "./ui";
|
||||||
import { markEpisodeTransition } from "../progress";
|
import { markEpisodeTransition } from "../progress";
|
||||||
import { safeLocalStorage } from "../storage";
|
import { safeLocalStorage } from "../storage";
|
||||||
@@ -105,6 +105,7 @@ export const goToNextEpisode = async (): Promise<void> => {
|
|||||||
updateQualityOptions();
|
updateQualityOptions();
|
||||||
updateModeButtons();
|
updateModeButtons();
|
||||||
updateOverlay(state.currentEpisode, data.episode_title ?? "");
|
updateOverlay(state.currentEpisode, data.episode_title ?? "");
|
||||||
|
void hydrateAlternateMode();
|
||||||
|
|
||||||
// update skip segments
|
// update skip segments
|
||||||
if (data.segments?.length) {
|
if (data.segments?.length) {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { setupKeyboard } from "./keyboard";
|
|||||||
import { setupSubtitles, updateSubtitleOptions, updateSubtitleRender } from "./subtitles";
|
import { setupSubtitles, updateSubtitleOptions, updateSubtitleRender } from "./subtitles";
|
||||||
import { setupSkip, updateSkipButton, updateAutoSkipButton } from "./skip";
|
import { setupSkip, updateSkipButton, updateAutoSkipButton } from "./skip";
|
||||||
import { setupQuality, updateQualityOptions } from "./quality";
|
import { setupQuality, updateQualityOptions } from "./quality";
|
||||||
import { setupMode, updateModeButtons } from "./mode";
|
import { hydrateAlternateMode, setupMode, updateModeButtons } from "./mode";
|
||||||
import { setupAutoplayButton, updateEpisodeHighlight, switchEpisodeRange } from "./episodes/ui";
|
import { setupAutoplayButton, updateEpisodeHighlight, switchEpisodeRange } from "./episodes/ui";
|
||||||
import { goToNextEpisode } from "./episodes/nav";
|
import { goToNextEpisode } from "./episodes/nav";
|
||||||
import { resolveActiveSegments, renderSegments } from "./skip/segments";
|
import { resolveActiveSegments, renderSegments } from "./skip/segments";
|
||||||
@@ -385,6 +385,7 @@ const initPlayer = (): void => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setupThumbnails();
|
setupThumbnails();
|
||||||
|
void hydrateAlternateMode(signal);
|
||||||
};
|
};
|
||||||
|
|
||||||
onReady(initPlayer);
|
onReady(initPlayer);
|
||||||
|
|||||||
@@ -5,6 +5,85 @@ import { updateQualityOptions } from "./quality";
|
|||||||
import { safeLocalStorage } from "./storage";
|
import { safeLocalStorage } from "./storage";
|
||||||
import { streamUrlForMode } from "./source";
|
import { streamUrlForMode } from "./source";
|
||||||
import { loadVideoSource } from "./video";
|
import { loadVideoSource } from "./video";
|
||||||
|
import type { ModeSource } from "./types";
|
||||||
|
|
||||||
|
const isRecord = (v: unknown): v is Record<string, unknown> =>
|
||||||
|
typeof v === "object" && v !== null && !Array.isArray(v);
|
||||||
|
|
||||||
|
const isStringArray = (v: unknown): v is string[] =>
|
||||||
|
Array.isArray(v) && v.every((item) => typeof item === "string");
|
||||||
|
|
||||||
|
const isSubtitleItemArray = (v: unknown): v is { lang: string; token: string }[] =>
|
||||||
|
Array.isArray(v) &&
|
||||||
|
v.every(
|
||||||
|
(item) => isRecord(item) && typeof item.lang === "string" && typeof item.token === "string",
|
||||||
|
);
|
||||||
|
|
||||||
|
const parseModeSources = (v: unknown): Record<string, ModeSource> => {
|
||||||
|
if (!isRecord(v)) return {};
|
||||||
|
|
||||||
|
const out: Record<string, ModeSource> = {};
|
||||||
|
for (const [key, value] of Object.entries(v)) {
|
||||||
|
if (!isRecord(value)) continue;
|
||||||
|
if (typeof value.token !== "string" || value.token === "") continue;
|
||||||
|
|
||||||
|
const subtitles = value.subtitles == null ? [] : value.subtitles;
|
||||||
|
if (!isSubtitleItemArray(subtitles)) continue;
|
||||||
|
|
||||||
|
const qualities = value.qualities;
|
||||||
|
out[key] = {
|
||||||
|
token: value.token,
|
||||||
|
subtitles,
|
||||||
|
qualities: isStringArray(qualities) ? qualities : undefined,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return out;
|
||||||
|
};
|
||||||
|
|
||||||
|
const alternateModeFor = (mode: string): "sub" | "dub" | null => {
|
||||||
|
if (mode === "sub") return "dub";
|
||||||
|
if (mode === "dub") return "sub";
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const mergeAvailableMode = (mode: string): void => {
|
||||||
|
if (state.availableModes.includes(mode)) return;
|
||||||
|
state.availableModes = [...state.availableModes, mode].sort();
|
||||||
|
};
|
||||||
|
|
||||||
|
export const hydrateAlternateMode = async (signal?: AbortSignal): Promise<void> => {
|
||||||
|
const alternateMode = alternateModeFor(state.currentMode);
|
||||||
|
if (!alternateMode) return;
|
||||||
|
if (state.modeSources[alternateMode]?.token) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch(
|
||||||
|
`/api/watch/episode/${state.malID}/${encodeURIComponent(state.currentEpisode)}?mode=${encodeURIComponent(alternateMode)}`,
|
||||||
|
{ signal },
|
||||||
|
);
|
||||||
|
if (!res.ok) return;
|
||||||
|
|
||||||
|
const data: unknown = await res.json();
|
||||||
|
if (!isRecord(data)) return;
|
||||||
|
|
||||||
|
const sources = parseModeSources(data.mode_sources);
|
||||||
|
const alternateSource = sources[alternateMode];
|
||||||
|
if (!alternateSource?.token) return;
|
||||||
|
|
||||||
|
state.modeSources = {
|
||||||
|
...state.modeSources,
|
||||||
|
[alternateMode]: alternateSource,
|
||||||
|
};
|
||||||
|
mergeAvailableMode(alternateMode);
|
||||||
|
|
||||||
|
updateSubtitleOptions();
|
||||||
|
updateQualityOptions();
|
||||||
|
updateModeButtons();
|
||||||
|
} catch (error: unknown) {
|
||||||
|
if (error instanceof DOMException && error.name === "AbortError") return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Switches between sub/dub mode.
|
* Switches between sub/dub mode.
|
||||||
|
|||||||
Reference in New Issue
Block a user