class SettingsManager { constructor(audioHandler) { this.audioHandler = audioHandler; this.micTestStream = null; this.micTestAnalyser = null; this.micTestAnimFrame = null; this.speakerTestAudio = null; this.modal = document.getElementById('settings-modal'); this.micSelect = document.getElementById('mic-select'); this.speakerSelect = document.getElementById('speaker-select'); this.micTestBtn = document.getElementById('mic-test-btn'); this.micLevel = document.getElementById('mic-level'); this.micTestStatus = document.getElementById('mic-test-status'); this.speakerTestBtn = document.getElementById('speaker-test-btn'); this.speakerTestStatus = document.getElementById('speaker-test-status'); this.micGainSlider = document.getElementById('mic-gain'); this.gainValueEl = document.getElementById('gain-value'); // Set initial slider value from saved preference this.micGainSlider.value = audioHandler.micGain; this.gainValueEl.textContent = audioHandler.micGain.toFixed(1); document.getElementById('settings-btn').addEventListener('click', () => this.open()); document.getElementById('settings-close').addEventListener('click', () => this.close()); document.getElementById('settings-save').addEventListener('click', () => this.save()); this.modal.addEventListener('click', (e) => { if (e.target === this.modal) this.close(); }); this.micTestBtn.addEventListener('click', () => this.toggleMicTest()); this.speakerTestBtn.addEventListener('click', () => this.testSpeaker()); this.micGainSlider.addEventListener('input', (e) => { const val = parseFloat(e.target.value); this.gainValueEl.textContent = val.toFixed(1); this.audioHandler.setGain(val); // Update test gain in real-time if (this._micTestGainNode) { this._micTestGainNode.gain.value = val; } }); } async open() { this.modal.classList.remove('hidden'); await this.loadDevices(); } close() { this.stopMicTest(); this.stopSpeakerTest(); this.modal.classList.add('hidden'); } save() { const micId = this.micSelect.value; const speakerId = this.speakerSelect.value; if (micId) this.audioHandler.setMic(micId); if (speakerId) this.audioHandler.setSpeaker(speakerId); this.close(); } async loadDevices() { // Request permission first to get labeled devices try { const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); stream.getTracks().forEach(t => t.stop()); } catch (e) { this.micTestStatus.textContent = 'Eroare: Nu am acces la microfon. Verifica permisiunile browserului.'; return; } const devices = await navigator.mediaDevices.enumerateDevices(); // Microphones this.micSelect.innerHTML = ''; const mics = devices.filter(d => d.kind === 'audioinput'); mics.forEach(device => { const opt = document.createElement('option'); opt.value = device.deviceId; opt.textContent = device.label || `Microfon ${this.micSelect.options.length + 1}`; if (device.deviceId === this.audioHandler.selectedMicId) { opt.selected = true; } this.micSelect.appendChild(opt); }); // Speakers this.speakerSelect.innerHTML = ''; const speakers = devices.filter(d => d.kind === 'audiooutput'); if (speakers.length === 0) { const opt = document.createElement('option'); opt.textContent = 'Difuzor implicit (browserul nu permite selectia)'; this.speakerSelect.appendChild(opt); this.speakerSelect.disabled = true; } else { this.speakerSelect.disabled = false; speakers.forEach(device => { const opt = document.createElement('option'); opt.value = device.deviceId; opt.textContent = device.label || `Difuzor ${this.speakerSelect.options.length + 1}`; if (device.deviceId === this.audioHandler.selectedSpeakerId) { opt.selected = true; } this.speakerSelect.appendChild(opt); }); } } async toggleMicTest() { if (this.micTestStream) { this.stopMicTest(); return; } const deviceId = this.micSelect.value; try { this.micTestStream = await navigator.mediaDevices.getUserMedia({ audio: { deviceId: deviceId ? { exact: deviceId } : undefined }, }); const ctx = new AudioContext(); const source = ctx.createMediaStreamSource(this.micTestStream); const gainNode = ctx.createGain(); gainNode.gain.value = this.audioHandler.micGain; this._micTestGainNode = gainNode; this.micTestAnalyser = ctx.createAnalyser(); this.micTestAnalyser.fftSize = 256; source.connect(gainNode); gainNode.connect(this.micTestAnalyser); this.micTestBtn.textContent = 'Opreste testul'; this.micTestBtn.classList.add('active'); this.micTestStatus.textContent = 'Vorbeste in microfon - ar trebui sa vezi bara miscandu-se.'; this._micTestCtx = ctx; this.updateMicLevel(); } catch (e) { this.micTestStatus.textContent = `Eroare: ${e.message}`; } } updateMicLevel() { if (!this.micTestAnalyser) return; const data = new Uint8Array(this.micTestAnalyser.frequencyBinCount); this.micTestAnalyser.getByteFrequencyData(data); let sum = 0; for (let i = 0; i < data.length; i++) sum += data[i]; const avg = sum / data.length; const pct = Math.min(100, (avg / 128) * 100); this.micLevel.style.width = pct + '%'; this.micTestAnimFrame = requestAnimationFrame(() => this.updateMicLevel()); } stopMicTest() { if (this.micTestStream) { this.micTestStream.getTracks().forEach(t => t.stop()); this.micTestStream = null; } if (this.micTestAnimFrame) { cancelAnimationFrame(this.micTestAnimFrame); this.micTestAnimFrame = null; } if (this._micTestCtx) { this._micTestCtx.close(); this._micTestCtx = null; } this.micTestAnalyser = null; this.micLevel.style.width = '0%'; this.micTestBtn.textContent = 'Testeaza microfonul'; this.micTestBtn.classList.remove('active'); this.micTestStatus.textContent = ''; } async testSpeaker() { this.stopSpeakerTest(); // Generate a short test tone const ctx = new AudioContext(); const oscillator = ctx.createOscillator(); const gainNode = ctx.createGain(); oscillator.type = 'sine'; oscillator.frequency.setValueAtTime(440, ctx.currentTime); gainNode.gain.setValueAtTime(0.3, ctx.currentTime); gainNode.gain.exponentialRampToValueAtTime(0.01, ctx.currentTime + 1); oscillator.connect(gainNode); gainNode.connect(ctx.destination); // Try to set output device const speakerId = this.speakerSelect.value; if (speakerId && ctx.setSinkId) { try { await ctx.setSinkId(speakerId); } catch (e) { // setSinkId not supported in all browsers } } oscillator.start(); oscillator.stop(ctx.currentTime + 1); this.speakerTestStatus.textContent = 'Se reda un sunet de test (440 Hz)...'; this._speakerTestCtx = ctx; setTimeout(() => { this.speakerTestStatus.textContent = 'Ai auzit sunetul? Daca nu, selecteaza alt difuzor.'; ctx.close(); this._speakerTestCtx = null; }, 1200); } stopSpeakerTest() { if (this._speakerTestCtx) { this._speakerTestCtx.close(); this._speakerTestCtx = null; } this.speakerTestStatus.textContent = ''; } }