Provider layer + hianime source (primary)
- providers/{anidb,anikoto,hianime}.py: per-source modules behind the
ANI_SOURCE fallback dispatch (upstream.py _call/_providers)
- hianime.py: pystardust/ani-cli#1897 flow — search/episode-list/
servers API, HD-1 hash -> base64 embed url, window.__P blob
(base64 + XOR 'otaku-embed-v1') -> master m3u8; registers CDN hosts
and the required Referer via stream_meta()
- upstream.py: get_stream/fetch_parallel accept extra headers;
default source order hianime,anidb,anikoto (anidb.app is 503)
- app.py: /stream allowlist accepts provider-registered hosts and
sends their Referer; status probe follows the primary provider
(hianime variant URIs are relative to the variant path)
- app.js: error-time position save floors at furthest point reached
(hls.js resets currentTime to 0 before playerFail on fatal errors)
- tests: default-order + hianime import checks; live browser suite green
This commit is contained in:
1 parent
ff8311c1a8
commit
834ca80a8c
8 files changed
+553
-123
No files matched your search
+18
-8
@@ -762,7 +762,7 @@ function videoEl() {
|
||||
|
||||
function stopHls() { if (hls) { hls.destroy(); hls = null; } if (bufTimer) { clearInterval(bufTimer); bufTimer = null; } }
|
||||
|
||||
let currentEp = null, lastSavedPos = 0, playerBroken = false, nextCta = null;
|
||||
let currentEp = null, lastSavedPos = 0, lastSeenPos = 0, playerBroken = false, nextCta = null;
|
||||
|
||||
function playerOverlay(text, onAction, actionLabel = "Retry", spin = false) {
|
||||
const p = document.getElementById("player");
|
||||
@@ -786,8 +786,9 @@ function playerFail(msg) {
|
||||
playerBroken = true;
|
||||
// a fatal error stops the periodic saves (timeupdate/pause never come
|
||||
// again); persist the position now or the user loses everything since
|
||||
// the last 10s save when they leave
|
||||
savePos();
|
||||
// the last 10s save when they leave. hls.js may have already reset
|
||||
// currentTime to 0, so floor at the furthest point reached this episode
|
||||
savePos(lastSeenPos);
|
||||
const { ep_id, ep_no } = currentEp;
|
||||
postEvent({type: "error", detail: msg, ep_id, ep: ep_no, lang, ua: navigator.userAgent});
|
||||
playerOverlay("Playback failed: " + msg, () => play(ep_id, ep_no));
|
||||
@@ -1008,6 +1009,7 @@ async function play(ep_id, ep_no) {
|
||||
}
|
||||
currentEp = {ep_id, ep_no, v};
|
||||
lastSavedPos = 0;
|
||||
lastSeenPos = 0;
|
||||
if (nextCta) { nextCta.remove(); nextCta = null; }
|
||||
const saved = history.find(x => x.slug_id === detail.slug_id && x.ep_id === ep_id);
|
||||
if (saved?.pos > 10) {
|
||||
@@ -1090,16 +1092,24 @@ const meta = v => ({...(Number.isFinite(v.duration) && v.duration > 0 ? {dur: v.
|
||||
|
||||
// one savePos covers all video elements: it reads currentEp, so stale
|
||||
// closures from earlier plays can't write the wrong episode's position
|
||||
function savePos() {
|
||||
if (!currentEp || currentEp.v.currentTime <= 3) return;
|
||||
lastSavedPos = currentEp.v.currentTime;
|
||||
function savePos(floor = 0) {
|
||||
if (!currentEp) return;
|
||||
// a fatal frag error can reset video.currentTime to 0 before this runs
|
||||
// (hls.js recovery); playerFail passes the furthest point reached as floor
|
||||
const pos = Math.max(currentEp.v.currentTime, floor);
|
||||
if (pos <= 3) return;
|
||||
lastSavedPos = pos;
|
||||
postHistory({...meta(currentEp.v), title: detail.name, slug_id: detail.slug_id, season_id: seasonId,
|
||||
ep_id: currentEp.ep_id, lang, ep: currentEp.ep_no, pos: currentEp.v.currentTime,
|
||||
ep_id: currentEp.ep_id, lang, ep: currentEp.ep_no, pos,
|
||||
...(epsList.length ? {total: epsList.length} : {})});
|
||||
}
|
||||
|
||||
function watchPos(v) {
|
||||
v.addEventListener("timeupdate", () => { if (v.currentTime - lastSavedPos >= 10) savePos(); });
|
||||
// 'seeking' fires when currentTime is set, even before the target segment
|
||||
// buffers — so a seek made just before a stream death is still captured
|
||||
const seen = () => { lastSeenPos = Math.max(lastSeenPos, v.currentTime); };
|
||||
v.addEventListener("timeupdate", () => { seen(); if (v.currentTime - lastSavedPos >= 10) savePos(); });
|
||||
v.addEventListener("seeking", seen);
|
||||
v.addEventListener("pause", savePos);
|
||||
// pagehide is bound once at boot (per-play listeners accumulated and fired
|
||||
// with stale video elements)
|
||||
|
||||
Reference in new issue
Block a user