Update web/templates/entry_view.html

This commit is contained in:
Joshua Laymon 2025-08-21 02:42:57 +00:00
parent 6408d0a589
commit 3ad80494a0

View File

@ -285,47 +285,38 @@
const ttsBtn = document.getElementById('ttsBtn');
if (!ttsBtn) return;
const TTS_URL = "{{ tts_url|default:'' }}"; // staff-only when present
let ttsAudio = null; // holds current playback (Audio or shim)
const TTS_URL = "{{ tts_url|default:'' }}"; // empty for non-staff → browser TTS
function stopPlayback(){
try {
if (window.speechSynthesis) speechSynthesis.cancel();
} catch(e){}
try {
if (ttsAudio && typeof ttsAudio.pause === 'function') {
ttsAudio.pause();
ttsAudio.currentTime = 0;
}
if (ttsAudio && typeof ttsAudio.stop === 'function') {
ttsAudio.stop();
}
} catch(e){}
ttsAudio = null;
// -------- persistent audio + helpers --------
let audioEl = null;
let currentURL = null; // objectURL for OpenAI response
let playing = false;
let fetchCtrl = null;
function ensureAudio(){
if (!audioEl) {
audioEl = new Audio();
audioEl.setAttribute('playsinline',''); // iOS
audioEl.preload = 'auto';
audioEl.addEventListener('ended', () => { playing = false; cleanupURL(); });
audioEl.addEventListener('pause', () => { /* keep playing=false only if really paused */ });
}
return audioEl;
}
async function playOpenAITTS() {
const r = await fetch(TTS_URL, { credentials: 'same-origin', cache: 'no-store' });
if (!r.ok) {
const msg = await r.text().catch(()=> String(r.status));
throw new Error(`HTTP ${r.status}: ${msg.slice(0,200)}`);
}
const ct = (r.headers.get('content-type') || '').toLowerCase();
if (!ct.startsWith('audio/')) {
const preview = await r.text().catch(()=> '(non-audio response)');
throw new Error(`Unexpected content-type "${ct}". Preview: ${preview.slice(0,200)}`);
}
const blob = await r.blob();
const url = URL.createObjectURL(blob);
const audio = new Audio(url);
audio.preload = 'auto';
audio.setAttribute('playsinline','');
audio.onended = () => { URL.revokeObjectURL(url); ttsAudio = null; };
await audio.play();
ttsAudio = audio;
function cleanupURL(){
if (currentURL) { URL.revokeObjectURL(currentURL); currentURL = null; }
}
function stopAll(){
try { if (fetchCtrl) fetchCtrl.abort(); } catch(_){}
fetchCtrl = null;
if (audioEl) { audioEl.pause(); audioEl.currentTime = 0; }
cleanupURL();
playing = false;
}
// -------- text builders --------
function buildCombinedText(){
const ill = (document.getElementById('illustration-text')?.innerText || '').trim();
const app = (document.getElementById('application-text')?.innerText || '').trim();
@ -338,43 +329,83 @@
if (!('speechSynthesis' in window) || !('SpeechSynthesisUtterance' in window)) {
throw new Error('Browser TTS not supported.');
}
speechSynthesis.cancel();
window.speechSynthesis.cancel();
const u = new SpeechSynthesisUtterance(text);
u.rate = 1.0; u.pitch = 1.0; u.volume = 1.0;
u.onend = () => { ttsAudio = null; };
speechSynthesis.speak(u);
// Create a tiny shim so our toggle can "stop" it
ttsAudio = {
pause: () => speechSynthesis.cancel(),
currentTime: 0
};
playing = true;
}
ttsBtn.addEventListener('click', async () => {
try {
// Toggle: if already playing, stop instead
if (ttsAudio) {
stopPlayback();
showToast("Playback stopped");
return;
}
async function playOpenAITTS(){
stopAll();
fetchCtrl = new AbortController();
const r = await fetch(TTS_URL, { credentials:'same-origin', cache:'no-store', signal: fetchCtrl.signal });
if (!r.ok) {
const preview = await r.text().catch(()=> String(r.status));
throw new Error(`HTTP ${r.status}: ${preview.slice(0,200)}`);
}
const ct = (r.headers.get('content-type') || '').toLowerCase();
if (!ct.startsWith('audio/')) {
const preview = await r.text().catch(()=> '(non-audio response)');
throw new Error(`Unexpected content-type "${ct}". Preview: ${preview.slice(0,200)}`);
}
const blob = await r.blob();
currentURL = URL.createObjectURL(blob);
const a = ensureAudio();
a.src = currentURL;
a.currentTime = 0;
a.muted = false;
try {
await a.play(); // <- may throw NotAllowedError on some iOS/Safari cases
playing = true;
} catch (err) {
// Autoplay/permission style block (Safari/iOS or Chrome heuristics)
if (err && (err.name === 'NotAllowedError' ||
/not allowed|denied permission/i.test(err.message))) {
showToast("Audio was blocked. Make sure the phone isn't on Silent and tap the speaker again.");
} else {
throw err;
}
}
}
// -------- button: toggle behavior --------
ttsBtn.addEventListener('click', async () => {
// Toggle OFF if something is already playing (either engine)
if (playing) {
try {
stopAll();
if ('speechSynthesis' in window) window.speechSynthesis.cancel();
} finally {
playing = false;
showToast("Stopped.");
}
return;
}
try {
ttsBtn.disabled = true;
if (TTS_URL) {
await playOpenAITTS();
showToast("Using OpenAI TTS");
if (playing) showToast("Playing with OpenAI TTS");
} else {
speakBrowserTTS();
showToast("Using Browser TTS");
showToast("Playing with Browser TTS");
}
} catch (err) {
alert('TTS error: ' + (err && err.message ? err.message : String(err)));
showToast("TTS error: " + (err?.message || String(err)));
} finally {
ttsBtn.disabled = false;
}
});
// Safety: stop audio when navigating away
window.addEventListener('pagehide', stopAll);
})();
</script>