Play pre-generated next chapter audio directly without page navigation to avoid autoplay block on locked screen
This commit is contained in:
@@ -2503,6 +2503,7 @@ const chapterTmpl = `
|
|||||||
|
|
||||||
// ── next-chapter prefetch at 80% ─────────────────────────────────────────────
|
// ── next-chapter prefetch at 80% ─────────────────────────────────────────────
|
||||||
var prefetchFired = false;
|
var prefetchFired = false;
|
||||||
|
var prefetchedUrl = null; // resolved proxy URL for the next chapter, if ready
|
||||||
|
|
||||||
// ── audio event handlers (named so they can be removed on next swap) ──────────
|
// ── audio event handlers (named so they can be removed on next swap) ──────────
|
||||||
function onTimeUpdate() {
|
function onTimeUpdate() {
|
||||||
@@ -2518,7 +2519,12 @@ const chapterTmpl = `
|
|||||||
body: JSON.stringify({ voice: voiceSel.value, speed: parseFloat(speedSlider.value) })
|
body: JSON.stringify({ voice: voiceSel.value, speed: parseFloat(speedSlider.value) })
|
||||||
})
|
})
|
||||||
.then(function (res) { return res.ok ? res.json() : Promise.reject(res.status); })
|
.then(function (res) { return res.ok ? res.json() : Promise.reject(res.status); })
|
||||||
.then(function () { if (!stale()) setNextState('ready'); })
|
.then(function (data) {
|
||||||
|
if (!stale()) {
|
||||||
|
prefetchedUrl = (data && data.url) ? data.url : null;
|
||||||
|
setNextState('ready');
|
||||||
|
}
|
||||||
|
})
|
||||||
.catch(function () { if (!stale()) setNextState('error'); });
|
.catch(function () { if (!stale()) setNextState('error'); });
|
||||||
}
|
}
|
||||||
// paragraph highlight
|
// paragraph highlight
|
||||||
@@ -2564,6 +2570,25 @@ const chapterTmpl = `
|
|||||||
// ── auto-next ────────────────────────────────────────────────────────────────
|
// ── auto-next ────────────────────────────────────────────────────────────────
|
||||||
function goNextChapter() {
|
function goNextChapter() {
|
||||||
if (!NEXT_N) return;
|
if (!NEXT_N) return;
|
||||||
|
|
||||||
|
// If audio was already pre-generated, play it directly without a page
|
||||||
|
// navigation. This keeps the existing audio context alive so the browser
|
||||||
|
// doesn't block autoplay on a locked screen where a user interaction was
|
||||||
|
// already granted for the current chapter.
|
||||||
|
if (prefetchedUrl) {
|
||||||
|
var url = prefetchedUrl;
|
||||||
|
prefetchedUrl = null;
|
||||||
|
audio.src = url;
|
||||||
|
audio.load();
|
||||||
|
// Update Media Session so lock-screen shows the next chapter.
|
||||||
|
if ('mediaSession' in navigator && navigator.mediaSession.metadata) {
|
||||||
|
navigator.mediaSession.metadata.title = 'Ch.\u00a0' + NEXT_N;
|
||||||
|
}
|
||||||
|
// audio.play() is called by onCanPlay once the element is ready.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fall back to HTMX navigation (audio not ready yet).
|
||||||
var nextURL = '/books/' + SLUG + '/chapters/' + NEXT_N;
|
var nextURL = '/books/' + SLUG + '/chapters/' + NEXT_N;
|
||||||
try { localStorage.setItem('tts_autostart', '1'); } catch(_) {}
|
try { localStorage.setItem('tts_autostart', '1'); } catch(_) {}
|
||||||
htmx.ajax('GET', nextURL, {
|
htmx.ajax('GET', nextURL, {
|
||||||
@@ -2579,6 +2604,7 @@ const chapterTmpl = `
|
|||||||
audio.pause();
|
audio.pause();
|
||||||
audio.src = '';
|
audio.src = '';
|
||||||
prefetchFired = false;
|
prefetchFired = false;
|
||||||
|
prefetchedUrl = null;
|
||||||
setStopped();
|
setStopped();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user