fix hifi mode
This commit is contained in:
1 parent
10481ad08c
commit
ce3d40aba8
3 files changed
+53
-12
No files matched your search
+45
-10
@@ -33,6 +33,11 @@ window.addEventListener('unhandledrejection', (event) => {
|
||||
// ---------------------------------------------------------------------------
|
||||
const ROOM_UNITS = 100; // spatial plane is 0..100 x 0..100
|
||||
const SPATIAL_SCALE = 0.12; // room units -> audio world units
|
||||
// Hi-Fi rooms: stereo Opus at 256 kbps with echo cancellation disabled —
|
||||
// for music/listening channels where fidelity beats echo-proofing. Marked
|
||||
// with a HI-FI badge in the channel list.
|
||||
const HIFI_ROOMS = new Set(['code', 'gaming']);
|
||||
function roomIsHifi(room = S.room) { return HIFI_ROOMS.has(room); }
|
||||
const FALLBACK_ICE = { iceServers: [{ urls: ['stun:turn.k6n.net:3478', 'stun:stun.l.google.com:19302'] }] };
|
||||
const LS_NAME = 'voicepoc.nick';
|
||||
const LS_ICE = 'voicepoc.iceOverride';
|
||||
@@ -139,15 +144,39 @@ async function tuneAudioSender(sender) {
|
||||
if (!sender?.getParameters || !sender?.setParameters) return;
|
||||
const params = sender.getParameters();
|
||||
if (!params.encodings?.length) params.encodings = [{}];
|
||||
// 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;
|
||||
// Hi-Fi rooms get 256 kbps stereo (see HIFI_ROOMS); elsewhere 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 = roomIsHifi() ? 256000 : 128000;
|
||||
params.encodings[0].dtx = false;
|
||||
params.encodings[0].priority = 'high';
|
||||
await sender.setParameters(params);
|
||||
}
|
||||
|
||||
// Hi-Fi rooms force Opus stereo and lift the in-band bitrate cap. The sender
|
||||
// encodings' maxBitrate alone is not enough: Chrome clamps Opus to the SDP's
|
||||
// maxaveragebandwidth, and stereo only survives the wire when both sides
|
||||
// declare stereo=1 in fmtp. Applied to our local offers AND answers.
|
||||
function mungeOpusHifi(sdp) {
|
||||
const pts = [...sdp.matchAll(/a=rtpmap:(\d+) opus\/48000\/2\r?\n/g)].map((m) => m[1]);
|
||||
if (!pts.length) return sdp;
|
||||
const extra = 'stereo=1;sprop-stereo=1;maxaveragebandwidth=256000;maxplaybackrate=48000';
|
||||
for (const pt of pts) {
|
||||
if (new RegExp(`a=fmtp:${pt} `).test(sdp)) {
|
||||
sdp = sdp.replace(new RegExp(`a=fmtp:${pt} `), `a=fmtp:${pt} ${extra};`);
|
||||
} else {
|
||||
sdp = sdp.replace(new RegExp(`(a=rtpmap:${pt} opus\\/48000\\/2\\r?\\n)`), `$1a=fmtp:${pt} ${extra}\r\n`);
|
||||
}
|
||||
}
|
||||
return sdp;
|
||||
}
|
||||
|
||||
function hifiDescription(desc) {
|
||||
if (!desc || !roomIsHifi()) return desc;
|
||||
return { type: desc.type, sdp: mungeOpusHifi(desc.sdp) };
|
||||
}
|
||||
|
||||
function preferOpus(pc) {
|
||||
const capabilities = globalThis.RTCRtpReceiver?.getCapabilities?.('audio');
|
||||
const codecs = capabilities?.codecs || [];
|
||||
@@ -269,12 +298,12 @@ function effectiveIce() {
|
||||
return S.iceServers || FALLBACK_ICE;
|
||||
}
|
||||
|
||||
function microphoneConstraints(deviceId = localStorage.getItem(LS_AUDIO_INPUT)) {
|
||||
function microphoneConstraints(deviceId = localStorage.getItem(LS_AUDIO_INPUT), hifi = roomIsHifi()) {
|
||||
const audio = {
|
||||
echoCancellation: true,
|
||||
echoCancellation: !hifi, // hi-fi rooms: raw capture — speakers may feed back, use headphones
|
||||
noiseSuppression: false,
|
||||
autoGainControl: false,
|
||||
channelCount: 1,
|
||||
channelCount: hifi ? 2 : 1, // ideal, not exact — mono-only mics downgrade gracefully
|
||||
sampleRate: 48000,
|
||||
sampleSize: 16,
|
||||
};
|
||||
@@ -340,6 +369,9 @@ async function joinVoice(room) {
|
||||
if (S.room) leaveVoice('switching channel');
|
||||
S.connecting = true;
|
||||
setStatus(`connecting to ${room}…`, 'connecting');
|
||||
// Hi-Fi profile must be resolved before capture: S.room still names the
|
||||
// previous channel here, so the target room is passed explicitly.
|
||||
const hifi = HIFI_ROOMS.has(room);
|
||||
|
||||
// Mic permission & AudioContext: request immediately on the user-click gesture
|
||||
// so browsers don't block the microphone permission prompt or audio autoplay policy.
|
||||
@@ -357,7 +389,7 @@ async function joinVoice(room) {
|
||||
window.addEventListener('keydown', resumeCtx, { passive: true });
|
||||
|
||||
try {
|
||||
S.rawMicStream = await navigator.mediaDevices.getUserMedia(microphoneConstraints());
|
||||
S.rawMicStream = await navigator.mediaDevices.getUserMedia(microphoneConstraints(undefined, hifi));
|
||||
S.micStream = await S.audioManager.attachInput(S.rawMicStream);
|
||||
populateAudioInputs();
|
||||
} catch {
|
||||
@@ -378,6 +410,7 @@ async function joinVoice(room) {
|
||||
S.room = room;
|
||||
S.connecting = false;
|
||||
playRetroCue('join');
|
||||
if (hifi) toast('Hi-Fi audio on: stereo, 256 kbps, echo cancellation off — headphones recommended', 'ok');
|
||||
renderAll();
|
||||
}
|
||||
|
||||
@@ -679,7 +712,8 @@ function ensurePC(peer, initiator) {
|
||||
pc.onnegotiationneeded = async () => {
|
||||
try {
|
||||
peer.makingOffer = true;
|
||||
await pc.setLocalDescription();
|
||||
const offer = await pc.createOffer();
|
||||
await pc.setLocalDescription(hifiDescription(offer));
|
||||
send({ type: 'offer', to: peer.id, sdp: pc.localDescription });
|
||||
} catch (err) {
|
||||
console.error('negotiation failed', err);
|
||||
@@ -840,7 +874,8 @@ async function onDescription(peer, desc) {
|
||||
});
|
||||
}
|
||||
if (desc.type === 'offer') {
|
||||
await pc.setLocalDescription();
|
||||
const answer = await pc.createAnswer();
|
||||
await pc.setLocalDescription(hifiDescription(answer));
|
||||
send({ type: 'answer', to: peer.id, sdp: pc.localDescription });
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -50,9 +50,9 @@
|
||||
general <span class="hint">matrix</span></div>
|
||||
<div class="channel-group-label">Voice channels</div>
|
||||
<div class="channel voice" data-room="general"><span class="material-symbols-outlined channel-icon">volume_up</span> General <span class="count" data-room-count="general"></span></div>
|
||||
<div class="channel voice" data-room="gaming"><span class="material-symbols-outlined channel-icon">volume_up</span> Gaming <span class="count" data-room-count="gaming"></span></div>
|
||||
<div class="channel voice" data-room="gaming"><span class="material-symbols-outlined channel-icon">volume_up</span> Gaming <span class="hifi-badge" title="Hi-Fi voice: stereo Opus, 256 kbps, echo cancellation off">HI-FI</span> <span class="count" data-room-count="gaming"></span></div>
|
||||
<div class="channel voice" data-room="lounge"><span class="material-symbols-outlined channel-icon">volume_up</span> Lounge <span class="count" data-room-count="lounge"></span></div>
|
||||
<div class="channel voice" data-room="code"><span class="material-symbols-outlined channel-icon">volume_up</span> Code <span class="count" data-room-count="code"></span></div>
|
||||
<div class="channel voice" data-room="code"><span class="material-symbols-outlined channel-icon">volume_up</span> Code <span class="hifi-badge" title="Hi-Fi voice: stereo Opus, 256 kbps, echo cancellation off">HI-FI</span> <span class="count" data-room-count="code"></span></div>
|
||||
<div class="channel voice" data-room="work"><span class="material-symbols-outlined channel-icon">volume_up</span> Work <span class="count" data-room-count="work"></span></div>
|
||||
|
||||
<footer id="user-panel">
|
||||
|
||||
@@ -116,6 +116,12 @@ body.sidebar-collapsed #btn-sidebar-expand { display: grid; }
|
||||
.channel.text .hint { font-size: 10px; margin-left: 4px; }
|
||||
.channel.voice.active { background: rgba(88,101,242,.25); color: var(--text-0); }
|
||||
.channel .count { margin-left: auto; color: var(--text-2); font-size: 12px; }
|
||||
.hifi-badge {
|
||||
font-size: 9px; font-weight: 700; letter-spacing: .5px; line-height: 1.5;
|
||||
color: #23a55a; border: 1px solid rgba(35,165,90,.55); border-radius: 999px;
|
||||
padding: 0 6px; flex-shrink: 0; user-select: none;
|
||||
}
|
||||
.channel.active .hifi-badge { color: #2dc770; border-color: rgba(45,199,112,.8); }
|
||||
|
||||
#user-panel {
|
||||
margin-top: auto; background: var(--bg-0); padding: 8px;
|
||||
|
||||
Reference in new issue
Block a user