Autosave: 20260330-091526

This commit is contained in:
Flatlogic Bot 2026-03-30 09:15:27 +00:00
parent 0647bdbfa7
commit 7696bf079c

View File

@ -52,6 +52,8 @@ class VoiceChannel {
this.voxHoldTime = 400;
this.speakingUsers = new Set();
this.remoteGainNodes = {}; // userId -> GainNode
this.userGains = JSON.parse(localStorage.getItem("voice_user_gains") || "{}");
this.setupPTTListeners();
this.loadWhisperSettings();
@ -295,24 +297,44 @@ class VoiceChannel {
pc.ontrack = (event) => {
const stream = event.streams[0] || new MediaStream([event.track]);
if (this.audioContext && this.audioContext.state === 'suspended') this.audioContext.resume();
if (!this.audioContext) {
this.audioContext = new (window.AudioContext || window.webkitAudioContext)({
sampleRate: 48000,
latencyHint: "interactive"
});
}
if (this.audioContext.state === "suspended") this.audioContext.resume();
if (this.remoteAudios[userId]) {
this.remoteAudios[userId].pause();
this.remoteAudios[userId].srcObject = null;
this.remoteAudios[userId].remove();
}
const source = this.audioContext.createMediaStreamSource(stream);
const gainNode = this.audioContext.createGain();
const destination = this.audioContext.createMediaStreamDestination();
// Initialiser le gain de l utilisateur
const userGain = this.userGains[userId] !== undefined ? parseFloat(this.userGains[userId]) : 1.0;
gainNode.gain.value = userGain;
source.connect(gainNode);
gainNode.connect(destination);
this.remoteGainNodes[userId] = gainNode;
const remoteAudio = new Audio();
remoteAudio.autoplay = true;
remoteAudio.style.display = 'none';
remoteAudio.srcObject = stream;
remoteAudio.style.display = "none";
remoteAudio.srcObject = destination.stream;
remoteAudio.muted = this.isDeafened;
remoteAudio.volume = this.settings.outputVolume || 1.0;
if (this.settings.outputDevice !== 'default' && typeof remoteAudio.setSinkId === 'function') {
if (this.settings.outputDevice !== "default" && typeof remoteAudio.setSinkId === "function") {
remoteAudio.setSinkId(this.settings.outputDevice);
}
document.body.appendChild(remoteAudio);
this.remoteAudios[userId] = remoteAudio;
remoteAudio.play().catch(e => console.warn('Autoplay prevented:', userId, e));
remoteAudio.play().catch(e => console.warn("Autoplay prevented:", userId, e));
};
if (isOfferor && pc.signalingState === 'stable') pc.onnegotiationneeded();
@ -529,6 +551,16 @@ class VoiceChannel {
this.settings.outputVolume = parseFloat(vol);
Object.values(this.remoteAudios).forEach(audio => { audio.volume = this.settings.outputVolume; });
}
setUserGain(userId, volume) {
userId = String(userId);
this.userGains[userId] = parseFloat(volume);
localStorage.setItem("voice_user_gains", JSON.stringify(this.userGains));
if (this.remoteGainNodes[userId]) {
this.remoteGainNodes[userId].gain.setTargetAtTime(this.userGains[userId], this.audioContext.currentTime, 0.01);
}
}
setInputVolume(vol) {
this.settings.inputVolume = parseFloat(vol);
if (this.inputGainNode && this.audioContext && !this.isSelfMuted) {
@ -606,7 +638,7 @@ class VoiceChannel {
if (this.microphone) { try { this.microphone.disconnect(); } catch(e) {} }
Object.values(this.peers).forEach(pc => pc.close());
Object.values(this.remoteAudios).forEach(audio => { audio.pause(); audio.remove(); audio.srcObject = null; });
this.peers = {}; this.remoteAudios = {}; this.participants = {}; this.currentChannelId = null; this.myPeerId = null; this.speakingUsers.clear();
this.peers = {}; this.remoteAudios = {}; this.remoteGainNodes = {}; this.participants = {}; this.currentChannelId = null; this.myPeerId = null; this.speakingUsers.clear();
document.querySelectorAll('.voice-item').forEach(el => el.classList.remove('active'));
this.updateVoiceUI();
}
@ -691,6 +723,8 @@ class VoiceChannel {
userEl.className = 'voice-user small text-muted d-flex align-items-center mb-1';
userEl.dataset.userId = userId;
userEl.style.paddingLeft = '8px';
userEl.style.cursor = 'pointer';
userEl.title = 'Click to adjust volume';
const avatarStyle = avatarUrl ? `background-image: url('${avatarUrl}'); background-size: cover;` : "background-color: #555;";
const boxShadow = isSpeaking ? 'box-shadow: 0 0 0 2px #23a559;' : '';
let icons = '';
@ -702,4 +736,66 @@ class VoiceChannel {
${icons}`;
container.appendChild(userEl);
}
}
}
// Individual Gain UI Logic
document.addEventListener('click', (e) => {
const voiceUser = e.target.closest('.voice-user');
if (!voiceUser) {
document.querySelectorAll('.voice-gain-popover').forEach(el => el.remove());
return;
}
const userId = voiceUser.dataset.userId;
if (userId === String(window.currentUserId)) return;
e.stopPropagation();
document.querySelectorAll('.voice-gain-popover').forEach(el => el.remove());
const rect = voiceUser.getBoundingClientRect();
const popover = document.createElement('div');
popover.className = 'voice-gain-popover p-2 rounded shadow border';
popover.style.position = 'fixed';
popover.style.left = (rect.right + 10) + 'px';
popover.style.top = rect.top + 'px';
popover.style.zIndex = '10000';
popover.style.width = '180px';
popover.style.backgroundColor = '#1e1f22';
popover.style.borderColor = '#313338';
popover.style.color = '#dbdee1';
const currentGain = (window.voiceHandler && window.voiceHandler.userGains[userId] !== undefined) ? window.voiceHandler.userGains[userId] : 1.0;
const percent = Math.round(currentGain * 100);
popover.innerHTML = `
<div class="small fw-bold mb-2 d-flex justify-content-between">
<span>Volume</span>
<span class="gain-value">${percent}%</span>
</div>
<input type="range" class="form-range gain-slider" min="0" max="4" step="0.05" value="${currentGain}" style="cursor: pointer; height: 4px;">
<div class="d-flex justify-content-between mt-1 mb-2" style="font-size: 9px; color: #8e9297;">
<span>0%</span>
<span>100%</span>
<span>400%</span>
</div>
<button class="btn btn-sm btn-secondary w-100 mt-1 btn-reset-gain" style="font-size: 11px; background-color: #4e5058; border: none;">Reset</button>
`;
document.body.appendChild(popover);
const slider = popover.querySelector('.gain-slider');
const display = popover.querySelector('.gain-value');
slider.oninput = () => {
const val = parseFloat(slider.value);
display.innerText = Math.round(val * 100) + '%';
if (window.voiceHandler) window.voiceHandler.setUserGain(userId, val);
};
popover.querySelector('.btn-reset-gain').onclick = () => {
slider.value = 1.0;
display.innerText = '100%';
if (window.voiceHandler) window.voiceHandler.setUserGain(userId, 1.0);
};
});