From dadfcb86b8a4c0d4b79130d6b1011766ef5c8fcf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Fuente=20P=C3=A9rez?= Date: Thu, 22 Oct 2026 10:33:59 +0000 Subject: [PATCH 1/2] Declare one spatial layer per stream for single-track video publishes --- localparticipant.go | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/localparticipant.go b/localparticipant.go index f55f61cb..d2ae668c 100644 --- a/localparticipant.go +++ b/localparticipant.go @@ -162,16 +162,22 @@ func (p *LocalParticipant) prepareTrackPublication(track webrtc.TrackLocal, opts } if primaryCodec.MimeType != "" && (kind == TrackKindVideo || withBackupCodec) { + layerMode := livekit.VideoLayer_MODE_UNUSED + if kind == TrackKindVideo { + layerMode = livekit.VideoLayer_ONE_SPATIAL_LAYER_PER_STREAM + } req.SimulcastCodecs = []*livekit.SimulcastCodec{ { - Codec: primaryCodec.MimeType, - Cid: track.ID(), + Codec: primaryCodec.MimeType, + Cid: track.ID(), + VideoLayerMode: layerMode, }, } if withBackupCodec { req.SimulcastCodecs = append(req.SimulcastCodecs, &livekit.SimulcastCodec{ - Codec: pubOptions.backupCodecTrack.Codec().MimeType, - Cid: pubOptions.backupCodecTrack.ID(), + Codec: pubOptions.backupCodecTrack.Codec().MimeType, + Cid: pubOptions.backupCodecTrack.ID(), + VideoLayerMode: layerMode, }) pub.setBackupCodecTrack(pubOptions.backupCodecTrack) } From 4dad01a533bafb0e50d7c0349f00f9da54a903de Mon Sep 17 00:00:00 2001 From: pabloFuente Date: Thu, 22 Oct 2026 12:59:50 +0200 Subject: [PATCH 2/2] Limit the one-spatial-layer declaration to sample tracks The previous commit set ONE_SPATIAL_LAYER_PER_STREAM on the codec entries of every single-track video publish. But publishTrack accepts any webrtc.TrackLocal, and a caller-owned track may relay SVC VP9 or AV1. Declaring one layer for it makes the server stop treating it as SVC, which is a change from v2.19.0, where the server inferred the mode from the codec. --- localparticipant.go | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/localparticipant.go b/localparticipant.go index d2ae668c..022a9e83 100644 --- a/localparticipant.go +++ b/localparticipant.go @@ -162,22 +162,18 @@ func (p *LocalParticipant) prepareTrackPublication(track webrtc.TrackLocal, opts } if primaryCodec.MimeType != "" && (kind == TrackKindVideo || withBackupCodec) { - layerMode := livekit.VideoLayer_MODE_UNUSED - if kind == TrackKindVideo { - layerMode = livekit.VideoLayer_ONE_SPATIAL_LAYER_PER_STREAM - } req.SimulcastCodecs = []*livekit.SimulcastCodec{ { Codec: primaryCodec.MimeType, Cid: track.ID(), - VideoLayerMode: layerMode, + VideoLayerMode: sampleTrackLayerMode(kind, track), }, } if withBackupCodec { req.SimulcastCodecs = append(req.SimulcastCodecs, &livekit.SimulcastCodec{ Codec: pubOptions.backupCodecTrack.Codec().MimeType, Cid: pubOptions.backupCodecTrack.ID(), - VideoLayerMode: layerMode, + VideoLayerMode: sampleTrackLayerMode(kind, pubOptions.backupCodecTrack), }) pub.setBackupCodecTrack(pubOptions.backupCodecTrack) } @@ -187,6 +183,13 @@ func (p *LocalParticipant) prepareTrackPublication(track webrtc.TrackLocal, opts return pub, req, nil } +func sampleTrackLayerMode(kind TrackKind, track webrtc.TrackLocal) livekit.VideoLayer_Mode { + if _, ok := track.(*LocalTrack); ok && kind == TrackKindVideo { + return livekit.VideoLayer_ONE_SPATIAL_LAYER_PER_STREAM + } + return livekit.VideoLayer_MODE_UNUSED +} + func (p *LocalParticipant) notifyTrackPublished(pub *LocalTrackPublication) { p.Callback.OnLocalTrackPublished(pub, p) p.roomCallback.OnLocalTrackPublished(pub, p)