fix: WebRTC audio — glare handling (Perfect Negotiation) + autoplay + signalingState guard

This commit is contained in:
Maxim Dolgolyov
2026-04-15 13:28:38 +03:00
parent 7f23cfdacd
commit c3050b9e43
+14 -1
View File
@@ -193,6 +193,18 @@ class ClassroomRTC {
async _handleOffer(fromUid, payload) {
const peer = this._getOrCreate(fromUid);
// Perfect Negotiation: resolve offer collision (both peers sending offers simultaneously).
// Polite peer (higher uid) yields — rolls back its own offer and handles the incoming one.
// Impolite peer (lower uid) ignores the collision offer and waits for the answer to its own.
const polite = this._uid > fromUid;
const collision = peer.pc.signalingState !== 'stable' || peer.negotiating;
if (collision) {
if (!polite) return;
// Polite: explicit rollback for older browsers; modern Chrome handles it implicitly
await peer.pc.setLocalDescription({ type: 'rollback' }).catch(() => {});
}
await peer.pc.setRemoteDescription({ type: 'offer', sdp: payload.sdp });
peer.remoteSet = true;
await this._flushCandidates(peer);
@@ -277,7 +289,7 @@ class ClassroomRTC {
/* Renegotiation: fires when tracks are added/removed (e.g. screen share) */
pc.onnegotiationneeded = async () => {
if (peer.negotiating || !peer.remoteSet) return;
if (peer.negotiating || !peer.remoteSet || pc.signalingState !== 'stable') return;
peer.negotiating = true;
try {
const offer = await pc.createOffer();
@@ -299,6 +311,7 @@ class ClassroomRTC {
document.body.appendChild(peer.audioEl);
}
peer.audioEl.srcObject = stream;
peer.audioEl.play().catch(() => {});
this._startVAD(uid, stream);
} else if (track.kind === 'video') {
if (this._onScreen) this._onScreen(stream);