fix: guard against stale SDP answers in wrong signaling state

Our own renegotiation answers can echo back through the relay and
get processed as if from the peer. Check that the peer connection
is in HAVE_LOCAL_OFFER state before applying a remote answer SDP,
preventing the "Called in wrong state: stable" error.

https://claude.ai/code/session_014espsysob7MrE8X8e75jFX
This commit is contained in:
Claude
2026-04-02 18:42:43 +00:00
parent ea96dc9fb9
commit 0f533ea43a
2 changed files with 15 additions and 2 deletions
@@ -40,6 +40,7 @@ import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.webrtc.IceCandidate
import org.webrtc.PeerConnection
import org.webrtc.SessionDescription
import org.webrtc.VideoFrame
import org.webrtc.VideoSink
@@ -228,8 +229,18 @@ class CallController(
}
fun onCallAnswerReceived(sdpAnswer: String) {
Log.d(TAG) { "Answer received, SDP length=${sdpAnswer.length}, session=${webRtcSession != null}" }
webRtcSession?.setRemoteDescription(SessionDescription(SessionDescription.Type.ANSWER, sdpAnswer))
val session = webRtcSession ?: return
val signalingState = session.getSignalingState()
Log.d(TAG) { "Answer received, SDP length=${sdpAnswer.length}, signalingState=$signalingState" }
// An answer is only valid when we have a pending local offer.
// Ignore stale answers (e.g. our own renegotiation answer echoed back by the relay).
if (signalingState != PeerConnection.SignalingState.HAVE_LOCAL_OFFER) {
Log.d(TAG) { "Ignoring answer in $signalingState state (no pending local offer)" }
return
}
session.setRemoteDescription(SessionDescription(SessionDescription.Type.ANSWER, sdpAnswer))
flushPendingIceCandidates()
}
@@ -263,6 +263,8 @@ class WebRtcCallSession(
peerConnection?.addIceCandidate(candidate)
}
fun getSignalingState(): PeerConnection.SignalingState? = peerConnection?.signalingState()
fun setAudioEnabled(enabled: Boolean) {
localAudioTrack?.setEnabled(enabled)
}