fix: refresh stale proxy token on video error
This commit is contained in:
@@ -243,6 +243,40 @@ describe("browser player flow", () => {
|
||||
assert.equal(modules.streamUrlForMode("sub", "best"), "");
|
||||
});
|
||||
|
||||
test("refreshes stale current stream token from episode data", async () => {
|
||||
modules.state.elements.video.currentTime = 64;
|
||||
modules.state.playback.modeSources = {
|
||||
sub: { token: "stale-token", type: "m3u8", qualities: ["720p"], subtitles: [] },
|
||||
};
|
||||
globalThis.fetch = ((url: string) => {
|
||||
fetchCalls.push({ url });
|
||||
return Promise.resolve({
|
||||
ok: true,
|
||||
status: 200,
|
||||
json: () =>
|
||||
Promise.resolve({
|
||||
mode_sources: {
|
||||
sub: { token: "fresh-token", type: "m3u8", qualities: ["720p"], subtitles: [] },
|
||||
},
|
||||
}),
|
||||
} as Response);
|
||||
}) as typeof fetch;
|
||||
|
||||
const refreshed = await modules.refreshCurrentModeSource();
|
||||
|
||||
assert.equal(refreshed, true);
|
||||
assert.deepEqual(
|
||||
fetchCalls.map((call) => call.url),
|
||||
["/api/watch/episode/42/3?mode=sub"],
|
||||
);
|
||||
assert.equal(modules.state.playback.modeSources.sub?.token, "fresh-token");
|
||||
assert.equal(
|
||||
modules.state.elements.video.src,
|
||||
"/watch/proxy/stream?mode=sub&token=fresh-token&hls=1",
|
||||
);
|
||||
assert.equal(modules.state.playback.pendingSeekTime, 64);
|
||||
});
|
||||
|
||||
test("saves progress on pause and after scrub mouseup", async () => {
|
||||
modules.setupProgress();
|
||||
modules.state.elements.video.currentTime = 30;
|
||||
|
||||
@@ -16,6 +16,7 @@ import { setupQuality, updateQualityOptions } from "./quality";
|
||||
import { setupSkip, updateSkipButton, updateAutoSkipButton } from "./skip";
|
||||
import { setupSegmentEditor } from "./skip/editor";
|
||||
import { resolveActiveSegments, renderSegments } from "./skip/segments";
|
||||
import { refreshCurrentModeSource } from "./source";
|
||||
import { state, initState, showEndState, hideEndState } from "./state";
|
||||
import { safeLocalStorage } from "./storage";
|
||||
import { setupSubtitles, updateSubtitleOptions, updateSubtitleRender } from "./subtitles";
|
||||
@@ -280,6 +281,27 @@ const initPlayer = async (): Promise<void> => {
|
||||
},
|
||||
{ signal },
|
||||
);
|
||||
let sourceRefreshInFlight = false;
|
||||
state.elements.video.addEventListener(
|
||||
"error",
|
||||
() => {
|
||||
if (sourceRefreshInFlight) {
|
||||
return;
|
||||
}
|
||||
sourceRefreshInFlight = true;
|
||||
refreshCurrentModeSource(signal)
|
||||
.catch((error) => {
|
||||
if (error instanceof DOMException && error.name === "AbortError") {
|
||||
return;
|
||||
}
|
||||
console.error("failed to refresh video source:", error);
|
||||
})
|
||||
.finally(() => {
|
||||
sourceRefreshInFlight = false;
|
||||
});
|
||||
},
|
||||
{ signal },
|
||||
);
|
||||
// update progress bar during buffering
|
||||
state.elements.video.addEventListener(
|
||||
"progress",
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import { state } from "./state";
|
||||
import { safeLocalStorage } from "./storage";
|
||||
import { isRecord, parseModeSources } from "./validate";
|
||||
import { loadVideoSource } from "./video";
|
||||
|
||||
export const streamUrlForMode = (mode: string, quality?: string): string => {
|
||||
const src = state.playback.modeSources[mode];
|
||||
@@ -16,3 +19,30 @@ export const streamUrlForMode = (mode: string, quality?: string): string => {
|
||||
|
||||
return url;
|
||||
};
|
||||
|
||||
export const refreshCurrentModeSource = async (signal?: AbortSignal): Promise<boolean> => {
|
||||
const mode = state.playback.currentMode;
|
||||
const res = await fetch(
|
||||
`/api/watch/episode/${state.episode.malID}/${encodeURIComponent(state.episode.current)}?mode=${encodeURIComponent(mode)}`,
|
||||
{ signal },
|
||||
);
|
||||
if (!res.ok) {
|
||||
throw new Error(`mode source refresh failed with status ${res.status}`);
|
||||
}
|
||||
|
||||
const data: unknown = await res.json();
|
||||
if (!isRecord(data)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const refreshedSource = parseModeSources(data.mode_sources)[mode];
|
||||
if (!refreshedSource?.token) {
|
||||
return false;
|
||||
}
|
||||
|
||||
state.playback.modeSources = { ...state.playback.modeSources, [mode]: refreshedSource };
|
||||
|
||||
const preferredQuality = safeLocalStorage.getItem("mal:preferred-quality") || "best";
|
||||
loadVideoSource(streamUrlForMode(mode, preferredQuality), refreshedSource.type);
|
||||
return true;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user