From db6cd01afe7b0f21930af6d3370fe514321d009b Mon Sep 17 00:00:00 2001 From: jevb Date: Wed, 18 Mar 2026 03:49:23 +0100 Subject: [PATCH] feat: add renegotiateParticipant with Perfect Negotiation Adds Hub.renegotiateParticipant to ws/voice_handlers.go. The function creates a new SDP offer for a client's PeerConnection and sends it as voice_offer, implementing the impolite side of Perfect Negotiation by skipping renegotiation when the PC is in have-remote-offer state. --- Server/ws/voice_handlers.go | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/Server/ws/voice_handlers.go b/Server/ws/voice_handlers.go index a92aa186..d248c7d0 100644 --- a/Server/ws/voice_handlers.go +++ b/Server/ws/voice_handlers.go @@ -68,6 +68,40 @@ func (h *Hub) setupICECallback(c *Client, channelID int64) { }) } +// renegotiateParticipant creates a new SDP offer for the given client +// and sends it as voice_offer. Implements the "impolite" side of +// Perfect Negotiation — skips if PC is in have-remote-offer state. +func (h *Hub) renegotiateParticipant(c *Client) { + pc := c.getPC() + if pc == nil { + return + } + + // Perfect Negotiation: server is impolite — skip if client + // already sent an offer we haven't answered yet. + if pc.SignalingState() == webrtc.SignalingStateHaveRemoteOffer { + slog.Info("renegotiate skipped: have-remote-offer", + "user_id", c.userID) + return + } + + offer, err := pc.CreateOffer(nil) + if err != nil { + slog.Error("renegotiateParticipant CreateOffer", + "err", err, "user_id", c.userID) + return + } + + if err := pc.SetLocalDescription(offer); err != nil { + slog.Error("renegotiateParticipant SetLocalDescription", + "err", err, "user_id", c.userID) + return + } + + channelID := c.getVoiceChID() + c.sendMsg(buildVoiceOffer(channelID, offer.SDP)) +} + // handleVoiceJoin processes a voice_join message. // 1. Parses channel_id. // 2. Checks CONNECT_VOICE permission.