diff --git a/pkg/config/config.go b/pkg/config/config.go index 798f11a8c..42de28851 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -139,7 +139,9 @@ type CongestionControlProbeConfig struct { type CongestionControlChannelObserverConfig struct { EstimateRequiredSamples int `yaml:"estimate_required_samples,omitempty"` + EstimateRequiredSamplesMin int `yaml:"estimate_required_samples_min,omitempty"` EstimateDownwardTrendThreshold float64 `yaml:"estimate_downward_trend_threshold,omitempty"` + EstimateDownwardTrendMaxWait time.Duration `yaml:"estimate_downward_trend_max_wait,omitempty"` EstimateValidityWindow time.Duration `yaml:"estimate_validity_window,omitempty"` NackMinPPS uint32 `yaml:"nack_min_pps,omitempty"` NackWindowMinDuration time.Duration `yaml:"nack_window_min_duration,omitempty"` @@ -331,7 +333,9 @@ var DefaultConfig = Config{ }, ChannelObserverProbeConfig: CongestionControlChannelObserverConfig{ EstimateRequiredSamples: 3, + EstimateRequiredSamplesMin: 3, EstimateDownwardTrendThreshold: 0.0, + EstimateDownwardTrendMaxWait: 5 * time.Second, EstimateValidityWindow: 10 * time.Second, NackMinPPS: 0, NackWindowMinDuration: 500 * time.Millisecond, @@ -339,12 +343,14 @@ var DefaultConfig = Config{ NackRatioThreshold: 0.04, }, ChannelObserverNonProbeConfig: CongestionControlChannelObserverConfig{ - EstimateRequiredSamples: 8, - EstimateDownwardTrendThreshold: -0.5, + EstimateRequiredSamples: 12, + EstimateRequiredSamplesMin: 8, + EstimateDownwardTrendThreshold: -0.6, + EstimateDownwardTrendMaxWait: 5 * time.Second, EstimateValidityWindow: 10 * time.Second, NackMinPPS: 50, - NackWindowMinDuration: 1 * time.Second, - NackWindowMaxDuration: 2 * time.Second, + NackWindowMinDuration: 2 * time.Second, + NackWindowMaxDuration: 3 * time.Second, NackRatioThreshold: 0.08, }, }, diff --git a/pkg/rtc/participant.go b/pkg/rtc/participant.go index aea4c9e94..cd0d61e9d 100644 --- a/pkg/rtc/participant.go +++ b/pkg/rtc/participant.go @@ -863,7 +863,9 @@ func (p *ParticipantImpl) ICERestart(iceConfig *livekit.ICEConfig) { t.(types.LocalMediaTrack).Restart() } - p.TransportManager.ICERestart(iceConfig) + if err := p.TransportManager.ICERestart(iceConfig); err != nil { + p.IssueFullReconnect(types.ParticipantCloseReasonNegotiateFailed) + } } func (p *ParticipantImpl) OnICEConfigChanged(f func(participant types.LocalParticipant, iceConfig *livekit.ICEConfig)) { diff --git a/pkg/rtc/transport.go b/pkg/rtc/transport.go index 0eaabd1ed..2c565f194 100644 --- a/pkg/rtc/transport.go +++ b/pkg/rtc/transport.go @@ -72,13 +72,14 @@ const ( ) var ( - ErrIceRestartWithoutLocalSDP = errors.New("ICE restart without local SDP settled") - ErrNoTransceiver = errors.New("no transceiver") - ErrNoSender = errors.New("no sender") - ErrNoICECandidateHandler = errors.New("no ICE candidate handler") - ErrNoOfferHandler = errors.New("no offer handler") - ErrNoAnswerHandler = errors.New("no answer handler") - ErrMidNotFound = errors.New("mid not found") + ErrIceRestartWithoutLocalSDP = errors.New("ICE restart without local SDP settled") + ErrIceRestartOnClosedPeerConnection = errors.New("ICE restart on closed peer connection") + ErrNoTransceiver = errors.New("no transceiver") + ErrNoSender = errors.New("no sender") + ErrNoICECandidateHandler = errors.New("no ICE candidate handler") + ErrNoOfferHandler = errors.New("no offer handler") + ErrNoAnswerHandler = errors.New("no answer handler") + ErrMidNotFound = errors.New("mid not found") ) // ------------------------------------------------------------------------- @@ -1103,10 +1104,16 @@ func (t *PCTransport) Negotiate(force bool) { } } -func (t *PCTransport) ICERestart() { +func (t *PCTransport) ICERestart() error { + if t.pc.ConnectionState() == webrtc.PeerConnectionStateClosed { + t.params.Logger.Warnw("trying to restart ICE on closed peer connection", nil) + return ErrIceRestartOnClosedPeerConnection + } + t.postEvent(event{ signal: signalICERestart, }) + return nil } func (t *PCTransport) ResetShortConnOnICERestart() { diff --git a/pkg/rtc/transportmanager.go b/pkg/rtc/transportmanager.go index 3f698ebe1..1b056b6e4 100644 --- a/pkg/rtc/transportmanager.go +++ b/pkg/rtc/transportmanager.go @@ -521,12 +521,12 @@ func (t *TransportManager) HandleClientReconnect(reason livekit.ReconnectReason) } } -func (t *TransportManager) ICERestart(iceConfig *livekit.ICEConfig) { +func (t *TransportManager) ICERestart(iceConfig *livekit.ICEConfig) error { if iceConfig != nil { t.SetICEConfig(iceConfig) } - t.subscriber.ICERestart() + return t.subscriber.ICERestart() } func (t *TransportManager) OnICEConfigChanged(f func(iceConfig *livekit.ICEConfig)) { diff --git a/pkg/sfu/streamallocator/channelobserver.go b/pkg/sfu/streamallocator/channelobserver.go index 380cefb1d..6c01552fb 100644 --- a/pkg/sfu/streamallocator/channelobserver.go +++ b/pkg/sfu/streamallocator/channelobserver.go @@ -90,7 +90,9 @@ func NewChannelObserver(params ChannelObserverParams, logger logger.Logger) *Cha Name: params.Name + "-estimate", Logger: logger, RequiredSamples: params.Config.EstimateRequiredSamples, + RequiredSamplesMin: params.Config.EstimateRequiredSamplesMin, DownwardTrendThreshold: params.Config.EstimateDownwardTrendThreshold, + DownwardTrendMaxWait: params.Config.EstimateDownwardTrendMaxWait, ValidityWindow: params.Config.EstimateValidityWindow, }), nackTracker: NewNackTracker(NackTrackerParams{ diff --git a/pkg/sfu/streamallocator/trenddetector.go b/pkg/sfu/streamallocator/trenddetector.go index 28b1ce73d..dba80e720 100644 --- a/pkg/sfu/streamallocator/trenddetector.go +++ b/pkg/sfu/streamallocator/trenddetector.go @@ -57,7 +57,9 @@ type TrendDetectorParams struct { Name string Logger logger.Logger RequiredSamples int + RequiredSamplesMin int DownwardTrendThreshold float64 + DownwardTrendMaxWait time.Duration ValidityWindow time.Duration } @@ -189,7 +191,7 @@ func (t *TrendDetector) prune() { } func (t *TrendDetector) updateDirection() { - if len(t.samples) < t.params.RequiredSamples { + if len(t.samples) < t.params.RequiredSamplesMin { t.direction = TrendDirectionNeutral return } @@ -199,9 +201,9 @@ func (t *TrendDetector) updateDirection() { t.direction = TrendDirectionNeutral switch { - case kt > 0: + case kt > 0 && len(t.samples) >= t.params.RequiredSamples: t.direction = TrendDirectionUpward - case kt < t.params.DownwardTrendThreshold: + case kt < t.params.DownwardTrendThreshold && (len(t.samples) >= t.params.RequiredSamples || t.samples[len(t.samples)-1].at.Sub(t.samples[0].at) > t.params.DownwardTrendMaxWait): t.direction = TrendDirectionDownward } }