Force renegotiation when attaching to null audio sender
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
// Maintainer-controlled web client version.
|
// Maintainer-controlled web client version.
|
||||||
// Format: YYYY.MM.DD Rn (example: 2026.02.20 R2)
|
// Format: YYYY.MM.DD Rn (example: 2026.02.20 R2)
|
||||||
window.CHGRID_WEB_VERSION = "2026.02.25 R265";
|
window.CHGRID_WEB_VERSION = "2026.02.25 R266";
|
||||||
// Optional display timezone for timestamps. Falls back to America/Detroit if unset/invalid.
|
// Optional display timezone for timestamps. Falls back to America/Detroit if unset/invalid.
|
||||||
window.CHGRID_TIME_ZONE = "America/Detroit";
|
window.CHGRID_TIME_ZONE = "America/Detroit";
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import type { RemoteUser } from '../network/protocol';
|
|||||||
export type PeerRuntime = SpatialPeerRuntime & {
|
export type PeerRuntime = SpatialPeerRuntime & {
|
||||||
id: string;
|
id: string;
|
||||||
pc: RTCPeerConnection;
|
pc: RTCPeerConnection;
|
||||||
|
initiator: boolean;
|
||||||
remoteStream?: MediaStream;
|
remoteStream?: MediaStream;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -38,6 +39,7 @@ export class PeerManager {
|
|||||||
|
|
||||||
const peer: PeerRuntime = {
|
const peer: PeerRuntime = {
|
||||||
id: targetId,
|
id: targetId,
|
||||||
|
initiator: isInitiator,
|
||||||
nickname: userData.nickname ?? 'user...',
|
nickname: userData.nickname ?? 'user...',
|
||||||
x: userData.x ?? 20,
|
x: userData.x ?? 20,
|
||||||
y: userData.y ?? 20,
|
y: userData.y ?? 20,
|
||||||
@@ -117,16 +119,47 @@ export class PeerManager {
|
|||||||
if (!newTrack) {
|
if (!newTrack) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const peersToRenegotiate: PeerRuntime[] = [];
|
||||||
for (const peer of this.peers.values()) {
|
for (const peer of this.peers.values()) {
|
||||||
|
let shouldRenegotiate = false;
|
||||||
const sender =
|
const sender =
|
||||||
peer.pc.getSenders().find((candidate) => candidate.track?.kind === 'audio') ??
|
peer.pc.getSenders().find((candidate) => candidate.track?.kind === 'audio') ??
|
||||||
peer.pc
|
peer.pc
|
||||||
.getTransceivers()
|
.getTransceivers()
|
||||||
.find((transceiver) => transceiver.receiver.track?.kind === 'audio' || transceiver.sender.track?.kind === 'audio')
|
.find((transceiver) => transceiver.receiver.track?.kind === 'audio' || transceiver.sender.track?.kind === 'audio')
|
||||||
?.sender;
|
?.sender;
|
||||||
if (!sender) continue;
|
if (!sender) {
|
||||||
|
peer.pc.addTrack(newTrack, stream);
|
||||||
|
shouldRenegotiate = true;
|
||||||
|
} else {
|
||||||
|
if (!sender.track) {
|
||||||
|
shouldRenegotiate = true;
|
||||||
|
}
|
||||||
await sender.replaceTrack(newTrack);
|
await sender.replaceTrack(newTrack);
|
||||||
}
|
}
|
||||||
|
const audioTransceiver = peer.pc
|
||||||
|
.getTransceivers()
|
||||||
|
.find((transceiver) => transceiver.receiver.track?.kind === 'audio' || transceiver.sender.track?.kind === 'audio');
|
||||||
|
if (audioTransceiver && (audioTransceiver.direction === 'recvonly' || audioTransceiver.direction === 'inactive')) {
|
||||||
|
audioTransceiver.direction = 'sendrecv';
|
||||||
|
shouldRenegotiate = true;
|
||||||
|
}
|
||||||
|
if (shouldRenegotiate && peer.initiator) {
|
||||||
|
peersToRenegotiate.push(peer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const peer of peersToRenegotiate) {
|
||||||
|
if (peer.pc.connectionState === 'closed') continue;
|
||||||
|
if (peer.pc.signalingState !== 'stable') continue;
|
||||||
|
try {
|
||||||
|
let offer = await peer.pc.createOffer();
|
||||||
|
offer = this.tuneOpus(offer);
|
||||||
|
await peer.pc.setLocalDescription(offer);
|
||||||
|
this.sendSignal(peer.id, { sdp: peer.pc.localDescription ?? undefined });
|
||||||
|
} catch {
|
||||||
|
// Best-effort renegotiation; transport-level failures recover on subsequent signaling.
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
removePeer(id: string): void {
|
removePeer(id: string): void {
|
||||||
|
|||||||
Reference in New Issue
Block a user