feat: autoplay uses full page navigation, auto-starts TTS on arrival
This commit is contained in:
@@ -2503,11 +2503,6 @@ const chapterTmpl = `
|
||||
var playerPlayIcon = document.getElementById('player-play-icon');
|
||||
var playerTitle = document.getElementById('player-title');
|
||||
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 seekRail = document.getElementById('seek-rail');
|
||||
var seekFill = document.getElementById('seek-fill');
|
||||
@@ -2706,7 +2701,6 @@ const chapterTmpl = `
|
||||
b.classList.toggle('text-zinc-300', !active);
|
||||
});
|
||||
try { localStorage.setItem(LS_SPEED, btn.dataset.speed); } catch(_) {}
|
||||
prefetchFired = false;
|
||||
}
|
||||
window.selectSpeed = selectSpeed;
|
||||
(function loadSettings() {
|
||||
@@ -2721,7 +2715,7 @@ const chapterTmpl = `
|
||||
var as = localStorage.getItem(LS_AUTOSCROLL);
|
||||
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'); });
|
||||
autoscrollChk.addEventListener('change', function () { if (stale()) return; localStorage.setItem(LS_AUTOSCROLL, autoscrollChk.checked ? 'true' : 'false'); });
|
||||
|
||||
@@ -2832,66 +2826,13 @@ const chapterTmpl = `
|
||||
hideSeek();
|
||||
}
|
||||
|
||||
// ── next-chapter prefetch display ─────────────────────────────────────────
|
||||
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;
|
||||
|
||||
// ── audio events ──────────────────────────────────────────────────────────
|
||||
function onTimeUpdate() {
|
||||
if (stale()) { audio.removeEventListener('timeupdate', onTimeUpdate); return; }
|
||||
updateSeek();
|
||||
if (audio.duration && isFinite(audio.duration) && !audio.paused) {
|
||||
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;
|
||||
var idx = Math.min(Math.floor((audio.currentTime / audio.duration) * paras.length), paras.length - 1);
|
||||
if (!activePara || activePara !== paras[idx]) highlightPara(idx);
|
||||
@@ -2905,7 +2846,6 @@ const chapterTmpl = `
|
||||
function onEnded() {
|
||||
if (stale()) { audio.removeEventListener('ended', onEnded); return; }
|
||||
audio.src = '';
|
||||
prefetchFired = false;
|
||||
if (autoplayChk.checked && NEXT_N) goNextChapter(); else setStopped();
|
||||
}
|
||||
|
||||
@@ -2921,22 +2861,13 @@ const chapterTmpl = `
|
||||
// ── auto-next ─────────────────────────────────────────────────────────────
|
||||
function goNextChapter() {
|
||||
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(_) {}
|
||||
htmx.ajax('GET', nextURL, { target: '#main-content', swap: 'innerHTML', pushURL: nextURL });
|
||||
window.location.href = '/books/' + SLUG + '/chapters/' + NEXT_N;
|
||||
}
|
||||
|
||||
function stop() {
|
||||
if (currentAudioCtrl) { currentAudioCtrl.abort(); currentAudioCtrl = null; }
|
||||
audio.pause(); audio.src = '';
|
||||
prefetchFired = false; prefetchedUrl = null;
|
||||
setStopped();
|
||||
}
|
||||
|
||||
@@ -3049,7 +2980,7 @@ window.selectVoiceOption = function (btn) {
|
||||
}
|
||||
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'));
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user