feat: add error handling to player core functions
This commit is contained in:
@@ -38,8 +38,9 @@ const mark = (name: string): void => {
|
|||||||
const measure = (name: string, startMark: string): void => {
|
const measure = (name: string, startMark: string): void => {
|
||||||
try {
|
try {
|
||||||
performance.measure(`mal.hls.${name}`, `mal.hls.${startMark}`);
|
performance.measure(`mal.hls.${name}`, `mal.hls.${startMark}`);
|
||||||
} catch {
|
} catch (error) {
|
||||||
// Missing marks can happen if HLS.js emits a later event after cleanup.
|
// Missing marks can happen if HLS.js emits a later event after cleanup.
|
||||||
|
console.debug("failed to measure performance:", error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -172,7 +172,8 @@ const initPlayer = (): void => {
|
|||||||
sessionStorage.removeItem("mal:resume-after-mode-switch");
|
sessionStorage.removeItem("mal:resume-after-mode-switch");
|
||||||
const parsed = Number(raw);
|
const parsed = Number(raw);
|
||||||
return Number.isFinite(parsed) && parsed >= 0 ? parsed : null;
|
return Number.isFinite(parsed) && parsed >= 0 ? parsed : null;
|
||||||
} catch {
|
} catch (error) {
|
||||||
|
console.error("failed to parse resume state:", error);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
@@ -200,7 +201,9 @@ const initPlayer = (): void => {
|
|||||||
// autoplay if not already playing (inline script may have already called play())
|
// autoplay if not already playing (inline script may have already called play())
|
||||||
// but don't autoplay if we've reached the end
|
// but don't autoplay if we've reached the end
|
||||||
if (!isAtEnd && (state.playback.shouldAutoPlay || state.elements.video.paused)) {
|
if (!isAtEnd && (state.playback.shouldAutoPlay || state.elements.video.paused)) {
|
||||||
state.elements.video.play().catch(() => undefined);
|
state.elements.video.play().catch((error) => {
|
||||||
|
console.debug("failed to autoplay video:", error);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
updateTimeline(state.elements.video.currentTime);
|
updateTimeline(state.elements.video.currentTime);
|
||||||
@@ -279,7 +282,8 @@ const initPlayer = (): void => {
|
|||||||
try {
|
try {
|
||||||
(e.currentTarget as HTMLElement).setPointerCapture((e as PointerEvent).pointerId);
|
(e.currentTarget as HTMLElement).setPointerCapture((e as PointerEvent).pointerId);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn("Failed to capture pointer:", e);
|
console.error("failed to capture pointer:", e);
|
||||||
|
throw e;
|
||||||
}
|
}
|
||||||
scrubToPointer(e.clientX, true);
|
scrubToPointer(e.clientX, true);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -75,8 +75,8 @@ export const switchMode = (mode: string): void => {
|
|||||||
const next = new URL(window.location.href);
|
const next = new URL(window.location.href);
|
||||||
next.searchParams.set("mode", expectedMode);
|
next.searchParams.set("mode", expectedMode);
|
||||||
window.location.href = next.toString();
|
window.location.href = next.toString();
|
||||||
} catch {
|
} catch (error) {
|
||||||
// no-op
|
console.error("failed to save resume state or switch mode:", error);
|
||||||
}
|
}
|
||||||
}, 800);
|
}, 800);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,7 +71,8 @@ export const saveProgress = async (
|
|||||||
seconds: savedTime,
|
seconds: savedTime,
|
||||||
};
|
};
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn("Progress save failed:", e);
|
console.error("progress save failed:", e);
|
||||||
|
throw e;
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|
||||||
@@ -113,7 +114,9 @@ export const markEpisodeTransition = (episodeNumber: number): void => {
|
|||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
keepalive: true,
|
keepalive: true,
|
||||||
body: payload,
|
body: payload,
|
||||||
}).catch(() => undefined);
|
}).catch((error) => {
|
||||||
|
console.debug("failed to save progress:", error);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -211,7 +211,12 @@ export const setupSegmentEditor = (): void => {
|
|||||||
});
|
});
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
let message = res.status === 401 ? "Login required." : "Failed to save segment.";
|
let message = res.status === 401 ? "Login required." : "Failed to save segment.";
|
||||||
const payload = (await res.json().catch(() => null)) as { error?: string } | null;
|
let payload: { error?: string } | null = null;
|
||||||
|
try {
|
||||||
|
payload = await res.json();
|
||||||
|
} catch (error) {
|
||||||
|
console.error("failed to parse response json:", error);
|
||||||
|
}
|
||||||
if (payload?.error) message = payload.error;
|
if (payload?.error) message = payload.error;
|
||||||
setError(message);
|
setError(message);
|
||||||
return;
|
return;
|
||||||
@@ -235,8 +240,10 @@ export const setupSegmentEditor = (): void => {
|
|||||||
|
|
||||||
window.showToast?.({ message: "Segment saved." });
|
window.showToast?.({ message: "Segment saved." });
|
||||||
close();
|
close();
|
||||||
} catch {
|
} catch (error) {
|
||||||
setError("Failed to save segment.");
|
setError("Failed To Save Segment.");
|
||||||
|
console.error("failed to save segment:", error);
|
||||||
|
throw error;
|
||||||
} finally {
|
} finally {
|
||||||
saveBtn.disabled = false;
|
saveBtn.disabled = false;
|
||||||
saveBtn.classList.remove("opacity-70");
|
saveBtn.classList.remove("opacity-70");
|
||||||
|
|||||||
@@ -204,7 +204,8 @@ export const initState = (c: HTMLElement): boolean => {
|
|||||||
const safeJsonUnknown = (raw: string | undefined): unknown => {
|
const safeJsonUnknown = (raw: string | undefined): unknown => {
|
||||||
try {
|
try {
|
||||||
return JSON.parse(raw ?? "");
|
return JSON.parse(raw ?? "");
|
||||||
} catch {
|
} catch (error) {
|
||||||
|
console.error("failed to parse json:", error);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -237,7 +238,8 @@ export const initState = (c: HTMLElement): boolean => {
|
|||||||
const modeParam = u.searchParams.get("mode");
|
const modeParam = u.searchParams.get("mode");
|
||||||
if (modeParam === "sub" || modeParam === "dub") return modeParam;
|
if (modeParam === "sub" || modeParam === "dub") return modeParam;
|
||||||
return null;
|
return null;
|
||||||
} catch {
|
} catch (error) {
|
||||||
|
console.error("failed to parse mode url:", error);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,7 +3,8 @@ export type StorageLike = Pick<Storage, "getItem" | "setItem" | "removeItem">;
|
|||||||
const getLocalStorage = (): StorageLike | null => {
|
const getLocalStorage = (): StorageLike | null => {
|
||||||
try {
|
try {
|
||||||
return window.localStorage;
|
return window.localStorage;
|
||||||
} catch {
|
} catch (error) {
|
||||||
|
console.error("failed to access localstorage:", error);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -14,7 +15,8 @@ export const safeLocalStorage = {
|
|||||||
if (!storage) return null;
|
if (!storage) return null;
|
||||||
try {
|
try {
|
||||||
return storage.getItem(key);
|
return storage.getItem(key);
|
||||||
} catch {
|
} catch (error) {
|
||||||
|
console.error(`failed to get localstorage item '${key}':`, error);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -23,8 +25,8 @@ export const safeLocalStorage = {
|
|||||||
if (!storage) return;
|
if (!storage) return;
|
||||||
try {
|
try {
|
||||||
storage.setItem(key, value);
|
storage.setItem(key, value);
|
||||||
} catch {
|
} catch (error) {
|
||||||
// ignore
|
console.error(`failed to set localstorage item '${key}':`, error);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
removeItem(key: string): void {
|
removeItem(key: string): void {
|
||||||
@@ -32,8 +34,8 @@ export const safeLocalStorage = {
|
|||||||
if (!storage) return;
|
if (!storage) return;
|
||||||
try {
|
try {
|
||||||
storage.removeItem(key);
|
storage.removeItem(key);
|
||||||
} catch {
|
} catch (error) {
|
||||||
// ignore
|
console.error(`failed to remove localstorage item '${key}':`, error);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -32,7 +32,8 @@ const loadSubtitle = async (url: string): Promise<SubtitleCue[]> => {
|
|||||||
const res = await fetch(url);
|
const res = await fetch(url);
|
||||||
if (!res.ok) return [];
|
if (!res.ok) return [];
|
||||||
return parseVtt(await res.text());
|
return parseVtt(await res.text());
|
||||||
} catch {
|
} catch (error) {
|
||||||
|
console.error("failed to load subtitle:", error);
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -26,7 +26,8 @@ const shouldUseHLS = (type: string | undefined, url: string): boolean => {
|
|||||||
const parsed = new URL(url, window.location.href);
|
const parsed = new URL(url, window.location.href);
|
||||||
if (parsed.searchParams.get("hls") === "1") return true;
|
if (parsed.searchParams.get("hls") === "1") return true;
|
||||||
return parsed.pathname.toLowerCase().endsWith(".m3u8");
|
return parsed.pathname.toLowerCase().endsWith(".m3u8");
|
||||||
} catch {
|
} catch (error) {
|
||||||
|
console.error("Failed to parse video URL:", error);
|
||||||
return url.toLowerCase().includes(".m3u8");
|
return url.toLowerCase().includes(".m3u8");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -65,6 +66,8 @@ export const loadVideoSource = (url: string, type?: string): void => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (wasPlaying) {
|
if (wasPlaying) {
|
||||||
state.elements.video.play().catch(() => undefined);
|
state.elements.video.play().catch((error) => {
|
||||||
|
console.debug("failed to play video:", error);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user