diff --git a/.changeset/configutil-derive.md b/.changeset/configutil-derive.md deleted file mode 100644 index ba3fc2d9a..000000000 --- a/.changeset/configutil-derive.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"github.com/livekit/protocol": patch -"@livekit/protocol": patch ---- - -Add `configutil.Derive` for narrowing an observable config onto a subtree diff --git a/.changeset/configutil-static-observer.md b/.changeset/configutil-static-observer.md deleted file mode 100644 index 6064b75df..000000000 --- a/.changeset/configutil-static-observer.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"github.com/livekit/protocol": patch -"@livekit/protocol": patch ---- - -Add `configutil.NewStaticObserver`, which builds an `Observer` over an already-built config with no file to watch, for stubbing observable config in tests. `EmitConfigUpdate` now also stores the config it emits, so `Load` reflects an update pushed by hand. diff --git a/.changeset/configutil-validator.md b/.changeset/configutil-validator.md deleted file mode 100644 index d7f43aa04..000000000 --- a/.changeset/configutil-validator.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"github.com/livekit/protocol": patch -"@livekit/protocol": patch ---- - -Add `configutil.Validator`, an optional builder hook run on every config load after `InitDefaults`. A config that fails validation, or whose `InitDefaults` returns an error, now fails `NewObserver`; on reload the failure is logged and the previous config stays in effect. diff --git a/.changeset/egressobs-v2-audio-only.md b/.changeset/egressobs-v2-audio-only.md deleted file mode 100644 index 70948704d..000000000 --- a/.changeset/egressobs-v2-audio-only.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"github.com/livekit/protocol": patch ---- - -`egressobs.GetAudioOnly` reports v2 `StartEgress` and replay export requests as audio-only when their Template or Web source sets `audio_only`. diff --git a/.changeset/logger-config-marshal-yaml.md b/.changeset/logger-config-marshal-yaml.md deleted file mode 100644 index 64d162246..000000000 --- a/.changeset/logger-config-marshal-yaml.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"github.com/livekit/protocol": patch -"@livekit/protocol": patch ---- - -`logger.Config` implements `yaml.Marshaler`, so marshaling a `*Config` snapshots it under the same lock `Update` takes instead of reading fields alongside a concurrent update. diff --git a/.changeset/psrpc-bus-split.md b/.changeset/psrpc-bus-split.md deleted file mode 100644 index 3ca877c3c..000000000 --- a/.changeset/psrpc-bus-split.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"github.com/livekit/protocol": patch -"@livekit/protocol": patch ---- - -Update psrpc to v0.8.0 diff --git a/.changeset/simulation-repeats.md b/.changeset/simulation-repeats.md deleted file mode 100644 index 52ca8ebf5..000000000 --- a/.changeset/simulation-repeats.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"github.com/livekit/protocol": patch -"@livekit/protocol": patch ---- - -agent_simulation: repeat scenarios within a run for pass@k / pass^k, and decide a scenario's verdict from a `Sampling.pass_rate` diff --git a/.changeset/sip-invite-time.md b/.changeset/sip-invite-time.md deleted file mode 100644 index a2e36f9c8..000000000 --- a/.changeset/sip-invite-time.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"github.com/livekit/protocol": patch -"@livekit/protocol": patch ---- - -Add `lk.sip.inviteTime` SIP participant attribute and agents `sip_invite_latency` reporter diff --git a/.changeset/utils-leaftest.md b/.changeset/utils-leaftest.md deleted file mode 100644 index cfff2e586..000000000 --- a/.changeset/utils-leaftest.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"github.com/livekit/protocol": patch -"@livekit/protocol": patch ---- - -Add `utils/leaftest`, which asserts that a package imports nothing beyond the standard library and an explicit allow list. `leaftest.Assert(t)` names the package from the working directory, so a guarded package's test carries no import path to keep in sync. Test-only imports are not counted. diff --git a/CHANGELOG.md b/CHANGELOG.md index 796cea03a..1393f5d10 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,29 @@ # github.com/livekit/protocol +## 1.52.1 + +### Patch Changes + +- Add `configutil.Derive` for narrowing an observable config onto a subtree - [#1795](https://github.com/livekit/protocol/pull/1795) ([@paulwe](https://github.com/paulwe)) + +- Add `configutil.NewStaticObserver`, which builds an `Observer` over an already-built config with no file to watch, for stubbing observable config in tests. `EmitConfigUpdate` now also stores the config it emits, so `Load` reflects an update pushed by hand. - [#1795](https://github.com/livekit/protocol/pull/1795) ([@paulwe](https://github.com/paulwe)) + +- Add `configutil.Validator`, an optional builder hook run on every config load after `InitDefaults`. A config that fails validation, or whose `InitDefaults` returns an error, now fails `NewObserver`; on reload the failure is logged and the previous config stays in effect. - [#1795](https://github.com/livekit/protocol/pull/1795) ([@paulwe](https://github.com/paulwe)) + +- `egressobs.GetAudioOnly` reports v2 `StartEgress` and replay export requests as audio-only when their Template or Web source sets `audio_only`. - [#1816](https://github.com/livekit/protocol/pull/1816) ([@milos-lk](https://github.com/milos-lk)) + +- Add FlexFEC stats to RTPStats - [#1818](https://github.com/livekit/protocol/pull/1818) ([@chenosaurus](https://github.com/chenosaurus)) + +- `logger.Config` implements `yaml.Marshaler`, so marshaling a `*Config` snapshots it under the same lock `Update` takes instead of reading fields alongside a concurrent update. - [#1813](https://github.com/livekit/protocol/pull/1813) ([@paulwe](https://github.com/paulwe)) + +- Update psrpc to v0.8.0 - [#1808](https://github.com/livekit/protocol/pull/1808) ([@paulwe](https://github.com/paulwe)) + +- agent_simulation: repeat scenarios within a run for pass@k / pass^k, and decide a scenario's verdict from a `Sampling.pass_rate` - [#1799](https://github.com/livekit/protocol/pull/1799) ([@u9g](https://github.com/u9g)) + +- Add `lk.sip.inviteTime` SIP participant attribute and agents `sip_invite_latency` reporter - [#1815](https://github.com/livekit/protocol/pull/1815) ([@paulwe](https://github.com/paulwe)) + +- Add `utils/leaftest`, which asserts that a package imports nothing beyond the standard library and an explicit allow list. `leaftest.Assert(t)` names the package from the working directory, so a guarded package's test carries no import path to keep in sync. Test-only imports are not counted. - [#1795](https://github.com/livekit/protocol/pull/1795) ([@paulwe](https://github.com/paulwe)) + ## 1.52.0 ### Minor Changes diff --git a/livekit/livekit_models.pb.go b/livekit/livekit_models.pb.go index de19ba522..1edb1986d 100644 --- a/livekit/livekit_models.pb.go +++ b/livekit/livekit_models.pb.go @@ -5453,7 +5453,11 @@ type RTPStats struct { PacketDrift *RTPDrift `protobuf:"bytes,44,opt,name=packet_drift,json=packetDrift,proto3" json:"packet_drift,omitempty"` NtpReportDrift *RTPDrift `protobuf:"bytes,45,opt,name=ntp_report_drift,json=ntpReportDrift,proto3" json:"ntp_report_drift,omitempty"` RebasedReportDrift *RTPDrift `protobuf:"bytes,46,opt,name=rebased_report_drift,json=rebasedReportDrift,proto3" json:"rebased_report_drift,omitempty"` - ReceivedReportDrift *RTPDrift `protobuf:"bytes,47,opt,name=received_report_drift,json=receivedReportDrift,proto3" json:"received_report_drift,omitempty"` // NEXT_ID: 48 + ReceivedReportDrift *RTPDrift `protobuf:"bytes,47,opt,name=received_report_drift,json=receivedReportDrift,proto3" json:"received_report_drift,omitempty"` + FecPackets uint32 `protobuf:"varint,48,opt,name=fec_packets,json=fecPackets,proto3" json:"fec_packets,omitempty"` + FecBytes uint64 `protobuf:"varint,49,opt,name=fec_bytes,json=fecBytes,proto3" json:"fec_bytes,omitempty"` + FecPacketsDiscarded uint32 `protobuf:"varint,50,opt,name=fec_packets_discarded,json=fecPacketsDiscarded,proto3" json:"fec_packets_discarded,omitempty"` + FecPacketsRecovered uint32 `protobuf:"varint,51,opt,name=fec_packets_recovered,json=fecPacketsRecovered,proto3" json:"fec_packets_recovered,omitempty"` // NEXT_ID: 52 unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -5803,6 +5807,34 @@ func (x *RTPStats) GetReceivedReportDrift() *RTPDrift { return nil } +func (x *RTPStats) GetFecPackets() uint32 { + if x != nil { + return x.FecPackets + } + return 0 +} + +func (x *RTPStats) GetFecBytes() uint64 { + if x != nil { + return x.FecBytes + } + return 0 +} + +func (x *RTPStats) GetFecPacketsDiscarded() uint32 { + if x != nil { + return x.FecPacketsDiscarded + } + return 0 +} + +func (x *RTPStats) GetFecPacketsRecovered() uint32 { + if x != nil { + return x.FecPacketsRecovered + } + return 0 +} + type RTCPSenderReportState struct { state protoimpl.MessageState `protogen:"open.v1"` RtpTimestamp uint32 `protobuf:"varint,1,opt,name=rtp_timestamp,json=rtpTimestamp,proto3" json:"rtp_timestamp,omitempty"` @@ -7287,7 +7319,7 @@ const file_livekit_models_proto_rawDesc = "" + "\rdrift_samples\x18\a \x01(\x03R\fdriftSamples\x12\x19\n" + "\bdrift_ms\x18\b \x01(\x01R\adriftMs\x12\x1d\n" + "\n" + - "clock_rate\x18\t \x01(\x01R\tclockRate\"\xc4\x0f\n" + + "clock_rate\x18\t \x01(\x01R\tclockRate\"\xea\x10\n" + "\bRTPStats\x129\n" + "\n" + "start_time\x18\x01 \x01(\v2\x1a.google.protobuf.TimestampR\tstartTime\x125\n" + @@ -7341,7 +7373,12 @@ const file_livekit_models_proto_rawDesc = "" + "\fpacket_drift\x18, \x01(\v2\x11.livekit.RTPDriftR\vpacketDrift\x12;\n" + "\x10ntp_report_drift\x18- \x01(\v2\x11.livekit.RTPDriftR\x0entpReportDrift\x12C\n" + "\x14rebased_report_drift\x18. \x01(\v2\x11.livekit.RTPDriftR\x12rebasedReportDrift\x12E\n" + - "\x15received_report_drift\x18/ \x01(\v2\x11.livekit.RTPDriftR\x13receivedReportDrift\x1a?\n" + + "\x15received_report_drift\x18/ \x01(\v2\x11.livekit.RTPDriftR\x13receivedReportDrift\x12\x1f\n" + + "\vfec_packets\x180 \x01(\rR\n" + + "fecPackets\x12\x1b\n" + + "\tfec_bytes\x181 \x01(\x04R\bfecBytes\x122\n" + + "\x15fec_packets_discarded\x182 \x01(\rR\x13fecPacketsDiscarded\x122\n" + + "\x15fec_packets_recovered\x183 \x01(\rR\x13fecPacketsRecovered\x1a?\n" + "\x11GapHistogramEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\x05R\x03key\x12\x14\n" + "\x05value\x18\x02 \x01(\rR\x05value:\x028\x01\"\xf0\x01\n" + diff --git a/package.json b/package.json index c9d025c24..7005ee2c5 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "github.com/livekit/protocol", "private": true, - "version": "1.52.0", + "version": "1.52.1", "scripts": { "changeset": "changeset", "ci:publish": "pnpm --filter @livekit/protocol run build && changeset publish" diff --git a/packages/javascript/CHANGELOG.md b/packages/javascript/CHANGELOG.md index 1a77d0d75..afed28e18 100644 --- a/packages/javascript/CHANGELOG.md +++ b/packages/javascript/CHANGELOG.md @@ -1,5 +1,27 @@ # @livekit/protocol +## 1.52.1 + +### Patch Changes + +- Add `configutil.Derive` for narrowing an observable config onto a subtree - [#1795](https://github.com/livekit/protocol/pull/1795) ([@paulwe](https://github.com/paulwe)) + +- Add `configutil.NewStaticObserver`, which builds an `Observer` over an already-built config with no file to watch, for stubbing observable config in tests. `EmitConfigUpdate` now also stores the config it emits, so `Load` reflects an update pushed by hand. - [#1795](https://github.com/livekit/protocol/pull/1795) ([@paulwe](https://github.com/paulwe)) + +- Add `configutil.Validator`, an optional builder hook run on every config load after `InitDefaults`. A config that fails validation, or whose `InitDefaults` returns an error, now fails `NewObserver`; on reload the failure is logged and the previous config stays in effect. - [#1795](https://github.com/livekit/protocol/pull/1795) ([@paulwe](https://github.com/paulwe)) + +- Add FlexFEC stats to RTPStats - [#1818](https://github.com/livekit/protocol/pull/1818) ([@chenosaurus](https://github.com/chenosaurus)) + +- `logger.Config` implements `yaml.Marshaler`, so marshaling a `*Config` snapshots it under the same lock `Update` takes instead of reading fields alongside a concurrent update. - [#1813](https://github.com/livekit/protocol/pull/1813) ([@paulwe](https://github.com/paulwe)) + +- Update psrpc to v0.8.0 - [#1808](https://github.com/livekit/protocol/pull/1808) ([@paulwe](https://github.com/paulwe)) + +- agent_simulation: repeat scenarios within a run for pass@k / pass^k, and decide a scenario's verdict from a `Sampling.pass_rate` - [#1799](https://github.com/livekit/protocol/pull/1799) ([@u9g](https://github.com/u9g)) + +- Add `lk.sip.inviteTime` SIP participant attribute and agents `sip_invite_latency` reporter - [#1815](https://github.com/livekit/protocol/pull/1815) ([@paulwe](https://github.com/paulwe)) + +- Add `utils/leaftest`, which asserts that a package imports nothing beyond the standard library and an explicit allow list. `leaftest.Assert(t)` names the package from the working directory, so a guarded package's test carries no import path to keep in sync. Test-only imports are not counted. - [#1795](https://github.com/livekit/protocol/pull/1795) ([@paulwe](https://github.com/paulwe)) + ## 1.52.0 ### Minor Changes diff --git a/packages/javascript/package.json b/packages/javascript/package.json index e1dd007d4..15b680dbc 100644 --- a/packages/javascript/package.json +++ b/packages/javascript/package.json @@ -1,6 +1,6 @@ { "name": "@livekit/protocol", - "version": "1.52.0", + "version": "1.52.1", "description": "", "type": "module", "require": "dist/index.cjs", diff --git a/protobufs/livekit_models.proto b/protobufs/livekit_models.proto index 4d4b59298..bff3bbd00 100644 --- a/protobufs/livekit_models.proto +++ b/protobufs/livekit_models.proto @@ -828,7 +828,12 @@ message RTPStats { RTPDrift ntp_report_drift = 45; RTPDrift rebased_report_drift = 46; RTPDrift received_report_drift = 47; - // NEXT_ID: 48 + + uint32 fec_packets = 48; + uint64 fec_bytes = 49; + uint32 fec_packets_discarded = 50; + uint32 fec_packets_recovered = 51; + // NEXT_ID: 52 } message RTCPSenderReportState { diff --git a/utils/rtpstats.go b/utils/rtpstats.go index b28c3b3ce..d1545844b 100644 --- a/utils/rtpstats.go +++ b/utils/rtpstats.go @@ -39,6 +39,10 @@ func AggregateRTPStats(statsList []*livekit.RTPStats, gapHistogramSize int) *liv packetsPadding := uint32(0) bytesPadding := uint64(0) headerBytesPadding := uint64(0) + fecPackets := uint32(0) + fecBytes := uint64(0) + fecPacketsDiscarded := uint32(0) + fecPacketsRecovered := uint32(0) packetsOutOfOrder := uint32(0) frames := uint32(0) keyFrames := uint32(0) @@ -82,6 +86,11 @@ func AggregateRTPStats(statsList []*livekit.RTPStats, gapHistogramSize int) *liv bytesPadding += stats.BytesPadding headerBytesPadding += stats.HeaderBytesPadding + fecPackets += stats.FecPackets + fecBytes += stats.FecBytes + fecPacketsDiscarded += stats.FecPacketsDiscarded + fecPacketsRecovered += stats.FecPacketsRecovered + packetsOutOfOrder += stats.PacketsOutOfOrder frames += stats.Frames @@ -166,6 +175,10 @@ func AggregateRTPStats(statsList []*livekit.RTPStats, gapHistogramSize int) *liv BytesPadding: bytesPadding, HeaderBytesPadding: headerBytesPadding, BitratePadding: bitratePadding, + FecPackets: fecPackets, + FecBytes: fecBytes, + FecPacketsDiscarded: fecPacketsDiscarded, + FecPacketsRecovered: fecPacketsRecovered, PacketsOutOfOrder: packetsOutOfOrder, Frames: frames, FrameRate: frameRate, diff --git a/utils/rtpstats_test.go b/utils/rtpstats_test.go new file mode 100644 index 000000000..6b777ff88 --- /dev/null +++ b/utils/rtpstats_test.go @@ -0,0 +1,53 @@ +// Copyright 2026 LiveKit, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package utils + +import ( + "testing" + "time" + + "github.com/stretchr/testify/require" + "google.golang.org/protobuf/types/known/timestamppb" + + "github.com/livekit/protocol/livekit" +) + +func TestAggregateRTPStatsFEC(t *testing.T) { + start := time.Now() + end := start.Add(time.Second) + stats := AggregateRTPStats([]*livekit.RTPStats{ + { + StartTime: timestamppb.New(start), + EndTime: timestamppb.New(end), + FecPackets: 3, + FecBytes: 900, + FecPacketsDiscarded: 1, + FecPacketsRecovered: 2, + }, + { + StartTime: timestamppb.New(start), + EndTime: timestamppb.New(end), + FecPackets: 4, + FecBytes: 1200, + FecPacketsDiscarded: 2, + FecPacketsRecovered: 3, + }, + }, 8) + + require.EqualValues(t, 7, stats.FecPackets) + require.EqualValues(t, 2100, stats.FecBytes) + require.EqualValues(t, 3, stats.FecPacketsDiscarded) + require.EqualValues(t, 5, stats.FecPacketsRecovered) +}