fix volume and quality
This commit is contained in:
1 parent
143dd26c31
commit
10481ad08c
1 file changed
+40
-12
+40
-12
@@ -139,7 +139,10 @@ async function tuneAudioSender(sender) {
|
||||
if (!sender?.getParameters || !sender?.setParameters) return;
|
||||
const params = sender.getParameters();
|
||||
if (!params.encodings?.length) params.encodings = [{}];
|
||||
params.encodings[0].maxBitrate = 64000;
|
||||
// 128 kbps keeps Opus in its fullband LD/MD modes with headroom for FEC;
|
||||
// 64 kbps audibly compresses louder/denser speech. DTX stays off so the
|
||||
// channel doesn't pump in and out of low-bitrate comfort noise.
|
||||
params.encodings[0].maxBitrate = 128000;
|
||||
params.encodings[0].dtx = false;
|
||||
params.encodings[0].priority = 'high';
|
||||
await sender.setParameters(params);
|
||||
@@ -1073,17 +1076,19 @@ function stopScreenShare(stream, silent) {
|
||||
// ---------------------------------------------------------------------------
|
||||
// position sync
|
||||
// ---------------------------------------------------------------------------
|
||||
// Fans new joiners out from directly in front of the room's center: the
|
||||
// first joiner lands straight ahead, then each next one alternates sides,
|
||||
// widening by 10deg per pair (0, +10, -10, +20, -20, ...) instead of the
|
||||
// old distance-maximizing grid search, which tended to dump joiners in a
|
||||
// far corner rather than somewhere natural to face the group.
|
||||
// First joiner takes the room's middle anchor; later joiners fan out around
|
||||
// them — the 2nd participant lands straight ahead (0deg), then each next one
|
||||
// alternates sides, widening by 10deg per pair (0, +10, -10, +20, -20, ...).
|
||||
// Chosen once per client at join and broadcast via the signaling 'pos'
|
||||
// message, so every client converges on the same layout.
|
||||
function chooseOpenParticipantPosition() {
|
||||
const ANCHOR = { x: 50, y: 50 };
|
||||
const RADIUS = 24;
|
||||
const n = S.peers.size;
|
||||
const step = Math.ceil(n / 2) * 10;
|
||||
const angleDeg = n === 0 ? 0 : (n % 2 === 1 ? step : -step);
|
||||
if (n === 0) return { x: ANCHOR.x, y: ANCHOR.y }; // first joiner sits in the middle
|
||||
const k = n - 1; // fan slot: 0deg, +10, -10, +20, -20...
|
||||
const mag = Math.ceil(k / 2) * 10;
|
||||
const angleDeg = k === 0 ? 0 : (k % 2 === 1 ? mag : -mag);
|
||||
const rad = (angleDeg * Math.PI) / 180;
|
||||
return {
|
||||
x: ANCHOR.x + RADIUS * Math.sin(rad),
|
||||
@@ -1123,18 +1128,24 @@ function updateMicSignal(level = S.audioManager.getLevel()) {
|
||||
if (clipping) clipping.classList.toggle('hidden', !level.clipping);
|
||||
}
|
||||
|
||||
let meRosterEl = null; // roster row for "me", kept so speaking highlights don't need a rebuild
|
||||
setInterval(() => {
|
||||
if (!S.audioCtx) return;
|
||||
const localLevel = S.audioManager.getLevel();
|
||||
updateMicSignal(localLevel);
|
||||
const meSpeaking = S.micStream && !S.muted && localLevel.rms > 0.04;
|
||||
els.meAvatar.classList.toggle('speaking', !!meSpeaking);
|
||||
let changed = false;
|
||||
// Toggle speaking highlights in place. A full renderTiles() here used to
|
||||
// wipe and rebuild the roster DOM on every start/stop of speech — which
|
||||
// destroyed the volume popup and its range input mid-drag, making the
|
||||
// per-user volume control feel broken exactly while a peer was talking.
|
||||
meRosterEl?.classList.toggle('speaking', !!meSpeaking);
|
||||
for (const peer of S.peers.values()) {
|
||||
const speaking = !!(peer.analyser && !S.deafened && rms(peer.analyser) > 0.03);
|
||||
if (speaking !== peer.speaking) { peer.speaking = speaking; changed = true; }
|
||||
if (speaking === peer.speaking) continue;
|
||||
peer.speaking = speaking;
|
||||
peer.els.rosterItem?.classList.toggle('speaking', speaking);
|
||||
}
|
||||
if (changed) renderTiles();
|
||||
updateScreenTileIndicators();
|
||||
}, 150);
|
||||
|
||||
@@ -1170,8 +1181,13 @@ function renderChannelCounts() {
|
||||
}
|
||||
|
||||
function renderTiles() {
|
||||
// Remember an open volume popup so it survives this rebuild (peer join/leave
|
||||
// or a mute/share badge change) without snapping closed under the cursor.
|
||||
const openVolPeerId = document.querySelector('.channel-user-item.show-vol[data-peer-id]')?.dataset.peerId || null;
|
||||
|
||||
// Clear any existing user rosters in voice channels
|
||||
document.querySelectorAll('.channel-users').forEach((el) => { el.innerHTML = ''; });
|
||||
meRosterEl = null;
|
||||
|
||||
// For each voice channel element in the sidebar
|
||||
document.querySelectorAll('.channel.voice').forEach((channelEl) => {
|
||||
@@ -1189,6 +1205,7 @@ function renderTiles() {
|
||||
// Current active room: render Me + live peers (with interactive volume sliders)
|
||||
const meItem = document.createElement('div');
|
||||
meItem.className = 'channel-user-item' + (els.meAvatar.classList.contains('speaking') ? ' speaking' : '');
|
||||
meRosterEl = meItem;
|
||||
const meAv = document.createElement('div');
|
||||
meAv.className = 'avatar user-line-avatar';
|
||||
setAvatar(meAv, S.name, S.myId || 'me');
|
||||
@@ -1226,6 +1243,7 @@ function renderTiles() {
|
||||
for (const peer of S.peers.values()) {
|
||||
const item = document.createElement('div');
|
||||
item.className = 'channel-user-item' + (peer.speaking ? ' speaking' : '');
|
||||
item.dataset.peerId = peer.id;
|
||||
|
||||
const av = document.createElement('div');
|
||||
av.className = 'avatar user-line-avatar';
|
||||
@@ -1271,7 +1289,8 @@ function renderTiles() {
|
||||
e.stopPropagation();
|
||||
peer.vol = Number(volInput.value);
|
||||
volLabel.textContent = `User Volume: ${Math.round(peer.vol * 100)}%`;
|
||||
if (peer.gain && !S.deafened) peer.gain.gain.value = peer.vol;
|
||||
// setTargetAtTime ramps smoothly instead of zipper-clicking on drag
|
||||
if (peer.gain && S.audioCtx && !S.deafened) peer.gain.gain.setTargetAtTime(peer.vol, S.audioCtx.currentTime, 0.02);
|
||||
};
|
||||
volPop.append(volLabel, volInput);
|
||||
|
||||
@@ -1283,6 +1302,7 @@ function renderTiles() {
|
||||
|
||||
item.append(av, name, badges, volPop);
|
||||
roster.appendChild(item);
|
||||
peer.els.rosterItem = item;
|
||||
}
|
||||
} else {
|
||||
// Remote / unjoined channel: render users from server roster snapshot
|
||||
@@ -1324,6 +1344,14 @@ function renderTiles() {
|
||||
}
|
||||
});
|
||||
|
||||
// Re-open the volume popup on the rebuilt row for the peer whose popup was
|
||||
// open before this structural rebuild.
|
||||
if (openVolPeerId) {
|
||||
document
|
||||
.querySelector(`.channel-user-item[data-peer-id="${CSS.escape(openVolPeerId)}"]`)
|
||||
?.classList.add('show-vol');
|
||||
}
|
||||
|
||||
renderStageEmpty();
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user