diff --git a/public/lyrics-view.js b/public/lyrics-view.js
index a0c4a37..c894955 100644
--- a/public/lyrics-view.js
+++ b/public/lyrics-view.js
@@ -43,12 +43,15 @@ async function lyrFromPlex(t) { // lyrics stored with the song (embedded, a .lrc
/** A transcript: the show's own if it publishes one, else (on request) one made on this server. Polls while that is being made. */
async function lyrSpoken(t, start) {
const q = new URLSearchParams({ key: t.key, src: t.src || '' });
- for (let n = 0; ; n++) {
+ for (let n = 0; ; n++) { // (n restarts at -1 after an automatic start, so the POST goes out once)
if (!lyr.el || lyr.key !== t.key) return null;
- let r; try { r = await jfetch('/api/transcript?' + q, start && n === 0 ? { method: 'POST' } : undefined); } catch (e) { return { found: false, error: e.message }; }
+ let r; try { r = await jfetch('/api/transcript?' + q, start && n <= 0 ? { method: 'POST' } : undefined); } catch (e) { return { found: false, error: e.message }; }
if (r.state === 'ready') { r.transcript.spoken = true; return r.transcript; }
if (r.state === 'error') return { found: false, error: r.error, canTranscribe: r.canTranscribe, none: true };
- if (r.state === 'none') return { found: false, none: true, canTranscribe: r.canTranscribe, why: r.why };
+ if (r.state === 'none') {
+ if (r.canTranscribe && !start && String(t.key).startsWith('pod:') && !lyrPref().noAuto) { start = true; n = -1; continue; } // nothing published: make one (an episode, not a whole book)
+ return { found: false, none: true, canTranscribe: r.canTranscribe, why: r.why };
+ }
const body = lyr.el.querySelector('#lyrbody'); if (body) body.innerHTML = `
Making a transcript on your server… ${Math.max(0, Math.round((Date.now() - r.since) / 60000))} min so far.
You can close this: it keeps going, and it is kept for next time.
`;
await new Promise(ok => setTimeout(ok, 4000));
}
@@ -147,3 +150,16 @@ function lyrTick() { // follows the song: which line is current, and how far int
if (l.words) on.querySelectorAll('.w').forEach((w, j) => { const x = l.words[j]; if (!x) return; const p = pos >= x.e ? 1 : pos <= x.t ? 0 : (pos - x.t) / Math.max(1, x.e - x.t); w.classList.toggle('sung', p >= 1); w.style.setProperty('--p', p); });
else { const e = l.e && l.e > l.t ? l.e : l.t + 4000, p = Math.max(0, Math.min(1, (pos - l.t) / Math.max(400, Math.min(e - l.t, 7000)))); on.style.setProperty('--p', p); } // (no word timing: the colour sweeps across the line)
}
+
+// Playing a podcast episode is also the signal to get its transcript going in the background (the published one if there is one, else one made on this
+// server), so the words are usually ready, or well on the way, by the time Transcript is opened. Once per episode per visit; nothing for audiobooks.
+const lyrAsked = new Set();
+pl.a.addEventListener('playing', async () => {
+ const t = typeof cur === 'function' ? cur() : null;
+ if (!t || !String(t.key).startsWith('pod:') || !t.src || lyrAsked.has(t.key) || !cfg.transcribe || lyrPref().noAuto) return;
+ lyrAsked.add(t.key);
+ try {
+ const q = new URLSearchParams({ key: t.key, src: t.src }), r = await jfetch('/api/transcript?' + q);
+ if (r.state === 'none' && r.canTranscribe) await jfetch('/api/transcript?' + q, { method: 'POST' });
+ } catch { lyrAsked.delete(t.key); }
+});
diff --git a/public/sw.js b/public/sw.js
index c31744a..b6039bc 100644
--- a/public/sw.js
+++ b/public/sw.js
@@ -1,5 +1,5 @@
// App-shell cache only; Seafile/API traffic always goes to the network.
-const CACHE = 'hub-v31';
+const CACHE = 'hub-v32';
const SHARE = 'hub-share'; // files shared into the app from other apps, held until the person picks a folder
const SHELL = ['/icon.svg', '/manifest.webmanifest', '/icons/icon-192.png', '/icons/apple-touch-icon.png'];