Skip to content

Re-apply subscribed codecs after a publisher answer - #1238

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

lukasIO wants to merge 1 commit into
mainfrom
fix/refresh-subscribed-codecs-after-publisher-answer

Conversation

@lukasIO

@lukasIO lukasIO commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

The SFU stopped echoing the RFC 8853 pause markers, so a publisher answer
re-enables every simulcast layer. Dynacast only signals a change, so the
pause was never re-sent and the layers it had paused kept uploading until
the subscribed quality changed again. The engine now emits a refresh once
the answer is applied, and the room re-applies each local video track's
last subscribed codecs to its sender.

Re-applying a pause forces the write, because the answer re-enabled the
layers without going through setParameters and a conditional write would
see no difference against a cached view of the sender. Only codecs that
actually have a paused layer are forced, so a renegotiation costs no
native call in the steady state and a backup codec's stored encodings are
not stamped over its sender. Dynacast updates keep their behaviour.

Also moves the shared media fakes into test/mock/.

Ports livekit/client-sdk-js#2121.

The SFU stopped echoing the RFC 8853 pause markers, so a publisher answer
re-enables every simulcast layer. Dynacast only signals a change, so the
pause was never re-sent and the layers it had paused kept uploading until
the subscribed quality changed again. The engine now emits a refresh once
the answer is applied, and the room re-applies each local video track's
last subscribed codecs to its sender.

Re-applying a pause forces the write, because the answer re-enabled the
layers without going through setParameters and a conditional write would
see no difference against a cached view of the sender. Only codecs that
actually have a paused layer are forced, so a renegotiation costs no
native call in the steady state and a backup codec's stored encodings are
not stamped over its sender. Dynacast updates keep their behaviour.

Also moves the shared media fakes into test/mock/.

Ports client-sdk-js#2121.

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

Copy link
Copy Markdown

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.

1 flag not posted on this PR by your GitHub settings — view it in Devin Review. (Configure)

Devin Review

Comment on lines +339 to +341
if (subscribedCodecs.isEmpty) {
return;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔴 Legacy dynacast pauses stay re-enabled

After a legacy subscribedQualities update, refreshSubscribedCodecs has no codecs to replay. The legacy update handler never records them, so a publisher answer leaves paused layers uploading.

Learn more

The room supports two shapes of dynacast update: a list of subscribed codecs and the older list of subscribed qualities. The latter goes straight to setPublishingLayers in the legacy update handler, without storing replayable state. After an answer re-enables paused layers, refreshSubscribedCodecs returns because its codec list is empty. Those layers keep uploading until the SFU changes the subscription again.

Example: A server sends only subscribedQualities asking a camera to disable LOW. An audio-track publication triggers a publisher renegotiation; the resulting answer re-enables LOW, and the refresh cannot restore the pause.

Recommended fix: Save the last legacy quality update per local video track and replay it through setPublishingLayers with a forced write after publisher answers. Clear this state when the corresponding publish session ends.

Devin Review


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

Comment on lines +338 to +343
Future<void> refreshSubscribedCodecs() async {
if (subscribedCodecs.isEmpty) {
return;
}
try {
await setPublishingCodecs(subscribedCodecs, this, force: true);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔴 Old pauses affect republished video

When a video track is republished, refreshSubscribedCodecs replays subscriptions from its previous publication. Unpublish clears senders but not subscribedCodecs, so the new video can remain paused until another update.

Learn more

A LocalVideoTrack object can be reused across publications, and a full reconnect also republishes the same object. Unpublish and rePublishAllTracks clear its sender-related simulcast state, but not its last subscribed codecs. The refresh now treats that old list as instructions for the new sender after its answer arrives. If the previous publication had all layers disabled, the new publication can start with all layers disabled too, until a new subscription update is processed.

Example: A camera's last subscription disables LOW and MEDIUM; the app unpublishes and republishes the same camera track. Its next publisher answer triggers a forced write of the previous publication's LOW/MEDIUM pause to the new sender.

Recommended fix: Reset subscription state when a track starts a new publish session, including unpublish and full reconnect, rather than carrying the old session's codec list into the new sender. Ensure a current-session SFU update can be applied before or after the answer without being overwritten.

Devin Review


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

This branch has not been deployed

No deployments
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