autoplay first episode by default, resume watched

- no ?ep= param: resume last watched ep of the season from history,
  else start at ep 1 (verified in headless Chromium, no click needed)
- if the browser blocks autoplay (no user gesture yet), show a
  'click to play' overlay instead of a silent black screen
This commit is contained in:
jozamudi committed 2026-08-19 15:21:15 +00:00
1 parent a3a58e7d3c
commit 32f93c0f65
2 files changed
+18

No files matched your search

+15
View File
@@ -126,6 +126,14 @@ async function showAnime(slug_id, opts = {}) {
if (opts.ep) { if (opts.ep) {
const cur = [...eps.children].find(b => +b.dataset.ep === opts.ep); const cur = [...eps.children].find(b => +b.dataset.ep === opts.ep);
if (cur) { markActive(cur); play(opts.ep, cur.dataset.no); } if (cur) { markActive(cur); play(opts.ep, cur.dataset.no); }
} else {
// default: resume last watched ep of this season, else start at ep 1
const h = hist().find(x => x.slug_id === slug_id && x.season_id === seasonId);
const auto = h ? h.ep_id : +eps.querySelector(".ep")?.dataset.ep;
if (auto) {
const cur = [...eps.children].find(b => +b.dataset.ep === auto);
if (cur) { markActive(cur); play(auto, cur.dataset.no); }
}
} }
if (detail.synopsis) main.append(el("p", "synopsis bottom", detail.synopsis)); if (detail.synopsis) main.append(el("p", "synopsis bottom", detail.synopsis));
@@ -164,6 +172,7 @@ async function play(ep_id, ep_no) {
hls.loadSource("/stream/" + s.token); hls.loadSource("/stream/" + s.token);
hls.attachMedia(v); hls.attachMedia(v);
v.onerror = () => showStreamError("media error: " + (v.error?.message || v.error?.code)); v.onerror = () => showStreamError("media error: " + (v.error?.message || v.error?.code));
v.play().catch(() => showPlayOverlay(v)); // autoplay blocked (no user gesture) -> visible prompt
} catch (e) { } catch (e) {
showStreamError(e.message); showStreamError(e.message);
} }
@@ -172,6 +181,12 @@ async function play(ep_id, ep_no) {
document.title = `${detail.name} E${ep_no} — ani-web`; document.title = `${detail.name} E${ep_no} — ani-web`;
} }
function showPlayOverlay(v) {
const o = el("button", "play-overlay", "▶ click to play");
o.onclick = () => { v.play(); o.remove(); };
v.parentElement.append(o);
}
// ---------- boot ---------- // ---------- boot ----------
(async () => { (async () => {
const p = new URLSearchParams(location.search); const p = new URLSearchParams(location.search);
+3
View File
@@ -23,7 +23,10 @@ h2 { margin: 0.4rem 0; }
.detail-head { margin-bottom: 1rem; } .detail-head { margin-bottom: 1rem; }
.synopsis { color: var(--muted); max-width: 70ch; margin: 0.3rem 0; } .synopsis { color: var(--muted); max-width: 70ch; margin: 0.3rem 0; }
.player { position: relative; }
.player video { width: 100%; max-height: 70vh; background: #000; border-radius: 10px; aspect-ratio: 16/9; } .player video { width: 100%; max-height: 70vh; background: #000; border-radius: 10px; aspect-ratio: 16/9; }
.play-overlay { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
font-size: 1.1rem; padding: 0.8rem 1.4rem; background: rgba(0, 0, 0, 0.75); }
.controls { display: flex; align-items: center; gap: 0.4rem; margin: 0.6rem 0; } .controls { display: flex; align-items: center; gap: 0.4rem; margin: 0.6rem 0; }
button { background: var(--card); color: var(--fg); border: 1px solid var(--bd); button { background: var(--card); color: var(--fg); border: 1px solid var(--bd);
border-radius: 8px; padding: 0.4rem 0.7rem; cursor: pointer; } border-radius: 8px; padding: 0.4rem 0.7rem; cursor: pointer; }