feat: autoplay uses full page navigation, auto-starts TTS on arrival
Some checks failed
CI / Lint (push) Has been cancelled
CI / Test (push) Has been cancelled
CI / Build (push) Has been cancelled

This commit is contained in:
Admin
2026-03-02 14:19:21 +05:00
parent 412b0fb478
commit 90a16a7223

View File

@@ -2503,11 +2503,6 @@ const chapterTmpl = `
var playerPlayIcon = document.getElementById('player-play-icon'); var playerPlayIcon = document.getElementById('player-play-icon');
var playerTitle = document.getElementById('player-title'); var playerTitle = document.getElementById('player-title');
var playerStateBadge= document.getElementById('player-state-badge'); var playerStateBadge= document.getElementById('player-state-badge');
var playerNextRow = document.getElementById('player-next-row');
var playerNextBadge = document.getElementById('player-next-badge');
var settingsPanel = document.getElementById('settings-panel');
var chapterListPanel= document.getElementById('chapter-list-panel');
var chapterListBtn = document.getElementById('chapter-list-btn');
var seekTrack = document.getElementById('seek-track'); var seekTrack = document.getElementById('seek-track');
var seekRail = document.getElementById('seek-rail'); var seekRail = document.getElementById('seek-rail');
var seekFill = document.getElementById('seek-fill'); var seekFill = document.getElementById('seek-fill');
@@ -2706,7 +2701,6 @@ const chapterTmpl = `
b.classList.toggle('text-zinc-300', !active); b.classList.toggle('text-zinc-300', !active);
}); });
try { localStorage.setItem(LS_SPEED, btn.dataset.speed); } catch(_) {} try { localStorage.setItem(LS_SPEED, btn.dataset.speed); } catch(_) {}
prefetchFired = false;
} }
window.selectSpeed = selectSpeed; window.selectSpeed = selectSpeed;
(function loadSettings() { (function loadSettings() {
@@ -2721,7 +2715,7 @@ const chapterTmpl = `
var as = localStorage.getItem(LS_AUTOSCROLL); var as = localStorage.getItem(LS_AUTOSCROLL);
if (as !== null) autoscrollChk.checked = as !== 'false'; if (as !== null) autoscrollChk.checked = as !== 'false';
})(); })();
voiceSel.addEventListener('change', function () { if (stale()) return; localStorage.setItem(LS_VOICE, voiceSel.value); prefetchFired = false; }); voiceSel.addEventListener('change', function () { if (stale()) return; localStorage.setItem(LS_VOICE, voiceSel.value); });
autoplayChk.addEventListener('change', function () { if (stale()) return; localStorage.setItem(LS_AUTONEXT, autoplayChk.checked ? 'true' : 'false'); }); autoplayChk.addEventListener('change', function () { if (stale()) return; localStorage.setItem(LS_AUTONEXT, autoplayChk.checked ? 'true' : 'false'); });
autoscrollChk.addEventListener('change', function () { if (stale()) return; localStorage.setItem(LS_AUTOSCROLL, autoscrollChk.checked ? 'true' : 'false'); }); autoscrollChk.addEventListener('change', function () { if (stale()) return; localStorage.setItem(LS_AUTOSCROLL, autoscrollChk.checked ? 'true' : 'false'); });
@@ -2832,66 +2826,13 @@ const chapterTmpl = `
hideSeek(); hideSeek();
} }
// ── next-chapter prefetch display ───────────────────────────────────────── // ── audio events ──────────────────────────────────────────────────────────
function setNextState(state) {
if (!NEXT_N) { playerNextRow.hidden = true; return; }
playerNextRow.hidden = false;
setBadge(playerNextBadge, state);
}
// ── server-side audio generation ──────────────────────────────────────────
var currentAudioCtrl = null;
var genStartTime = 0;
function generateAudio(chapterN, cb) {
if (currentAudioCtrl) currentAudioCtrl.abort();
var ctrl = new AbortController();
currentAudioCtrl = ctrl;
genStartTime = Date.now();
fetch('/ui/audio/' + SLUG + '/' + chapterN, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ voice: voiceSel.value, speed: getSpeed() }),
signal: ctrl.signal
})
.then(function (res) {
if (!res.ok) return res.text().then(function (t) { throw new Error(res.status + ': ' + t); });
return res.json();
})
.then(function (data) {
if (stale()) return;
if (!data || !data.url) throw new Error('no url in response');
cb(data.url);
})
.catch(function (e) {
if (e.name === 'AbortError' || stale()) return;
setError(e.message);
});
}
// ── next-chapter prefetch at 80% ──────────────────────────────────────────
var prefetchFired = false;
var prefetchedUrl = null;
function onTimeUpdate() { function onTimeUpdate() {
if (stale()) { audio.removeEventListener('timeupdate', onTimeUpdate); return; } if (stale()) { audio.removeEventListener('timeupdate', onTimeUpdate); return; }
updateSeek(); updateSeek();
if (audio.duration && isFinite(audio.duration) && !audio.paused) { if (audio.duration && isFinite(audio.duration) && !audio.paused) {
playerStateBadge.textContent = Math.round((audio.currentTime / audio.duration) * 100) + '%'; playerStateBadge.textContent = Math.round((audio.currentTime / audio.duration) * 100) + '%';
} }
if (NEXT_N && !prefetchFired && audio.duration && isFinite(audio.duration) &&
audio.currentTime / audio.duration >= 0.8) {
prefetchFired = true;
setNextState('generating');
fetch('/ui/audio/' + SLUG + '/' + NEXT_N, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ voice: voiceSel.value, speed: getSpeed() })
})
.then(function (res) { return res.ok ? res.json() : Promise.reject(res.status); })
.then(function (data) { if (!stale()) { prefetchedUrl = (data && data.url) ? data.url : null; setNextState('ready'); } })
.catch(function () { if (!stale()) setNextState('error'); });
}
if (paras.length === 0 || !audio.duration || !isFinite(audio.duration)) return; if (paras.length === 0 || !audio.duration || !isFinite(audio.duration)) return;
var idx = Math.min(Math.floor((audio.currentTime / audio.duration) * paras.length), paras.length - 1); var idx = Math.min(Math.floor((audio.currentTime / audio.duration) * paras.length), paras.length - 1);
if (!activePara || activePara !== paras[idx]) highlightPara(idx); if (!activePara || activePara !== paras[idx]) highlightPara(idx);
@@ -2905,7 +2846,6 @@ const chapterTmpl = `
function onEnded() { function onEnded() {
if (stale()) { audio.removeEventListener('ended', onEnded); return; } if (stale()) { audio.removeEventListener('ended', onEnded); return; }
audio.src = ''; audio.src = '';
prefetchFired = false;
if (autoplayChk.checked && NEXT_N) goNextChapter(); else setStopped(); if (autoplayChk.checked && NEXT_N) goNextChapter(); else setStopped();
} }
@@ -2921,22 +2861,13 @@ const chapterTmpl = `
// ── auto-next ───────────────────────────────────────────────────────────── // ── auto-next ─────────────────────────────────────────────────────────────
function goNextChapter() { function goNextChapter() {
if (!NEXT_N) return; if (!NEXT_N) return;
if (prefetchedUrl) {
var url = prefetchedUrl; prefetchedUrl = null;
audio.src = url; audio.load();
if ('mediaSession' in navigator && navigator.mediaSession.metadata)
navigator.mediaSession.metadata.title = 'Ch.\u00a0' + NEXT_N;
return;
}
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, { target: '#main-content', swap: 'innerHTML', pushURL: nextURL }); window.location.href = '/books/' + SLUG + '/chapters/' + NEXT_N;
} }
function stop() { function stop() {
if (currentAudioCtrl) { currentAudioCtrl.abort(); currentAudioCtrl = null; } if (currentAudioCtrl) { currentAudioCtrl.abort(); currentAudioCtrl = null; }
audio.pause(); audio.src = ''; audio.pause(); audio.src = '';
prefetchFired = false; prefetchedUrl = null;
setStopped(); setStopped();
} }
@@ -3049,7 +2980,7 @@ window.selectVoiceOption = function (btn) {
} }
if (chevron) chevron.style.transform = ''; if (chevron) chevron.style.transform = '';
// fire change event so existing listener persists prefetchFired reset // fire change event so existing voice listener persists
voiceSel.dispatchEvent(new Event('change')); voiceSel.dispatchEvent(new Event('change'));
}; };