From 0315356f892a9d3ef41868eaa0624268bcba02eb Mon Sep 17 00:00:00 2001 From: Josue Zamudio Date: Mon, 31 Aug 2026 21:01:26 +0000 Subject: [PATCH] iOS root cause: start playback on BUFFER_APPENDED, not canplay The live timeline caught it: iPhone buffered 513s, readyState stuck at 1, canplay NEVER fired -> play() never called -> eternal Loading. Now: first BUFFER_APPENDED (hls.js's own 'data is in the pipe' signal) triggers play, canplay still works, plus a 3s safety retry if still paused with a non-empty buffer. play() calls are guarded + idempotent. Desktop + mobile emu regression: playing. assets v=14. --- static/app.js | 22 ++++++++++++---------- static/index.html | 4 ++-- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/static/app.js b/static/app.js index 616bce3..5665a38 100644 --- a/static/app.js +++ b/static/app.js @@ -1,5 +1,5 @@ // app.js — ani-web frontend: search -> detail -> play, vanilla JS + hls.js -const JSV = 13; // bump with every app.js change; play-probe reports it so a +const JSV = 14; // bump with every app.js change; play-probe reports it so a // stale-cache phone is detectable server-side // BASE = this page's directory, so the app works at / or behind a // prefixing reverse proxy (e.g. https://host/ani/) @@ -932,7 +932,7 @@ async function play(ep_id, ep_no) { hls.on(Hls.Events.MANIFEST_LOADED, () => dbg("manifest-loaded")); hls.on(Hls.Events.LEVEL_LOADED, () => dbg("level-loaded")); let _fn = 0; hls.on(Hls.Events.FRAG_LOADED, () => { if (++_fn <= 2) dbg("frag-loaded", {n: _fn}); }); - let _ba = false; hls.on(Hls.Events.BUFFER_APPENDED, () => { if (!_ba) { _ba = true; dbg("buffer-appended"); } }); + let _ba = false; hls.on(Hls.Events.BUFFER_APPENDED, () => { if (!_ba) { _ba = true; dbg("buffer-appended"); doPlay(); } }); dbg("new-hls-ok"); hls.loadSource("/stream/" + s.token); dbg("loadsource-called"); @@ -972,24 +972,26 @@ async function play(ep_id, ep_no) { v.addEventListener("ended", onEnded); // ponytail: play() before first data can reject with NotAllowedError in // Chrome — the same error a real autoplay block produces — so it used to - // show a scary "browser blocked" overlay on every initial play. Wait for - // first data (canplay; hls.js's recommended pattern), THEN play(). A - // NotAllowedError at that point is a genuine policy block (common on - // phones) — offer a simple tap-to-play; escalate to the settings hint - // only if the tap itself is rejected. 'playing' still clears the - // Loading… overlay either way. + // show a scary "browser blocked" overlay on every initial play. Start as + // soon as data exists (first BUFFER_APPENDED; canplay too) — but NEVER + // wait on canplay alone: the iPhone (iOS 18.7) once sat with 513s + // buffered, readyState stuck at 1, and canplay never fired — so the + // video never started. 3s safety net covers any remaining variant. + let started = false; const tap = () => v.play().catch(() => blocked(true)); const blocked = (again) => playerOverlay( again ? "Playback still blocked — allow autoplay for this site in " + "the browser's site settings" : "Tap to play", tap, "\u25b6 Play"); const doPlay = () => { - dbg("do-play"); - v.play().then(() => dbg("play-resolved")) + if (started) return; + dbg("do-play", {rs: v.readyState, buf: v.buffered.length}); + v.play().then(() => { started = true; dbg("play-resolved"); }) .catch(err => { dbg("play-rejected", {name: err && err.name}); if (err && err.name === "NotAllowedError") blocked(false); }); }; if (v.readyState >= 2) doPlay(); else v.addEventListener("canplay", doPlay, {once: true}); + setTimeout(() => { if (v.paused && v.buffered.length) doPlay(); }, 3000); watchPos(v); setupIntro(v, ep_no); postHistory({...meta(v), title: detail.name, slug_id: detail.slug_id, season_id: seasonId, diff --git a/static/index.html b/static/index.html index 9ec3071..23691ba 100644 --- a/static/index.html +++ b/static/index.html @@ -6,7 +6,7 @@ ani-web - +
@@ -30,6 +30,6 @@ max-age=300 in browser AND possibly in your HTTPS proxy, and a stale copy is exactly how users keep seeing an old UI after a deploy --> - +