Skip to content

Refresh subscribed codecs after publisher answer - #1037

Open
lukasIO wants to merge 2 commits into
mainfrom
fix/refresh-subscribed-codecs-after-publisher-answer
Open

lukasIO wants to merge 2 commits into
mainfrom
fix/refresh-subscribed-codecs-after-publisher-answer

Conversation

@lukasIO

@lukasIO lukasIO commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor

keep dynacast-paused simulcast layers paused across publisher renegotiations (publish/unpublish, codec change, ICE restart), instead of silently uploading them until the next subscribed-quality change.

Android publishers keep dynacast-paused simulcast layers paused across publisher renegotiations (publish/unpublish, codec change, ICE restart), instead of silently uploading them until the next subscribed-quality change.
@changeset-bot

changeset-bot Bot commented Oct 7, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: ae9fca5

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

This PR includes changesets to release 1 package
Name Type
client-sdk-android 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

@lukasIO lukasIO changed the title Refresh subscribed codecs after publisher answer (Android) Refresh subscribed codecs after publisher answer Oct 7, 2026

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 2 potential issues.

Devin Review

Comment on lines +440 to +443
internal fun refreshSubscribedCodecs() {
val codecs = subscribedCodecs ?: return
setPublishingCodecs(codecs)
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Quality-only pauses vanish after renegotiation

After a quality-only update, refreshSubscribedCodecs has no cached codecs to replay. The publisher answer re-enables paused layers, leaving them uploading until another quality update.

Learn more

A subscribed-quality update can contain a flat list of qualities without any subscribed codecs. handleSubscribedQualityUpdate applies that list directly with setPublishingLayers. The new replay only remembers subscribedCodecs, so these paused layers have no state to restore after a publisher answer re-enables them.

Example: A server sends an update for camera A with subscribedCodecs=[] and subscribedQualities=[HIGH disabled, LOW enabled]. The high layer pauses; an ICE-restart answer re-enables it, and refresh returns without pausing it again.

Recommended fix: Store the last flat quality update as well as the codec update and replay whichever form most recently controlled the primary sender. Clear both on unpublish or republish, and cover quality-only updates in the renegotiation test.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

when (val outcome = publisher?.setRemoteDescription(sessionDescription, offerId).nullSafe()) {
is Either.Left -> {
// do nothing.
listener?.onRequestSubscribedCodecRefresh()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Sender refresh bypasses the RTC thread

On a publisher answer, onRequestSubscribedCodecRefresh accesses WebRTC sender parameters from the engine's I/O coroutine. This can race with RTC-thread negotiation and leave paused layers active.

Learn more

The publisher answer is applied by setRemoteDescription on the RTC thread, but the suspended caller resumes on the engine's I/O dispatcher. The new synchronous callback reaches setPublishingLayersForSender, which reads and writes RtpSender.parameters. Sender operations belong on the RTC thread according to the repository's WebRTC threading rule; otherwise they can overlap further negotiation work.

Example: An answer for an ICE restart arrives while another publisher negotiation starts. The refresh accesses the sender on the I/O worker while the other negotiation uses that sender on the RTC executor.

Recommended fix: Schedule the refresh's WebRTC sender operations through launchBlockingOnRTCThread or executeBlockingOnRTCThread with the room's RTC token. Keep the cached codec state synchronized with signaling updates if the callback is dispatched asynchronously.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

@github-actions

github-actions Bot commented Oct 7, 2026

Copy link
Copy Markdown
Contributor

Diffuse output:

OLD: diffuse-source-file
NEW: livekit-android-sdk-release.aar

 AAR      │ old      │ new      │ diff   
──────────┼──────────┼──────────┼────────
      jar │    3 MiB │    3 MiB │ +610 B 
 manifest │  1.5 KiB │  1.5 KiB │    0 B 
 lint-jar │ 12.7 KiB │ 12.7 KiB │    0 B 
    other │  2.4 KiB │  2.4 KiB │    0 B 
──────────┼──────────┼──────────┼────────
    total │    3 MiB │    3 MiB │ +610 B 

 JAR     │ old   │ new   │ diff       
─────────┼───────┼───────┼────────────
 classes │  1706 │  1706 │  0 (+0 -0) 
 methods │ 21983 │ 21988 │ +5 (+5 -0) 
  fields │  5703 │  5703 │  0 (+0 -0)
AAR
 size  │ diff   │ path          
───────┼────────┼───────────────
 3 MiB │ +610 B │ ∆ classes.jar 
───────┼────────┼───────────────
 3 MiB │ +610 B │ (total)
JAR
METHODS:

   old   │ new   │ diff       
  ───────┼───────┼────────────
   21983 │ 21988 │ +5 (+5 -0) 
  
  + io.livekit.android.room.RTCEngine_Listener onRequestSubscribedCodecRefresh()
  + io.livekit.android.room.RTCEngine_Listener_DefaultImpls onRequestSubscribedCodecRefresh(RTCEngine_Listener)
  + io.livekit.android.room.Room onRequestSubscribedCodecRefresh()
  + io.livekit.android.room.participant.LocalParticipant handleSubscribedCodecRefresh_livekit_android_sdk_release()
  + io.livekit.android.room.track.LocalVideoTrack refreshSubscribedCodecs_livekit_android_sdk_release()

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.

1 participant