rtc: apply simulcast rids from SDP for WHIP (one-shot) publishers (#5004)

* rtc: apply simulcast rids from SDP for WHIP (one-shot) publishers

In one-shot signalling mode HandleRemoteDescription set the remote offer
directly and never called OnSetRemoteDescriptionOffer, so
updateRidsFromSDP did not run for WHIP participants. Every simulcast layer
then mapped to spatial layer 0 and all but the first were rejected with
'duplicate layer'.

* rtc: test that a one-shot simulcast offer applies rids to the pending track

* rtc: use h/q rids in one-shot simulcast test, cover single-layer offer

Name the simulcast test layers medium/h and low/q per convention, and add
TestOneShotSingleLayerOfferUnchanged to check a one-shot offer without rids
still yields a single layer with rid "" at spatial layer 0.
This commit is contained in:
Ramlal
2026-10-14 16:30:22 +05:30
committed by GitHub
parent d8aaa45dd1
commit 29d7079164
2 changed files with 107 additions and 19 deletions
+104 -19
View File
@@ -824,6 +824,89 @@ func TestPreferMediaCodecForPublisher(t *testing.T) {
}
}
func TestOneShotSimulcastOfferAppliesRids(t *testing.T) {
participant := newParticipantForTestWithOpts("whip", &participantOpts{
publisher: true,
oneShot: true,
})
participant.SetMigrateState(types.MigrateStateComplete)
pc, err := webrtc.NewPeerConnection(webrtc.Configuration{})
require.NoError(t, err)
defer pc.Close()
codec := webrtc.RTPCodecCapability{MimeType: mime.MimeTypeVP8.String()}
medium, err := webrtc.NewTrackLocalStaticRTP(codec, "video", "stream", webrtc.WithRTPStreamID("h"))
require.NoError(t, err)
low, err := webrtc.NewTrackLocalStaticRTP(codec, "video", "stream", webrtc.WithRTPStreamID("q"))
require.NoError(t, err)
sender, err := pc.AddTrack(medium)
require.NoError(t, err)
require.NoError(t, sender.AddEncoding(low))
offer, err := pc.CreateOffer(nil)
require.NoError(t, err)
require.NoError(t, pc.SetLocalDescription(offer))
require.Contains(t, offer.SDP, "a=simulcast:send h;q")
require.NoError(t, participant.HandleOffer(&livekit.SessionDescription{
Type: webrtc.SDPTypeOffer.String(),
Sdp: offer.SDP,
}))
participant.pendingTracksLock.RLock()
pti := participant.pendingTracks["video"]
participant.pendingTracksLock.RUnlock()
require.NotNil(t, pti)
layers := pti.trackInfos[0].Codecs[0].Layers
require.Len(t, layers, 2)
require.Equal(t, livekit.VideoQuality_LOW, layers[0].Quality)
require.Equal(t, "q", layers[0].Rid)
require.EqualValues(t, 0, layers[0].SpatialLayer)
require.Equal(t, livekit.VideoQuality_MEDIUM, layers[1].Quality)
require.Equal(t, "h", layers[1].Rid)
require.EqualValues(t, 1, layers[1].SpatialLayer)
}
func TestOneShotSingleLayerOfferUnchanged(t *testing.T) {
participant := newParticipantForTestWithOpts("whip", &participantOpts{
publisher: true,
oneShot: true,
})
participant.SetMigrateState(types.MigrateStateComplete)
pc, err := webrtc.NewPeerConnection(webrtc.Configuration{})
require.NoError(t, err)
defer pc.Close()
track, err := webrtc.NewTrackLocalStaticRTP(webrtc.RTPCodecCapability{MimeType: mime.MimeTypeVP8.String()}, "video", "stream")
require.NoError(t, err)
_, err = pc.AddTransceiverFromTrack(track, webrtc.RTPTransceiverInit{Direction: webrtc.RTPTransceiverDirectionSendonly})
require.NoError(t, err)
offer, err := pc.CreateOffer(nil)
require.NoError(t, err)
require.NoError(t, pc.SetLocalDescription(offer))
require.NotContains(t, offer.SDP, "a=simulcast")
require.NotContains(t, offer.SDP, "a=rid")
require.NoError(t, participant.HandleOffer(&livekit.SessionDescription{
Type: webrtc.SDPTypeOffer.String(),
Sdp: offer.SDP,
}))
participant.pendingTracksLock.RLock()
pti := participant.pendingTracks["video"]
participant.pendingTracksLock.RUnlock()
require.NotNil(t, pti)
layers := pti.trackInfos[0].Codecs[0].Layers
require.Len(t, layers, 1)
require.Equal(t, "", layers[0].Rid)
require.EqualValues(t, 0, layers[0].SpatialLayer)
}
func TestPreferAudioCodecForRed(t *testing.T) {
participant := newParticipantForTestWithOpts("123", &participantOpts{
publisher: true,
@@ -969,6 +1052,7 @@ type participantOpts struct {
clientConf *livekit.ClientConfiguration
clientInfo *livekit.ClientInfo
migration bool
oneShot bool
}
func newParticipantForTestWithOpts(identity livekit.ParticipantIdentity, opts *participantOpts) *ParticipantImpl {
@@ -1005,25 +1089,26 @@ func newParticipantForTestWithOpts(identity livekit.ParticipantIdentity, opts *p
}
sid := livekit.ParticipantID(guid.New(utils.ParticipantPrefix))
p, _ := NewParticipant(ParticipantParams{
SID: sid,
Identity: identity,
Config: rtcConf,
Sink: &routingfakes.FakeMessageSink{},
ProtocolVersion: opts.protocolVersion,
SessionStartTime: time.Now(),
PLIThrottleConfig: conf.RTC.PLIThrottle,
Grants: grants,
PublishEnabledCodecs: enabledCodecs,
SubscribeEnabledCodecs: enabledCodecs,
ClientConf: opts.clientConf,
ClientInfo: ClientInfo{ClientInfo: opts.clientInfo},
Logger: LoggerWithParticipant(logger.GetLogger(), identity, sid, false),
Reporter: roomobs.NewNoopParticipantSessionReporter(),
TelemetryListener: &typesfakes.FakeParticipantTelemetryListener{},
VersionGenerator: utils.NewDefaultTimedVersionGenerator(),
ParticipantListener: &typesfakes.FakeLocalParticipantListener{},
ParticipantHelper: &typesfakes.FakeLocalParticipantHelper{},
Migration: opts.migration,
SID: sid,
Identity: identity,
Config: rtcConf,
Sink: &routingfakes.FakeMessageSink{},
ProtocolVersion: opts.protocolVersion,
SessionStartTime: time.Now(),
PLIThrottleConfig: conf.RTC.PLIThrottle,
Grants: grants,
PublishEnabledCodecs: enabledCodecs,
SubscribeEnabledCodecs: enabledCodecs,
ClientConf: opts.clientConf,
ClientInfo: ClientInfo{ClientInfo: opts.clientInfo},
Logger: LoggerWithParticipant(logger.GetLogger(), identity, sid, false),
Reporter: roomobs.NewNoopParticipantSessionReporter(),
TelemetryListener: &typesfakes.FakeParticipantTelemetryListener{},
VersionGenerator: utils.NewDefaultTimedVersionGenerator(),
ParticipantListener: &typesfakes.FakeLocalParticipantListener{},
ParticipantHelper: &typesfakes.FakeLocalParticipantHelper{},
Migration: opts.migration,
UseOneShotSignallingMode: opts.oneShot,
})
p.isPublisher.Store(opts.publisher)
p.updateState(livekit.ParticipantInfo_ACTIVE)
+3
View File
@@ -1709,6 +1709,9 @@ func (t *PCTransport) HandleRemoteDescription(sd webrtc.SessionDescription, remo
return err
}
t.setRemoteOfferParsed(sd.Type, parsed)
if sd.Type == webrtc.SDPTypeOffer {
t.params.Handler.OnSetRemoteDescriptionOffer()
}
rtxRepairs := nonSimulcastRTXRepairsFromSDP(parsed, t.params.Logger)
if len(rtxRepairs) > 0 {