feat: restore preferred audio mode on player init
This commit is contained in:
@@ -5,7 +5,12 @@ 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 { hydrateAlternateMode, setupMode, updateModeButtons } from "./mode";
|
import {
|
||||||
|
ensurePreferredModeSource,
|
||||||
|
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";
|
||||||
@@ -88,7 +93,7 @@ const updatePreviewUI = (ratio: number): void => {
|
|||||||
state.elements.previewPopover.style.left = `${Math.max(popoverWidth / 2, Math.min(barWidth - popoverWidth / 2, ratio * barWidth))}px`;
|
state.elements.previewPopover.style.left = `${Math.max(popoverWidth / 2, Math.min(barWidth - popoverWidth / 2, ratio * barWidth))}px`;
|
||||||
};
|
};
|
||||||
|
|
||||||
const initPlayer = (): void => {
|
const initPlayer = async (): Promise<void> => {
|
||||||
const container = document.querySelector("[data-video-player]") as HTMLElement | null;
|
const container = document.querySelector("[data-video-player]") as HTMLElement | null;
|
||||||
if (!container) return;
|
if (!container) return;
|
||||||
if (container === currentContainer) return;
|
if (container === currentContainer) return;
|
||||||
@@ -119,15 +124,6 @@ const initPlayer = (): void => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// build video src from mode, token, and saved quality preference
|
|
||||||
const preferredQuality = safeLocalStorage.getItem("mal:preferred-quality") || "best";
|
|
||||||
const streamToken = state.playback.modeSources[state.playback.currentMode]?.token;
|
|
||||||
if (streamToken) {
|
|
||||||
const source = state.playback.modeSources[state.playback.currentMode];
|
|
||||||
const url = `${state.playback.streamURL}?mode=${encodeURIComponent(state.playback.currentMode)}&token=${encodeURIComponent(streamToken)}${source?.type === "m3u8" ? "&hls=1" : ""}${preferredQuality !== "best" ? `&quality=${encodeURIComponent(preferredQuality)}` : ""}`;
|
|
||||||
loadVideoSource(url, source?.type);
|
|
||||||
}
|
|
||||||
|
|
||||||
setupProgress();
|
setupProgress();
|
||||||
setupControls();
|
setupControls();
|
||||||
setupKeyboard();
|
setupKeyboard();
|
||||||
@@ -143,6 +139,22 @@ const initPlayer = (): void => {
|
|||||||
setupAutoplayButton();
|
setupAutoplayButton();
|
||||||
updateAutoSkipButton();
|
updateAutoSkipButton();
|
||||||
showControls();
|
showControls();
|
||||||
|
|
||||||
|
await ensurePreferredModeSource(signal);
|
||||||
|
|
||||||
|
// build video src from mode, token, and saved quality preference
|
||||||
|
const preferredQuality = safeLocalStorage.getItem("mal:preferred-quality") || "best";
|
||||||
|
const streamToken = state.playback.modeSources[state.playback.currentMode]?.token;
|
||||||
|
if (streamToken) {
|
||||||
|
const source = state.playback.modeSources[state.playback.currentMode];
|
||||||
|
const url = `${state.playback.streamURL}?mode=${encodeURIComponent(state.playback.currentMode)}&token=${encodeURIComponent(streamToken)}${source?.type === "m3u8" ? "&hls=1" : ""}${preferredQuality !== "best" ? `&quality=${encodeURIComponent(preferredQuality)}` : ""}`;
|
||||||
|
loadVideoSource(url, source?.type);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateSubtitleOptions();
|
||||||
|
updateQualityOptions();
|
||||||
|
updateModeButtons();
|
||||||
|
|
||||||
if (state.playback.modeSwitchedFrom === "dub" && state.playback.currentMode === "sub") {
|
if (state.playback.modeSwitchedFrom === "dub" && state.playback.currentMode === "sub") {
|
||||||
window.showToast?.({
|
window.showToast?.({
|
||||||
message: `Episode ${state.episode.current} is only available in sub, switched from dub.`,
|
message: `Episode ${state.episode.current} is only available in sub, switched from dub.`,
|
||||||
@@ -418,9 +430,11 @@ const initPlayer = (): void => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
onReady(initPlayer);
|
onReady(() => {
|
||||||
|
void initPlayer();
|
||||||
|
});
|
||||||
onHtmxLoad((root) => {
|
onHtmxLoad((root) => {
|
||||||
if (root.matches("[data-video-player]") || root.querySelector("[data-video-player]")) {
|
if (root.matches("[data-video-player]") || root.querySelector("[data-video-player]")) {
|
||||||
initPlayer();
|
void initPlayer();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import type { ModeSource } from "./types";
|
||||||
import { state } from "./state";
|
import { state } from "./state";
|
||||||
import { showControls } from "./controls";
|
import { showControls } from "./controls";
|
||||||
import { updateSubtitleOptions } from "./subtitles";
|
import { updateSubtitleOptions } from "./subtitles";
|
||||||
@@ -13,23 +14,58 @@ const alternateModeFor = (mode: string): "sub" | "dub" | null => {
|
|||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const fetchModeSource = async (
|
||||||
|
episode: string,
|
||||||
|
mode: "sub" | "dub",
|
||||||
|
signal?: AbortSignal,
|
||||||
|
): Promise<ModeSource | null> => {
|
||||||
|
const res = await fetch(
|
||||||
|
`/api/watch/episode/${state.episode.malID}/${encodeURIComponent(episode)}?mode=${encodeURIComponent(mode)}`,
|
||||||
|
{ signal },
|
||||||
|
);
|
||||||
|
if (!res.ok) return null;
|
||||||
|
|
||||||
|
const data: unknown = await res.json();
|
||||||
|
if (!isRecord(data)) return null;
|
||||||
|
|
||||||
|
const sources = parseModeSources(data.mode_sources);
|
||||||
|
return sources[mode] ?? null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ensurePreferredModeSource = async (signal?: AbortSignal): Promise<string> => {
|
||||||
|
const storedMode = safeLocalStorage.getItem("player-audio-mode");
|
||||||
|
const preferredMode = storedMode === "sub" || storedMode === "dub" ? storedMode : null;
|
||||||
|
if (!preferredMode) return state.playback.currentMode;
|
||||||
|
if (state.playback.modeSources[preferredMode]?.token) {
|
||||||
|
state.playback.currentMode = preferredMode;
|
||||||
|
return preferredMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const preferredSource = await fetchModeSource(state.episode.current, preferredMode, signal);
|
||||||
|
if (!preferredSource?.token) return state.playback.currentMode;
|
||||||
|
|
||||||
|
state.playback.modeSources = {
|
||||||
|
...state.playback.modeSources,
|
||||||
|
[preferredMode]: preferredSource,
|
||||||
|
};
|
||||||
|
state.playback.currentMode = preferredMode;
|
||||||
|
} catch (error: unknown) {
|
||||||
|
if (error instanceof DOMException && error.name === "AbortError") {
|
||||||
|
return state.playback.currentMode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return state.playback.currentMode;
|
||||||
|
};
|
||||||
|
|
||||||
export const hydrateAlternateMode = async (signal?: AbortSignal): Promise<void> => {
|
export const hydrateAlternateMode = async (signal?: AbortSignal): Promise<void> => {
|
||||||
const alternateMode = alternateModeFor(state.playback.currentMode);
|
const alternateMode = alternateModeFor(state.playback.currentMode);
|
||||||
if (!alternateMode) return;
|
if (!alternateMode) return;
|
||||||
if (state.playback.modeSources[alternateMode]?.token) return;
|
if (state.playback.modeSources[alternateMode]?.token) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await fetch(
|
const alternateSource = await fetchModeSource(state.episode.current, alternateMode, signal);
|
||||||
`/api/watch/episode/${state.episode.malID}/${encodeURIComponent(state.episode.current)}?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;
|
if (!alternateSource?.token) return;
|
||||||
|
|
||||||
state.playback.modeSources = {
|
state.playback.modeSources = {
|
||||||
|
|||||||
Reference in New Issue
Block a user