Skip to content

Fix the backup codec publish on native platforms - #1225

Open
ecstra wants to merge 2 commits into
livekit:mainfrom
ecstra:fix/backup-codec-publish
Open

ecstra wants to merge 2 commits into
livekit:mainfrom
ecstra:fix/backup-codec-publish

Conversation

@ecstra

@ecstra ecstra commented Sep 29, 2026

Copy link
Copy Markdown

What

Two fixes to the backup codec publish (LocalParticipant.publishAdditionalCodecForPublication) on native platforms.

  1. Engine.createSimulcastTransceiverSender passed track.kind.toString().toLowerCase() to setPreferredCodec, which is tracktype.video (the TrackType enum's toString), not video. The desktop plugin (common/cpp, Windows and Linux) refuses that kind in getRtpReceiverCapabilities with PlatformException(getRtpSenderCapabilities, getRtpSenderCapabilities() kind is null or empty), and the darwin plugin's mediaTypeFromString falls back to audio, so macOS and iOS built the backup's codec preferences from audio capabilities. The transceiver is always video here, so the literal video is passed, the same as the primary publish already does in local.dart.

  2. The transceiver is added to the publisher peer connection before the server is told about it, and nothing removed it when the publish failed afterwards. The next offer then carried a media section the server had no signalled track for, and the server (getPendingTrack, "if no match on client id, find first one matching type") bound it to whichever video track was pending. In the field that was a screen share being published at the same moment: the camera's frames arrived under the share's publication. Both createSimulcastTransceiverSender and publishAdditionalCodecForPublication now remove the sender and drop the simulcast entry when anything fails before the add track request succeeds, and rethrow.

How it was found

Flutter desktop clients (Windows and macOS, flutter_webrtc 1.6.0, livekit_client 2.11.0, livekit-server v1.13.6): every camera and share publish advertising a backup codec threw the exception above on Windows the moment the server asked for the backup, and a share published under the earlier share's WebRTC track id with "duplicate layer" warnings in the server log. With the kind passed as video the backup publish completes and signals its add track request under the publication's sid.

Tests

No unit test is attached: MockPeerConnection.addTransceiver throws UnimplementedError, so the path is not reachable from the mock harness without extending it. Happy to add one if you would like the mock to grow a transceiver.

createSimulcastTransceiverSender passed the TrackType enum's toString,
'tracktype.video', as the media kind for the capability lookup. The desktop
plugin refuses it and the darwin plugin reads it as audio. The transceiver is
always video, so pass 'video', the same as the primary publish does.

The transceiver was also added to the publisher peer connection before the
server was told about it, and nothing removed it when the publish failed
afterwards, so the next offer carried a media section the server could not
match and it bound the track's frames to whichever video track was pending.
Remove the sender and drop the simulcast entry when anything fails before the
add track request succeeds.
@CLAassistant

CLAassistant commented Sep 29, 2026 •

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

devin-ai-integration[bot]

This comment was marked as resolved.

A republish that raced the failure owns the codec key by then, so a stale
attempt must not erase the newer entry (Devin's review).

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.

2 participants