Commit Graph
1640 Commits
Author SHA1 Message Date
Anunay Maheshwari 6087e6bef5 fix(rtc): don't miss publishing tracks on resume, and rewrite the opus answer fmtp by key (#5148)
* fix(rtc): look up pending before published tracks on resume so a moving track is not missed

* fix(rtc): rewrite the opus answer fmtp by key and drop the line when nothing remains

* chore(rtc): note why a track closed between the resume lookups reads as unknown

* test(rtc): keep the parked reader helper beside its only user in room_test

* fix(rtc): match the opus fmtp by exact payload type in the publisher answer
2026-11-06 15:41:27 +05:30
2d4bd8af55 rtc: disable transceiver reuse for all WebKit clients, incl. iOS (#5133)
* rtc: disable transceiver reuse for all WebKit clients, incl. iOS

#4474 turned off transceiver reuse on Safari because of an insertable
streams issue that can corrupt E2EE tracks. The check matched only a
browser name of exactly "Safari". The server's user agent parser reports
iPhone Safari as "Mobile Safari", WKWebView as "Mobile Safari
UI/WKWebView", and other iOS browsers as "Chrome Mobile iOS",
"Firefox iOS", "Edge Mobile" or an app name. All of these run on WebKit,
but they kept transceiver reuse.

Match any browser name that contains "safari", and any JS SDK client on
iOS. React Native, Swift and Flutter do not match. isSafari() and audio
RED are not changed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* rtc: reuse isSafari() in isWebKit()

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* rtc: use isWebKit() for audio RED support too

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: lukasIO <mail@lukasseiler.de>
2026-11-06 11:05:13 +01:00
Anunay Maheshwari beee7b0eaa fix(rtc): hold pendingTracksLock across both track lookups in mute and track updates (#5147)
* test(rtc): cover mute and track updates while a track moves to published

* fix(rtc): hold pendingTracksLock across both track lookups in mute and track updates

* fix(rtc): update queued replacements before the published track in mute and track updates
2026-11-06 12:55:44 +05:30
Anunay Maheshwari 0dd174b3ed fix(rtc): hold pendingTracksLock across both track lookups when configuring the publisher answer (#5146)
* test(rtc): cover the publisher answer while its track moves to published

* fix(rtc): hold pendingTracksLock across both track lookups when configuring the publisher answer
2026-11-06 03:23:26 +05:30
cnderrauber 22ba651703 Pick DTLS server role when ice-lite is enabled (#5143)
To speed up data channel and warp. Exclude sdks (unknown ||
esp32) that may have problem to support answer dtls server role.
2026-11-05 22:01:40 +08:00
cnderrauber e269e3e355 Update webrtc fork to dtls1.3 (#5142)
* Update webrtc fork to dtls1.3

* update dep

* Remove debug line

* Fix test
2026-11-05 18:20:18 +08:00
armaan4658 7da670eef7 rtc: read subscriber track settings under the lock in applySettings (#5126)
applySettings releases settingsLock before it computes the video layers,
then reads t.settings.Quality, Width, Height and Fps without the lock.
UpdateSubscriberSettings replaces t.settings under the lock, and the
debouncer and UpdateVideoLayer run applySettings on other goroutines, so
the reads race with the write. go test -race reports it on the pointer
and on the fields of the newly cloned settings.

Take the settings pointer while holding the lock and compute the layers
from that snapshot. UpdateSubscriberSettings always stores a fresh clone
and never mutates it, so the snapshot is safe to read after unlocking,
and all four values come from the same update.
2026-11-03 16:17:17 +05:30
lukasIOandClaude Opus 5.5 0d762b4e50 rtc: match publisher m-sections by mid, not by position (#5122)
* rtc: match publisher m-sections by mid, not by position

A client that stops a transceiver lets the next one recycle its m-line
(JSEP 5.2.2), so a new m-section can come before negotiated ones.
GetUnmatchMediaForOffer stopped at the last answered mid and missed it,
so its codec preferences were never applied.

Bump the protocol so clients know they can stop transceivers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Update protocol_version.go

* Remove comments from GetUnmatchMediaForOffer function

Removed comments explaining the GetUnmatchMediaForOffer function.

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 17:20:11 +01:00
armaan4658andClaude Opus 5.5 b7af480c2d rtc: reject AddTrack with a repeated video layer quality (#5115)
ValidVideoLayers checks that each layer's quality is in range but lets a
quality repeat. GetQualityForDimension uses the index into the sorted
layer sizes as the quality, so a list such as LOW, LOW, MEDIUM, HIGH maps
the largest size to index 3, which is OFF, and a subscriber asking for
that size receives no video.

Reject an AddTrack whose layer list repeats a quality. Each list (the
request's and each simulcast codec's) is checked on its own, so the same
qualities on the request and a codec are still accepted. With only three
qualities this also bounds a list to three layers.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 14:03:42 +05:30
Raja SubramanianandClaude Opus 5.5 94b5e9a4e5 rtc: stop TestPreferAudioCodecForRed from hanging on a stalled ICE write (#5119)
The test client pc gathered host candidates on every interface. On a
host with a network content filter, a STUN binding request can block
in sendto forever. The write runs inside the ICE agent taskloop, so
SetRemoteDescription and the next CreateOffer wait on that loop and
the package hits its timeout.

The test checks only SDP, so the client pc now gathers no host
candidates and neither agent sends a connectivity check.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 13:03:03 +05:30
Raja SubramanianandClaude Opus 5.5 8eb162f104 rtc: do not advertise a subscribed track the subscriber cannot decode (#5117)
* rtc: do not advertise a subscribed track the subscriber cannot decode

In single peer connection mode a subscriber offers a recvonly section with
its own codecs. If the published track uses a codec the subscriber did not
offer, DownTrack.Bind finds no match but returns nil, because returning an
error aborts the whole transport. pion then keeps the sender, so the answer
still carries the track msid and ssrc. The subscriber sees a short-lived
TrackSubscribed before the bind failure removes the track again.

Park such a sender before building the answer: drop its track so the
section answers inactive, then report the codec failure out of band so the
subscription manager unsubscribes without reentering negotiation.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* rtc: report codec mismatch via Handler, not the downtrack

Route the parked sender's failure through a new Handler method into the
participant, instead of reaching from transport into the downtrack. The
participant unsubscribes and reports the codec error. Keeps transport
free of the sfu package.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* rtc: use CodecsFromMediaDescription for the offered-codec check

Reuse the protocol sdp helper instead of hand-parsing rtpmap lines.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* rtc: reword codec-suppression comments

Describe it as a subscription, drop the stale prototype label.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* rtc: check every bindable codec before suppressing a sender

The check before the answer looked only at the primary codec. A subscriber
that offers a later upstream codec, for example Opus for a RED-first track,
lost a track that Bind can bind.

DownTrack.BindableCodecs returns the codecs Bind can pick, and both Bind and
the check use it. It also fixes the encrypted RED/Opus rule in Bind: the old
condition was never true, so an encrypted RED track could bind as Opus.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* rtc: keep published encrypted codecs bindable, record codec rejections

The encrypted RED/Opus rule cut every RED/Opus pair to the primary codec.
That also dropped a second codec the publisher sends on its own stream.
WrappedReceiver now decides which codecs bind: for an encrypted source,
only its published codecs, never the RED or Opus alternative it adds.

A sender parked before the answer now ends its subscription through the
same path as a bind error, so it records OnTrackSubscribeFailed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-11-02 12:32:28 +05:30
Raja SubramanianandClaude Fable 5.1 db2e810958 rtc: log the attributes of every data channel (#5116)
* rtc: log the attributes of every data channel

Both transports log "data channel open" at info with the channel's
label, id, ordered, maxRetransmits, maxPacketLifeTime, protocol and
negotiated, through one helper.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* paul feedback

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-01 11:29:19 +05:30
Raja SubramanianandClaude Opus 5.5 9a26932591 Harden media and room handling against bad inputs and races (#5114)
* sfu: ignore a nil publisher sender report in the forwarder

A stream restart replaces the receiver stats between the sender report
callback and its read, so the report can be nil. SetRefSenderReport now
keeps the previous report instead of reading the nil one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* dynacast: skip a codec regression when the target codec has no quality

HandleCodecRegression looked up the target codec after it released the
lock. If the manager closed in between, or the target mime type was
unknown, the lookup returned nil and RegressTo called Restart or Replace on
it. The target is now looked up with the source, and a missing one stops
the regression.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* service: create the internal record for a stored room that has none

RedisStore.LoadRoom returns a nil RoomInternal without an error when the
room exists but its internal field does not, for example after a delete
races a store. CreateRoom then wrote egress, agent, playout delay or sync
settings into the nil record. It now starts from an empty record, which
the store call writes back.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* sfu: drop a dependency descriptor whose chains do not match the structure

The parser takes a new structure from any packet, but DDForwarder keeps
the structure of the last packet it forwarded. When the publisher attached
a structure with fewer chains to a packet this downtrack dropped, the next
descriptor had fewer chain diffs than the forwarder's structure, and the
writer indexed past them. The forwarder now drops such a packet, and the
writer returns an error instead of indexing past the chain diffs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* deps: bump mediatransportutil for the empty VP9 payload fix

A VP9 packet without a dependency descriptor and with a 1-byte payload such
as 0x08 parses to an empty VP9 payload, and IsVP9KeyFrame read its first
byte. mediatransportutil#106 returns false for an empty payload. This pins
the transport-v4 commit that carries it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* sfu: keep the template test comment with its test

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* dynacast: create the target quality only for a valid regression

A target quality starts a timer that notifies the publisher. Look it up
only after the source codec is found, so a rejected regression creates
nothing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* service: restore the internal defaults for a room that lost them

A stored room without an internal record now gets the configured playout
delay and sync streams defaults. Its own settings, such as the empty
timeout and codecs, stay as stored.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 19:29:11 +05:30
Raja SubramanianandClaude Fable 5.1 adb7cb4f29 rtc: hold unlabeled data for a participant whose reliable channel is opening (#5113)
An unlabeled data message sent to a participant without an unlabeled
channel is converted to a UserPacket and written to the reliable
channel. That write failed while the channel was still opening, and the
message was lost. The participant now builds the UserPacket and sends
it through SendDataMessage, which holds unsequenced reliable data until
the channel is writable. The transport keeps only the raw path.

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-31 16:16:57 +05:30
Raja SubramanianandClaude Fable 5.1 6b172bfb4a rtc: broadcast an unlabeled data message from a source identity (#5112)
* rtc: broadcast an unlabeled data message from a source identity

BroadcastDataMessageForRoomFrom takes the source ID and identity instead
of a LocalParticipant, so a caller can stamp the sender of a message
whose source is not on this node. BroadcastDataMessageForRoom keeps its
signature and calls it.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* rtc: say what sourceID does in the BroadcastDataMessageForRoomFrom comment

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-31 15:33:34 +05:30
Raja SubramanianandClaude Opus 5.5 1681f36610 rtc: guard nil source in BroadcastDataMessageForRoom (#5110)
Relayed unlabeled data arrives with no local source participant, and
the sender identity lookup dereferenced it.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 13:16:19 +05:30
Raja SubramanianandClaude Opus 4.8 996b5bfb9a rtc: reject AddTrack with an out-of-range video layer mode (#5106)
A client sets SimulcastCodec.VideoLayerMode on AddTrack. Any value other
than MODE_UNUSED is used as-is, so a forged out-of-range value flows into
the forwarder. Reject it in ValidVideoLayers, alongside the quality check.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-30 20:37:35 +05:30
Raja Subramanian ebc71a6d11 rtc: reject AddTrack with an out-of-range video layer quality (#5102) 2026-10-30 02:16:04 +05:30
Paul Wells c3605dfa6c Sign refresh tokens with the join token's API key (#5088)
Refresh tokens were signed with the first configured key pair, which is
an arbitrary map entry when several keys are configured. Keep the join
token's API key on the participant and sign with it when its secret is
still configured, falling back to the first key pair otherwise.
2026-10-28 04:52:48 -07:00
Raja SubramanianandClaude Opus 5.5 2f7337b759 rtc: ignore server-owned video layer fields in AddTrack (#5087)
The server sets the spatial layer, rid, ssrc and repair ssrc of each
video layer from SDP and RTP. Clear these fields on AddTrack request
layers, including each simulcast codec's layers.

Clamp the layer in ReceiverBase.SetMaxExpectedSpatialLayer to
[InvalidLayerSpatial, DefaultMaxLayerSpatial].

Keep a client set video layer mode, but warn when it differs from the
server determined mode.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 16:23:52 +05:30
cnderrauber 37901309a3 Revert #5033 (#5077)
WHIP passthrough ingress has been failing DTLS
2026-10-27 21:04:38 +08:00
Raja SubramanianandClaude Opus 5.5 7c575a5c31 sfu: cap retransmissions per down track (#5054)
* sfu: cap retransmissions per down track (rtc.rtx config, off by default)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* sfu: rtx budget review fixes (sequencer undo, sent bytes, newest first, rtx_budget)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* sfu: rtx budget second review (order unchanged when off, keep NACK window, metric in down track)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* sfu: rtx budget sorts NACKs newest first by extended sequence number

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 11:05:54 +05:30
Raja SubramanianandClaude Opus 5.5 04f7cb71e3 rtc: fix flaky TestSetPermissionDataTrackSubscriptions (#5056)
* rtc: wait for the recorded data track subscription before revoking in test

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* rtc: drop a subscription whose canSubscribe was revoked while it was in flight

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 11:05:33 +05:30
Raja SubramanianandClaude Opus 5.5 704c3280fe rtc: measure subscribe pacing delay in shadow (#5046)
* rtc: measure subscribe pacing delay in shadow

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* prometheus: raise the subscribe pace shadow top bucket to 120 s

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* rtc: pace video subscribes per node in shadow, record the wait only

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* rtc: subscribe pacer doc wording, make the wiring test independent of timing

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* rtc: sample shadow pacing at arrival, skip one-shot, keep sub-millisecond waits

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* rtc: take the shadow pacing token before setup, in arrival order

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* rtc: build shadow pacing buckets in one pass

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 16:17:12 +05:30
Raja Subramanian d8a4cdac11 Extend the TTL of data channel cache to 10s. (#5045)
To cover slow subscribers. Size still stays so that it does not grow
beyond a certain size.
2026-10-23 12:07:58 +05:30
6342d8fd1b Enable upstream FlexFEC03 (#4845)
* Add upstream FlexFEC recovery support

* Guard short TWCC extensions in FEC packets

* Release completed FlexFEC states

* Validate FlexFEC payload type range

* Invoke FEC callbacks after unlocking

* Reduce FlexFEC protection state allocations

* Avoid allocations for complete FlexFEC packets

* Optimize ordered FlexFEC packet insertion

* Reuse RTP packet storage for FlexFEC recovery

* Limit pending FlexFEC repair packets

* Lower FlexFEC repair window to two seconds

* Harden upstream FlexFEC handling

* Test FlexFEC with E2EE media

* Fix FlexFEC negotiation with libwebrtc publishers

* Enhance payload length validation in decoder

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* remove the ability to configure payload type for FlexFEC

* moved some helpers into protocol

* Handle big gaps in media sequence numbers

Reset buffers when a big gap in media sequence numbers is detected.

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* bump protocol & psrpc deps

* Handle FEC packet recovery failure

Increment FEC packets discarded counter and remove packet.

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* do not NACK on fec recovered packet (#4896)

* do not NACK on fec recovered packet

* fix test race in CI

* Fix FEC write return after merging RTX feedback changes

* PR fixes for FlexFEC implementation (#4898)

* do not NACK on fec recovered packet

* refactor common code into unlockAndInvokeFECRecovery

* add FEC stats to RTPStats

* add log for when fecdecoder unavailable after creation

* rename markAsFECRepair to setFECRepairBufferLimit for clarity

* refactor packet generation for tests into util

* fix test race in CI

* move the unlocks back inline for readability

* update RTPstats

* Fix FEC write return after merging RTX feedback changes

* Use shared RTP packet helper in merged FEC NACK test

* Remove redundant return from FEC write handler

* packet post process (#5020)

* packet post process

* remove redundant writeRTX

* send out RTCP (NACK and RR) from packetPostProcess only

* handle FEC decoder on receiver restart (#5032)

* handle FEC decoder on receiver restart

* move onStreamRestartLocked into BufferBaseParams

---------

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Raja Subramanian <raja.gobi@tutanota.com>
2026-10-22 13:58:04 -07:00
Raja SubramanianandClaude Opus 4.8 22f91ce383 Align SFU RPC caller timeouts with client SDK (#5037)
Pick up protocol#1879 (round-trip 7s, default response 15s) so a
server-initiated PerformRpc tolerates the same ack and response windows
as the client SDK caller.

Also log an RPC response the v1 caller cannot decode (compressed_payload
or data-stream) instead of dropping it, which otherwise surfaces only as
a response timeout.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-21 12:55:11 +05:30
cnderrauber 19b6c60347 Pick dtls server role when ice-lite is enabled (#5033)
Speed up the connection with warp/negotiated data channel
2026-10-20 21:48:45 +08:00
Raja SubramanianandClaude Fable 5.1 65f0c01d15 Echo offered video payload types in single peer connection answers (#5031)
* Echo offered video payload types in single peer connection answers

restrictReceiverCodecsToPublishList pinned server payload types on a video
sender that had no media section yet. The later answer then ignored the
payload types the client offered, and Firefox never decoded the track.
Skip transceivers with an empty mid. Fixes #5030.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* Shorten the mid guard comment

Point at sendersPendingConfig, which already documents the rule.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-19 23:36:43 +05:30
cnderrauber 7f6224d823 Use constant data channel ids in proto (#5029)
Also don't create neogitated channels in migration case.
2026-10-19 19:41:05 +08:00
cnderrauber 4b0c9ece0a Use negotiated data channels in single PC mode (#5017)
* Use negotiated data channels in single PC mode

Bump the protocol version to 18. For protocol >= 18 single peer connection
clients, the server creates its publisher data channels as negotiated with
fixed IDs (100/101/102), so it can send on them without waiting for the
client's DCEP OPEN. Dual PC keeps the in-band channels.

* solve comment
2026-10-18 17:25:59 +08:00
Martin Njalale 7dc6483fdc rtc: remove data track subscriptions when canSubscribe is revoked (#5011) 2026-10-14 20:01:44 +05:30
Ramlal 29d7079164 rtc: apply simulcast rids from SDP for WHIP (one-shot) publishers (#5004)
* rtc: apply simulcast rids from SDP for WHIP (one-shot) publishers

In one-shot signalling mode HandleRemoteDescription set the remote offer
directly and never called OnSetRemoteDescriptionOffer, so
updateRidsFromSDP did not run for WHIP participants. Every simulcast layer
then mapped to spatial layer 0 and all but the first were rejected with
'duplicate layer'.

* rtc: test that a one-shot simulcast offer applies rids to the pending track

* rtc: use h/q rids in one-shot simulcast test, cover single-layer offer

Name the simulcast test layers medium/h and low/q per convention, and add
TestOneShotSingleLayerOfferUnchanged to check a one-shot offer without rids
still yields a single layer with rid "" at spatial layer 0.
2026-10-14 16:30:22 +05:30
Raja SubramanianandClaude Opus 5.5 308554daf1 rtc: release timed out pending tracks at the pending track limit (#4998)
* rtc: reclaim abandoned pending tracks

A pending track is freed only when its first media arrives or the
participant leaves. A client that calls AddTrack and then gives up
before any media flows leaves the slot held for the whole session.
After 20 such slots, every later AddTrack is rejected.

Reclaim these slots when the cap is hit, if no media arrived and the
slot is older than the publish timeout. Keep the supervisor monitor in
step for queued tracks that share a track id. Add a publish_timeout
config value, default 30s.

Fixes: #4970

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* rtc: fix publish_timeout comment to not name an internal constant

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* rtc: release supervisor-timed-out pending tracks at the limit

Replace the createdAt sweep with the supervisor's publish timeout. The
supervisor counts time only while the publisher is connected and the
track is unmuted, and never reports a live track, so a successor queued
behind a live track is not released.

Restart the publish timer when a queued publish reaches the front of
the queue, so it does not inherit the clock of the track ahead of it.

Write the publication error under the write lock, since it is now read
from another goroutine.

Drop the publish_timeout config value.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* rtc: skip stale supervisor timeouts and keep live monitors

TimedOutPublications re-checks the monitor, because the cached error is
cleared only on the next tick. A track that published since is not
reported, so its queued successor is not released.

RemovePublication keeps a monitor that still holds a published track.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* rtc: drop late media for released pending tracks

Media or an offer with no matching pending track falls back to the first
pending track of the same type. After a release, late media for the
released track could take another pending track's identity.

Record the released track's signal and SDP client ids, the 32 most
recent, and drop media or offers for them. An AddTrack that reuses the
client id clears it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* rtc: clear all ids of a released track when it is re-added

A re-add cleared only the signal client id, so a Firefox republish that
keeps the old SDP id had its offer and media dropped. Keep the ids of
each released track together, and clear the whole track when a new
AddTrack reuses its signal or simulcast codec client id. The limit now
counts released tracks, and a track ages out with all of its ids.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-13 16:40:46 +05:30
Raphael Fakhri 09d69dabba rtc: keep update cache eviction order when listing recent disconnects (#4975)
GetRecentlyDisconnectedParticipants walked the participant update cache
with Get, which moves each entry to the front of the LRU. Walking from
newest to oldest left the cache in reverse order, so after a reconnect
the next updates evicted the most recently updated participants first.
In a room where the subscriber has tracked 128 or more participants, a
participant that had just disconnected was then dropped from the cache
and a later reconnect did not report it as disconnected, and the
out-of-order version check was lost for recently updated participants.

Use Peek, which reads an entry without changing its recency.
2026-10-11 01:09:04 +05:30
Raja SubramanianandClaude Opus 5.5 b955b79953 fix: reconcile everything when the reconcile queue overflows (#4948)
A subscribe that found the reconcile queue full was dropped and waited
for the 3 s ticker. Flag the overflow, and have the worker reconcile
all subscriptions after its next queued item.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 11:57:15 +05:30
Raja SubramanianandClaude Opus 5.5 6816ccdd9c fix: keep one subscription when first subscribes race (#4944)
* fix: keep one subscription when first subscribes race

SubscribeToTrack and SubscribeToDataTrack looked up the subscription,
then created one and stored it under a separate lock. Two concurrent
first subscribes, or a subscribe and a settings update, could each
store their own entry. The replaced entry could still add the down
track, which nothing owned after that, and the settings could land on
the replaced entry.

Find or create the subscription and set it as desired under one lock.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: keep early settings and a racing data track resubscribe

A settings or options update before the first subscribe creates an
entry that is not desired. Cleanup removed it on the next pass, so the
subscribe created a new entry without the settings. Keep such an entry
for notFoundTimeout.

After a data track unsubscribe, the entry was deleted even if a
subscribe set it as desired during the removal. Delete it only if it
is still not desired, else reconcile it again.

Cleanup deletes only the entry it checked.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 00:58:49 +05:30
Raja Subramanian 8fac34bdc2 fix: skip DTLS HelloVerifyRequest (#4937) 2026-10-05 17:54:02 +05:30
Raja SubramanianandClaude Opus 5.5 af2959d2f8 fix: use the fast debounce for the first negotiation (#4936)
* fix: use the fast debounce for the first negotiation

A new transport set lastNegotiate to its creation time. A non-forced
Negotiate soon after creation then waited for the 150 ms debounce
instead of 10 ms, although nothing was negotiated yet. Start from the
zero time so that the first offer uses the fast debounce.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test: check the first negotiation condition without a deadline

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test: check that the first offer goes out before a debounced one

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 15:11:42 +05:30
Raja SubramanianandClaude Opus 5.5 44b791ad50 test: give mock participants a connection quality, close test rooms (#4933)
* test: give mock participants a connection quality

A real participant never returns a nil connection quality. The mock
did, which can crash code that does not expect nil.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test: close test rooms when each test ends

Most room tests left their rooms open, so each room's workers kept
running for the rest of the package.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 23:54:14 +05:30
Raja SubramanianandClaude Opus 4.8 c2339b2b85 fix: skip RTX/FEC when picking a fallback video codec (#4930)
selectAlternativeVideoCodec walked the enabled codecs and returned the
first one with a video/ prefix. video/rtx and video/flexfec-03 both have
that prefix, so when the requested codec was not enabled the track could
fall back to RTX. RTX is a retransmission stream, not a real codec, so
the track got registered as video/rtx and subscribers saw a blank tile.

Skip RTX and FEC entries so the fallback is always a valid video codec.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-04 21:20:28 +05:30
Nikolay IvankoandClaude Opus 5 aa08becc6a config: reject unknown send_side_bwe_pacer, warn on unwired fallback (#4804)
send_side_bwe_pacer accepted any string and silently ran no-queue. leaky-bucket
is a defined PacerBehavior with a full LeakyBucket implementation, but the
selection switch in NewPCTransport only handled pass-through and no-queue, so
both leaky-bucket and outright typos fell through to the default with no
diagnostic.

leaky-bucket is deliberately left unwired here. LeakyBucket paces to a bitrate
supplied via Pacer.SetBitrate, and no production code calls SetBitrate or
SetInterval -- Base no-ops both and the only caller in the tree is the
test-only capturingPacer. Wiring it as-is would pace at a static bitrate and
ignore the bandwidth estimate, which is worse than the fallback.

Instead, make the existing behaviour visible: CongestionControlConfig.Validate
rejects unrecognised values at startup, the fallback logs a warning naming the
requested pacer, and config-sample.yaml documents send_side_bwe_pacer and
use_send_side_bwe for the first time.

Refs #4724

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-09-30 16:07:19 +05:30
RaphaelFakhri 2d9bf12581 rtc: measure data blob keys by their content, not their text form (#4923) 2026-09-29 23:17:42 +05:30
Raja Subramanian d91b16238a rtc: accept ICE Completed in ICE-restart connect assertions (#4921) 2026-09-29 08:02:36 +05:30
Martin Njalale 5cd0f37342 rtc: record a room's departure when the participant leaves it (#4913)
* rtc: record a room's departure when the participant leaves it

RemoveParticipant removed the participant from the room, closed it, and
only then recorded leftAt. CloseIfEmpty runs every second, so a tick
landing during the close saw an empty room with a stale or zero
departure time and closed it at once: with no departure recorded it
compares against the room's creation time and empty_timeout, and with an
earlier departure it measures the grace period from that older
timestamp. Either way departure_timeout is skipped.

Record the departure where the participant is removed, under the lock
that already covers it.

* rtc: drop the removal wait from the departure timeout test

The close signal already pins the interleaving, and the room's close
state is decided by the CloseIfEmpty call itself.
2026-09-28 14:06:26 +05:30
Raja SubramanianandClaude Fable 5.1 6cfd19bd9b rtc: split EndSession out of MoveToRoom (#4917)
* rtc: split EndSession out of MoveToRoom

A participant's session in a room can end without the participant
closing: today on a move, and next on a room re-key, where a room ended
by the sweeper while participants were joining restarts under a new room
id with the same participants in it.

EndSession is the room-independent half of MoveToRoom: report the
published tracks unpublished, report the session end, run the caller's
leave under the ending session's telemetry guard, then take a fresh
guard and reset the deferred resolvers for the next session. The guard
matters: ParticipantLeft releases it and the telemetry service refuses
to open a stats worker for a released guard, so without a fresh one the
next session's join would silently get no worker.

MoveToRoom keeps the subscriber teardown and the onClose fan-out, passed
in as the leave.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* rtc: rename EndSession to LeaveSession, add JoinSession

EndSession read as terminal, but the participant survives it and joins
another session, so name the pair for what happens to the participant:
LeaveSession and JoinSession.

JoinSession is the other half that review pointed out was missing from
the API: the caller reports the join, then the published tracks are
reported published again under the new session, pairing the unpublish
LeaveSession reported. Without it the re-publish lived only in the
caller, with nothing in OSS proving the two halves balance.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-28 13:41:30 +05:30
Raja Subramanian 93f5b3eb61 Add a local participant method to get recently disconnected (#4911)
participants.

While migrating, the old node will have the recently disconnected
participants in the migrating out participant's cache and needs to sent
out to the participant from the migrating in node via the new WebSocket.
This method can be used to exchange that data between the two nodes.

Keeping it as `recently` disconnected and leaving the semantics upto how
recent for implementation.
2026-09-26 01:33:38 +05:30
Raja Subramanian 9d8ee5a319 Report disconnected for older disconnected participants. (#4910)
There are reports of disconnected participant update missing -
https://github.com/livekit/client-sdk-js/pull/2096. That results in
clients retaining disconnected participant(s) in its roster.

Two changes
1. Look for participant disconnects a little farther back than last
   signal time as client app might have lost visibility earlier.
2. Look at all participants within the relaxed window above. The update
   cache is LRU. So, in theory can stop when an entry is before the time
   threshold above. But, theoretically it is possible that `updatedAt`
   time and LRU queuing time can flip ordering although very unlikely.

I think we should do one of the following to make it more robust, but
both require client side changes
1. Send full participant list in `ReconnectResponse`. Clients can use
   that to drop participants not in the list.
2. Add a sequence number to all signal responses (maybe signal requests
   as well) and have client send up the last response it saw in
   `SyncState` message. Server can then replay all the messages after
   that. This also requires a message cache on server side.
2026-09-26 00:08:42 +05:30
Pablo Fuente Pérez f7ec53ca73 dynacast: re-send subscribed qualities after every publisher answer (#4907)
* dynacast: re-send subscribed qualities after every publisher answer

The SFU answers publisher offers without the simulcast pause markers
(SetIgnoreRidPauseForRecv), so applying the answer re-enables in the
browser the layers that dynacast had paused. Dynacast only notifies
changes, so those layers stayed on until the subscribed quality of the
track changed again.

Send the publisher its committed subscribed qualities again 1 s after
each answer, once the client has applied it: livekit-client handles the
next signal message without waiting for the answer to be applied.

Fixes https://github.com/livekit/livekit/issues/4906

* dynacast: resend once per burst of publisher answers, only for paused layers

In SPC, every subscription change of a participant goes through a new publisher offer and answer, so answers come in bursts when a room is busy. Each answer scheduled its own resend of the subscribed qualities, sending the same update several times in a row.

Thist change keeps one pending resend per participant instead. It fires 2 s after the last answer, and never later than 8 s after the first answer of the burst, so a steady stream of answers cannot postpone it forever. It also skips the resend for tracks whose committed qualities are all HIGH: with every layer enabled, an answer has nothing paused to re-enable. We can save those messages.
2026-09-25 16:23:40 +05:30
Paul Wells 1ba2cadccd agent: release JobTerminate handler when job ends before registration (#4908)
* agent: release JobTerminate handler when job ends before registration

JobRequest registered the job's JobTerminate handler after AssignJob
returned, so an ended UpdateJobStatus or worker disconnect handled in
between deregistered nothing and the handler leaked. AssignJob also sent
the assignment before recording the job as running, so an immediate
ended update was dropped.

Record the job before sending the assignment, and after registering the
handler recheck under h.mu that the job is still running and its worker
still registered, deregistering otherwise.

Fixes #4901

* replace utils.CloneProto with proto.CloneOf

* agent: wait on worker readiness and poll for released handlers in test
2026-09-25 00:58:22 -07:00