// ── Rehearser voice-picker presentation ───────────────────────────────────── // Matching/import actions remain in rehearser.js for now; this module owns the // reusable preview state and HTML for the online/local voice choices. let _rehAudEl = null, _rehAudBtn = null, _rehAudObjectUrl = null; function _rehStopAudition() { if (_rehAudEl) { try { _rehAudEl.pause(); } catch (_) {} } if (_rehAudObjectUrl) { URL.revokeObjectURL(_rehAudObjectUrl); _rehAudObjectUrl = null; } if (_rehAudBtn) { _rehAudBtn.classList.remove('playing'); const i = _rehAudBtn.querySelector('.mdi'); if (i) i.className = 'mdi mdi-play'; } _rehAudBtn = null; } function _rehAudition(url, btn) { if (!url) { toast('No preview audio for this voice', 'error'); return; } if (_rehAudBtn === btn && _rehAudEl && !_rehAudEl.paused) { _rehStopAudition(); return; } _rehStopAudition(); if (!_rehAudEl) { _rehAudEl = new Audio(); _rehAudEl.addEventListener('ended', _rehStopAudition); } _rehAudEl.src = url; _rehAudEl.play().then(() => { _rehAudBtn = btn; btn.classList.add('playing'); const i = btn.querySelector('.mdi'); if (i) i.className = 'mdi mdi-stop'; }).catch(() => toast('Could not play preview', 'error')); } const _REH_PREVIEW_TEXT = 'Hello — this is how this voice sounds.'; const _rehSearchCache = {}; // sp -> last fish.audio search results (simplified candidates) function _rehVoiceRow({ name, meta, playUrl, playVoice, isCurrent, useAttrs }) { const play = ``; const right = isCurrent ? ` Selected` : ``; return `
${play}
${escHtml(name)}${escHtml(meta)}
${right}
`; } function castOnlinePanelHtml(sp, c) { const o = c.online; if (!o || !Array.isArray(o.candidates) || c.voice === 'me') return ''; const curCand = o.candidates.find(x => x.voice_id && x.voice_id === c.voice); let curRow; if (curCand) { curRow = _rehVoiceRow({ name: curCand.title || c.voice, meta: [curCand.gender, curCand.language].filter(Boolean).join(' · ') || 'fish.audio', playUrl: curCand.sample_audio, isCurrent: true }); } else if (c.voice) { const vd = getVoiceData(c.voice); curRow = _rehVoiceRow({ name: vd?.name || c.voice, meta: vd?.group || vd?.tag || 'library voice', playVoice: c.voice, isCurrent: true }); } else { curRow = `
No voice assignedpick one below
`; } const alts = o.candidates.map((cand, i) => ({ cand, i })).filter(({ cand }) => cand.voice_id !== c.voice || !cand.voice_id); const altRows = alts.map(({ cand, i }) => _rehVoiceRow({ name: cand.title || 'Voice', meta: [cand.gender, cand.language].filter(Boolean).join(' · ') || 'fish.audio', playUrl: cand.sample_audio, useAttrs: ` data-act="alt" data-idx="${i}"`, })).join(''); return `
Voice — try & change ${alts.length ? `` : ''}
${curRow}
${alts.length ? `` : ''}
`; } window.TTVC = window.TTVC || {}; window.TTVC.rehearser = window.TTVC.rehearser || {}; window.TTVC.rehearser.voicePicker = { castOnlinePanelHtml, preview: _rehAudition, stopPreview: _rehStopAudition };