diff --git a/web.py b/web.py index 6a7c190..20f7a33 100644 --- a/web.py +++ b/web.py @@ -680,6 +680,7 @@ tr.data-row:hover td{background:var(--surf)} color:var(--red);border:1px solid #7f1d1d;background:#2d0808; animation:pulse 1.5s ease-in-out infinite} @keyframes pulse{0%,100%{opacity:1}50%{opacity:.45}} +@media (prefers-reduced-motion:reduce){.badge-rec{animation:none}} .muted{color:var(--muted)} button{cursor:pointer;border:1px solid var(--brd);background:var(--surf); color:var(--txt);padding:4px 10px;border-radius:5px;font-size:12px;white-space:nowrap} @@ -806,10 +807,6 @@ const fmtSize = b => { if (b<1<<30) return (b/(1<<20)).toFixed(1)+' MB'; return (b/(1<<30)).toFixed(2)+' GB'; }; -const fmtT = s => { - const h=Math.floor(s/3600),m=Math.floor((s%3600)/60),sec=Math.floor(s%60); - return h?`${h}:${pad(m)}:${pad(sec)}`:`${m}:${pad(sec)}`; -}; const pad = n => String(n).padStart(2,'0'); function announce(msg) { @@ -825,8 +822,8 @@ const getPreroll = () => { function setCutFields(idx, startSec, endSec) { const startEl = document.getElementById('cut-start-'+idx); const endEl = document.getElementById('cut-end-'+idx); - if (startEl) startEl.value = fmtT(startSec); - if (endEl && endSec != null) endEl.value = fmtT(endSec); + if (startEl) startEl.value = fmtDur(startSec); + if (endEl && endSec != null) endEl.value = fmtDur(endSec); } // idx -> filename, for live-status polling @@ -853,32 +850,41 @@ function groupByDay(files) { return map; } +function closePlayer(idx) { + const prow = document.getElementById('prow-'+idx); + if (!prow || prow.hidden) return; + document.getElementById('aud-'+idx)?.pause(); + prow.hidden = true; + const btn = document.getElementById('pbtn-'+idx); + if (btn) { + btn.setAttribute('aria-expanded','false'); + btn.textContent = '▶ Play'; + btn.setAttribute('aria-label','Play '+(recMap.get(idx) || '')); + } +} + function togglePlayer(idx, filename) { const prow = document.getElementById('prow-'+idx); const btn = document.getElementById('pbtn-'+idx); const audio = document.getElementById('aud-'+idx); const open = btn.getAttribute('aria-expanded') === 'true'; - if (!open) { - if (!audio.getAttribute('data-src-set')) { - audio.preload = 'auto'; - audio.src = '/stream/' + encodeURIComponent(filename); - audio.load(); - audio.setAttribute('data-src-set','1'); - } - activePlayerIdx = idx; - prow.hidden = false; - btn.setAttribute('aria-expanded','true'); - btn.textContent = '⏹ Hide'; - btn.setAttribute('aria-label','Hide player for '+filename); - audio.focus(); - } else { - audio.pause(); - prow.hidden = true; - btn.setAttribute('aria-expanded','false'); - btn.textContent = '▶ Play'; - btn.setAttribute('aria-label','Play '+filename); + if (open) { + closePlayer(idx); + return; } + if (!audio.getAttribute('data-src-set')) { + audio.preload = 'auto'; + audio.src = '/stream/' + encodeURIComponent(filename); + audio.load(); + audio.setAttribute('data-src-set','1'); + } + activePlayerIdx = idx; + prow.hidden = false; + btn.setAttribute('aria-expanded','true'); + btn.textContent = '⏹ Hide'; + btn.setAttribute('aria-label','Hide player for '+filename); + audio.focus(); } function drawWave(rms, sections, duration, filename) { @@ -939,7 +945,7 @@ function seekToSection(idx, filename, startSec, endSec, sectionIdx) { setCutFields(idx, startSec, endSec); if (sectionIdx != null) { const total = (sectionMap.get(idx) || []).length; - announce(`Section ${sectionIdx + 1} of ${total}: ${fmtT(startSec)} to ${fmtT(endSec)}`); + announce(`Section ${sectionIdx + 1} of ${total}: ${fmtDur(startSec)} to ${fmtDur(endSec)}`); } } @@ -972,22 +978,22 @@ async function analyse(idx, filename, cell, btn) { const chips = document.createElement('div'); chips.className='chips'; - chips.setAttribute('role','list'); + chips.setAttribute('role','group'); chips.setAttribute('aria-label','Loud sections — click to jump, J/K to step'); if (d.sections && d.sections.length) { sectionMap.set(idx, d.sections); d.sections.forEach((s, si) => { const c = document.createElement('button'); - c.className='chip'; c.setAttribute('role','listitem'); + c.className='chip'; c.title = 'Jump to this section (or use J/K keys)'; - c.textContent = `${fmtT(s.start)} – ${fmtT(s.end)}`; + c.textContent = `${fmtDur(s.start)} – ${fmtDur(s.end)}`; c.addEventListener('click', () => seekToSection(idx, filename, s.start, s.end, si)); chips.appendChild(c); }); } else { sectionMap.delete(idx); const q = document.createElement('span'); - q.className='quiet'; q.setAttribute('role','listitem'); + q.className='quiet'; q.textContent='No loud sections found'; chips.appendChild(q); } @@ -1043,7 +1049,7 @@ document.addEventListener('keydown', e => { const s = sections[targetIdx]; audio.currentTime = Math.max(0, s.start - preroll); setCutFields(activePlayerIdx, s.start, s.end); - announce(`Section ${targetIdx + 1} of ${sections.length}: ${fmtT(s.start)} to ${fmtT(s.end)}`); + announce(`Section ${targetIdx + 1} of ${sections.length}: ${fmtDur(s.start)} to ${fmtDur(s.end)}`); } else { announce('Beginning of sections'); } @@ -1055,7 +1061,7 @@ document.addEventListener('keydown', e => { const s = sections[i]; audio.currentTime = Math.max(0, s.start - preroll); setCutFields(activePlayerIdx, s.start, s.end); - announce(`Section ${i + 1} of ${sections.length}: ${fmtT(s.start)} to ${fmtT(s.end)}`); + announce(`Section ${i + 1} of ${sections.length}: ${fmtDur(s.start)} to ${fmtDur(s.end)}`); jumped = true; break; } @@ -1242,7 +1248,7 @@ function renderFiles(files) { tr.id = 'row-'+i; const recBadge = ` + aria-label="Currently recording"> REC`; tr.innerHTML = ` @@ -1310,19 +1316,7 @@ function renderFiles(files) { headBar.classList.toggle('open', nowExp); document.getElementById('daytbl-' + dayId).hidden = !nowExp; if (!nowExp) { - dayFiles.forEach(f => { - const prow = document.getElementById('prow-' + f._idx); - if (prow && !prow.hidden) { - document.getElementById('aud-' + f._idx)?.pause(); - prow.hidden = true; - const pbtn = document.getElementById('pbtn-' + f._idx); - if (pbtn) { - pbtn.setAttribute('aria-expanded', 'false'); - pbtn.textContent = '▶ Play'; - pbtn.setAttribute('aria-label', 'Play ' + f.name); - } - } - }); + dayFiles.forEach(f => closePlayer(f._idx)); document.getElementById('dayhl-' + dayId).hidden = true; if (dayActiveId === dayId) { dayActiveSections = []; @@ -1492,13 +1486,12 @@ async function dayHighlights(dayId, analyzableFiles) { } else { const chips = document.createElement('div'); chips.className = 'chips'; - chips.setAttribute('role', 'list'); + chips.setAttribute('role', 'group'); chips.setAttribute('aria-label', 'Day loud sections — click to jump, J/K to step across files'); dayActiveSections.forEach((sec, si) => { const c = document.createElement('button'); c.className = 'chip'; - c.setAttribute('role', 'listitem'); - c.title = sec.filename + ' @ ' + fmtT(sec.start); + c.title = sec.filename + ' @ ' + fmtDur(sec.start); const d = new Date(sec.absStart * 1000); const hms = d.getHours().toString().padStart(2,'0') + ':' + d.getMinutes().toString().padStart(2,'0') + ':' @@ -1525,39 +1518,13 @@ async function dayHighlights(dayId, analyzableFiles) { function jumpToDaySection(si) { if (si < 0 || si >= dayActiveSections.length) return; dayActiveSectionCursor = si; - const sec = dayActiveSections[si]; - const { fileIdx, filename, start, end } = sec; + const { fileIdx, filename, start, end } = dayActiveSections[si]; // Close the previous player if switching to a different file - if (activePlayerIdx !== null && activePlayerIdx !== fileIdx) { - const prevProw = document.getElementById('prow-' + activePlayerIdx); - if (prevProw && !prevProw.hidden) { - document.getElementById('aud-' + activePlayerIdx)?.pause(); - prevProw.hidden = true; - const prevPbtn = document.getElementById('pbtn-' + activePlayerIdx); - if (prevPbtn) { - prevPbtn.setAttribute('aria-expanded', 'false'); - prevPbtn.textContent = '▶ Play'; - prevPbtn.setAttribute('aria-label', 'Play ' + (recMap.get(activePlayerIdx) || '')); - } - } - } + if (activePlayerIdx !== null && activePlayerIdx !== fileIdx) closePlayer(activePlayerIdx); - // Open this file's player - const pbtn = document.getElementById('pbtn-' + fileIdx); - if (pbtn && pbtn.getAttribute('aria-expanded') !== 'true') togglePlayer(fileIdx, filename); - activePlayerIdx = fileIdx; - - const audio = document.getElementById('aud-' + fileIdx); - if (!audio) return; - audio.preload = 'auto'; - const seekTo = Math.max(0, start - getPreroll()); - const doSeek = () => { audio.currentTime = seekTo; audio.play().catch(() => {}); }; - if (audio.readyState >= 1) doSeek(); - else audio.addEventListener('loadedmetadata', doSeek, { once: true }); - - setCutFields(fileIdx, start, end); - announce(`Day section ${si + 1} of ${dayActiveSections.length}: ${fmtT(start)}–${fmtT(end)} in ${filename}`); + seekToSection(fileIdx, filename, start, end, null); + announce(`Day section ${si + 1} of ${dayActiveSections.length}: ${fmtDur(start)}–${fmtDur(end)} in ${filename}`); } function applyFilters() { @@ -1600,10 +1567,7 @@ async function pollStatus() { const active = new Set(s.active || []); recMap.forEach((filename, idx) => { const badge = document.getElementById('rec-'+idx); - if (!badge) return; - const on = active.has(filename); - badge.hidden = !on; - badge.setAttribute('aria-hidden', on ? 'false' : 'true'); + if (badge) badge.hidden = !active.has(filename); }); } catch(e) {} }