Skip to content

Apply degradation preference to the backup codec sender - #2040

Merged
xianshijing-lk merged 2 commits into
mainfrom
sxian/degradation-preference-backup-codec
Aug 10, 2026
Merged

Apply degradation preference to the backup codec sender#2040
xianshijing-lk merged 2 commits into
mainfrom
sxian/degradation-preference-backup-codec

Conversation

@xianshijing-lk

@xianshijing-lk xianshijing-lk commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Degradation preference is a property of the sender, not of the track — it is a top-level field on RtpParameters, not per-encoding. A backup codec publishes over its own transceiver and therefore its own sender, so it needs the preference applied separately.

setDegradationPreference only ever configured this.sender (the primary). The backup sender, registered via setSimulcastTrackSender from createSimulcastTransceiverSender, was never touched, so it fell back to the browser's implicit resolution and could adapt along a different axis than the primary encoder.

Concretely, with a VP9/AV1 primary and a VP8 backup:

  • an application-supplied degradationPreference reached the primary encoder only
  • the source-based default from getDefaultDegradationPreference (camera → maintain-framerate, screen share → maintain-resolution, other → balanced) reached the primary encoder only

Changes

  • Extract the sender-level write into applyDegradationPreference(sender).
  • Apply it in setSimulcastTrackSender, so a backup sender gets the preference the primary already resolved to.
  • Make setDegradationPreference fan out to every sender, so a change after the backup is published keeps the two in sync.

Using the track's stored resolved preference (rather than re-deriving in publishAdditionalCodecForTrack) means the two encoders can't disagree.

Note simulcast itself is unaffected — all simulcast encodings live under one sender and already share its preference. Only the backup codec is a separate sender.

Tests

Three tests in LocalVideoTrack.test.ts covering the primary sender, the backup sender receiving the resolved preference, and a later preference change reaching both. Verified they fail without the fix.

Full suite matches the baseline on main (same 3 pre-existing data-stream failures, +3 passing). eslint, prettier --check and tsc --noEmit clean for the touched files.

Cross-SDK

This is the JS half of aligning the SDKs on the behavior landed in client-sdk-android (livekit/client-sdk-android#991). The Rust SDK already resolves the same source-based defaults and has no backup-codec publish path. Flutter (livekit/client-sdk-flutter#1155) and Swift (livekit/client-sdk-swift#1083) follow separately.

🤖 Generated with Claude Code

Degradation preference is a sender-level property, and a backup codec
publishes over its own sender. Only the primary sender was configured, so
the backup encoder resolved a preference implicitly from the browser and
could adapt along a different axis than the primary.

Apply the resolved preference when a simulcast (backup codec) sender is
registered, and keep every sender in sync when the preference changes
after publish.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@changeset-bot

changeset-bot Bot commented Aug 7, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 4560812

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
livekit-client Patch

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

devin-ai-integration[bot]

This comment was marked as resolved.

@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

size-limit report 📦

Path Size
dist/livekit-client.esm.mjs 104.88 KB (+0.03% 🔺)
dist/livekit-client.umd.js 113.95 KB (-0.03% 🔽)

Comment thread src/room/track/LocalVideoTrack.ts Outdated
hiroshihorie added a commit to livekit/client-sdk-flutter that referenced this pull request Aug 7, 2026
…ackup codec (#1155)

Aligns Flutter with the behavior landed in client-sdk-android
(livekit/client-sdk-android#991). Two related changes.

## Source-based defaults

Previously every video track fell back to `maintainResolution`, and the
preference was only applied to camera and screen share tracks at all:

```dart
if ([TrackSource.camera, TrackSource.screenShareVideo].contains(track.source)) {
  final degradationPreference = options.degradationPreference ?? DegradationPreference.maintainResolution;
  await track.setDegradationPreference(degradationPreference);
}
```

Now `getDefaultDegradationPreference(source)` resolves camera →
`maintainFramerate` (smoother video for real-time communication), screen
share → `maintainResolution` (clarity matters for text/UI), other →
`balanced`, and it is applied to every video sender. Custom sources
previously got whatever WebRTC derived implicitly from the native
source; `balanced` is the preference the WebRTC spec mandates as the
default and is the honest choice when the application declined to
declare a motion-vs-detail intent.

An explicitly set `degradationPreference` still wins in all cases — the
default only fills a null.

## Backup codec sender

Degradation preference is a property of the **sender**, not of the track
— a top-level field on `RtpParameters`, not per-encoding.
`publishAdditionalCodecForPublication` adds a second transceiver and
therefore a second sender, which was never configured, so the backup
encoder resolved a preference implicitly and could adapt along a
different axis than the primary.

Both senders sink from the same video source, so a diverging backup does
not just degrade itself — its restriction is merged onto the shared
source and affects the primary too.

`setDegradationPreference` now stores the resolved preference and fans
out to every sender, and `publishAdditionalCodecForPublication` applies
it to the backup sender once created. Using the track's stored resolved
value means the two encoders cannot disagree.

Note simulcast is unaffected — all simulcast encodings live under one
sender and already share its preference. Only the backup codec is a
separate sender.

## Tests

`test/options/degradation_preference_test.dart` covers the three source
mappings. Full suite passes (379 tests), `flutter analyze lib/ test/`
clean, `dart format` clean at the repo's 120-column width.

## Cross-SDK

client-sdk-js gets the backup-sender half in livekit/client-sdk-js#2040
(its source-based defaults already matched). The Rust SDK already
resolves the same defaults and has no backup-codec publish path. Swift
follows separately.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: Hiroshi Horie <548776+hiroshihorie@users.noreply.github.com>
setParameters returns a promise; not awaiting it meant failures were
invisible and the write could still be in flight when the caller returned.
Await it, and await applyDegradationPreference at both call sites, which
makes setSimulcastTrackSender async.

Keep the senders sequential rather than Promise.all: setParameters is only
valid against the parameters most recently returned by getParameters, which
is the same hazard senderLock exists for in this file. There are at most a
couple of senders, so there is nothing to gain from overlapping them.

The test mock now resolves setParameters on a later task, like the real one,
so a missing await is observable.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@xianshijing-lk

Copy link
Copy Markdown
Contributor Author

tested it with some local hack for the backup codec with safari and chrome, things work. I am landing this PR.

@xianshijing-lk
xianshijing-lk merged commit d2ec3e3 into main Aug 10, 2026
6 checks passed
@xianshijing-lk
xianshijing-lk deleted the sxian/degradation-preference-backup-codec branch August 10, 2026 14:57
@github-actions github-actions Bot mentioned this pull request Aug 10, 2026
xianshijing-lk added a commit to livekit/client-sdk-swift that referenced this pull request Aug 10, 2026
…ng the backup codec (#1083)

Aligns Swift with the behavior landed in client-sdk-android
(livekit/client-sdk-android#991). Two related changes.

## Source-based defaults

`VideoPublishOptions.degradationPreference` already defaults to `.auto`,
documented as "The SDK will decide which preference is suitable" — but
`.auto` collapsed to `.maintainResolution` for every video track:

```swift
let degradationPreference = publishOptions.degradationPreference.toRTCType() ?? .maintainResolution
```

`.auto` now resolves from the track source: camera →
`.maintainFramerate` (smoother video for real-time communication),
screen share → `.maintainResolution` (clarity matters for text/UI),
other → `.balanced`. `balanced` is the preference the WebRTC spec
mandates as the default and is the honest choice when the application
declined to declare a motion-vs-detail intent.

No API change — `.auto` already existed with exactly these semantics. An
explicitly set preference still wins; the source default only applies to
`.auto`.

## Backup codec sender

Degradation preference is a property of the **sender**, not of the track
— a top-level field on `RtpParameters`, not per-encoding.
`publish(additionalVideoCodec:for:)` adds a second transceiver and
therefore a second sender, which was never configured, so the backup
encoder resolved a preference implicitly from the native source and
could adapt along a different axis than the primary.

Both senders sink from the same video source, so a diverging backup does
not just degrade itself — its restriction is merged onto the shared
source and affects the primary too.

Both publish paths now resolve the preference the same way and share
`LKRTCRtpSender.set(degradationPreference:)`, extracted from the
existing inline parameter write (the "changing params directly doesn't
work" dance is preserved).

Note simulcast is unaffected — all simulcast encodings live under one
sender and already share its preference. Only the backup codec is a
separate sender.

## Tests

`Tests/LiveKitCoreTests/DegradationPreferenceTests.swift` covers the
three source mappings for `.auto`, explicit preferences winning over the
source default, and the `.auto` default on `VideoPublishOptions`.

`swift build` clean, the 4 new tests pass, and the `LiveKitCoreTests`
failure count is unchanged from `main` (132 issues on both, all from
integration tests that need a local LiveKit server). `swiftformat
--lint` clean on all touched files.

## Cross-SDK

client-sdk-js gets the backup-sender half in livekit/client-sdk-js#2040
(its source-based defaults already matched); client-sdk-flutter gets
both in livekit/client-sdk-flutter#1155. The Rust SDK already resolves
the same defaults and has no backup-codec publish path.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants