/* * audio_autodetect.js — Auto-detection of the radio's audio card (admin, * Configuration tab). * * By default the server automatically detects the "USB Audio CODEC" * (IC-7300, IC-705) or "SCU-17" (Yaesu) card by name. This module shows * the detection status and lets you: * - refresh the detection (e.g. after plugging in the radio after server start) * - switch to "manual" configuration mode (expert) for unusual radios */ window.AudioAutoDetect = (function() { let _expertMode = false; async function load() { try { const r = await fetch('/api/audio/detect', { credentials: 'include' }); if (!r.ok) return; const data = await r.json(); if (!data.ok) return; _render(data); } catch(e) { console.warn('[audio-detect] load error:', e); } } function _render(data) { const info = document.getElementById('audio-detect-info'); const badge = document.getElementById('audio-detect-badge'); if (!info || !badge) return; badge.removeAttribute('data-i18n'); // see the note at rot-status-badge (rotormini.js) const det = data.detection || {}; const cur = data.current || {}; if (det.detected) { badge.textContent = I18n.t('cfg_detected_badge'); badge.style.color = 'var(--green)'; badge.style.background = 'rgba(var(--green-rgb),0.15)'; info.style.borderLeftColor = 'var(--green2)'; info.innerHTML = `
${I18n.t('cfg_card_detected_msg').replace('{pattern}', '' + _esc(det.pattern) + '')}
${I18n.t('cfg_rx_arrow_lbl')} ${_esc(det.rx || '—')}
${I18n.t('cfg_tx_arrow_lbl')} ${_esc(det.tx || '—')}
${(cur.rxDevice && cur.rxDevice !== det.rx) || (cur.txDevice && cur.txDevice !== det.tx) ? `
${I18n.t('cfg_other_cards_active')} RX=${_esc(cur.rxDevice || '—')}, TX=${_esc(cur.txDevice || '—')}
` : ''} `; } else { badge.textContent = I18n.t('cfg_not_detected_badge'); badge.style.color = 'var(--amber)'; badge.style.background = 'rgba(var(--amber-rgb),0.15)'; info.style.borderLeftColor = 'var(--amber)'; const rxList = (det.all_rx || []).slice(0, 5); const txList = (det.all_tx || []).slice(0, 5); info.innerHTML = `
${I18n.t('cfg_not_detected_msg')}
${I18n.t('cfg_not_detected_desc')}
${I18n.t('cfg_avail_capture_lbl')} ${rxList.length ? rxList.map(x => `${_esc(x)}`).join(', ') : I18n.t('cfg_none_paren')}
${I18n.t('cfg_avail_playback_lbl')} ${txList.length ? txList.map(x => `${_esc(x)}`).join(', ') : I18n.t('cfg_none_paren')}
`; } } async function rescan() { const badge = document.getElementById('audio-detect-badge'); if (badge) { badge.textContent = I18n.t('cfg_scanning_badge'); badge.style.color = 'var(--amber)'; } try { const r = await fetch('/api/audio/detect', { method: 'POST', credentials: 'include' }); const data = await r.json(); if (data.ok) { window.UI?.showToast?.( data.detection?.detected ? I18n.t('cfg_toast_detected').replace('{pattern}', data.detection.pattern) : I18n.t('cfg_toast_not_detected'), data.detection?.detected ? 'info' : 'warning' ); await load(); } else { window.UI?.showToast?.(I18n.t('cfg_toast_scan_error_prefix') + (data.error || ''), 'error'); } } catch(e) { window.UI?.showToast?.(I18n.t('cfg_toast_network_error_prefix') + e.message, 'error'); } } function toggleExpert() { _expertMode = !_expertMode; console.log('[audio-cfg] toggleExpert() ->', _expertMode); const el = document.getElementById('audio-manual-config'); if (el) el.style.display = _expertMode ? '' : 'none'; // When showing expert mode, load the cards AND the saved values // (rx/tx/bitrate/txVolume). It used to call _audioDeviceRefresh, which // loads ONLY the card list — it didn't set the saved values or // txVolume, so the slider stayed at the HTML default of 4x while the // cards showed the actual ones. _audioDeviceLoad does both (it calls // refresh internally anyway). if (_expertMode && typeof window._audioDeviceLoad === 'function') { window._audioDeviceLoad(); } else if (_expertMode && typeof window._audioDeviceRefresh === 'function') { window._audioDeviceRefresh(); } } function _esc(s) { return String(s || '').replace(/[<>&"']/g, m => ({ '<':'<','>':'>','&':'&','"':'"',"'":''' }[m])); } return { load, rescan, toggleExpert }; })();