228 lines
8.3 KiB
JavaScript
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 = '';
|
|
}
|
|
}
|