Autosave: 20260330-091526
This commit is contained in:
parent
0647bdbfa7
commit
7696bf079c
@ -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);
|
||||
};
|
||||
});
|
||||
Loading…
x
Reference in New Issue
Block a user