On outbound calls, livekit/sip doesn't relay any audio the far end sends before the final 200 OK. If the callee's network plays something during ringing via a 183 Session Progress (or 180 Ringing) with an SDP body — a custom ringback, an IVR announcement, or a carrier-side personalized ringback/caller-tune service — that audio never reaches the LiveKit room. The room just stays silent (or hears the local play_dialtone tone if that option is set) until the call is actually answered.
I traced this to pkg/sip/outbound.go:
NewMediaPort is created for outbound calls with IgnorePreanswerData: true (MediaOptions.IgnorePreanswerData), so any RTP that arrives before the RTP/SRTP session is set up gets explicitly discarded (media_port.go's startDiscarding/discard loop).
The RTP/SRTP session itself is only ever created once, in sipSignal, via c.media.SetAnswer(...) + c.media.SetConfig(...) — and that only runs after Invite() returns with the final 200 OK body.
The response-handling loop (sipResponse, used by attemptInvite/Invite) does read every provisional (1xx) response, but only extracts the status code and headers for call-state tracking (RingingTime, CallRinging, etc.) — the SDP body of a 1xx response is currently discarded, never passed to anything that could act on it.
So even though SetAnswer/SetConfig are perfectly capable of negotiating from any SDP answer, there's currently no path that calls them with a provisional response's body — only the final one.
Proposed behavior
When a provisional response carries an SDP body (most commonly a 183 Session Progress), negotiate and connect the RTP/SRTP pipeline from that SDP immediately, the same way it's done for the final 200 OK today. That lets real early-media audio reach the room during ringing. When the actual 200 OK arrives afterward, if it points to a different remote address, update the destination (there's already an UpdateRemote/updateRemoteFromSDP helper used for re-INVITEs that fits this) instead of tearing down and rebuilding the session.
Two things worth calling out for the design:
This should probably be opt-in (e.g. a feature flag alongside the existing sip.signal_logging / sip.outbound_route_headers flags), since some trunks may send inconsistent or incomplete SDP on provisional responses, and this is new codepath that not everyone will want by default.
It should only affect the SIP-audio-into-room direction. The room-to-SIP (agent/caller audio) direction is currently gated behind EnableOut() at final accept, and that should probably stay as-is — you don't want to start sending audio into a call the far end hasn't answered yet.
On outbound calls, livekit/sip doesn't relay any audio the far end sends before the final 200 OK. If the callee's network plays something during ringing via a 183 Session Progress (or 180 Ringing) with an SDP body — a custom ringback, an IVR announcement, or a carrier-side personalized ringback/caller-tune service — that audio never reaches the LiveKit room. The room just stays silent (or hears the local play_dialtone tone if that option is set) until the call is actually answered.
I traced this to pkg/sip/outbound.go:
NewMediaPort is created for outbound calls with IgnorePreanswerData: true (MediaOptions.IgnorePreanswerData), so any RTP that arrives before the RTP/SRTP session is set up gets explicitly discarded (media_port.go's startDiscarding/discard loop).
The RTP/SRTP session itself is only ever created once, in sipSignal, via c.media.SetAnswer(...) + c.media.SetConfig(...) — and that only runs after Invite() returns with the final 200 OK body.
The response-handling loop (sipResponse, used by attemptInvite/Invite) does read every provisional (1xx) response, but only extracts the status code and headers for call-state tracking (RingingTime, CallRinging, etc.) — the SDP body of a 1xx response is currently discarded, never passed to anything that could act on it.
So even though SetAnswer/SetConfig are perfectly capable of negotiating from any SDP answer, there's currently no path that calls them with a provisional response's body — only the final one.
Proposed behavior
When a provisional response carries an SDP body (most commonly a 183 Session Progress), negotiate and connect the RTP/SRTP pipeline from that SDP immediately, the same way it's done for the final 200 OK today. That lets real early-media audio reach the room during ringing. When the actual 200 OK arrives afterward, if it points to a different remote address, update the destination (there's already an UpdateRemote/updateRemoteFromSDP helper used for re-INVITEs that fits this) instead of tearing down and rebuilding the session.
Two things worth calling out for the design:
This should probably be opt-in (e.g. a feature flag alongside the existing sip.signal_logging / sip.outbound_route_headers flags), since some trunks may send inconsistent or incomplete SDP on provisional responses, and this is new codepath that not everyone will want by default.
It should only affect the SIP-audio-into-room direction. The room-to-SIP (agent/caller audio) direction is currently gated behind EnableOut() at final accept, and that should probably stay as-is — you don't want to start sending audio into a call the far end hasn't answered yet.