fix: skip duplicate backup codec publish instead of throwing - #1010
Open
davibittencourtome wants to merge 1 commit into
Open
fix: skip duplicate backup codec publish instead of throwing#1010davibittencourtome wants to merge 1 commit into
davibittencourtome wants to merge 1 commit into
Conversation
A duplicate SubscribedQualityUpdate arriving while the backup codec
publication is still in flight (sender not yet attached) re-enters
addSimulcastTrack and crashed the app with
IllegalStateException("VP8 already added!").
Make addSimulcastTrack idempotent: log and return null for an
already-added codec, and bail out of publishAdditionalCodecForTrack so
no duplicate transceiver or AddTrackRequest is created. Mirrors the JS
SDK behavior. Dedup is per track instance, so a track republished after
a reconnect still publishes its backup codec normally.
Fixes livekit#1000
davibittencourtome
requested review from
MaxHeimbrock,
davidliu and
xianshijing-lk
as code owners
August 25, 2026 18:42
🦋 Changeset detectedLatest commit: 39f3ce4 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #1000
Problem
When the server sends a
SubscribedQualityUpdaterequesting a backup codec that is already added (or whose publication is still in flight), the SDK crashes the app with:The exception is thrown on the SDK's internal coroutine dispatcher (
DefaultDispatcher-worker-N), so there is no way for the application to catch it — it takes down the whole process during a live call.We hit this in production (self-hosted SFU, publisher on VP9 with the default VP8 backup,
PREFER_REGRESSION). Repeated identical updates are expected server behavior by design:MediaTrack.Restart()(ICE restart / session resume) andMediaTrack.SetMuted(false)(unmuting / enabling the camera) both re-emit the current state —dynacastManager.ForceUpdate()explicitly skips the "changed" check. A client therefore has to handle a repeatedSubscribedQualityUpdateidempotently.Root cause
There is a window between the two updates in which the codec entry exists but its sender is not yet attached:
setPublishingCodecsfinds no entry for VP8 → returns it as a new codec →publishAdditionalCodecForTrackcallsaddSimulcastTrack, which inserts the entry, and then launches a coroutine to create the transceiver.simulcastTrackInfo.senderis only assigned inside that coroutine, after the signaling round-trip.setPublishingCodecsseessimulcastInfo?.sender == nulland treats the codec as new again →addSimulcastTrackfinds the key already present and throws.The check at
setPublishingCodecsconflates "never started" with "started, still in flight".Fix
Make
addSimulcastTrackidempotent: if the codec is already present, log a warning and returnnullinstead of throwing;publishAdditionalCodecForTracktreatsnullas a no-op and returns, so no duplicate transceiver is created and no duplicateAddTrackRequestis sent.This mirrors the JS SDK, where
LocalVideoTrack.addSimulcastTracklogs and returnsundefinedfor an already-added codec and the caller bails out.Note the dedup is intentionally per track instance (the
simulcastCodecsmap lives on theLocalVideoTrack): a track republished after a reconnect is a new track/sid and must still go through backup-codec publication normally. Session-level dedup would break that case.Testing
addSimulcastTrackForAlreadyAddedCodecIsNoOp— callingaddSimulcastTracktwice for the same codec no longer throws; the second call returnsnull.duplicateSubscribedQualityUpdateDoesNotRepublishBackupCodec— receiving the sameSubscribedQualityUpdatetwice keeps a single backup transceiver (2 transceivers total).LocalParticipantMockE2ETestsuite passes.