Ecobot_Scoala_Verde/frontend/js/settings.js
Stefan Caramizoiu d7a7d2cafd Initial commit
2026-04-01 11:14:26 +03:00

228 lines
8.3 KiB
JavaScript

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 = '';
}
}