// The per-row full-file player and its cut fields. Section review normally // goes through clips.js; this is for listening around a section ("Open in // file") or to a whole recording. import {$, fmtDur} from './util.js'; import {fileByIdx, fileSpan} from './store.js'; import {streamUrl} from './api.js'; import {getPreroll} from './settings.js'; // Row of the full-file player opened last. The J/K/U/I fallback steps through // its sections when no clip queue is armed. Live binding — read, don't assign. export let activePlayerIdx = null; export function forgetPlayer(idx) { if (activePlayerIdx === idx) activePlayerIdx = null; } export function setCutFields(idx, startSec, endSec) { const startEl = $('cut-start-' + idx); const endEl = $('cut-end-' + idx); if (startEl) startEl.value = fmtDur(startSec); if (endEl && endSec != null) endEl.value = fmtDur(endSec); } const isOpen = idx => $('pbtn-' + idx)?.getAttribute('aria-expanded') === 'true'; function setPlayButton(idx, open) { const btn = $('pbtn-' + idx); const f = fileByIdx(idx); if (!btn || !f) return; btn.setAttribute('aria-expanded', open); btn.textContent = open ? 'Hide' : 'Play'; btn.setAttribute('aria-label', (open ? 'Hide player for ' : 'Play ') + fileSpan(f)); } // The player lives in its own table cell: the Play button stays, and the // audio element appears right after it export function closePlayer(idx) { const audio = $('aud-' + idx); if (!audio || audio.hidden) return; audio.pause(); audio.hidden = true; setPlayButton(idx, false); } export function togglePlayer(idx) { if (isOpen(idx)) { closePlayer(idx); return; } const f = fileByIdx(idx); const audio = $('aud-' + idx); if (!f || !audio) return; if (!audio.dataset.srcSet) { audio.preload = 'auto'; audio.src = streamUrl(f.name); audio.load(); audio.dataset.srcSet = '1'; } else if (!$('rec-' + idx)?.hidden) { // Still recording: re-fetch so duration and seek range cover the audio // captured since the source was last loaded audio.load(); } activePlayerIdx = idx; audio.hidden = false; setPlayButton(idx, true); audio.focus(); } export function seekToSection(idx, startSec, endSec) { if (!isOpen(idx)) togglePlayer(idx); activePlayerIdx = idx; const audio = $('aud-' + idx); if (!audio) return; audio.preload = 'auto'; const seekTo = Math.max(0, startSec - getPreroll()); const doSeek = () => { audio.currentTime = seekTo; audio.play().catch(() => {}); }; if (audio.readyState >= 1) doSeek(); else audio.addEventListener('loadedmetadata', doSeek, {once: true}); setCutFields(idx, startSec, endSec); } // Cut button: show/hide the start/end fields inside the options cell. Opening // puts focus in Start. Clip playback pre-fills the fields either way. export function toggleCut(idx) { const btn = $('cutbtn-' + idx); const panel = $('cut-' + idx); if (!btn || !panel) return; const open = panel.hidden; panel.hidden = !open; btn.setAttribute('aria-expanded', open); btn.querySelector('.day-arrow').textContent = open ? '▾ ' : '▸ '; if (open) $('cut-start-' + idx)?.focus(); }