Player: defer play() to canplay; friendly tap-to-play fallback
Root cause of the spurious 'Browser blocked playback' on initial play: play() was called right after hls.js attachMedia, before any data was buffered. Chrome rejects such early play() calls with NotAllowedError — indistinguishable from a real autoplay block — so the scary 'allow autoplay in site settings' overlay appeared on every start. - 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): show a simple 'Tap to play' + Play button - escalate to the 'allow autoplay' settings hint only if the tap is itself rejected - dead-stream case is unaffected: hls.js fatal error still shows its own 'Playback failed + Retry' overlay - also aligns with test_autoplay_blocked's expected 'Tap to play' text
This commit is contained in:
1 parent
0613cf7e27
commit
eb5082bd3b
1 file changed
+16
-16
+16
-16
@@ -822,23 +822,23 @@ async function play(ep_id, ep_no) {
|
|||||||
if (currentEp && !playerBroken && !v.ended) playerOverlay("Buffering\u2026");
|
if (currentEp && !playerBroken && !v.ended) playerOverlay("Buffering\u2026");
|
||||||
});
|
});
|
||||||
v.addEventListener("ended", onEnded);
|
v.addEventListener("ended", onEnded);
|
||||||
// NotAllowedError = the browser blocked autoplay: 'playing' will never
|
// ponytail: play() before first data can reject with NotAllowedError in
|
||||||
// fire, so the Loading… overlay (which only clears on 'playing') would
|
// Chrome — the same error a real autoplay block produces — so it used to
|
||||||
// sit forever over the unreachable native controls. Other rejection
|
// show a scary "browser blocked" overlay on every initial play. Wait for
|
||||||
// reasons (AbortError when the stream has no data yet, …) belong to the
|
// first data (canplay; hls.js's recommended pattern), THEN play(). A
|
||||||
// hls.js fatal-error path — leave its overlay alone.
|
// NotAllowedError at that point is a genuine policy block (common on
|
||||||
v.play().catch(err => {
|
// phones) — offer a simple tap-to-play; escalate to the settings hint
|
||||||
if (!err || err.name !== "NotAllowedError") return;
|
// only if the tap itself is rejected. 'playing' still clears the
|
||||||
// Not fatal: a tap (a real gesture) usually starts playback. If the
|
// Loading… overlay either way.
|
||||||
// browser rejects even that (autoplay disallowed for the site, …),
|
const tap = () => v.play().catch(() => blocked(true));
|
||||||
// stay on the tappable overlay and say what to change — retrying
|
const blocked = (again) => playerOverlay(
|
||||||
// play() would just hit the same wall
|
again ? "Playback still blocked — allow autoplay for this site in " +
|
||||||
const tap = () => v.play().catch(() => blocked());
|
"the browser's site settings" : "Tap to play", tap, "\u25b6 Play");
|
||||||
const blocked = () => playerOverlay(
|
const doPlay = () => v.play().catch(err => {
|
||||||
"Browser blocked playback. Tap Play, or allow autoplay for this " +
|
if (err && err.name === "NotAllowedError") blocked(false);
|
||||||
"site in the browser's site settings", tap, "Play");
|
|
||||||
blocked();
|
|
||||||
});
|
});
|
||||||
|
if (v.readyState >= 2) doPlay();
|
||||||
|
else v.addEventListener("canplay", doPlay, {once: true});
|
||||||
watchPos(v);
|
watchPos(v);
|
||||||
setupIntro(v, ep_no);
|
setupIntro(v, ep_no);
|
||||||
postHistory({...meta(v), title: detail.name, slug_id: detail.slug_id, season_id: seasonId,
|
postHistory({...meta(v), title: detail.name, slug_id: detail.slug_id, season_id: seasonId,
|
||||||
|
|||||||
Reference in new issue
Block a user