fix mic meter and distribute participant positions
Build and Publish / Metadata (push) Successful in 1s
Build and Publish / BuildAndDeployAmd64 (push) Failing after 10m18s

This commit is contained in:
mats committed 2026-09-22 17:24:34 +02:00
1 parent 7b9ff4c098
commit 2888a54259
2 files changed
+23 -25

No files matched your search

+16 -23
View File
@@ -589,6 +589,7 @@ function handleSignal(msg) {
// deterministic initiator: lower id offers (no glare storm on join)
ensurePC(S.peers.get(p.id), S.myId < p.id);
}
S.me = chooseOpenParticipantPosition();
sendMyPos(true);
renderAll();
setStatus(`voice connected — ${msg.peers.length + 1} in ${msg.room}`, 'online');
@@ -1090,6 +1091,21 @@ function stopScreenShare(stream, silent) {
// ---------------------------------------------------------------------------
// position sync
// ---------------------------------------------------------------------------
function chooseOpenParticipantPosition() {
const occupied = [...S.peers.values()].map((peer) => ({ x: peer.x, y: peer.y }));
const candidates = [];
for (let row = 0; row < 5; row++) {
for (let col = 0; col < 7; col++) {
candidates.push({ x: 15 + col * 11.7, y: 15 + row * 17.5 });
}
}
if (!occupied.length) return { x: 50, y: 50 };
return candidates.sort((a, b) => {
const distance = (point) => Math.min(...occupied.map((other) => Math.hypot(point.x - other.x, point.y - other.y)));
return distance(b) - distance(a);
})[0];
}
let lastPosSend = 0;
function sendMyPos(force) {
const now = performance.now();
@@ -2166,29 +2182,6 @@ setInterval(() => {
}
}, 5000);
// Spawn me slightly off-center so people don't land on top of each other.
// A single hashed angle on a *fixed* radius ring (the previous approach)
// puts anyone whose name hashes to a nearby angle almost exactly on top of
// each other too — on a small ring, "nearby angle" is only a few pixels of
// arc, and with just 360 angle buckets that's common (especially with the
// same recurring guest-xxxx test names). Hashing the radius independently
// from the angle means an angle collision no longer implies a full overlap.
// Both stay small enough that at refDistance=4/rolloffFactor=1.4 (see
// updatePanner) nobody starts out attenuated — this is purely about the
// dots being visually distinguishable, not audio range.
(() => {
const key = S.myId || S.name;
let hAngle = 0, hRadius = 0;
for (const c of key) {
hAngle = (hAngle * 33 + c.charCodeAt(0)) >>> 0;
hRadius = (hRadius * 17 + c.charCodeAt(0) + 7) >>> 0;
}
const a = (hAngle % 360) * (Math.PI / 180);
const r = 8 + (hRadius % 12); // 8..19 room units
S.me.x = 50 + r * Math.cos(a);
S.me.y = 50 + r * Math.sin(a);
})();
window.addEventListener('keydown', (ev) => {
if (ev.target.tagName === 'INPUT' || ev.target.tagName === 'TEXTAREA') return;
if (ev.key === 'm') toggleMic();
+7 -2
View File
@@ -6,6 +6,7 @@ export class AudioManager {
this.source = null;
this.gainNode = null;
this.analyser = null;
this.meterSink = null;
this.meterData = new Float32Array(1024);
this.gain = Number(localStorage.getItem('voicepoc.micGain') || 1);
}
@@ -25,20 +26,24 @@ export class AudioManager {
this.gainNode.gain.value = this.gain;
this.analyser = this.context.createAnalyser();
this.analyser.fftSize = 1024;
this.meterSink = this.context.createGain();
this.meterSink.gain.value = 0;
const destination = this.context.createMediaStreamDestination();
this.source.connect(this.gainNode).connect(this.analyser);
// Keep the analyser alive without sending the microphone to the speakers.
this.analyser.connect(this.meterSink).connect(this.context.destination);
this.gainNode.connect(destination);
this.outputStream = destination.stream;
return this.outputStream;
}
detachInput() {
for (const node of [this.source, this.gainNode, this.analyser]) {
for (const node of [this.source, this.gainNode, this.analyser, this.meterSink]) {
try { node?.disconnect(); } catch {}
}
this.outputStream?.getTracks().forEach((track) => track.stop());
this.rawStream?.getTracks().forEach((track) => track.stop());
this.source = this.gainNode = this.analyser = null;
this.source = this.gainNode = this.analyser = this.meterSink = null;
this.outputStream = this.rawStream = null;
}