fix: reduce hls playback churn

This commit is contained in:
2026-06-18 20:50:44 +02:00
committed by Milas Holsting
parent 9e0f2231b5
commit bda3c58a98
4 changed files with 91 additions and 18 deletions

View File

@@ -156,13 +156,27 @@ const initPlayer = async (): Promise<void> => {
await ensurePreferredModeSource(signal);
const resumeAfterModeSwitch = (() => {
try {
const raw = sessionStorage.getItem("mal:resume-after-mode-switch");
if (raw === null) return null;
sessionStorage.removeItem("mal:resume-after-mode-switch");
const parsed = Number(raw);
return Number.isFinite(parsed) && parsed >= 0 ? parsed : null;
} catch (error) {
console.error("failed to parse resume state:", error);
return null;
}
})();
const initialStartTime = resumeAfterModeSwitch ?? state.playback.startTimeSeconds;
// 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);
loadVideoSource(url, source?.type, initialStartTime);
}
updateSubtitleOptions();
@@ -190,20 +204,6 @@ const initPlayer = async (): Promise<void> => {
const resumeTime = bounds.duration > 0 ? Math.min(startTime, bounds.duration) : 0;
const isAtEnd = startTime > 0 && bounds.duration > 0 && startTime >= bounds.duration - 2;
// Resume after a mode-switch page reload (best effort, session-scoped).
const resumeAfterModeSwitch = (() => {
try {
const raw = sessionStorage.getItem("mal:resume-after-mode-switch");
if (raw === null) return null;
sessionStorage.removeItem("mal:resume-after-mode-switch");
const parsed = Number(raw);
return Number.isFinite(parsed) && parsed >= 0 ? parsed : null;
} catch (error) {
console.error("failed to parse resume state:", error);
return null;
}
})();
if (resumeAfterModeSwitch !== null) {
const clamped = bounds.duration > 0 ? Math.min(resumeAfterModeSwitch, bounds.duration) : 0;
if (clamped > 0) {
@@ -425,7 +425,9 @@ const initPlayer = async (): Promise<void> => {
}
setupThumbnails();
void hydrateAlternateMode(signal);
window.setTimeout(() => {
if (!signal.aborted) void hydrateAlternateMode(signal);
}, 3000);
document.body.addEventListener(
"htmx:beforeSwap",

View File

@@ -38,7 +38,7 @@ const shouldUseHLS = (type: string | undefined, url: string): boolean => {
* Some browsers can be flaky when switching between HLS URLs while playing.
* Clearing `src` first ensures the media element fully resets before the new URL is set.
*/
export const loadVideoSource = (url: string, type?: string): void => {
export const loadVideoSource = (url: string, type?: string, startTimeSeconds?: number): void => {
if (!url) return;
const wasPlaying = !state.elements.video.paused;
@@ -49,10 +49,24 @@ export const loadVideoSource = (url: string, type?: string): void => {
destroyVideoSource();
if (shouldUseHLS(type, url) && Hls.isSupported()) {
hls = new Hls();
hls = new Hls({
autoStartLoad: false,
backBufferLength: 30,
maxBufferLength: 18,
maxMaxBufferLength: 30,
maxBufferSize: 20 * 1000 * 1000,
startFragPrefetch: false,
});
stopHLSProfile = attachHLSProfile(hls, state.elements.video);
hls.loadSource(url);
hls.attachMedia(state.elements.video);
hls.once(Hls.Events.MEDIA_ATTACHED, () => {
const startPosition =
startTimeSeconds !== undefined && Number.isFinite(startTimeSeconds) && startTimeSeconds > 0
? startTimeSeconds
: -1;
hls?.startLoad(startPosition);
});
} else {
state.elements.video.src = url;
state.elements.video.load();