102 lines
3.6 KiB
TypeScript
102 lines
3.6 KiB
TypeScript
import { state } from "./state";
|
|
import { displayTimeFromAbsolute } from "./timeline";
|
|
import { showControls } from "./controls";
|
|
import { updateSubtitleOptions } from "./subtitles";
|
|
import { updateQualityOptions } from "./quality";
|
|
import { safeLocalStorage } from "./storage";
|
|
|
|
// builds stream URL with mode, token, and optional quality param
|
|
const streamUrlForMode = (mode: string, quality?: string): string => {
|
|
const src = state.modeSources[mode];
|
|
if (!src?.token) return "";
|
|
let url = `${state.streamURL}?mode=${encodeURIComponent(mode)}&token=${encodeURIComponent(src.token)}`;
|
|
if (quality && quality !== "best") url += `&quality=${encodeURIComponent(quality)}`;
|
|
return url;
|
|
};
|
|
|
|
// switches video src while preserving playback position
|
|
const loadVideo = (url: string): void => {
|
|
if (!url) return;
|
|
const wasPlaying = !state.video.paused;
|
|
const prevTime = displayTimeFromAbsolute(state.video.currentTime);
|
|
state.video.src = url;
|
|
state.video.load();
|
|
state.pendingSeekTime = prevTime; // restored in loadedmetadata handler
|
|
if (wasPlaying) {
|
|
state.video.play().catch(() => undefined);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Switches between sub/dub mode.
|
|
* Saves preference to localStorage, reloads video src.
|
|
*/
|
|
export const switchMode = (mode: string): void => {
|
|
if (!state.availableModes.includes(mode) || mode === state.currentMode) return;
|
|
state.currentMode = mode;
|
|
safeLocalStorage.setItem("player-audio-mode", mode);
|
|
const qualitySelect = state.container.querySelector(
|
|
"[data-quality-select]",
|
|
) as HTMLSelectElement | null;
|
|
loadVideo(streamUrlForMode(mode, qualitySelect?.value));
|
|
updateSubtitleOptions();
|
|
updateQualityOptions();
|
|
updateModeButtons();
|
|
};
|
|
|
|
/**
|
|
* Updates dub/sub button styling based on current mode.
|
|
* Disables unavailable modes.
|
|
*/
|
|
export const updateModeButtons = (): void => {
|
|
const dub = state.container.querySelector("[data-mode-dub]") as HTMLButtonElement | null;
|
|
const sub = state.container.querySelector("[data-mode-sub]") as HTMLButtonElement | null;
|
|
const m = state.currentMode;
|
|
|
|
dub?.classList.toggle("text-accent", m === "dub");
|
|
dub?.classList.toggle("text-foreground", m !== "dub");
|
|
dub?.classList.toggle("opacity-50", !state.availableModes.includes("dub"));
|
|
dub?.classList.toggle("cursor-not-allowed", !state.availableModes.includes("dub"));
|
|
if (dub) {
|
|
dub.disabled = !state.availableModes.includes("dub");
|
|
}
|
|
|
|
sub?.classList.toggle("text-accent", m === "sub");
|
|
sub?.classList.toggle("text-foreground", m !== "sub");
|
|
sub?.classList.toggle("opacity-50", !state.availableModes.includes("sub"));
|
|
sub?.classList.toggle("cursor-not-allowed", !state.availableModes.includes("sub"));
|
|
if (sub) {
|
|
sub.disabled = !state.availableModes.includes("sub");
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Binds click handlers for mode buttons and autoplay toggle.
|
|
*/
|
|
export const setupMode = (): void => {
|
|
const dub = state.container.querySelector("[data-mode-dub]") as HTMLButtonElement | null;
|
|
const sub = state.container.querySelector("[data-mode-sub]") as HTMLButtonElement | null;
|
|
|
|
dub?.addEventListener("click", () => {
|
|
if (state.availableModes.includes("dub")) {
|
|
switchMode("dub");
|
|
showControls();
|
|
}
|
|
});
|
|
sub?.addEventListener("click", () => {
|
|
if (state.availableModes.includes("sub")) {
|
|
switchMode("sub");
|
|
showControls();
|
|
}
|
|
});
|
|
|
|
const autoplayBtn = document.querySelector("[data-autoplay]") as HTMLInputElement | null;
|
|
autoplayBtn?.addEventListener("change", (e) => {
|
|
safeLocalStorage.setItem(
|
|
"mal:autoplay-enabled",
|
|
(e.target as HTMLInputElement).checked ? "true" : "false",
|
|
);
|
|
showControls();
|
|
});
|
|
};
|