mirror of
https://github.com/livekit/livekit.git
synced 2026-05-26 01:05:07 +00:00
Merge remote-tracking branch 'origin/master' into raja_1833
This commit is contained in:
@@ -38,7 +38,7 @@ jobs:
|
||||
go-version: '>=1.21'
|
||||
|
||||
- name: Run GoReleaser
|
||||
uses: goreleaser/goreleaser-action@v5
|
||||
uses: goreleaser/goreleaser-action@v6
|
||||
with:
|
||||
distribution: goreleaser
|
||||
version: latest
|
||||
|
||||
@@ -28,6 +28,7 @@ import (
|
||||
|
||||
"github.com/livekit/protocol/auth"
|
||||
"github.com/livekit/protocol/utils"
|
||||
"github.com/livekit/protocol/utils/guid"
|
||||
|
||||
"github.com/livekit/livekit-server/pkg/config"
|
||||
"github.com/livekit/livekit-server/pkg/routing"
|
||||
@@ -35,7 +36,7 @@ import (
|
||||
)
|
||||
|
||||
func generateKeys(_ *cli.Context) error {
|
||||
apiKey := utils.NewGuid(utils.APIKeyPrefix)
|
||||
apiKey := guid.New(utils.APIKeyPrefix)
|
||||
secret := utils.RandomSecret()
|
||||
fmt.Println("API Key: ", apiKey)
|
||||
fmt.Println("API Secret: ", secret)
|
||||
|
||||
@@ -143,8 +143,6 @@ rtc:
|
||||
|
||||
# when enabled, LiveKit will expose prometheus metrics on :6789/metrics
|
||||
# prometheus_port: 6789
|
||||
# set a custom environment variable. prometheus metrics will be labeled with this value. defaults to an empty string
|
||||
# environment: custom-value
|
||||
|
||||
# API key / secret pairs.
|
||||
# Keys are used for JWT authentication, server APIs would require a keypair in order to generate access tokens
|
||||
|
||||
@@ -3,7 +3,7 @@ module github.com/livekit/livekit-server
|
||||
go 1.22
|
||||
|
||||
require (
|
||||
github.com/avast/retry-go/v4 v4.5.1
|
||||
github.com/avast/retry-go/v4 v4.6.0
|
||||
github.com/bep/debounce v1.2.1
|
||||
github.com/d5/tengo/v2 v2.17.0
|
||||
github.com/dustin/go-humanize v1.0.1
|
||||
@@ -14,44 +14,44 @@ require (
|
||||
github.com/gammazero/workerpool v1.1.3
|
||||
github.com/google/wire v0.6.0
|
||||
github.com/gorilla/websocket v1.5.1
|
||||
github.com/hashicorp/go-version v1.6.0
|
||||
github.com/hashicorp/go-version v1.7.0
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7
|
||||
github.com/jellydator/ttlcache/v3 v3.2.0
|
||||
github.com/jxskiss/base62 v1.1.0
|
||||
github.com/livekit/mageutil v0.0.0-20230125210925-54e8a70427c1
|
||||
github.com/livekit/mediatransportutil v0.0.0-20240416023643-881d3dc5423e
|
||||
github.com/livekit/protocol v1.12.1-0.20240426063020-fd19ad24b86b
|
||||
github.com/livekit/psrpc v0.5.3-0.20240426045048-8ba067a45715
|
||||
github.com/livekit/mediatransportutil v0.0.0-20240501132628-6105557bbb9a
|
||||
github.com/livekit/protocol v1.17.1-0.20240606023900-429fec77a69b
|
||||
github.com/livekit/psrpc v0.5.3-0.20240526192918-fbdaf10e6aa5
|
||||
github.com/mackerelio/go-osstat v0.2.4
|
||||
github.com/magefile/mage v1.15.0
|
||||
github.com/maxbrunsfeld/counterfeiter/v6 v6.8.1
|
||||
github.com/mitchellh/go-homedir v1.1.0
|
||||
github.com/olekukonko/tablewriter v0.0.5
|
||||
github.com/pion/dtls/v2 v2.2.10
|
||||
github.com/pion/ice/v2 v2.3.19
|
||||
github.com/pion/dtls/v2 v2.2.11
|
||||
github.com/pion/ice/v2 v2.3.24
|
||||
github.com/pion/interceptor v0.1.29
|
||||
github.com/pion/rtcp v1.2.14
|
||||
github.com/pion/rtp v1.8.6
|
||||
github.com/pion/sctp v1.8.16
|
||||
github.com/pion/sdp/v3 v3.0.9
|
||||
github.com/pion/transport/v2 v2.2.4
|
||||
github.com/pion/transport/v2 v2.2.5
|
||||
github.com/pion/turn/v2 v2.1.6
|
||||
github.com/pion/webrtc/v3 v3.2.38
|
||||
github.com/pion/webrtc/v3 v3.2.40
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/prometheus/client_golang v1.19.0
|
||||
github.com/prometheus/client_golang v1.19.1
|
||||
github.com/redis/go-redis/v9 v9.5.1
|
||||
github.com/rs/cors v1.10.1
|
||||
github.com/rs/cors v1.11.0
|
||||
github.com/stretchr/testify v1.9.0
|
||||
github.com/thoas/go-funk v0.9.3
|
||||
github.com/twitchtv/twirp v8.1.3+incompatible
|
||||
github.com/ua-parser/uap-go v0.0.0-20240113215029-33f8e6d47f38
|
||||
github.com/urfave/cli/v2 v2.27.1
|
||||
github.com/urfave/cli/v2 v2.27.2
|
||||
github.com/urfave/negroni/v3 v3.1.0
|
||||
go.uber.org/atomic v1.11.0
|
||||
go.uber.org/zap v1.27.0
|
||||
golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f
|
||||
golang.org/x/exp v0.0.0-20240529005216-23cca8864a10
|
||||
golang.org/x/sync v0.7.0
|
||||
google.golang.org/protobuf v1.33.0
|
||||
google.golang.org/protobuf v1.34.1
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
)
|
||||
|
||||
@@ -59,14 +59,14 @@ require (
|
||||
github.com/benbjohnson/clock v1.3.5 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
github.com/eapache/channels v1.1.0 // indirect
|
||||
github.com/eapache/queue v1.1.0 // indirect
|
||||
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
||||
github.com/go-jose/go-jose/v3 v3.0.3 // indirect
|
||||
github.com/go-logr/logr v1.4.1 // indirect
|
||||
github.com/go-logr/logr v1.4.2 // indirect
|
||||
github.com/google/go-cmp v0.6.0 // indirect
|
||||
github.com/google/subcommands v1.2.0 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
@@ -80,7 +80,7 @@ require (
|
||||
github.com/mattn/go-runewidth v0.0.9 // indirect
|
||||
github.com/mdlayher/netlink v1.7.1 // indirect
|
||||
github.com/mdlayher/socket v0.4.0 // indirect
|
||||
github.com/nats-io/nats.go v1.34.1 // indirect
|
||||
github.com/nats-io/nats.go v1.35.0 // indirect
|
||||
github.com/nats-io/nkeys v0.4.7 // indirect
|
||||
github.com/nats-io/nuid v1.0.1 // indirect
|
||||
github.com/pion/datachannel v1.5.5 // indirect
|
||||
@@ -95,17 +95,17 @@ require (
|
||||
github.com/prometheus/procfs v0.12.0 // indirect
|
||||
github.com/puzpuzpuz/xsync/v3 v3.1.0 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
|
||||
github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913 // indirect
|
||||
github.com/zeebo/xxh3 v1.0.2 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
go.uber.org/zap/exp v0.2.0 // indirect
|
||||
golang.org/x/crypto v0.22.0 // indirect
|
||||
golang.org/x/crypto v0.23.0 // indirect
|
||||
golang.org/x/mod v0.17.0 // indirect
|
||||
golang.org/x/net v0.24.0 // indirect
|
||||
golang.org/x/sys v0.19.0 // indirect
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
golang.org/x/tools v0.20.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240415180920-8c6c420018be // indirect
|
||||
google.golang.org/grpc v1.63.2 // indirect
|
||||
golang.org/x/net v0.25.0 // indirect
|
||||
golang.org/x/sys v0.20.0 // indirect
|
||||
golang.org/x/text v0.15.0 // indirect
|
||||
golang.org/x/tools v0.21.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240521202816-d264139d666e // indirect
|
||||
google.golang.org/grpc v1.64.0 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
github.com/avast/retry-go/v4 v4.5.1 h1:AxIx0HGi4VZ3I02jr78j5lZ3M6x1E0Ivxa6b0pUUh7o=
|
||||
github.com/avast/retry-go/v4 v4.5.1/go.mod h1:/sipNsvNB3RRuT5iNcb6h73nw3IBmXJ/H3XrCQYSOpc=
|
||||
github.com/avast/retry-go/v4 v4.6.0 h1:K9xNA+KeB8HHc2aWFuLb25Offp+0iVRXEvFx8IinRJA=
|
||||
github.com/avast/retry-go/v4 v4.6.0/go.mod h1:gvWlPhBVsvBbLkVGDg/KwvBv0bEkCOLRRSHKIr2PyOE=
|
||||
github.com/benbjohnson/clock v1.3.5 h1:VvXlSJBzZpA/zum6Sj74hxwYI2DIxRWuNIoXAzHZz5o=
|
||||
github.com/benbjohnson/clock v1.3.5/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
@@ -16,8 +16,8 @@ github.com/cilium/ebpf v0.5.0/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJ
|
||||
github.com/cilium/ebpf v0.7.0/go.mod h1:/oI2+1shJiTGAMgl6/RgJr36Eo1jzrRcAWbcXO2usCA=
|
||||
github.com/cilium/ebpf v0.8.1 h1:bLSSEbBLqGPXxls55pGr5qWZaTqcmfDJHhou7t254ao=
|
||||
github.com/cilium/ebpf v0.8.1/go.mod h1:f5zLIM0FSNuAkSyLAN7X+Hy6yznlF1mNiWUMfxMtrgk=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/d5/tengo/v2 v2.17.0 h1:BWUN9NoJzw48jZKiYDXDIF3QrIVZRm1uV1gTzeZ2lqM=
|
||||
github.com/d5/tengo/v2 v2.17.0/go.mod h1:XRGjEs5I9jYIKTxly6HCF8oiiilk5E/RYXOZ5b0DZC8=
|
||||
@@ -48,8 +48,8 @@ github.com/gammazero/workerpool v1.1.3 h1:WixN4xzukFoN0XSeXF6puqEqFTl2mECI9S6W44
|
||||
github.com/gammazero/workerpool v1.1.3/go.mod h1:wPjyBLDbyKnUn2XwwyD3EEwo9dHutia9/fwNmSHWACc=
|
||||
github.com/go-jose/go-jose/v3 v3.0.3 h1:fFKWeig/irsp7XD2zBxvnmA/XaRWp5V3CBsZXJF7G7k=
|
||||
github.com/go-jose/go-jose/v3 v3.0.3/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ=
|
||||
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
|
||||
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
||||
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
@@ -77,8 +77,8 @@ github.com/hashicorp/go-hclog v0.9.2 h1:CG6TE5H9/JXsFWJCfoIVpKFIkFe6ysEuHirp4DxC
|
||||
github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
|
||||
github.com/hashicorp/go-retryablehttp v0.7.5 h1:bJj+Pj19UZMIweq/iie+1u5YCdGrnxCT9yvm0e+Nd5M=
|
||||
github.com/hashicorp/go-retryablehttp v0.7.5/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8=
|
||||
github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek=
|
||||
github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||
github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY=
|
||||
github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||
github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
|
||||
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
|
||||
@@ -118,12 +118,12 @@ github.com/lithammer/shortuuid/v4 v4.0.0 h1:QRbbVkfgNippHOS8PXDkti4NaWeyYfcBTHtw
|
||||
github.com/lithammer/shortuuid/v4 v4.0.0/go.mod h1:Zs8puNcrvf2rV9rTH51ZLLcj7ZXqQI3lv67aw4KiB1Y=
|
||||
github.com/livekit/mageutil v0.0.0-20230125210925-54e8a70427c1 h1:jm09419p0lqTkDaKb5iXdynYrzB84ErPPO4LbRASk58=
|
||||
github.com/livekit/mageutil v0.0.0-20230125210925-54e8a70427c1/go.mod h1:Rs3MhFwutWhGwmY1VQsygw28z5bWcnEYmS1OG9OxjOQ=
|
||||
github.com/livekit/mediatransportutil v0.0.0-20240416023643-881d3dc5423e h1:ss4VwrouYiDpuNJ9BUTH+WsW+GDdJS70iZp8ii3/0Lc=
|
||||
github.com/livekit/mediatransportutil v0.0.0-20240416023643-881d3dc5423e/go.mod h1:jwKUCmObuiEDH0iiuJHaGMXwRs3RjrB4G6qqgkr/5oE=
|
||||
github.com/livekit/protocol v1.12.1-0.20240426063020-fd19ad24b86b h1:hPgkp/LJzhx+U2CHOc68yxGIyfFspagsyupAaqx1Ulw=
|
||||
github.com/livekit/protocol v1.12.1-0.20240426063020-fd19ad24b86b/go.mod h1:pnn0Dv+/0K0OFqKHX6J6SreYO1dZxl6tDuAZ1ns8L/w=
|
||||
github.com/livekit/psrpc v0.5.3-0.20240426045048-8ba067a45715 h1:vhDMOe8fxEc/amYTFo799LySPM12Fk3vc+Nc6o4gYZQ=
|
||||
github.com/livekit/psrpc v0.5.3-0.20240426045048-8ba067a45715/go.mod h1:CQUBSPfYYAaevg1TNCc6/aYsa8DJH4jSRFdCeSZk5u0=
|
||||
github.com/livekit/mediatransportutil v0.0.0-20240501132628-6105557bbb9a h1:ATbv0x7G5tW2HgiouQ57csFE/G4gekl2oV1cxb2Dy24=
|
||||
github.com/livekit/mediatransportutil v0.0.0-20240501132628-6105557bbb9a/go.mod h1:jwKUCmObuiEDH0iiuJHaGMXwRs3RjrB4G6qqgkr/5oE=
|
||||
github.com/livekit/protocol v1.17.1-0.20240606023900-429fec77a69b h1:VZMvqc23x/dXRpJQLc6CIkCuLUjev0HDLFO9NCEqfOk=
|
||||
github.com/livekit/protocol v1.17.1-0.20240606023900-429fec77a69b/go.mod h1:cN8WmGQR+kWz1+UWcAQdFFUcbW76PnfZDdkLAbYIqd4=
|
||||
github.com/livekit/psrpc v0.5.3-0.20240526192918-fbdaf10e6aa5 h1:mTZyrjk5WEWMsvaYtJ42pG7DuxysKj21DKPINpGSIto=
|
||||
github.com/livekit/psrpc v0.5.3-0.20240526192918-fbdaf10e6aa5/go.mod h1:CQUBSPfYYAaevg1TNCc6/aYsa8DJH4jSRFdCeSZk5u0=
|
||||
github.com/mackerelio/go-osstat v0.2.4 h1:qxGbdPkFo65PXOb/F/nhDKpF2nGmGaCFDLXoZjJTtUs=
|
||||
github.com/mackerelio/go-osstat v0.2.4/go.mod h1:Zy+qzGdZs3A9cuIqmgbJvwbmLQH9dJvtio5ZjJTbdlQ=
|
||||
github.com/magefile/mage v1.15.0 h1:BvGheCMAsG3bWUDbZ8AyXXpCNwU9u5CB6sM+HNb9HYg=
|
||||
@@ -153,8 +153,8 @@ github.com/mdlayher/socket v0.4.0 h1:280wsy40IC9M9q1uPGcLBwXpcTQDtoGwVt+BNoITxIw
|
||||
github.com/mdlayher/socket v0.4.0/go.mod h1:xxFqz5GRCUN3UEOm9CZqEJsAbe1C8OwSK46NlmWuVoc=
|
||||
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/nats-io/nats.go v1.34.1 h1:syWey5xaNHZgicYBemv0nohUPPmaLteiBEUT6Q5+F/4=
|
||||
github.com/nats-io/nats.go v1.34.1/go.mod h1:Ubdu4Nh9exXdSz0RVWRFBbRfrbSxOYd26oF0wkWclB8=
|
||||
github.com/nats-io/nats.go v1.35.0 h1:XFNqNM7v5B+MQMKqVGAyHwYhyKb48jrenXNxIU20ULk=
|
||||
github.com/nats-io/nats.go v1.35.0/go.mod h1:Ubdu4Nh9exXdSz0RVWRFBbRfrbSxOYd26oF0wkWclB8=
|
||||
github.com/nats-io/nkeys v0.4.7 h1:RwNJbbIdYCoClSDNY7QVKZlyb/wfT6ugvFCiKy6vDvI=
|
||||
github.com/nats-io/nkeys v0.4.7/go.mod h1:kqXRgRDPlGy7nGaEDMuYzmiJCIAAWDK0IMBtDmGD0nc=
|
||||
github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
|
||||
@@ -166,10 +166,10 @@ github.com/onsi/gomega v1.30.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8P
|
||||
github.com/pion/datachannel v1.5.5 h1:10ef4kwdjije+M9d7Xm9im2Y3O6A6ccQb0zcqZcJew8=
|
||||
github.com/pion/datachannel v1.5.5/go.mod h1:iMz+lECmfdCMqFRhXhcA/219B0SQlbpoR2V118yimL0=
|
||||
github.com/pion/dtls/v2 v2.2.7/go.mod h1:8WiMkebSHFD0T+dIU+UeBaoV7kDhOW5oDCzZ7WZ/F9s=
|
||||
github.com/pion/dtls/v2 v2.2.10 h1:u2Axk+FyIR1VFTPurktB+1zoEPGIW3bmyj3LEFrXjAA=
|
||||
github.com/pion/dtls/v2 v2.2.10/go.mod h1:d9SYc9fch0CqK90mRk1dC7AkzzpwJj6u2GU3u+9pqFE=
|
||||
github.com/pion/ice/v2 v2.3.19 h1:1GoMRTMnB6bCP4aGy2MjxK3w4laDkk+m7svJb/eqybc=
|
||||
github.com/pion/ice/v2 v2.3.19/go.mod h1:KXJJcZK7E8WzrBEYnV4UtqEZsGeWfHxsNqhVcVvgjxw=
|
||||
github.com/pion/dtls/v2 v2.2.11 h1:9U/dpCYl1ySttROPWJgqWKEylUdT0fXp/xst6JwY5Ks=
|
||||
github.com/pion/dtls/v2 v2.2.11/go.mod h1:d9SYc9fch0CqK90mRk1dC7AkzzpwJj6u2GU3u+9pqFE=
|
||||
github.com/pion/ice/v2 v2.3.24 h1:RYgzhH/u5lH0XO+ABatVKCtRd+4U1GEaCXSMjNr13tI=
|
||||
github.com/pion/ice/v2 v2.3.24/go.mod h1:KXJJcZK7E8WzrBEYnV4UtqEZsGeWfHxsNqhVcVvgjxw=
|
||||
github.com/pion/interceptor v0.1.29 h1:39fsnlP1U8gw2JzOFWdfCU82vHvhW9o0rZnZF56wF+M=
|
||||
github.com/pion/interceptor v0.1.29/go.mod h1:ri+LGNjRUc5xUNtDEPzfdkmSqISixVTBF/z/Zms/6T4=
|
||||
github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY=
|
||||
@@ -198,22 +198,23 @@ github.com/pion/transport v0.14.1/go.mod h1:4tGmbk00NeYA3rUa9+n+dzCCoKkcy3YlYb99
|
||||
github.com/pion/transport/v2 v2.2.1/go.mod h1:cXXWavvCnFF6McHTft3DWS9iic2Mftcz1Aq29pGcU5g=
|
||||
github.com/pion/transport/v2 v2.2.2/go.mod h1:OJg3ojoBJopjEeECq2yJdXH9YVrUJ1uQ++NjXLOUorc=
|
||||
github.com/pion/transport/v2 v2.2.3/go.mod h1:q2U/tf9FEfnSBGSW6w5Qp5PFWRLRj3NjLhCCgpRK4p0=
|
||||
github.com/pion/transport/v2 v2.2.4 h1:41JJK6DZQYSeVLxILA2+F4ZkKb4Xd/tFJZRFZQ9QAlo=
|
||||
github.com/pion/transport/v2 v2.2.4/go.mod h1:q2U/tf9FEfnSBGSW6w5Qp5PFWRLRj3NjLhCCgpRK4p0=
|
||||
github.com/pion/transport/v2 v2.2.5 h1:iyi25i/21gQck4hfRhomF6SktmUQjRsRW4WJdhfc3Kc=
|
||||
github.com/pion/transport/v2 v2.2.5/go.mod h1:q2U/tf9FEfnSBGSW6w5Qp5PFWRLRj3NjLhCCgpRK4p0=
|
||||
github.com/pion/transport/v3 v3.0.1/go.mod h1:UY7kiITrlMv7/IKgd5eTUcaahZx5oUN3l9SzK5f5xE0=
|
||||
github.com/pion/transport/v3 v3.0.2 h1:r+40RJR25S9w3jbA6/5uEPTzcdn7ncyU44RWCbHkLg4=
|
||||
github.com/pion/transport/v3 v3.0.2/go.mod h1:nIToODoOlb5If2jF9y2Igfx3PFYWfuXi37m0IlWa/D0=
|
||||
github.com/pion/turn/v2 v2.1.3/go.mod h1:huEpByKKHix2/b9kmTAM3YoX6MKP+/D//0ClgUYR2fY=
|
||||
github.com/pion/turn/v2 v2.1.6 h1:Xr2niVsiPTB0FPtt+yAWKFUkU1eotQbGgpTIld4x1Gc=
|
||||
github.com/pion/turn/v2 v2.1.6/go.mod h1:huEpByKKHix2/b9kmTAM3YoX6MKP+/D//0ClgUYR2fY=
|
||||
github.com/pion/webrtc/v3 v3.2.38 h1:oA52VJAJhOjSi1JpKjf0CM+cCiZ3b7jBxvsoOiajeDU=
|
||||
github.com/pion/webrtc/v3 v3.2.38/go.mod h1:AQ8p56OLbm3MjhYovYdgPuyX6oc+JcKx/HFoCGFcYzA=
|
||||
github.com/pion/webrtc/v3 v3.2.40 h1:Wtfi6AZMQg+624cvCXUuSmrKWepSB7zfgYDOYqsSOVU=
|
||||
github.com/pion/webrtc/v3 v3.2.40/go.mod h1:M1RAe3TNTD1tzyvqHrbVODfwdPGSXOUo/OgpoGGJqFY=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_golang v1.19.0 h1:ygXvpU1AoN1MhdzckN+PyD9QJOSD4x7kmXYlnfbA6JU=
|
||||
github.com/prometheus/client_golang v1.19.0/go.mod h1:ZRM9uEAypZakd+q/x7+gmsvXdURP+DABIEIjnmDdp+k=
|
||||
github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE=
|
||||
github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho=
|
||||
github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw=
|
||||
github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI=
|
||||
github.com/prometheus/common v0.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSzKKE=
|
||||
@@ -227,8 +228,8 @@ github.com/redis/go-redis/v9 v9.5.1/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0
|
||||
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
|
||||
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
|
||||
github.com/rs/cors v1.10.1 h1:L0uuZVXIKlI1SShY2nhFfo44TYvDPQ1w4oFkUJNfhyo=
|
||||
github.com/rs/cors v1.10.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
|
||||
github.com/rs/cors v1.11.0 h1:0B9GE/r9Bc2UxRMMtymBkHTenPkHDv0CW4Y98GBY+po=
|
||||
github.com/rs/cors v1.11.0/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
|
||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/sclevine/spec v1.4.0 h1:z/Q9idDcay5m5irkZ28M7PtQM4aOISzOpj4bUPkDee8=
|
||||
@@ -253,12 +254,12 @@ github.com/twitchtv/twirp v8.1.3+incompatible h1:+F4TdErPgSUbMZMwp13Q/KgDVuI7HJX
|
||||
github.com/twitchtv/twirp v8.1.3+incompatible/go.mod h1:RRJoFSAmTEh2weEqWtpPE3vFK5YBhA6bqp2l1kfCC5A=
|
||||
github.com/ua-parser/uap-go v0.0.0-20240113215029-33f8e6d47f38 h1:F04Na0QJP9GJrwmK3vQDuDrCuGllrrfngW8CIeF1aag=
|
||||
github.com/ua-parser/uap-go v0.0.0-20240113215029-33f8e6d47f38/go.mod h1:BUbeWZiieNxAuuADTBNb3/aeje6on3DhU3rpWsQSB1E=
|
||||
github.com/urfave/cli/v2 v2.27.1 h1:8xSQ6szndafKVRmfyeUMxkNUJQMjL1F2zmsZ+qHpfho=
|
||||
github.com/urfave/cli/v2 v2.27.1/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ=
|
||||
github.com/urfave/cli/v2 v2.27.2 h1:6e0H+AkS+zDckwPCUrZkKX38mRaau4nL2uipkJpbkcI=
|
||||
github.com/urfave/cli/v2 v2.27.2/go.mod h1:g0+79LmHHATl7DAcHO99smiR/T7uGLw84w8Y42x+4eM=
|
||||
github.com/urfave/negroni/v3 v3.1.0 h1:lzmuxGSpnJCT/ujgIAjkU3+LW3NX8alCglO/L6KjIGQ=
|
||||
github.com/urfave/negroni/v3 v3.1.0/go.mod h1:jWvnX03kcSjDBl/ShB0iHvx5uOs7mAzZXW+JvJ5XYAs=
|
||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
|
||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
|
||||
github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913 h1:+qGGcbkzsfDQNPPe9UDgpxAWQrhbbBXOYJFQDq/dtJw=
|
||||
github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913/go.mod h1:4aEEwZQutDLsQv2Deui4iYQ6DWTxR14g6m8Wv88+Xqk=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ=
|
||||
github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=
|
||||
@@ -283,10 +284,10 @@ golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98y
|
||||
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
||||
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
|
||||
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
|
||||
golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f h1:99ci1mjWVBWwJiEKYY6jWa4d2nTQVIEhZIptnrVb1XY=
|
||||
golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f/go.mod h1:/lliqkxwWAhPjf5oSOIJup2XcqJaw8RGS6k3TGEc7GI=
|
||||
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/exp v0.0.0-20240529005216-23cca8864a10 h1:vpzMC/iZhYFAjJzHU0Cfuq+w1vLLsF2vLkDrPjzKYck=
|
||||
golang.org/x/exp v0.0.0-20240529005216-23cca8864a10/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
@@ -317,8 +318,8 @@ golang.org/x/net v0.13.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
|
||||
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
|
||||
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
||||
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
|
||||
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
|
||||
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
|
||||
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@@ -364,8 +365,8 @@ golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
|
||||
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
@@ -387,25 +388,26 @@ golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
|
||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
||||
golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps=
|
||||
golang.org/x/tools v0.20.0 h1:hz/CVckiOxybQvFw6h7b/q80NTr9IUQb4s1IIzW7KNY=
|
||||
golang.org/x/tools v0.20.0/go.mod h1:WvitBU7JJf6A4jOdg4S1tviW9bhUxkgeCui/0JHctQg=
|
||||
golang.org/x/tools v0.21.0 h1:qc0xYgIbsSDt9EyWz05J5wfa7LOVW0YTLOXrqdLAWIw=
|
||||
golang.org/x/tools v0.21.0/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240415180920-8c6c420018be h1:LG9vZxsWGOmUKieR8wPAUR3u3MpnYFQZROPIMaXh7/A=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240415180920-8c6c420018be/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY=
|
||||
google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM=
|
||||
google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA=
|
||||
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
|
||||
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240521202816-d264139d666e h1:Elxv5MwEkCI9f5SkoL6afed6NTdxaGoAo39eANBwHL8=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240521202816-d264139d666e/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0=
|
||||
google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY=
|
||||
google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg=
|
||||
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
|
||||
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
|
||||
+2
-1
@@ -27,6 +27,7 @@ import (
|
||||
"github.com/livekit/protocol/logger"
|
||||
"github.com/livekit/protocol/rpc"
|
||||
"github.com/livekit/protocol/utils"
|
||||
"github.com/livekit/protocol/utils/guid"
|
||||
"github.com/livekit/psrpc"
|
||||
)
|
||||
|
||||
@@ -121,7 +122,7 @@ func (c *agentClient) LaunchJob(ctx context.Context, desc *JobDescription) {
|
||||
target.ForEach(func(ns string) {
|
||||
c.workers.Submit(func() {
|
||||
_, err := c.client.JobRequest(ctx, ns, jobTypeTopic, &livekit.Job{
|
||||
Id: utils.NewGuid(utils.AgentJobPrefix),
|
||||
Id: guid.New(utils.AgentJobPrefix),
|
||||
Type: desc.JobType,
|
||||
Room: desc.Room,
|
||||
Participant: desc.Participant,
|
||||
|
||||
+2
-1
@@ -28,6 +28,7 @@ import (
|
||||
"github.com/livekit/protocol/logger"
|
||||
"github.com/livekit/protocol/utils"
|
||||
putil "github.com/livekit/protocol/utils"
|
||||
"github.com/livekit/protocol/utils/guid"
|
||||
)
|
||||
|
||||
type WorkerProtocolVersion int
|
||||
@@ -353,7 +354,7 @@ func (w *Worker) handleSimulateJob(simulate *livekit.SimulateJobRequest) {
|
||||
}
|
||||
|
||||
job := &livekit.Job{
|
||||
Id: utils.NewGuid(utils.AgentJobPrefix),
|
||||
Id: guid.New(utils.AgentJobPrefix),
|
||||
Type: jobType,
|
||||
Room: simulate.Room,
|
||||
Participant: simulate.Participant,
|
||||
|
||||
+23
-11
@@ -117,6 +117,8 @@ type RTCConfig struct {
|
||||
|
||||
// max number of bytes to buffer for data channel. 0 means unlimited
|
||||
DataChannelMaxBufferedAmount uint64 `yaml:"data_channel_max_buffered_amount,omitempty"`
|
||||
|
||||
ForwardStats ForwardStatsConfig `yaml:"forward_stats,omitempty"`
|
||||
}
|
||||
|
||||
type TURNServer struct {
|
||||
@@ -229,15 +231,17 @@ type VideoConfig struct {
|
||||
|
||||
type RoomConfig struct {
|
||||
// enable rooms to be automatically created
|
||||
AutoCreate bool `yaml:"auto_create,omitempty"`
|
||||
EnabledCodecs []CodecSpec `yaml:"enabled_codecs,omitempty"`
|
||||
MaxParticipants uint32 `yaml:"max_participants,omitempty"`
|
||||
EmptyTimeout uint32 `yaml:"empty_timeout,omitempty"`
|
||||
DepartureTimeout uint32 `yaml:"departure_timeout,omitempty"`
|
||||
EnableRemoteUnmute bool `yaml:"enable_remote_unmute,omitempty"`
|
||||
MaxMetadataSize uint32 `yaml:"max_metadata_size,omitempty"`
|
||||
PlayoutDelay PlayoutDelayConfig `yaml:"playout_delay,omitempty"`
|
||||
SyncStreams bool `yaml:"sync_streams,omitempty"`
|
||||
AutoCreate bool `yaml:"auto_create,omitempty"`
|
||||
EnabledCodecs []CodecSpec `yaml:"enabled_codecs,omitempty"`
|
||||
MaxParticipants uint32 `yaml:"max_participants,omitempty"`
|
||||
EmptyTimeout uint32 `yaml:"empty_timeout,omitempty"`
|
||||
DepartureTimeout uint32 `yaml:"departure_timeout,omitempty"`
|
||||
EnableRemoteUnmute bool `yaml:"enable_remote_unmute,omitempty"`
|
||||
MaxMetadataSize uint32 `yaml:"max_metadata_size,omitempty"`
|
||||
PlayoutDelay PlayoutDelayConfig `yaml:"playout_delay,omitempty"`
|
||||
SyncStreams bool `yaml:"sync_streams,omitempty"`
|
||||
MaxRoomNameLength int `yaml:"max_room_name_length,omitempty"`
|
||||
MaxParticipantIdentityLength int `yaml:"max_participant_identity_length,omitempty"`
|
||||
}
|
||||
|
||||
type CodecSpec struct {
|
||||
@@ -316,6 +320,12 @@ type APIConfig struct {
|
||||
MaxCheckInterval time.Duration `yaml:"max_check_interval,omitempty"`
|
||||
}
|
||||
|
||||
type ForwardStatsConfig struct {
|
||||
SummaryInterval time.Duration `yaml:"summary_interval,omitempty"`
|
||||
ReportInterval time.Duration `yaml:"report_interval,omitempty"`
|
||||
ReportWindow time.Duration `yaml:"report_window,omitempty"`
|
||||
}
|
||||
|
||||
func DefaultAPIConfig() APIConfig {
|
||||
return APIConfig{
|
||||
ExecutionTimeout: 2 * time.Second,
|
||||
@@ -484,8 +494,10 @@ var DefaultConfig = Config{
|
||||
{Mime: webrtc.MimeTypeVP9},
|
||||
{Mime: webrtc.MimeTypeAV1},
|
||||
},
|
||||
EmptyTimeout: 5 * 60,
|
||||
DepartureTimeout: 20,
|
||||
EmptyTimeout: 5 * 60,
|
||||
DepartureTimeout: 20,
|
||||
MaxRoomNameLength: 256,
|
||||
MaxParticipantIdentityLength: 256,
|
||||
},
|
||||
Logging: LoggingConfig{
|
||||
PionLevel: "error",
|
||||
|
||||
+2
-1
@@ -20,6 +20,7 @@ import (
|
||||
|
||||
"github.com/livekit/protocol/livekit"
|
||||
"github.com/livekit/protocol/utils"
|
||||
"github.com/livekit/protocol/utils/guid"
|
||||
|
||||
"github.com/livekit/livekit-server/pkg/config"
|
||||
)
|
||||
@@ -27,7 +28,7 @@ import (
|
||||
type LocalNode *livekit.Node
|
||||
|
||||
func NewLocalNode(conf *config.Config) (LocalNode, error) {
|
||||
nodeID := utils.NewGuid(utils.NodePrefix)
|
||||
nodeID := guid.New(utils.NodePrefix)
|
||||
if conf.RTC.NodeIP == "" {
|
||||
return nil, ErrIPNotSet
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
|
||||
"github.com/livekit/protocol/livekit"
|
||||
"github.com/livekit/protocol/utils"
|
||||
"github.com/livekit/protocol/utils/guid"
|
||||
|
||||
"github.com/livekit/livekit-server/pkg/config"
|
||||
"github.com/livekit/livekit-server/pkg/routing/selector"
|
||||
@@ -152,7 +153,7 @@ func newTestNodeInRegion(region string, available bool) *livekit.Node {
|
||||
load = 1.0
|
||||
}
|
||||
return &livekit.Node{
|
||||
Id: utils.NewGuid(utils.NodePrefix),
|
||||
Id: guid.New(utils.NodePrefix),
|
||||
Region: region,
|
||||
State: livekit.NodeState_SERVING,
|
||||
Stats: &livekit.NodeStats{
|
||||
|
||||
@@ -28,7 +28,7 @@ import (
|
||||
"github.com/livekit/protocol/livekit"
|
||||
"github.com/livekit/protocol/logger"
|
||||
"github.com/livekit/protocol/rpc"
|
||||
"github.com/livekit/protocol/utils"
|
||||
"github.com/livekit/protocol/utils/guid"
|
||||
"github.com/livekit/psrpc"
|
||||
"github.com/livekit/psrpc/pkg/middleware"
|
||||
)
|
||||
@@ -84,7 +84,7 @@ func (r *signalClient) StartParticipantSignal(
|
||||
resSource MessageSource,
|
||||
err error,
|
||||
) {
|
||||
connectionID = livekit.ConnectionID(utils.NewGuid("CO_"))
|
||||
connectionID = livekit.ConnectionID(guid.New("CO_"))
|
||||
ss, err := pi.ToStartSession(roomName, connectionID)
|
||||
if err != nil {
|
||||
return
|
||||
|
||||
@@ -70,6 +70,7 @@ type MediaTrackParams struct {
|
||||
Logger logger.Logger
|
||||
SimTracks map[uint32]SimulcastTrackInfo
|
||||
OnRTCP func([]rtcp.Packet)
|
||||
ForwardStats *sfu.ForwardStats
|
||||
}
|
||||
|
||||
func NewMediaTrack(params MediaTrackParams, ti *livekit.TrackInfo) *MediaTrack {
|
||||
@@ -281,6 +282,7 @@ func (t *MediaTrack) AddReceiver(receiver *webrtc.RTPReceiver, track *webrtc.Tra
|
||||
sfu.WithAudioConfig(t.params.AudioConfig),
|
||||
sfu.WithLoadBalanceThreshold(20),
|
||||
sfu.WithStreamTrackers(),
|
||||
sfu.WithForwardStats(t.params.ForwardStats),
|
||||
)
|
||||
newWR.OnCloseHandler(func() {
|
||||
t.MediaTrackReceiver.SetClosing()
|
||||
|
||||
@@ -469,7 +469,7 @@ func (t *MediaTrackReceiver) AddSubscriber(sub types.LocalParticipant) (types.Su
|
||||
codec := receiver.Codec()
|
||||
var found bool
|
||||
for _, pc := range potentialCodecs {
|
||||
if codec.MimeType == pc.MimeType {
|
||||
if strings.EqualFold(codec.MimeType, pc.MimeType) {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
@@ -673,20 +673,29 @@ func (t *MediaTrackReceiver) UpdateAudioTrack(update *livekit.UpdateLocalAudioTr
|
||||
}
|
||||
|
||||
t.lock.Lock()
|
||||
t.trackInfo.AudioFeatures = update.Features
|
||||
t.trackInfo.Stereo = false
|
||||
t.trackInfo.DisableDtx = false
|
||||
clonedInfo := proto.Clone(t.trackInfo).(*livekit.TrackInfo)
|
||||
clonedInfo.AudioFeatures = update.Features
|
||||
clonedInfo.Stereo = false
|
||||
clonedInfo.DisableDtx = false
|
||||
for _, feature := range update.Features {
|
||||
switch feature {
|
||||
case livekit.AudioTrackFeature_TF_STEREO:
|
||||
t.trackInfo.Stereo = true
|
||||
clonedInfo.Stereo = true
|
||||
case livekit.AudioTrackFeature_TF_NO_DTX:
|
||||
t.trackInfo.DisableDtx = true
|
||||
clonedInfo.DisableDtx = true
|
||||
}
|
||||
}
|
||||
if proto.Equal(t.trackInfo, clonedInfo) {
|
||||
t.lock.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
t.trackInfo = clonedInfo
|
||||
t.lock.Unlock()
|
||||
|
||||
t.updateTrackInfoOfReceivers()
|
||||
|
||||
t.params.Telemetry.TrackPublishedUpdate(context.Background(), t.PublisherID(), clonedInfo)
|
||||
}
|
||||
|
||||
func (t *MediaTrackReceiver) UpdateVideoTrack(update *livekit.UpdateLocalVideoTrack) {
|
||||
@@ -695,11 +704,20 @@ func (t *MediaTrackReceiver) UpdateVideoTrack(update *livekit.UpdateLocalVideoTr
|
||||
}
|
||||
|
||||
t.lock.Lock()
|
||||
t.trackInfo.Width = update.Width
|
||||
t.trackInfo.Height = update.Height
|
||||
clonedInfo := proto.Clone(t.trackInfo).(*livekit.TrackInfo)
|
||||
clonedInfo.Width = update.Width
|
||||
clonedInfo.Height = update.Height
|
||||
if proto.Equal(t.trackInfo, clonedInfo) {
|
||||
t.lock.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
t.trackInfo = clonedInfo
|
||||
t.lock.Unlock()
|
||||
|
||||
t.updateTrackInfoOfReceivers()
|
||||
|
||||
t.params.Telemetry.TrackPublishedUpdate(context.Background(), t.PublisherID(), clonedInfo)
|
||||
}
|
||||
|
||||
func (t *MediaTrackReceiver) TrackInfo() *livekit.TrackInfo {
|
||||
@@ -709,11 +727,15 @@ func (t *MediaTrackReceiver) TrackInfo() *livekit.TrackInfo {
|
||||
return t.trackInfo
|
||||
}
|
||||
|
||||
func (t *MediaTrackReceiver) trackInfoCloneLocked() *livekit.TrackInfo {
|
||||
return proto.Clone(t.trackInfo).(*livekit.TrackInfo)
|
||||
}
|
||||
|
||||
func (t *MediaTrackReceiver) TrackInfoClone() *livekit.TrackInfo {
|
||||
t.lock.RLock()
|
||||
defer t.lock.RUnlock()
|
||||
|
||||
return proto.Clone(t.trackInfo).(*livekit.TrackInfo)
|
||||
return t.trackInfoCloneLocked()
|
||||
}
|
||||
|
||||
func (t *MediaTrackReceiver) NotifyMaxLayerChange(maxLayer int32) {
|
||||
|
||||
@@ -16,6 +16,7 @@ package rtc
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/pion/rtcp"
|
||||
@@ -258,7 +259,7 @@ func (t *MediaTrackSubscriptions) AddSubscriber(sub types.LocalParticipant, wr *
|
||||
Stereo: info.Stereo,
|
||||
Red: !info.DisableRed,
|
||||
}
|
||||
if addTrackParams.Red && (len(codecs) == 1 && codecs[0].MimeType == webrtc.MimeTypeOpus) {
|
||||
if addTrackParams.Red && (len(codecs) == 1 && strings.EqualFold(codecs[0].MimeType, webrtc.MimeTypeOpus)) {
|
||||
addTrackParams.Red = false
|
||||
}
|
||||
|
||||
@@ -288,6 +289,20 @@ func (t *MediaTrackSubscriptions) AddSubscriber(sub types.LocalParticipant, wr *
|
||||
// negotiation isn't required if we've replaced track
|
||||
subTrack.SetNeedsNegotiation(!replacedTrack)
|
||||
subTrack.SetRTPSender(sender)
|
||||
// it is possible that subscribed track is closed before subscription manager sets
|
||||
// the `OnClose` callback. That handler in subscription manager removes the track
|
||||
// from the peer connection.
|
||||
//
|
||||
// But, the subscription could be removed early if the published track is closed
|
||||
// while adding subscription. In those cases, subscription manager would not have set
|
||||
// the `OnClose` callback. So, set it here to handle cases of early close.
|
||||
subTrack.OnClose(func(willBeResumed bool) {
|
||||
if !willBeResumed {
|
||||
if err := sub.RemoveTrackFromSubscriber(sender); err != nil {
|
||||
t.params.Logger.Warnw("could not remove track from peer connection", err)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
downTrack.SetTransceiver(transceiver)
|
||||
|
||||
@@ -329,14 +344,6 @@ func (t *MediaTrackSubscriptions) closeSubscribedTrack(subTrack types.Subscribed
|
||||
}
|
||||
}
|
||||
|
||||
func (t *MediaTrackSubscriptions) ResyncAllSubscribers() {
|
||||
t.params.Logger.Debugw("resyncing all subscribers")
|
||||
|
||||
for _, subTrack := range t.getAllSubscribedTracks() {
|
||||
subTrack.DownTrack().Resync()
|
||||
}
|
||||
}
|
||||
|
||||
func (t *MediaTrackSubscriptions) GetAllSubscribers() []livekit.ParticipantID {
|
||||
t.subscribedTracksMu.RLock()
|
||||
defer t.subscribedTracksMu.RUnlock()
|
||||
@@ -354,7 +361,7 @@ func (t *MediaTrackSubscriptions) GetAllSubscribersForMime(mime string) []liveki
|
||||
|
||||
subs := make([]livekit.ParticipantID, 0, len(t.subscribedTracks))
|
||||
for id, subTrack := range t.subscribedTracks {
|
||||
if subTrack.DownTrack().Codec().MimeType != mime {
|
||||
if !strings.EqualFold(subTrack.DownTrack().Codec().MimeType, mime) {
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
+36
-57
@@ -38,6 +38,7 @@ import (
|
||||
"github.com/livekit/protocol/livekit"
|
||||
"github.com/livekit/protocol/logger"
|
||||
"github.com/livekit/protocol/utils"
|
||||
"github.com/livekit/protocol/utils/guid"
|
||||
|
||||
"github.com/livekit/livekit-server/pkg/config"
|
||||
"github.com/livekit/livekit-server/pkg/routing"
|
||||
@@ -140,6 +141,7 @@ type ParticipantParams struct {
|
||||
SubscriptionLimitVideo int32
|
||||
PlayoutDelay *livekit.PlayoutDelay
|
||||
SyncStreams bool
|
||||
ForwardStats *sfu.ForwardStats
|
||||
}
|
||||
|
||||
type ParticipantImpl struct {
|
||||
@@ -158,8 +160,7 @@ type ParticipantImpl struct {
|
||||
resSinkMu sync.Mutex
|
||||
resSink routing.MessageSink
|
||||
|
||||
grants *auth.ClaimGrants
|
||||
hidden atomic.Bool
|
||||
grants atomic.Pointer[auth.ClaimGrants]
|
||||
isPublisher atomic.Bool
|
||||
|
||||
sessionStartRecorded atomic.Bool
|
||||
@@ -186,7 +187,6 @@ type ParticipantImpl struct {
|
||||
*TransportManager
|
||||
*UpTrackManager
|
||||
*SubscriptionManager
|
||||
*ParticipantTrafficLoad
|
||||
|
||||
// keeps track of unpublished tracks in order to reuse trackID
|
||||
unpublishedTracks []*livekit.TrackInfo
|
||||
@@ -275,8 +275,7 @@ func NewParticipant(params ParticipantParams) (*ParticipantImpl, error) {
|
||||
p.timedVersion.Update(params.VersionGenerator.Next())
|
||||
p.migrateState.Store(types.MigrateStateInit)
|
||||
p.state.Store(livekit.ParticipantInfo_JOINING)
|
||||
p.grants = params.Grants
|
||||
p.hidden.Store(p.grants.Video.Hidden)
|
||||
p.grants.Store(params.Grants)
|
||||
p.SetResponseSink(params.Sink)
|
||||
p.setupEnabledCodecs(params.PublishEnabledCodecs, params.SubscribeEnabledCodecs, params.ClientConf.GetDisabledCodecs())
|
||||
|
||||
@@ -297,7 +296,6 @@ func NewParticipant(params ParticipantParams) (*ParticipantImpl, error) {
|
||||
|
||||
p.setupUpTrackManager()
|
||||
p.setupSubscriptionManager()
|
||||
p.setupParticipantTrafficLoad()
|
||||
|
||||
return p, nil
|
||||
}
|
||||
@@ -333,28 +331,21 @@ func (p *ParticipantImpl) State() livekit.ParticipantInfo_State {
|
||||
}
|
||||
|
||||
func (p *ParticipantImpl) Kind() livekit.ParticipantInfo_Kind {
|
||||
p.lock.RLock()
|
||||
defer p.lock.RUnlock()
|
||||
|
||||
return p.grants.GetParticipantKind()
|
||||
return p.grants.Load().GetParticipantKind()
|
||||
}
|
||||
|
||||
func (p *ParticipantImpl) IsRecorder() bool {
|
||||
p.lock.RLock()
|
||||
defer p.lock.RUnlock()
|
||||
|
||||
return p.grants.GetParticipantKind() == livekit.ParticipantInfo_EGRESS || p.grants.Video.Recorder
|
||||
grants := p.grants.Load()
|
||||
return grants.GetParticipantKind() == livekit.ParticipantInfo_EGRESS || grants.Video.Recorder
|
||||
}
|
||||
|
||||
func (p *ParticipantImpl) IsDependent() bool {
|
||||
p.lock.RLock()
|
||||
defer p.lock.RUnlock()
|
||||
|
||||
switch p.grants.GetParticipantKind() {
|
||||
grants := p.grants.Load()
|
||||
switch grants.GetParticipantKind() {
|
||||
case livekit.ParticipantInfo_AGENT, livekit.ParticipantInfo_EGRESS:
|
||||
return true
|
||||
default:
|
||||
return p.grants.Video.Agent || p.grants.Video.Recorder
|
||||
return grants.Video.Agent || grants.Video.Recorder
|
||||
}
|
||||
}
|
||||
|
||||
@@ -417,12 +408,15 @@ func (p *ParticipantImpl) GetBufferFactory() *buffer.Factory {
|
||||
// SetName attaches name to the participant
|
||||
func (p *ParticipantImpl) SetName(name string) {
|
||||
p.lock.Lock()
|
||||
if p.grants.Name == name {
|
||||
grants := p.grants.Load()
|
||||
if grants.Name == name {
|
||||
p.lock.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
p.grants.Name = name
|
||||
grants = grants.Clone()
|
||||
grants.Name = name
|
||||
p.grants.Store(grants)
|
||||
p.dirty.Store(true)
|
||||
|
||||
onParticipantUpdate := p.onParticipantUpdate
|
||||
@@ -440,12 +434,15 @@ func (p *ParticipantImpl) SetName(name string) {
|
||||
// SetMetadata attaches metadata to the participant
|
||||
func (p *ParticipantImpl) SetMetadata(metadata string) {
|
||||
p.lock.Lock()
|
||||
if p.grants.Metadata == metadata {
|
||||
grants := p.grants.Load()
|
||||
if grants.Metadata == metadata {
|
||||
p.lock.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
p.grants.Metadata = metadata
|
||||
grants = grants.Clone()
|
||||
grants.Metadata = metadata
|
||||
p.grants.Store(grants)
|
||||
p.requireBroadcast = p.requireBroadcast || metadata != ""
|
||||
p.dirty.Store(true)
|
||||
|
||||
@@ -462,10 +459,7 @@ func (p *ParticipantImpl) SetMetadata(metadata string) {
|
||||
}
|
||||
|
||||
func (p *ParticipantImpl) ClaimGrants() *auth.ClaimGrants {
|
||||
p.lock.RLock()
|
||||
defer p.lock.RUnlock()
|
||||
|
||||
return p.grants.Clone()
|
||||
return p.grants.Load()
|
||||
}
|
||||
|
||||
func (p *ParticipantImpl) SetPermission(permission *livekit.ParticipantPermission) bool {
|
||||
@@ -473,21 +467,22 @@ func (p *ParticipantImpl) SetPermission(permission *livekit.ParticipantPermissio
|
||||
return false
|
||||
}
|
||||
p.lock.Lock()
|
||||
video := p.grants.Video
|
||||
grants := p.grants.Load()
|
||||
|
||||
if video.MatchesPermission(permission) {
|
||||
if grants.Video.MatchesPermission(permission) {
|
||||
p.lock.Unlock()
|
||||
return false
|
||||
}
|
||||
|
||||
p.params.Logger.Infow("updating participant permission", "permission", permission)
|
||||
|
||||
video.UpdateFromPermission(permission)
|
||||
p.hidden.Store(permission.Hidden)
|
||||
grants = grants.Clone()
|
||||
grants.Video.UpdateFromPermission(permission)
|
||||
p.grants.Store(grants)
|
||||
p.dirty.Store(true)
|
||||
|
||||
canPublish := video.GetCanPublish()
|
||||
canSubscribe := video.GetCanSubscribe()
|
||||
canPublish := grants.Video.GetCanPublish()
|
||||
canSubscribe := grants.Video.GetCanSubscribe()
|
||||
|
||||
onParticipantUpdate := p.onParticipantUpdate
|
||||
onClaimsChanged := p.onClaimsChanged
|
||||
@@ -498,7 +493,7 @@ func (p *ParticipantImpl) SetPermission(permission *livekit.ParticipantPermissio
|
||||
|
||||
// publish permission has been revoked then remove offending tracks
|
||||
for _, track := range p.GetPublishedTracks() {
|
||||
if !video.GetCanPublishSource(track.Source()) {
|
||||
if !grants.Video.GetCanPublishSource(track.Source()) {
|
||||
p.removePublishedTrack(track)
|
||||
}
|
||||
}
|
||||
@@ -541,8 +536,8 @@ func (p *ParticipantImpl) ToProtoWithVersion() (*livekit.ParticipantInfo, utils.
|
||||
p.lock.Unlock()
|
||||
}
|
||||
|
||||
grants := p.ClaimGrants()
|
||||
p.lock.RLock()
|
||||
grants := p.grants.Load()
|
||||
v := p.version.Load()
|
||||
piv := p.timedVersion
|
||||
|
||||
@@ -868,7 +863,6 @@ func (p *ParticipantImpl) Close(sendLeave bool, reason types.ParticipantCloseRea
|
||||
go func() {
|
||||
p.SubscriptionManager.Close(isExpectedToResume)
|
||||
p.TransportManager.Close()
|
||||
p.ParticipantTrafficLoad.Close()
|
||||
}()
|
||||
|
||||
p.dataChannelStats.Stop()
|
||||
@@ -1104,27 +1098,19 @@ func (p *ParticipantImpl) IsPublisher() bool {
|
||||
}
|
||||
|
||||
func (p *ParticipantImpl) CanPublishSource(source livekit.TrackSource) bool {
|
||||
p.lock.RLock()
|
||||
defer p.lock.RUnlock()
|
||||
return p.grants.Video.GetCanPublishSource(source)
|
||||
return p.grants.Load().Video.GetCanPublishSource(source)
|
||||
}
|
||||
|
||||
func (p *ParticipantImpl) CanSubscribe() bool {
|
||||
p.lock.RLock()
|
||||
defer p.lock.RUnlock()
|
||||
|
||||
return p.grants.Video.GetCanSubscribe()
|
||||
return p.grants.Load().Video.GetCanSubscribe()
|
||||
}
|
||||
|
||||
func (p *ParticipantImpl) CanPublishData() bool {
|
||||
p.lock.RLock()
|
||||
defer p.lock.RUnlock()
|
||||
|
||||
return p.grants.Video.GetCanPublishData()
|
||||
return p.grants.Load().Video.GetCanPublishData()
|
||||
}
|
||||
|
||||
func (p *ParticipantImpl) Hidden() bool {
|
||||
return p.hidden.Load()
|
||||
return p.grants.Load().Video.Hidden
|
||||
}
|
||||
|
||||
func (p *ParticipantImpl) VerifySubscribeParticipantInfo(pID livekit.ParticipantID, version uint32) {
|
||||
@@ -1382,14 +1368,6 @@ func (p *ParticipantImpl) setupSubscriptionManager() {
|
||||
})
|
||||
}
|
||||
|
||||
func (p *ParticipantImpl) setupParticipantTrafficLoad() {
|
||||
p.ParticipantTrafficLoad = NewParticipantTrafficLoad(ParticipantTrafficLoadParams{
|
||||
Participant: p,
|
||||
DataChannelStats: p.dataChannelStats,
|
||||
Logger: p.params.Logger,
|
||||
})
|
||||
}
|
||||
|
||||
func (p *ParticipantImpl) updateState(state livekit.ParticipantInfo_State) {
|
||||
oldState := p.state.Swap(state).(livekit.ParticipantInfo_State)
|
||||
if oldState == state {
|
||||
@@ -2121,6 +2099,7 @@ func (p *ParticipantImpl) addMediaTrack(signalCid string, sdpCid string, ti *liv
|
||||
PLIThrottleConfig: p.params.PLIThrottleConfig,
|
||||
SimTracks: p.params.SimTracks,
|
||||
OnRTCP: p.postRtcp,
|
||||
ForwardStats: p.params.ForwardStats,
|
||||
}, ti)
|
||||
|
||||
mt.OnSubscribedMaxQualityChange(p.onSubscribedMaxQualityChange)
|
||||
@@ -2319,7 +2298,7 @@ func (p *ParticipantImpl) setStableTrackID(cid string, info *livekit.TrackInfo)
|
||||
case livekit.TrackSource_SCREEN_SHARE_AUDIO:
|
||||
trackPrefix += "s"
|
||||
}
|
||||
trackID = utils.NewGuid(trackPrefix)
|
||||
trackID = guid.New(trackPrefix)
|
||||
}
|
||||
info.Sid = trackID
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ import (
|
||||
"github.com/livekit/protocol/livekit"
|
||||
"github.com/livekit/protocol/logger"
|
||||
"github.com/livekit/protocol/utils"
|
||||
"github.com/livekit/protocol/utils/guid"
|
||||
|
||||
"github.com/livekit/livekit-server/pkg/config"
|
||||
"github.com/livekit/livekit-server/pkg/routing"
|
||||
@@ -749,7 +750,7 @@ func newParticipantForTestWithOpts(identity livekit.ParticipantIdentity, opts *p
|
||||
FmtpLine: c.FmtpLine,
|
||||
})
|
||||
}
|
||||
sid := livekit.ParticipantID(utils.NewGuid(utils.ParticipantPrefix))
|
||||
sid := livekit.ParticipantID(guid.New(utils.ParticipantPrefix))
|
||||
p, _ := NewParticipant(ParticipantParams{
|
||||
SID: sid,
|
||||
Identity: identity,
|
||||
|
||||
@@ -314,9 +314,7 @@ func (p *ParticipantImpl) writeMessage(msg *livekit.SignalResponse) error {
|
||||
func (p *ParticipantImpl) CloseSignalConnection(reason types.SignallingCloseReason) {
|
||||
sink := p.getResponseSink()
|
||||
if sink != nil {
|
||||
if reason != types.SignallingCloseReasonParticipantClose {
|
||||
p.params.Logger.Infow("closing signal connection", "reason", reason, "connID", sink.ConnectionID())
|
||||
}
|
||||
p.params.Logger.Debugw("closing signal connection", "reason", reason, "connID", sink.ConnectionID())
|
||||
sink.Close()
|
||||
p.SetResponseSink(nil)
|
||||
}
|
||||
|
||||
@@ -1,211 +0,0 @@
|
||||
// Copyright 2023 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 rtc
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/frostbyte73/core"
|
||||
"github.com/livekit/protocol/livekit"
|
||||
"github.com/livekit/protocol/logger"
|
||||
|
||||
"github.com/livekit/livekit-server/pkg/rtc/types"
|
||||
"github.com/livekit/livekit-server/pkg/telemetry"
|
||||
)
|
||||
|
||||
const (
|
||||
reportInterval = 10 * time.Second
|
||||
)
|
||||
|
||||
type ParticipantTrafficLoadParams struct {
|
||||
Participant *ParticipantImpl
|
||||
DataChannelStats *telemetry.BytesTrackStats
|
||||
Logger logger.Logger
|
||||
}
|
||||
|
||||
type ParticipantTrafficLoad struct {
|
||||
params ParticipantTrafficLoadParams
|
||||
|
||||
lock sync.RWMutex
|
||||
onTrafficLoad func(trafficLoad *types.TrafficLoad)
|
||||
tracksStatsMedia map[livekit.TrackID]*livekit.RTPStats
|
||||
dataChannelTraffic *telemetry.TrafficTotals
|
||||
trafficLoad *types.TrafficLoad
|
||||
|
||||
closed core.Fuse
|
||||
}
|
||||
|
||||
func NewParticipantTrafficLoad(params ParticipantTrafficLoadParams) *ParticipantTrafficLoad {
|
||||
p := &ParticipantTrafficLoad{
|
||||
params: params,
|
||||
tracksStatsMedia: make(map[livekit.TrackID]*livekit.RTPStats),
|
||||
}
|
||||
go p.reporter()
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *ParticipantTrafficLoad) Close() {
|
||||
p.closed.Break()
|
||||
}
|
||||
|
||||
func (p *ParticipantTrafficLoad) OnTrafficLoad(f func(trafficLoad *types.TrafficLoad)) {
|
||||
p.lock.Lock()
|
||||
p.onTrafficLoad = f
|
||||
p.lock.Unlock()
|
||||
}
|
||||
|
||||
func (p *ParticipantTrafficLoad) getOnTrafficLoad() func(trafficLoad *types.TrafficLoad) {
|
||||
p.lock.RLock()
|
||||
defer p.lock.RUnlock()
|
||||
|
||||
return p.onTrafficLoad
|
||||
}
|
||||
|
||||
func (p *ParticipantTrafficLoad) GetTrafficLoad() *types.TrafficLoad {
|
||||
p.lock.RLock()
|
||||
defer p.lock.RUnlock()
|
||||
|
||||
return p.trafficLoad
|
||||
}
|
||||
|
||||
func (p *ParticipantTrafficLoad) updateTrafficLoad() *types.TrafficLoad {
|
||||
publishedTracks := p.params.Participant.GetPublishedTracks()
|
||||
subscribedTracks := p.params.Participant.SubscriptionManager.GetSubscribedTracks()
|
||||
|
||||
availableTracks := make(map[livekit.TrackID]bool, len(publishedTracks)+len(subscribedTracks))
|
||||
|
||||
upstreamAudioStats := make([]*types.TrafficStats, 0, len(publishedTracks))
|
||||
upstreamVideoStats := make([]*types.TrafficStats, 0, len(publishedTracks))
|
||||
|
||||
downstreamAudioStats := make([]*types.TrafficStats, 0, len(subscribedTracks))
|
||||
downstreamVideoStats := make([]*types.TrafficStats, 0, len(subscribedTracks))
|
||||
|
||||
p.lock.Lock()
|
||||
defer p.lock.Unlock()
|
||||
for _, pt := range publishedTracks {
|
||||
lmt, ok := pt.(types.LocalMediaTrack)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
trackID := lmt.ID()
|
||||
stats := lmt.GetTrackStats()
|
||||
trafficStats := types.RTPStatsDiffToTrafficStats(p.tracksStatsMedia[trackID], stats)
|
||||
if stats != nil {
|
||||
p.tracksStatsMedia[trackID] = stats
|
||||
availableTracks[trackID] = true
|
||||
}
|
||||
if trafficStats != nil {
|
||||
switch lmt.Kind() {
|
||||
case livekit.TrackType_AUDIO:
|
||||
upstreamAudioStats = append(upstreamAudioStats, trafficStats)
|
||||
case livekit.TrackType_VIDEO:
|
||||
upstreamVideoStats = append(upstreamVideoStats, trafficStats)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, st := range subscribedTracks {
|
||||
trackID := st.ID()
|
||||
stats := st.DownTrack().GetTrackStats()
|
||||
trafficStats := types.RTPStatsDiffToTrafficStats(p.tracksStatsMedia[trackID], stats)
|
||||
if stats != nil {
|
||||
p.tracksStatsMedia[trackID] = stats
|
||||
availableTracks[trackID] = true
|
||||
}
|
||||
if trafficStats != nil {
|
||||
switch st.MediaTrack().Kind() {
|
||||
case livekit.TrackType_AUDIO:
|
||||
downstreamAudioStats = append(downstreamAudioStats, trafficStats)
|
||||
case livekit.TrackType_VIDEO:
|
||||
downstreamVideoStats = append(downstreamVideoStats, trafficStats)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// remove unavailable tracks from track stats cache
|
||||
for trackID := range p.tracksStatsMedia {
|
||||
if !availableTracks[trackID] {
|
||||
delete(p.tracksStatsMedia, trackID)
|
||||
}
|
||||
}
|
||||
|
||||
trafficTypeStats := make([]*types.TrafficTypeStats, 0, 6)
|
||||
addTypeStats := func(statsList []*types.TrafficStats, trackType livekit.TrackType, streamType livekit.StreamType) {
|
||||
agg := types.AggregateTrafficStats(statsList...)
|
||||
if agg != nil {
|
||||
trafficTypeStats = append(trafficTypeStats, &types.TrafficTypeStats{
|
||||
TrackType: trackType,
|
||||
StreamType: streamType,
|
||||
TrafficStats: agg,
|
||||
})
|
||||
}
|
||||
}
|
||||
addTypeStats(upstreamAudioStats, livekit.TrackType_AUDIO, livekit.StreamType_UPSTREAM)
|
||||
addTypeStats(upstreamVideoStats, livekit.TrackType_VIDEO, livekit.StreamType_UPSTREAM)
|
||||
addTypeStats(downstreamAudioStats, livekit.TrackType_VIDEO, livekit.StreamType_DOWNSTREAM)
|
||||
addTypeStats(downstreamVideoStats, livekit.TrackType_VIDEO, livekit.StreamType_DOWNSTREAM)
|
||||
|
||||
if p.params.DataChannelStats != nil {
|
||||
dataChannelTraffic := p.params.DataChannelStats.GetTrafficTotals()
|
||||
if p.dataChannelTraffic != nil {
|
||||
trafficTypeStats = append(trafficTypeStats, &types.TrafficTypeStats{
|
||||
TrackType: livekit.TrackType_DATA,
|
||||
StreamType: livekit.StreamType_UPSTREAM,
|
||||
TrafficStats: &types.TrafficStats{
|
||||
StartTime: p.dataChannelTraffic.At,
|
||||
EndTime: dataChannelTraffic.At,
|
||||
Packets: dataChannelTraffic.RecvMessages - p.dataChannelTraffic.RecvMessages,
|
||||
Bytes: dataChannelTraffic.RecvBytes - p.dataChannelTraffic.RecvBytes,
|
||||
},
|
||||
})
|
||||
|
||||
trafficTypeStats = append(trafficTypeStats, &types.TrafficTypeStats{
|
||||
TrackType: livekit.TrackType_DATA,
|
||||
StreamType: livekit.StreamType_DOWNSTREAM,
|
||||
TrafficStats: &types.TrafficStats{
|
||||
StartTime: p.dataChannelTraffic.At,
|
||||
EndTime: dataChannelTraffic.At,
|
||||
Packets: dataChannelTraffic.SendMessages - p.dataChannelTraffic.SendMessages,
|
||||
Bytes: dataChannelTraffic.SendBytes - p.dataChannelTraffic.SendBytes,
|
||||
},
|
||||
})
|
||||
}
|
||||
p.dataChannelTraffic = dataChannelTraffic
|
||||
}
|
||||
|
||||
p.trafficLoad = &types.TrafficLoad{
|
||||
TrafficTypeStats: trafficTypeStats,
|
||||
}
|
||||
return p.trafficLoad
|
||||
}
|
||||
|
||||
func (p *ParticipantTrafficLoad) reporter() {
|
||||
ticker := time.NewTicker(reportInterval)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-p.closed.Watch():
|
||||
return
|
||||
|
||||
case <-ticker.C:
|
||||
trafficLoad := p.updateTrafficLoad()
|
||||
if onTrafficLoad := p.getOnTrafficLoad(); onTrafficLoad != nil {
|
||||
onTrafficLoad(trafficLoad)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
"github.com/livekit/protocol/livekit"
|
||||
"github.com/livekit/protocol/logger"
|
||||
"github.com/livekit/protocol/utils"
|
||||
"github.com/livekit/protocol/utils/guid"
|
||||
|
||||
"github.com/livekit/livekit-server/pkg/rtc/types"
|
||||
"github.com/livekit/livekit-server/pkg/rtc/types/typesfakes"
|
||||
@@ -25,7 +26,7 @@ import (
|
||||
|
||||
func NewMockParticipant(identity livekit.ParticipantIdentity, protocol types.ProtocolVersion, hidden bool, publisher bool) *typesfakes.FakeLocalParticipant {
|
||||
p := &typesfakes.FakeLocalParticipant{}
|
||||
sid := utils.NewGuid(utils.ParticipantPrefix)
|
||||
sid := guid.New(utils.ParticipantPrefix)
|
||||
p.IDReturns(livekit.ParticipantID(sid))
|
||||
p.IdentityReturns(identity)
|
||||
p.StateReturns(livekit.ParticipantInfo_JOINED)
|
||||
@@ -80,7 +81,7 @@ func NewMockParticipant(identity livekit.ParticipantIdentity, protocol types.Pro
|
||||
|
||||
func NewMockTrack(kind livekit.TrackType, name string) *typesfakes.FakeMediaTrack {
|
||||
t := &typesfakes.FakeMediaTrack{}
|
||||
t.IDReturns(livekit.TrackID(utils.NewGuid(utils.TrackPrefix)))
|
||||
t.IDReturns(livekit.TrackID(guid.New(utils.TrackPrefix)))
|
||||
t.KindReturns(kind)
|
||||
t.NameReturns(name)
|
||||
t.ToProtoReturns(&livekit.TrackInfo{
|
||||
|
||||
@@ -97,6 +97,7 @@ const (
|
||||
ParticipantCloseReasonSimulateMigration
|
||||
ParticipantCloseReasonSimulateNodeFailure
|
||||
ParticipantCloseReasonSimulateServerLeave
|
||||
ParticipantCloseReasonSimulateLeaveRequest
|
||||
ParticipantCloseReasonNegotiateFailed
|
||||
ParticipantCloseReasonMigrationRequested
|
||||
ParticipantCloseReasonPublicationError
|
||||
@@ -140,6 +141,8 @@ func (p ParticipantCloseReason) String() string {
|
||||
return "SIMULATE_NODE_FAILURE"
|
||||
case ParticipantCloseReasonSimulateServerLeave:
|
||||
return "SIMULATE_SERVER_LEAVE"
|
||||
case ParticipantCloseReasonSimulateLeaveRequest:
|
||||
return "SIMULATE_LEAVE_REQUEST"
|
||||
case ParticipantCloseReasonNegotiateFailed:
|
||||
return "NEGOTIATE_FAILED"
|
||||
case ParticipantCloseReasonMigrationRequested:
|
||||
@@ -161,7 +164,7 @@ func (p ParticipantCloseReason) String() string {
|
||||
|
||||
func (p ParticipantCloseReason) ToDisconnectReason() livekit.DisconnectReason {
|
||||
switch p {
|
||||
case ParticipantCloseReasonClientRequestLeave:
|
||||
case ParticipantCloseReasonClientRequestLeave, ParticipantCloseReasonSimulateLeaveRequest:
|
||||
return livekit.DisconnectReason_CLIENT_INITIATED
|
||||
case ParticipantCloseReasonRoomManagerStop:
|
||||
return livekit.DisconnectReason_SERVER_SHUTDOWN
|
||||
@@ -391,7 +394,6 @@ type LocalParticipant interface {
|
||||
OnSubscribeStatusChanged(fn func(publisherID livekit.ParticipantID, subscribed bool))
|
||||
OnClose(callback func(LocalParticipant))
|
||||
OnClaimsChanged(callback func(LocalParticipant))
|
||||
OnTrafficLoad(callback func(trafficLoad *TrafficLoad))
|
||||
|
||||
HandleReceiverReport(dt *sfu.DownTrack, report *rtcp.ReceiverReport)
|
||||
|
||||
@@ -420,8 +422,6 @@ type LocalParticipant interface {
|
||||
SetSubscriberChannelCapacity(channelCapacity int64)
|
||||
|
||||
GetPacer() pacer.Pacer
|
||||
|
||||
GetTrafficLoad() *TrafficLoad
|
||||
}
|
||||
|
||||
// Room is a container of participants, and can provide room-level actions
|
||||
|
||||
@@ -16,7 +16,7 @@ package types
|
||||
|
||||
type ProtocolVersion int
|
||||
|
||||
const CurrentProtocol = 13
|
||||
const CurrentProtocol = 14
|
||||
|
||||
func (v ProtocolVersion) SupportsPackedStreamId() bool {
|
||||
return v > 0
|
||||
|
||||
@@ -345,16 +345,6 @@ type FakeLocalParticipant struct {
|
||||
getSubscribedTracksReturnsOnCall map[int]struct {
|
||||
result1 []types.SubscribedTrack
|
||||
}
|
||||
GetTrafficLoadStub func() *types.TrafficLoad
|
||||
getTrafficLoadMutex sync.RWMutex
|
||||
getTrafficLoadArgsForCall []struct {
|
||||
}
|
||||
getTrafficLoadReturns struct {
|
||||
result1 *types.TrafficLoad
|
||||
}
|
||||
getTrafficLoadReturnsOnCall map[int]struct {
|
||||
result1 *types.TrafficLoad
|
||||
}
|
||||
GetTrailerStub func() []byte
|
||||
getTrailerMutex sync.RWMutex
|
||||
getTrailerArgsForCall []struct {
|
||||
@@ -636,11 +626,6 @@ type FakeLocalParticipant struct {
|
||||
onTrackUpdatedArgsForCall []struct {
|
||||
arg1 func(types.LocalParticipant, types.MediaTrack)
|
||||
}
|
||||
OnTrafficLoadStub func(func(trafficLoad *types.TrafficLoad))
|
||||
onTrafficLoadMutex sync.RWMutex
|
||||
onTrafficLoadArgsForCall []struct {
|
||||
arg1 func(trafficLoad *types.TrafficLoad)
|
||||
}
|
||||
ProtocolVersionStub func() types.ProtocolVersion
|
||||
protocolVersionMutex sync.RWMutex
|
||||
protocolVersionArgsForCall []struct {
|
||||
@@ -2722,59 +2707,6 @@ func (fake *FakeLocalParticipant) GetSubscribedTracksReturnsOnCall(i int, result
|
||||
}{result1}
|
||||
}
|
||||
|
||||
func (fake *FakeLocalParticipant) GetTrafficLoad() *types.TrafficLoad {
|
||||
fake.getTrafficLoadMutex.Lock()
|
||||
ret, specificReturn := fake.getTrafficLoadReturnsOnCall[len(fake.getTrafficLoadArgsForCall)]
|
||||
fake.getTrafficLoadArgsForCall = append(fake.getTrafficLoadArgsForCall, struct {
|
||||
}{})
|
||||
stub := fake.GetTrafficLoadStub
|
||||
fakeReturns := fake.getTrafficLoadReturns
|
||||
fake.recordInvocation("GetTrafficLoad", []interface{}{})
|
||||
fake.getTrafficLoadMutex.Unlock()
|
||||
if stub != nil {
|
||||
return stub()
|
||||
}
|
||||
if specificReturn {
|
||||
return ret.result1
|
||||
}
|
||||
return fakeReturns.result1
|
||||
}
|
||||
|
||||
func (fake *FakeLocalParticipant) GetTrafficLoadCallCount() int {
|
||||
fake.getTrafficLoadMutex.RLock()
|
||||
defer fake.getTrafficLoadMutex.RUnlock()
|
||||
return len(fake.getTrafficLoadArgsForCall)
|
||||
}
|
||||
|
||||
func (fake *FakeLocalParticipant) GetTrafficLoadCalls(stub func() *types.TrafficLoad) {
|
||||
fake.getTrafficLoadMutex.Lock()
|
||||
defer fake.getTrafficLoadMutex.Unlock()
|
||||
fake.GetTrafficLoadStub = stub
|
||||
}
|
||||
|
||||
func (fake *FakeLocalParticipant) GetTrafficLoadReturns(result1 *types.TrafficLoad) {
|
||||
fake.getTrafficLoadMutex.Lock()
|
||||
defer fake.getTrafficLoadMutex.Unlock()
|
||||
fake.GetTrafficLoadStub = nil
|
||||
fake.getTrafficLoadReturns = struct {
|
||||
result1 *types.TrafficLoad
|
||||
}{result1}
|
||||
}
|
||||
|
||||
func (fake *FakeLocalParticipant) GetTrafficLoadReturnsOnCall(i int, result1 *types.TrafficLoad) {
|
||||
fake.getTrafficLoadMutex.Lock()
|
||||
defer fake.getTrafficLoadMutex.Unlock()
|
||||
fake.GetTrafficLoadStub = nil
|
||||
if fake.getTrafficLoadReturnsOnCall == nil {
|
||||
fake.getTrafficLoadReturnsOnCall = make(map[int]struct {
|
||||
result1 *types.TrafficLoad
|
||||
})
|
||||
}
|
||||
fake.getTrafficLoadReturnsOnCall[i] = struct {
|
||||
result1 *types.TrafficLoad
|
||||
}{result1}
|
||||
}
|
||||
|
||||
func (fake *FakeLocalParticipant) GetTrailer() []byte {
|
||||
fake.getTrailerMutex.Lock()
|
||||
ret, specificReturn := fake.getTrailerReturnsOnCall[len(fake.getTrailerArgsForCall)]
|
||||
@@ -4357,38 +4289,6 @@ func (fake *FakeLocalParticipant) OnTrackUpdatedArgsForCall(i int) func(types.Lo
|
||||
return argsForCall.arg1
|
||||
}
|
||||
|
||||
func (fake *FakeLocalParticipant) OnTrafficLoad(arg1 func(trafficLoad *types.TrafficLoad)) {
|
||||
fake.onTrafficLoadMutex.Lock()
|
||||
fake.onTrafficLoadArgsForCall = append(fake.onTrafficLoadArgsForCall, struct {
|
||||
arg1 func(trafficLoad *types.TrafficLoad)
|
||||
}{arg1})
|
||||
stub := fake.OnTrafficLoadStub
|
||||
fake.recordInvocation("OnTrafficLoad", []interface{}{arg1})
|
||||
fake.onTrafficLoadMutex.Unlock()
|
||||
if stub != nil {
|
||||
fake.OnTrafficLoadStub(arg1)
|
||||
}
|
||||
}
|
||||
|
||||
func (fake *FakeLocalParticipant) OnTrafficLoadCallCount() int {
|
||||
fake.onTrafficLoadMutex.RLock()
|
||||
defer fake.onTrafficLoadMutex.RUnlock()
|
||||
return len(fake.onTrafficLoadArgsForCall)
|
||||
}
|
||||
|
||||
func (fake *FakeLocalParticipant) OnTrafficLoadCalls(stub func(func(trafficLoad *types.TrafficLoad))) {
|
||||
fake.onTrafficLoadMutex.Lock()
|
||||
defer fake.onTrafficLoadMutex.Unlock()
|
||||
fake.OnTrafficLoadStub = stub
|
||||
}
|
||||
|
||||
func (fake *FakeLocalParticipant) OnTrafficLoadArgsForCall(i int) func(trafficLoad *types.TrafficLoad) {
|
||||
fake.onTrafficLoadMutex.RLock()
|
||||
defer fake.onTrafficLoadMutex.RUnlock()
|
||||
argsForCall := fake.onTrafficLoadArgsForCall[i]
|
||||
return argsForCall.arg1
|
||||
}
|
||||
|
||||
func (fake *FakeLocalParticipant) ProtocolVersion() types.ProtocolVersion {
|
||||
fake.protocolVersionMutex.Lock()
|
||||
ret, specificReturn := fake.protocolVersionReturnsOnCall[len(fake.protocolVersionArgsForCall)]
|
||||
@@ -6577,8 +6477,6 @@ func (fake *FakeLocalParticipant) Invocations() map[string][][]interface{} {
|
||||
defer fake.getSubscribedParticipantsMutex.RUnlock()
|
||||
fake.getSubscribedTracksMutex.RLock()
|
||||
defer fake.getSubscribedTracksMutex.RUnlock()
|
||||
fake.getTrafficLoadMutex.RLock()
|
||||
defer fake.getTrafficLoadMutex.RUnlock()
|
||||
fake.getTrailerMutex.RLock()
|
||||
defer fake.getTrailerMutex.RUnlock()
|
||||
fake.handleAnswerMutex.RLock()
|
||||
@@ -6653,8 +6551,6 @@ func (fake *FakeLocalParticipant) Invocations() map[string][][]interface{} {
|
||||
defer fake.onTrackUnpublishedMutex.RUnlock()
|
||||
fake.onTrackUpdatedMutex.RLock()
|
||||
defer fake.onTrackUpdatedMutex.RUnlock()
|
||||
fake.onTrafficLoadMutex.RLock()
|
||||
defer fake.onTrafficLoadMutex.RUnlock()
|
||||
fake.protocolVersionMutex.RLock()
|
||||
defer fake.protocolVersionMutex.RUnlock()
|
||||
fake.removePublishedTrackMutex.RLock()
|
||||
|
||||
@@ -90,7 +90,7 @@ func (r *WrappedReceiver) StreamID() string {
|
||||
func (r *WrappedReceiver) DetermineReceiver(codec webrtc.RTPCodecCapability) {
|
||||
r.determinedCodec = codec
|
||||
for _, receiver := range r.receivers {
|
||||
if c := receiver.Codec(); c.MimeType == codec.MimeType {
|
||||
if c := receiver.Codec(); strings.EqualFold(c.MimeType, codec.MimeType) {
|
||||
r.TrackReceiver = receiver
|
||||
break
|
||||
} else if strings.EqualFold(c.MimeType, sfu.MimeTypeAudioRed) && strings.EqualFold(codec.MimeType, webrtc.MimeTypeOpus) {
|
||||
|
||||
@@ -24,6 +24,7 @@ import (
|
||||
"github.com/livekit/protocol/logger"
|
||||
"github.com/livekit/protocol/rpc"
|
||||
"github.com/livekit/protocol/utils"
|
||||
"github.com/livekit/protocol/utils/guid"
|
||||
)
|
||||
|
||||
//go:generate go run github.com/maxbrunsfeld/counterfeiter/v6 -generate
|
||||
@@ -73,7 +74,7 @@ func (s *egressLauncher) StartEgressWithClusterId(ctx context.Context, clusterId
|
||||
|
||||
// Ensure we have an Egress ID
|
||||
if req.EgressId == "" {
|
||||
req.EgressId = utils.NewGuid(utils.EgressPrefix)
|
||||
req.EgressId = guid.New(utils.EgressPrefix)
|
||||
}
|
||||
|
||||
return s.client.StartEgress(ctx, clusterId, req)
|
||||
|
||||
+21
-19
@@ -19,23 +19,25 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
ErrEgressNotFound = psrpc.NewErrorf(psrpc.NotFound, "egress does not exist")
|
||||
ErrEgressNotConnected = psrpc.NewErrorf(psrpc.Internal, "egress not connected (redis required)")
|
||||
ErrIdentityEmpty = psrpc.NewErrorf(psrpc.InvalidArgument, "identity cannot be empty")
|
||||
ErrIngressNotConnected = psrpc.NewErrorf(psrpc.Internal, "ingress not connected (redis required)")
|
||||
ErrIngressNotFound = psrpc.NewErrorf(psrpc.NotFound, "ingress does not exist")
|
||||
ErrIngressNonReusable = psrpc.NewErrorf(psrpc.InvalidArgument, "ingress is not reusable and cannot be modified")
|
||||
ErrMetadataExceedsLimits = psrpc.NewErrorf(psrpc.InvalidArgument, "metadata size exceeds limits")
|
||||
ErrOperationFailed = psrpc.NewErrorf(psrpc.Internal, "operation cannot be completed")
|
||||
ErrParticipantNotFound = psrpc.NewErrorf(psrpc.NotFound, "participant does not exist")
|
||||
ErrRoomNotFound = psrpc.NewErrorf(psrpc.NotFound, "requested room does not exist")
|
||||
ErrRoomLockFailed = psrpc.NewErrorf(psrpc.Internal, "could not lock room")
|
||||
ErrRoomUnlockFailed = psrpc.NewErrorf(psrpc.Internal, "could not unlock room, lock token does not match")
|
||||
ErrRemoteUnmuteNoteEnabled = psrpc.NewErrorf(psrpc.FailedPrecondition, "remote unmute not enabled")
|
||||
ErrTrackNotFound = psrpc.NewErrorf(psrpc.NotFound, "track is not found")
|
||||
ErrWebHookMissingAPIKey = psrpc.NewErrorf(psrpc.InvalidArgument, "api_key is required to use webhooks")
|
||||
ErrSIPNotConnected = psrpc.NewErrorf(psrpc.Internal, "sip not connected (redis required)")
|
||||
ErrSIPTrunkNotFound = psrpc.NewErrorf(psrpc.NotFound, "requested sip trunk does not exist")
|
||||
ErrSIPDispatchRuleNotFound = psrpc.NewErrorf(psrpc.NotFound, "requested sip dispatch rule does not exist")
|
||||
ErrSIPParticipantNotFound = psrpc.NewErrorf(psrpc.NotFound, "requested sip participant does not exist")
|
||||
ErrEgressNotFound = psrpc.NewErrorf(psrpc.NotFound, "egress does not exist")
|
||||
ErrEgressNotConnected = psrpc.NewErrorf(psrpc.Internal, "egress not connected (redis required)")
|
||||
ErrIdentityEmpty = psrpc.NewErrorf(psrpc.InvalidArgument, "identity cannot be empty")
|
||||
ErrIngressNotConnected = psrpc.NewErrorf(psrpc.Internal, "ingress not connected (redis required)")
|
||||
ErrIngressNotFound = psrpc.NewErrorf(psrpc.NotFound, "ingress does not exist")
|
||||
ErrIngressNonReusable = psrpc.NewErrorf(psrpc.InvalidArgument, "ingress is not reusable and cannot be modified")
|
||||
ErrMetadataExceedsLimits = psrpc.NewErrorf(psrpc.InvalidArgument, "metadata size exceeds limits")
|
||||
ErrRoomNameExceedsLimits = psrpc.NewErrorf(psrpc.InvalidArgument, "room name length exceeds limits")
|
||||
ErrParticipantIdentityExceedsLimits = psrpc.NewErrorf(psrpc.InvalidArgument, "participant identity length exceeds limits")
|
||||
ErrOperationFailed = psrpc.NewErrorf(psrpc.Internal, "operation cannot be completed")
|
||||
ErrParticipantNotFound = psrpc.NewErrorf(psrpc.NotFound, "participant does not exist")
|
||||
ErrRoomNotFound = psrpc.NewErrorf(psrpc.NotFound, "requested room does not exist")
|
||||
ErrRoomLockFailed = psrpc.NewErrorf(psrpc.Internal, "could not lock room")
|
||||
ErrRoomUnlockFailed = psrpc.NewErrorf(psrpc.Internal, "could not unlock room, lock token does not match")
|
||||
ErrRemoteUnmuteNoteEnabled = psrpc.NewErrorf(psrpc.FailedPrecondition, "remote unmute not enabled")
|
||||
ErrTrackNotFound = psrpc.NewErrorf(psrpc.NotFound, "track is not found")
|
||||
ErrWebHookMissingAPIKey = psrpc.NewErrorf(psrpc.InvalidArgument, "api_key is required to use webhooks")
|
||||
ErrSIPNotConnected = psrpc.NewErrorf(psrpc.Internal, "sip not connected (redis required)")
|
||||
ErrSIPTrunkNotFound = psrpc.NewErrorf(psrpc.NotFound, "requested sip trunk does not exist")
|
||||
ErrSIPDispatchRuleNotFound = psrpc.NewErrorf(psrpc.NotFound, "requested sip dispatch rule does not exist")
|
||||
ErrSIPParticipantNotFound = psrpc.NewErrorf(psrpc.NotFound, "requested sip participant does not exist")
|
||||
)
|
||||
|
||||
+28
-8
@@ -26,6 +26,7 @@ import (
|
||||
"github.com/livekit/protocol/logger"
|
||||
"github.com/livekit/protocol/rpc"
|
||||
"github.com/livekit/protocol/utils"
|
||||
"github.com/livekit/protocol/utils/guid"
|
||||
"github.com/livekit/psrpc"
|
||||
)
|
||||
|
||||
@@ -145,18 +146,18 @@ func (s *IngressService) CreateIngressWithUrl(ctx context.Context, urlStr string
|
||||
|
||||
var sk string
|
||||
if req.InputType != livekit.IngressInput_URL_INPUT {
|
||||
sk = utils.NewGuid("")
|
||||
sk = guid.New("")
|
||||
}
|
||||
|
||||
info := &livekit.IngressInfo{
|
||||
IngressId: utils.NewGuid(utils.IngressPrefix),
|
||||
IngressId: guid.New(utils.IngressPrefix),
|
||||
Name: req.Name,
|
||||
StreamKey: sk,
|
||||
Url: urlStr,
|
||||
InputType: req.InputType,
|
||||
Audio: req.Audio,
|
||||
Video: req.Video,
|
||||
BypassTranscoding: req.BypassTranscoding,
|
||||
EnableTranscoding: req.EnableTranscoding,
|
||||
RoomName: req.RoomName,
|
||||
ParticipantIdentity: req.ParticipantIdentity,
|
||||
ParticipantName: req.ParticipantName,
|
||||
@@ -179,9 +180,7 @@ func (s *IngressService) CreateIngressWithUrl(ctx context.Context, urlStr string
|
||||
return nil, ingress.ErrInvalidIngressType
|
||||
}
|
||||
|
||||
if err := ingress.ValidateForSerialization(info); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
updateEnableTranscoding(info)
|
||||
|
||||
if req.InputType == livekit.IngressInput_URL_INPUT {
|
||||
retInfo, err := s.launcher.LaunchPullIngress(ctx, info)
|
||||
@@ -221,6 +220,24 @@ func (s *IngressService) LaunchPullIngress(ctx context.Context, info *livekit.In
|
||||
return s.psrpcClient.StartIngress(ctx, req)
|
||||
}
|
||||
|
||||
func updateEnableTranscoding(info *livekit.IngressInfo) {
|
||||
// Set BypassTranscoding as well for backward compatiblity
|
||||
if info.EnableTranscoding != nil {
|
||||
info.BypassTranscoding = !*info.EnableTranscoding
|
||||
return
|
||||
}
|
||||
|
||||
switch info.InputType {
|
||||
case livekit.IngressInput_WHIP_INPUT:
|
||||
f := false
|
||||
info.EnableTranscoding = &f
|
||||
info.BypassTranscoding = true
|
||||
default:
|
||||
t := true
|
||||
info.EnableTranscoding = &t
|
||||
}
|
||||
}
|
||||
|
||||
func updateInfoUsingRequest(req *livekit.UpdateIngressRequest, info *livekit.IngressInfo) error {
|
||||
if req.Name != "" {
|
||||
info.Name = req.Name
|
||||
@@ -234,9 +251,10 @@ func updateInfoUsingRequest(req *livekit.UpdateIngressRequest, info *livekit.Ing
|
||||
if req.ParticipantName != "" {
|
||||
info.ParticipantName = req.ParticipantName
|
||||
}
|
||||
if req.BypassTranscoding != nil {
|
||||
info.BypassTranscoding = *req.BypassTranscoding
|
||||
if req.EnableTranscoding != nil {
|
||||
info.EnableTranscoding = req.EnableTranscoding
|
||||
}
|
||||
|
||||
if req.ParticipantMetadata != "" {
|
||||
info.ParticipantMetadata = req.ParticipantMetadata
|
||||
}
|
||||
@@ -251,6 +269,8 @@ func updateInfoUsingRequest(req *livekit.UpdateIngressRequest, info *livekit.Ing
|
||||
return err
|
||||
}
|
||||
|
||||
updateEnableTranscoding(info)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ func (s *IOInfoService) matchSIPTrunk(ctx context.Context, calling, called strin
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return sip.MatchTrunk(trunks, calling, called)
|
||||
return sip.MatchTrunk(trunks, "", calling, called)
|
||||
}
|
||||
|
||||
// matchSIPDispatchRule finds the best dispatch rule matching the request parameters. Returns an error if no rule matched.
|
||||
@@ -47,38 +47,56 @@ func (s *IOInfoService) matchSIPDispatchRule(ctx context.Context, trunk *livekit
|
||||
}
|
||||
|
||||
func (s *IOInfoService) EvaluateSIPDispatchRules(ctx context.Context, req *rpc.EvaluateSIPDispatchRulesRequest) (*rpc.EvaluateSIPDispatchRulesResponse, error) {
|
||||
log := logger.GetLogger()
|
||||
log = log.WithValues("toUser", req.CalledNumber, "fromUser", req.CallingNumber)
|
||||
trunk, err := s.matchSIPTrunk(ctx, req.CallingNumber, req.CalledNumber)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
trunkID := ""
|
||||
if trunk != nil {
|
||||
logger.Debugw("SIP trunk matched", "trunkID", trunk.SipTrunkId, "called", req.CalledNumber, "calling", req.CallingNumber)
|
||||
trunkID = trunk.SipTrunkId
|
||||
}
|
||||
log = log.WithValues("sipTrunk", trunkID)
|
||||
if trunk != nil {
|
||||
log.Debugw("SIP trunk matched")
|
||||
} else {
|
||||
logger.Debugw("No SIP trunk matched", "trunkID", "", "called", req.CalledNumber, "calling", req.CallingNumber)
|
||||
log.Debugw("No SIP trunk matched")
|
||||
}
|
||||
best, err := s.matchSIPDispatchRule(ctx, trunk, req)
|
||||
if err != nil {
|
||||
if e := (*sip.ErrNoDispatchMatched)(nil); errors.As(err, &e) {
|
||||
return &rpc.EvaluateSIPDispatchRulesResponse{Result: rpc.SIPDispatchResult_DROP}, nil
|
||||
return &rpc.EvaluateSIPDispatchRulesResponse{
|
||||
SipTrunkId: trunkID,
|
||||
Result: rpc.SIPDispatchResult_DROP,
|
||||
}, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
logger.Debugw("SIP dispatch rule matched", "dispatchRule", best.SipDispatchRuleId, "called", req.CalledNumber, "calling", req.CallingNumber)
|
||||
return sip.EvaluateDispatchRule(best, req)
|
||||
log.Debugw("SIP dispatch rule matched", "sipRule", best.SipDispatchRuleId)
|
||||
resp, err := sip.EvaluateDispatchRule(best, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp.SipTrunkId = trunkID
|
||||
return resp, err
|
||||
}
|
||||
|
||||
func (s *IOInfoService) GetSIPTrunkAuthentication(ctx context.Context, req *rpc.GetSIPTrunkAuthenticationRequest) (*rpc.GetSIPTrunkAuthenticationResponse, error) {
|
||||
log := logger.GetLogger()
|
||||
log = log.WithValues("toUser", req.To, "fromUser", req.From)
|
||||
trunk, err := s.matchSIPTrunk(ctx, req.From, req.To)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if trunk == nil {
|
||||
logger.Debugw("No SIP trunk matched for auth", "trunkID", "", "called", req.To, "calling", req.From)
|
||||
log.Debugw("No SIP trunk matched for auth", "sipTrunk", "")
|
||||
return &rpc.GetSIPTrunkAuthenticationResponse{}, nil
|
||||
}
|
||||
logger.Debugw("SIP trunk matched for auth", "trunkID", trunk.SipTrunkId, "called", req.To, "calling", req.From)
|
||||
log.Debugw("SIP trunk matched for auth", "sipTrunk", trunk.SipTrunkId)
|
||||
return &rpc.GetSIPTrunkAuthenticationResponse{
|
||||
Username: trunk.InboundUsername,
|
||||
Password: trunk.InboundPassword,
|
||||
SipTrunkId: trunk.SipTrunkId,
|
||||
Username: trunk.InboundUsername,
|
||||
Password: trunk.InboundPassword,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ import (
|
||||
"github.com/livekit/protocol/ingress"
|
||||
"github.com/livekit/protocol/livekit"
|
||||
"github.com/livekit/protocol/logger"
|
||||
"github.com/livekit/protocol/utils"
|
||||
"github.com/livekit/protocol/utils/guid"
|
||||
|
||||
"github.com/livekit/livekit-server/version"
|
||||
)
|
||||
@@ -240,7 +240,7 @@ func (s *RedisStore) DeleteRoom(ctx context.Context, roomName livekit.RoomName)
|
||||
}
|
||||
|
||||
func (s *RedisStore) LockRoom(_ context.Context, roomName livekit.RoomName, duration time.Duration) (string, error) {
|
||||
token := utils.NewGuid("LOCK")
|
||||
token := guid.New("LOCK")
|
||||
key := RoomLockPrefix + string(roomName)
|
||||
|
||||
startTime := time.Now()
|
||||
|
||||
@@ -26,6 +26,7 @@ import (
|
||||
"github.com/livekit/protocol/ingress"
|
||||
"github.com/livekit/protocol/livekit"
|
||||
"github.com/livekit/protocol/utils"
|
||||
"github.com/livekit/protocol/utils/guid"
|
||||
|
||||
"github.com/livekit/livekit-server/pkg/service"
|
||||
)
|
||||
@@ -164,8 +165,8 @@ func TestEgressStore(t *testing.T) {
|
||||
|
||||
// store egress info
|
||||
info := &livekit.EgressInfo{
|
||||
EgressId: utils.NewGuid(utils.EgressPrefix),
|
||||
RoomId: utils.NewGuid(utils.RoomPrefix),
|
||||
EgressId: guid.New(utils.EgressPrefix),
|
||||
RoomId: guid.New(utils.RoomPrefix),
|
||||
RoomName: roomName,
|
||||
Status: livekit.EgressStatus_EGRESS_STARTING,
|
||||
Request: &livekit.EgressInfo_RoomComposite{
|
||||
@@ -184,8 +185,8 @@ func TestEgressStore(t *testing.T) {
|
||||
|
||||
// store another
|
||||
info2 := &livekit.EgressInfo{
|
||||
EgressId: utils.NewGuid(utils.EgressPrefix),
|
||||
RoomId: utils.NewGuid(utils.RoomPrefix),
|
||||
EgressId: guid.New(utils.EgressPrefix),
|
||||
RoomId: guid.New(utils.RoomPrefix),
|
||||
RoomName: "another-egress-test",
|
||||
Status: livekit.EgressStatus_EGRESS_STARTING,
|
||||
Request: &livekit.EgressInfo_RoomComposite{
|
||||
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
"github.com/livekit/protocol/livekit"
|
||||
"github.com/livekit/protocol/logger"
|
||||
"github.com/livekit/protocol/utils"
|
||||
"github.com/livekit/protocol/utils/guid"
|
||||
|
||||
"github.com/livekit/livekit-server/pkg/config"
|
||||
"github.com/livekit/livekit-server/pkg/routing"
|
||||
@@ -66,7 +67,7 @@ func (r *StandardRoomAllocator) CreateRoom(ctx context.Context, req *livekit.Cre
|
||||
if errors.Is(err, ErrRoomNotFound) {
|
||||
created = true
|
||||
rm = &livekit.Room{
|
||||
Sid: utils.NewGuid(utils.RoomPrefix),
|
||||
Sid: guid.New(utils.RoomPrefix),
|
||||
Name: req.Name,
|
||||
CreationTime: time.Now().Unix(),
|
||||
TurnPassword: utils.RandomSecret(),
|
||||
|
||||
@@ -25,6 +25,7 @@ import (
|
||||
"golang.org/x/exp/maps"
|
||||
|
||||
"github.com/livekit/livekit-server/pkg/agent"
|
||||
"github.com/livekit/livekit-server/pkg/sfu"
|
||||
sutils "github.com/livekit/livekit-server/pkg/utils"
|
||||
"github.com/livekit/mediatransportutil/pkg/rtcconfig"
|
||||
"github.com/livekit/protocol/auth"
|
||||
@@ -32,6 +33,7 @@ import (
|
||||
"github.com/livekit/protocol/logger"
|
||||
"github.com/livekit/protocol/rpc"
|
||||
"github.com/livekit/protocol/utils"
|
||||
"github.com/livekit/protocol/utils/guid"
|
||||
"github.com/livekit/protocol/utils/must"
|
||||
"github.com/livekit/psrpc"
|
||||
|
||||
@@ -83,6 +85,8 @@ type RoomManager struct {
|
||||
participantServers utils.MultitonService[rpc.ParticipantTopic]
|
||||
|
||||
iceConfigCache *sutils.IceConfigCache[iceConfigCacheKey]
|
||||
|
||||
forwardStats *sfu.ForwardStats
|
||||
}
|
||||
|
||||
func NewLocalRoomManager(
|
||||
@@ -97,6 +101,7 @@ func NewLocalRoomManager(
|
||||
versionGenerator utils.TimedVersionGenerator,
|
||||
turnAuthHandler *TURNAuthHandler,
|
||||
bus psrpc.MessageBus,
|
||||
forwardStats *sfu.ForwardStats,
|
||||
) (*RoomManager, error) {
|
||||
rtcConf, err := rtc.NewWebRTCConfig(conf)
|
||||
if err != nil {
|
||||
@@ -116,6 +121,7 @@ func NewLocalRoomManager(
|
||||
versionGenerator: versionGenerator,
|
||||
turnAuthHandler: turnAuthHandler,
|
||||
bus: bus,
|
||||
forwardStats: forwardStats,
|
||||
|
||||
rooms: make(map[livekit.RoomName]*rtc.Room),
|
||||
|
||||
@@ -232,6 +238,10 @@ func (r *RoomManager) Stop() {
|
||||
}
|
||||
|
||||
r.iceConfigCache.Stop()
|
||||
|
||||
if r.forwardStats != nil {
|
||||
r.forwardStats.Stop()
|
||||
}
|
||||
}
|
||||
|
||||
// StartSession starts WebRTC session when a new participant is connected, takes place on RTC node
|
||||
@@ -369,7 +379,7 @@ func (r *RoomManager) StartSession(
|
||||
pv := types.ProtocolVersion(pi.Client.Protocol)
|
||||
rtcConf := *r.rtcConfig
|
||||
rtcConf.SetBufferFactory(room.GetBufferFactory())
|
||||
sid := livekit.ParticipantID(utils.NewGuid(utils.ParticipantPrefix))
|
||||
sid := livekit.ParticipantID(guid.New(utils.ParticipantPrefix))
|
||||
pLogger := rtc.LoggerWithParticipant(
|
||||
rtc.LoggerWithRoom(logger.GetLogger(), room.Name(), room.ID()),
|
||||
pi.Identity,
|
||||
@@ -440,6 +450,7 @@ func (r *RoomManager) StartSession(
|
||||
SubscriptionLimitVideo: r.config.Limit.SubscriptionLimitVideo,
|
||||
PlayoutDelay: roomInternal.GetPlayoutDelay(),
|
||||
SyncStreams: roomInternal.GetSyncStreams(),
|
||||
ForwardStats: r.forwardStats,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
+59
-12
@@ -16,19 +16,21 @@ package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/avast/retry-go/v4"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/twitchtv/twirp"
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
"github.com/livekit/livekit-server/pkg/agent"
|
||||
"github.com/livekit/livekit-server/pkg/config"
|
||||
"github.com/livekit/livekit-server/pkg/routing"
|
||||
"github.com/livekit/livekit-server/pkg/rtc"
|
||||
"github.com/livekit/protocol/egress"
|
||||
"github.com/livekit/protocol/livekit"
|
||||
"github.com/livekit/protocol/rpc"
|
||||
"github.com/livekit/psrpc"
|
||||
)
|
||||
|
||||
// A rooms service that supports a single node
|
||||
@@ -76,29 +78,30 @@ func NewRoomService(
|
||||
}
|
||||
|
||||
func (s *RoomService) CreateRoom(ctx context.Context, req *livekit.CreateRoomRequest) (*livekit.Room, error) {
|
||||
AppendLogFields(ctx, "room", req.Name, "request", req)
|
||||
clone := redactCreateRoomRequest(req)
|
||||
|
||||
AppendLogFields(ctx, "room", clone.Name, "request", clone)
|
||||
if err := EnsureCreatePermission(ctx); err != nil {
|
||||
return nil, twirpAuthError(err)
|
||||
} else if req.Egress != nil && s.egressLauncher == nil {
|
||||
return nil, ErrEgressNotConnected
|
||||
}
|
||||
|
||||
if limit := s.roomConf.MaxRoomNameLength; limit > 0 && len(req.Name) > limit {
|
||||
return nil, fmt.Errorf("%w: max length %d", ErrRoomNameExceedsLimits, limit)
|
||||
}
|
||||
|
||||
rm, created, err := s.roomAllocator.CreateRoom(ctx, req)
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, "could not create room")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// actually start the room on an RTC node, to ensure metadata & empty timeout functionality
|
||||
res, err := s.router.StartParticipantSignal(ctx,
|
||||
livekit.RoomName(req.Name),
|
||||
routing.ParticipantInit{},
|
||||
)
|
||||
done, err := s.startRoom(ctx, livekit.RoomName(req.Name))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer res.RequestSink.Close()
|
||||
defer res.ResponseSource.Close()
|
||||
defer done()
|
||||
|
||||
if created {
|
||||
go s.agentClient.LaunchJob(ctx, &agent.JobDescription{
|
||||
@@ -153,9 +156,20 @@ func (s *RoomService) DeleteRoom(ctx context.Context, req *livekit.DeleteRoomReq
|
||||
return nil, twirpAuthError(err)
|
||||
}
|
||||
|
||||
_, err := s.roomClient.DeleteRoom(ctx, s.topicFormatter.RoomTopic(ctx, livekit.RoomName(req.Room)), req)
|
||||
if !errors.Is(err, psrpc.ErrNoResponse) {
|
||||
return &livekit.DeleteRoomResponse{}, err
|
||||
_, _, err := s.roomStore.LoadRoom(ctx, livekit.RoomName(req.Room), false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
done, err := s.startRoom(ctx, livekit.RoomName(req.Room))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer done()
|
||||
|
||||
_, err = s.roomClient.DeleteRoom(ctx, s.topicFormatter.RoomTopic(ctx, livekit.RoomName(req.Room)), req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = s.roomStore.DeleteRoom(ctx, livekit.RoomName(req.Room))
|
||||
@@ -320,3 +334,36 @@ func (s *RoomService) confirmExecution(ctx context.Context, f func() error) erro
|
||||
retry.DelayType(retry.BackOffDelay),
|
||||
)
|
||||
}
|
||||
|
||||
// startRoom starts the room on an RTC node, to ensure metadata & empty timeout functionality
|
||||
func (s *RoomService) startRoom(ctx context.Context, roomName livekit.RoomName) (func(), error) {
|
||||
res, err := s.router.StartParticipantSignal(ctx, roomName, routing.ParticipantInit{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return func() {
|
||||
res.RequestSink.Close()
|
||||
res.ResponseSource.Close()
|
||||
}, nil
|
||||
}
|
||||
|
||||
func redactCreateRoomRequest(req *livekit.CreateRoomRequest) *livekit.CreateRoomRequest {
|
||||
if req.Egress == nil {
|
||||
// nothing to redact
|
||||
return req
|
||||
}
|
||||
|
||||
clone := proto.Clone(req).(*livekit.CreateRoomRequest)
|
||||
|
||||
if clone.Egress.Room != nil {
|
||||
egress.RedactEncodedOutputs(clone.Egress.Room)
|
||||
}
|
||||
if clone.Egress.Participant != nil {
|
||||
egress.RedactAutoEncodedOutput(clone.Egress.Participant)
|
||||
}
|
||||
if clone.Egress.Tracks != nil {
|
||||
egress.RedactUpload(clone.Egress.Tracks)
|
||||
}
|
||||
|
||||
return clone
|
||||
}
|
||||
|
||||
@@ -120,6 +120,9 @@ func (s *RTCService) validate(r *http.Request) (livekit.RoomName, routing.Partic
|
||||
if claims.Identity == "" {
|
||||
return "", pi, http.StatusBadRequest, ErrIdentityEmpty
|
||||
}
|
||||
if limit := s.config.Room.MaxParticipantIdentityLength; limit > 0 && len(claims.Identity) > limit {
|
||||
return "", pi, http.StatusBadRequest, fmt.Errorf("%w: max length %d", ErrParticipantIdentityExceedsLimits, limit)
|
||||
}
|
||||
|
||||
roomName := livekit.RoomName(r.FormValue("room"))
|
||||
reconnectParam := r.FormValue("reconnect")
|
||||
@@ -133,6 +136,9 @@ func (s *RTCService) validate(r *http.Request) (livekit.RoomName, routing.Partic
|
||||
if onlyName != "" {
|
||||
roomName = onlyName
|
||||
}
|
||||
if limit := s.config.Room.MaxRoomNameLength; limit > 0 && len(roomName) > limit {
|
||||
return "", pi, http.StatusBadRequest, fmt.Errorf("%w: max length %d", ErrRoomNameExceedsLimits, limit)
|
||||
}
|
||||
|
||||
// this is new connection for existing participant - with publish only permissions
|
||||
if publishParam != "" {
|
||||
@@ -311,6 +317,8 @@ func (s *RTCService) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
defer func() {
|
||||
// when the source is terminated, this means Participant.Close had been called and RTC connection is done
|
||||
// we would terminate the signal connection as well
|
||||
closeMsg := websocket.FormatCloseMessage(websocket.CloseNormalClosure, "")
|
||||
_ = conn.WriteControl(websocket.CloseMessage, closeMsg, time.Now().Add(time.Second))
|
||||
_ = conn.Close()
|
||||
}()
|
||||
defer func() {
|
||||
|
||||
+18
-23
@@ -24,6 +24,7 @@ import (
|
||||
"github.com/livekit/protocol/rpc"
|
||||
"github.com/livekit/protocol/sip"
|
||||
"github.com/livekit/protocol/utils"
|
||||
"github.com/livekit/protocol/utils/guid"
|
||||
"github.com/livekit/psrpc"
|
||||
|
||||
"github.com/livekit/livekit-server/pkg/config"
|
||||
@@ -76,6 +77,8 @@ func (s *SIPService) CreateSIPTrunk(ctx context.Context, req *livekit.CreateSIPT
|
||||
InboundPassword: req.InboundPassword,
|
||||
OutboundUsername: req.OutboundUsername,
|
||||
OutboundPassword: req.OutboundPassword,
|
||||
Name: req.Name,
|
||||
Metadata: req.Metadata,
|
||||
}
|
||||
|
||||
// Validate all trunks including the new one first.
|
||||
@@ -89,7 +92,7 @@ func (s *SIPService) CreateSIPTrunk(ctx context.Context, req *livekit.CreateSIPT
|
||||
}
|
||||
|
||||
// Now we can generate ID and store.
|
||||
info.SipTrunkId = utils.NewGuid(utils.SIPTrunkPrefix)
|
||||
info.SipTrunkId = guid.New(utils.SIPTrunkPrefix)
|
||||
if err := s.store.StoreSIPTrunk(ctx, info); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -136,6 +139,8 @@ func (s *SIPService) CreateSIPDispatchRule(ctx context.Context, req *livekit.Cre
|
||||
Rule: req.Rule,
|
||||
TrunkIds: req.TrunkIds,
|
||||
HidePhoneNumber: req.HidePhoneNumber,
|
||||
Name: req.Name,
|
||||
Metadata: req.Metadata,
|
||||
}
|
||||
|
||||
// Validate all rules including the new one first.
|
||||
@@ -149,7 +154,7 @@ func (s *SIPService) CreateSIPDispatchRule(ctx context.Context, req *livekit.Cre
|
||||
}
|
||||
|
||||
// Now we can generate ID and store.
|
||||
info.SipDispatchRuleId = utils.NewGuid(utils.SIPDispatchRulePrefix)
|
||||
info.SipDispatchRuleId = guid.New(utils.SIPDispatchRulePrefix)
|
||||
if err := s.store.StoreSIPDispatchRule(ctx, info); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -190,28 +195,17 @@ func (s *SIPService) CreateSIPParticipantWithToken(ctx context.Context, req *liv
|
||||
if s.store == nil {
|
||||
return nil, ErrSIPNotConnected
|
||||
}
|
||||
callID := sip.NewCallID()
|
||||
log := logger.GetLogger()
|
||||
log = log.WithValues("callId", callID, "roomName", req.RoomName, "sipTrunk", req.SipTrunkId, "toUser", req.SipCallTo)
|
||||
|
||||
AppendLogFields(ctx, "room", req.RoomName, "trunk", req.SipTrunkId, "to", req.SipCallTo)
|
||||
ireq := &rpc.InternalCreateSIPParticipantRequest{
|
||||
CallTo: req.SipCallTo,
|
||||
RoomName: req.RoomName,
|
||||
ParticipantIdentity: req.ParticipantIdentity,
|
||||
Dtmf: req.Dtmf,
|
||||
PlayRingtone: req.PlayRingtone,
|
||||
WsUrl: wsUrl,
|
||||
Token: token,
|
||||
}
|
||||
if req.SipTrunkId != "" {
|
||||
trunk, err := s.store.LoadSIPTrunk(ctx, req.SipTrunkId)
|
||||
if err != nil {
|
||||
logger.Errorw("cannot get trunk to update sip participant", err)
|
||||
return nil, err
|
||||
}
|
||||
ireq.Address = trunk.OutboundAddress
|
||||
ireq.Number = trunk.OutboundNumber
|
||||
ireq.Username = trunk.OutboundUsername
|
||||
ireq.Password = trunk.OutboundPassword
|
||||
trunk, err := s.store.LoadSIPTrunk(ctx, req.SipTrunkId)
|
||||
if err != nil {
|
||||
log.Errorw("cannot get trunk to update sip participant", err)
|
||||
return nil, err
|
||||
}
|
||||
log = log.WithValues("fromUser", trunk.OutboundNumber, "toHost", trunk.OutboundAddress)
|
||||
ireq := rpc.NewCreateSIPParticipantRequest(callID, wsUrl, token, req, trunk)
|
||||
|
||||
// CreateSIPParticipant will wait for LiveKit Participant to be created and that can take some time.
|
||||
// Thus, we must set a higher deadline for it, if it's not set already.
|
||||
@@ -226,13 +220,14 @@ func (s *SIPService) CreateSIPParticipantWithToken(ctx context.Context, req *liv
|
||||
}
|
||||
resp, err := s.psrpcClient.CreateSIPParticipant(ctx, "", ireq, psrpc.WithRequestTimeout(timeout))
|
||||
if err != nil {
|
||||
logger.Errorw("cannot update sip participant", err)
|
||||
log.Errorw("cannot update sip participant", err)
|
||||
return nil, err
|
||||
}
|
||||
return &livekit.SIPParticipantInfo{
|
||||
ParticipantId: resp.ParticipantId,
|
||||
ParticipantIdentity: resp.ParticipantIdentity,
|
||||
RoomName: req.RoomName,
|
||||
SipCallId: callID,
|
||||
}, nil
|
||||
}
|
||||
func (s *SIPService) CreateSIPParticipant(ctx context.Context, req *livekit.CreateSIPParticipantRequest) (*livekit.SIPParticipantInfo, error) {
|
||||
|
||||
+10
-1
@@ -27,10 +27,11 @@ import (
|
||||
"github.com/redis/go-redis/v9"
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/livekit/livekit-server/pkg/agent"
|
||||
"github.com/livekit/livekit-server/pkg/clientconfiguration"
|
||||
"github.com/livekit/livekit-server/pkg/config"
|
||||
"github.com/livekit/livekit-server/pkg/routing"
|
||||
"github.com/livekit/livekit-server/pkg/agent"
|
||||
"github.com/livekit/livekit-server/pkg/sfu"
|
||||
"github.com/livekit/livekit-server/pkg/telemetry"
|
||||
"github.com/livekit/protocol/auth"
|
||||
"github.com/livekit/protocol/livekit"
|
||||
@@ -51,6 +52,7 @@ func InitializeServer(conf *config.Config, currentNode routing.LocalNode) (*Live
|
||||
createKeyProvider,
|
||||
createWebhookNotifier,
|
||||
createClientConfiguration,
|
||||
createForwardStats,
|
||||
routing.CreateRouter,
|
||||
getRoomConf,
|
||||
config.DefaultAPIConfig,
|
||||
@@ -235,6 +237,13 @@ func getPSRPCClientParams(config rpc.PSRPCConfig, bus psrpc.MessageBus) rpc.Clie
|
||||
return rpc.NewClientParams(config, bus, logger.GetLogger(), rpc.PSRPCMetricsObserver{})
|
||||
}
|
||||
|
||||
func createForwardStats(conf *config.Config) *sfu.ForwardStats {
|
||||
if conf.RTC.ForwardStats.SummaryInterval == 0 || conf.RTC.ForwardStats.ReportInterval == 0 || conf.RTC.ForwardStats.ReportWindow == 0 {
|
||||
return nil
|
||||
}
|
||||
return sfu.NewForwardStats(conf.RTC.ForwardStats.SummaryInterval, conf.RTC.ForwardStats.ReportInterval, conf.RTC.ForwardStats.ReportWindow)
|
||||
}
|
||||
|
||||
func newInProcessTurnServer(conf *config.Config, authHandler turn.AuthHandler) (*turn.Server, error) {
|
||||
return NewTurnServer(conf, authHandler, false)
|
||||
}
|
||||
|
||||
+10
-1
@@ -12,6 +12,7 @@ import (
|
||||
"github.com/livekit/livekit-server/pkg/clientconfiguration"
|
||||
"github.com/livekit/livekit-server/pkg/config"
|
||||
"github.com/livekit/livekit-server/pkg/routing"
|
||||
"github.com/livekit/livekit-server/pkg/sfu"
|
||||
"github.com/livekit/livekit-server/pkg/telemetry"
|
||||
"github.com/livekit/protocol/auth"
|
||||
"github.com/livekit/protocol/livekit"
|
||||
@@ -120,7 +121,8 @@ func InitializeServer(conf *config.Config, currentNode routing.LocalNode) (*Live
|
||||
clientConfigurationManager := createClientConfiguration()
|
||||
timedVersionGenerator := utils.NewDefaultTimedVersionGenerator()
|
||||
turnAuthHandler := NewTURNAuthHandler(keyProvider)
|
||||
roomManager, err := NewLocalRoomManager(conf, objectStore, currentNode, router, telemetryService, clientConfigurationManager, client, rtcEgressLauncher, timedVersionGenerator, turnAuthHandler, messageBus)
|
||||
forwardStats := createForwardStats(conf)
|
||||
roomManager, err := NewLocalRoomManager(conf, objectStore, currentNode, router, telemetryService, clientConfigurationManager, client, rtcEgressLauncher, timedVersionGenerator, turnAuthHandler, messageBus, forwardStats)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -286,6 +288,13 @@ func getPSRPCClientParams(config2 rpc.PSRPCConfig, bus psrpc.MessageBus) rpc.Cli
|
||||
return rpc.NewClientParams(config2, bus, logger.GetLogger(), rpc.PSRPCMetricsObserver{})
|
||||
}
|
||||
|
||||
func createForwardStats(conf *config.Config) *sfu.ForwardStats {
|
||||
if conf.RTC.ForwardStats.SummaryInterval == 0 || conf.RTC.ForwardStats.ReportInterval == 0 || conf.RTC.ForwardStats.ReportWindow == 0 {
|
||||
return nil
|
||||
}
|
||||
return sfu.NewForwardStats(conf.RTC.ForwardStats.SummaryInterval, conf.RTC.ForwardStats.ReportInterval, conf.RTC.ForwardStats.ReportWindow)
|
||||
}
|
||||
|
||||
func newInProcessTurnServer(conf *config.Config, authHandler turn.AuthHandler) (*turn.Server, error) {
|
||||
return NewTurnServer(conf, authHandler, false)
|
||||
}
|
||||
|
||||
@@ -162,8 +162,10 @@ func (c *WSSignalConnection) WriteServerMessage(msg *livekit.ServerMessage) (int
|
||||
}
|
||||
|
||||
func (c *WSSignalConnection) pingWorker() {
|
||||
for {
|
||||
<-time.After(pingFrequency)
|
||||
ticker := time.NewTicker(pingFrequency)
|
||||
defer ticker.Stop()
|
||||
|
||||
for range ticker.C {
|
||||
err := c.conn.WriteControl(websocket.PingMessage, []byte(""), time.Now().Add(pingTimeout))
|
||||
if err != nil {
|
||||
return
|
||||
|
||||
+52
-17
@@ -336,13 +336,14 @@ func (b *Buffer) Write(pkt []byte) (n int, err error) {
|
||||
arrivalTime: now,
|
||||
})
|
||||
b.Unlock()
|
||||
b.readCond.Broadcast()
|
||||
return
|
||||
}
|
||||
|
||||
b.payloadType = rtpPacket.PayloadType
|
||||
b.calc(pkt, &rtpPacket, time.Now(), false)
|
||||
b.Unlock()
|
||||
b.readCond.Signal()
|
||||
b.readCond.Broadcast()
|
||||
return
|
||||
}
|
||||
|
||||
@@ -392,26 +393,24 @@ func (b *Buffer) writeRTX(rtxPkt *rtp.Packet, arrivalTime time.Time) (n int, err
|
||||
}
|
||||
|
||||
func (b *Buffer) Read(buff []byte) (n int, err error) {
|
||||
b.Lock()
|
||||
for {
|
||||
if b.closed.Load() {
|
||||
err = io.EOF
|
||||
return
|
||||
b.Unlock()
|
||||
return 0, io.EOF
|
||||
}
|
||||
b.Lock()
|
||||
if b.pPackets != nil && len(b.pPackets) > b.lastPacketRead {
|
||||
if len(buff) < len(b.pPackets[b.lastPacketRead].packet) {
|
||||
err = bucket.ErrBufferTooSmall
|
||||
b.Unlock()
|
||||
return
|
||||
return 0, bucket.ErrBufferTooSmall
|
||||
}
|
||||
n = len(b.pPackets[b.lastPacketRead].packet)
|
||||
copy(buff, b.pPackets[b.lastPacketRead].packet)
|
||||
|
||||
n = copy(buff, b.pPackets[b.lastPacketRead].packet)
|
||||
b.lastPacketRead++
|
||||
b.Unlock()
|
||||
return
|
||||
}
|
||||
b.Unlock()
|
||||
time.Sleep(25 * time.Millisecond)
|
||||
b.readCond.Wait()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -552,7 +551,13 @@ func (b *Buffer) calc(rawPkt []byte, rtpPacket *rtp.Packet, arrivalTime time.Tim
|
||||
// 44 - padding only - out-of-order + duplicate - dropped as duplicate
|
||||
//
|
||||
if err := b.snRangeMap.ExcludeRange(flowState.ExtSequenceNumber, flowState.ExtSequenceNumber+1); err != nil {
|
||||
b.logger.Errorw("could not exclude range", err, "sn", rtpPacket.SequenceNumber, "esn", flowState.ExtSequenceNumber)
|
||||
b.logger.Errorw(
|
||||
"could not exclude range", err,
|
||||
"sn", rtpPacket.SequenceNumber,
|
||||
"esn", flowState.ExtSequenceNumber,
|
||||
"rtpStats", b.rtpStats,
|
||||
"snRangeMap", b.snRangeMap,
|
||||
)
|
||||
}
|
||||
}
|
||||
return
|
||||
@@ -561,7 +566,14 @@ func (b *Buffer) calc(rawPkt []byte, rtpPacket *rtp.Packet, arrivalTime time.Tim
|
||||
// add to RTX buffer using sequence number after accounting for dropped padding only packets
|
||||
snAdjustment, err := b.snRangeMap.GetValue(flowState.ExtSequenceNumber)
|
||||
if err != nil {
|
||||
b.logger.Errorw("could not get sequence number adjustment", err, "sn", flowState.ExtSequenceNumber, "payloadSize", len(rtpPacket.Payload))
|
||||
b.logger.Errorw(
|
||||
"could not get sequence number adjustment", err,
|
||||
"sn", rtpPacket.SequenceNumber,
|
||||
"esn", flowState.ExtSequenceNumber,
|
||||
"payloadSize", len(rtpPacket.Payload),
|
||||
"rtpStats", b.rtpStats,
|
||||
"snRangeMap", b.snRangeMap,
|
||||
)
|
||||
return
|
||||
}
|
||||
flowState.ExtSequenceNumber -= snAdjustment
|
||||
@@ -571,10 +583,25 @@ func (b *Buffer) calc(rawPkt []byte, rtpPacket *rtp.Packet, arrivalTime time.Tim
|
||||
if errors.Is(err, bucket.ErrPacketTooOld) {
|
||||
packetTooOldCount := b.packetTooOldCount.Inc()
|
||||
if (packetTooOldCount-1)%100 == 0 {
|
||||
b.logger.Warnw("could not add packet to bucket", err, "count", packetTooOldCount)
|
||||
b.logger.Warnw(
|
||||
"could not add packet to bucket", err,
|
||||
"count", packetTooOldCount,
|
||||
"flowState", &flowState,
|
||||
"snAdjustment", snAdjustment,
|
||||
"incomingSequenceNumber", flowState.ExtSequenceNumber+snAdjustment,
|
||||
"rtpStats", b.rtpStats,
|
||||
"snRangeMap", b.snRangeMap,
|
||||
)
|
||||
}
|
||||
} else if err != bucket.ErrRTXPacket {
|
||||
b.logger.Warnw("could not add packet to bucket", err)
|
||||
b.logger.Warnw(
|
||||
"could not add packet to bucket", err,
|
||||
"flowState", &flowState,
|
||||
"snAdjustment", snAdjustment,
|
||||
"incomingSequenceNumber", flowState.ExtSequenceNumber+snAdjustment,
|
||||
"rtpStats", b.rtpStats,
|
||||
"snRangeMap", b.snRangeMap,
|
||||
)
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -599,7 +626,14 @@ func (b *Buffer) patchExtPacket(ep *ExtPacket, buf []byte) *ExtPacket {
|
||||
if err != nil {
|
||||
packetNotFoundCount := b.packetNotFoundCount.Inc()
|
||||
if (packetNotFoundCount-1)%20 == 0 {
|
||||
b.logger.Warnw("could not get packet from bucket", err, "sn", ep.Packet.SequenceNumber, "headSN", b.bucket.HeadSequenceNumber(), "count", packetNotFoundCount)
|
||||
b.logger.Warnw(
|
||||
"could not get packet from bucket", err,
|
||||
"sn", ep.Packet.SequenceNumber,
|
||||
"headSN", b.bucket.HeadSequenceNumber(),
|
||||
"count", packetNotFoundCount,
|
||||
"rtpStats", b.rtpStats,
|
||||
"snRangeMap", b.snRangeMap,
|
||||
)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -866,12 +900,13 @@ func (b *Buffer) SetSenderReportData(rtpTime uint32, ntpTime uint64) {
|
||||
At: time.Now(),
|
||||
}
|
||||
|
||||
didSet := false
|
||||
if b.rtpStats != nil {
|
||||
b.rtpStats.SetRtcpSenderReportData(srData)
|
||||
didSet = b.rtpStats.SetRtcpSenderReportData(srData)
|
||||
}
|
||||
b.RUnlock()
|
||||
|
||||
if b.onRtcpSenderReport != nil {
|
||||
if didSet && b.onRtcpSenderReport != nil {
|
||||
b.onRtcpSenderReport()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,6 +35,8 @@ const (
|
||||
|
||||
cFirstPacketTimeAdjustWindow = 2 * time.Minute
|
||||
cFirstPacketTimeAdjustThreshold = 15 * time.Second
|
||||
|
||||
cPassthroughNTPTimestamp = true
|
||||
)
|
||||
|
||||
// -------------------------------------------------------
|
||||
@@ -118,6 +120,10 @@ type RTCPSenderReportData struct {
|
||||
}
|
||||
|
||||
func (r *RTCPSenderReportData) PropagationDelay() time.Duration {
|
||||
if cPassthroughNTPTimestamp {
|
||||
return 0
|
||||
}
|
||||
|
||||
return r.AtAdjusted.Sub(r.NTPTimestamp.Time())
|
||||
}
|
||||
|
||||
@@ -529,6 +535,12 @@ func (r *rtpStatsBase) maybeAdjustFirstPacketTime(srData *RTCPSenderReportData,
|
||||
"adjustment", r.firstTime.Sub(firstTime).String(),
|
||||
"extNowTS", extNowTS,
|
||||
"extStartTS", extStartTS,
|
||||
"srData", srData,
|
||||
"tsOffset", tsOffset,
|
||||
"timeSinceReceive", timeSinceReceive.String(),
|
||||
"timeSinceFirst", timeSinceFirst.String(),
|
||||
"samplesDiff", samplesDiff,
|
||||
"samplesDuration", samplesDuration,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -623,6 +635,82 @@ func (r *rtpStatsBase) deltaInfo(snapshotID uint32, extStartSN uint64, extHighes
|
||||
}
|
||||
}
|
||||
|
||||
func (r *rtpStatsBase) MarshalLogObject(e zapcore.ObjectEncoder) error {
|
||||
if r == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
e.AddTime("startTime", r.startTime)
|
||||
e.AddTime("endTime", r.endTime)
|
||||
e.AddTime("firstTime", r.firstTime)
|
||||
e.AddTime("highestTime", r.highestTime)
|
||||
|
||||
e.AddUint64("bytes", r.bytes)
|
||||
e.AddUint64("headerBytes", r.headerBytes)
|
||||
|
||||
e.AddUint64("packetsDuplicate", r.packetsDuplicate)
|
||||
e.AddUint64("bytesDuplicate", r.bytesDuplicate)
|
||||
e.AddUint64("headerBytesDuplicate", r.headerBytesDuplicate)
|
||||
|
||||
e.AddUint64("packetsPadding", r.packetsPadding)
|
||||
e.AddUint64("bytesPadding", r.bytesPadding)
|
||||
e.AddUint64("headerBytesPadding", r.headerBytesPadding)
|
||||
|
||||
e.AddUint64("packetsOutOfOrder", r.packetsOutOfOrder)
|
||||
|
||||
e.AddUint64("packetsLost", r.packetsLost)
|
||||
|
||||
e.AddUint32("frames", r.frames)
|
||||
|
||||
e.AddFloat64("jitter", r.jitter)
|
||||
e.AddFloat64("maxJitter", r.maxJitter)
|
||||
|
||||
hasLoss := false
|
||||
first := true
|
||||
str := "["
|
||||
for burst, count := range r.gapHistogram {
|
||||
if count == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
hasLoss = true
|
||||
|
||||
if !first {
|
||||
str += ", "
|
||||
}
|
||||
first = false
|
||||
str += fmt.Sprintf("%d:%d", burst+1, count)
|
||||
}
|
||||
str += "]"
|
||||
if hasLoss {
|
||||
e.AddString("gapHistogram", str)
|
||||
}
|
||||
|
||||
e.AddUint32("nacks", r.nacks)
|
||||
e.AddUint32("nackAcks", r.nackAcks)
|
||||
e.AddUint32("nackMisses", r.nackMisses)
|
||||
e.AddUint32("nackRepeated", r.nackRepeated)
|
||||
|
||||
e.AddUint32("plis", r.plis)
|
||||
e.AddTime("lastPli", r.lastPli)
|
||||
|
||||
e.AddUint32("layerLockPlis", r.layerLockPlis)
|
||||
e.AddTime("lastLayerLockPli", r.lastLayerLockPli)
|
||||
|
||||
e.AddUint32("firs", r.firs)
|
||||
e.AddTime("lastFir", r.lastFir)
|
||||
|
||||
e.AddUint32("keyFrames", r.keyFrames)
|
||||
e.AddTime("lastKeyFrame", r.lastKeyFrame)
|
||||
|
||||
e.AddUint32("rtt", r.rtt)
|
||||
e.AddUint32("maxRtt", r.maxRtt)
|
||||
|
||||
e.AddObject("srFirst", r.srFirst)
|
||||
e.AddObject("srNewest", r.srNewest)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *rtpStatsBase) toString(
|
||||
extStartSN, extHighestSN, extStartTS, extHighestTS uint64,
|
||||
packetsLost uint64,
|
||||
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/pion/rtcp"
|
||||
"go.uber.org/zap/zapcore"
|
||||
|
||||
"github.com/livekit/livekit-server/pkg/sfu/utils"
|
||||
"github.com/livekit/protocol/livekit"
|
||||
@@ -61,6 +62,8 @@ const (
|
||||
cReportSlack = float64(60.0)
|
||||
)
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
type RTPFlowState struct {
|
||||
IsNotHandled bool
|
||||
|
||||
@@ -75,6 +78,24 @@ type RTPFlowState struct {
|
||||
ExtTimestamp uint64
|
||||
}
|
||||
|
||||
func (r *RTPFlowState) MarshalLogObject(e zapcore.ObjectEncoder) error {
|
||||
if r == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
e.AddBool("IsNotHandled", r.IsNotHandled)
|
||||
e.AddBool("HasLoss", r.HasLoss)
|
||||
e.AddUint64("LossStartInclusive", r.LossStartInclusive)
|
||||
e.AddUint64("LossEndExclusive", r.LossEndExclusive)
|
||||
e.AddBool("IsDuplicate", r.IsDuplicate)
|
||||
e.AddBool("IsOutOfOrder", r.IsOutOfOrder)
|
||||
e.AddUint64("ExtSequenceNumber", r.ExtSequenceNumber)
|
||||
e.AddUint64("ExtTimestamp", r.ExtTimestamp)
|
||||
return nil
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
type RTPStatsReceiver struct {
|
||||
*rtpStatsBase
|
||||
|
||||
@@ -90,8 +111,10 @@ type RTPStatsReceiver struct {
|
||||
propagationDelayDeltaHighStartTime time.Time
|
||||
propagationDelaySpike time.Duration
|
||||
|
||||
clockSkewCount int
|
||||
outOfOrderSsenderReportCount int
|
||||
clockSkewCount int
|
||||
outOfOrderSenderReportCount int
|
||||
largeJumpCount int
|
||||
largeJumpNegativeCount int
|
||||
}
|
||||
|
||||
func NewRTPStatsReceiver(params RTPStatsParams) *RTPStatsReceiver {
|
||||
@@ -169,27 +192,39 @@ func (r *RTPStatsReceiver) Update(
|
||||
|
||||
pktSize := uint64(hdrSize + payloadSize + paddingSize)
|
||||
gapSN := int64(resSN.ExtendedVal - resSN.PreExtendedHighest)
|
||||
getLoggingFields := func() []interface{} {
|
||||
return []interface{}{
|
||||
"extStartSN", r.sequenceNumber.GetExtendedStart(),
|
||||
"extHighestSN", r.sequenceNumber.GetExtendedHighest(),
|
||||
"extStartTS", r.timestamp.GetExtendedStart(),
|
||||
"extHighestTS", r.timestamp.GetExtendedHighest(),
|
||||
"startTime", r.startTime.String(),
|
||||
"firstTime", r.firstTime.String(),
|
||||
"highestTime", r.highestTime.String(),
|
||||
"prevSN", resSN.PreExtendedHighest,
|
||||
"currSN", resSN.ExtendedVal,
|
||||
"gapSN", gapSN,
|
||||
"prevTS", resTS.PreExtendedHighest,
|
||||
"currTS", resTS.ExtendedVal,
|
||||
"gapTS", resTS.ExtendedVal - resTS.PreExtendedHighest,
|
||||
"packetTime", packetTime.String(),
|
||||
"sequenceNumber", sequenceNumber,
|
||||
"timestamp", timestamp,
|
||||
"marker", marker,
|
||||
"hdrSize", hdrSize,
|
||||
"payloadSize", payloadSize,
|
||||
"paddingSize", paddingSize,
|
||||
}
|
||||
}
|
||||
if gapSN <= 0 { // duplicate OR out-of-order
|
||||
if -gapSN >= cNumSequenceNumbers/2 {
|
||||
r.logger.Warnw(
|
||||
"large sequence number gap negative", nil,
|
||||
"extStartSN", r.sequenceNumber.GetExtendedStart(),
|
||||
"extHighestSN", r.sequenceNumber.GetExtendedHighest(),
|
||||
"extStartTS", r.timestamp.GetExtendedStart(),
|
||||
"extHighestTS", r.timestamp.GetExtendedHighest(),
|
||||
"firstTime", r.firstTime.String(),
|
||||
"highestTime", r.highestTime.String(),
|
||||
"prev", resSN.PreExtendedHighest,
|
||||
"curr", resSN.ExtendedVal,
|
||||
"gap", gapSN,
|
||||
"packetTime", packetTime.String(),
|
||||
"sequenceNumber", sequenceNumber,
|
||||
"timestamp", timestamp,
|
||||
"marker", marker,
|
||||
"hdrSize", hdrSize,
|
||||
"payloadSize", payloadSize,
|
||||
"paddingSize", paddingSize,
|
||||
)
|
||||
if r.largeJumpNegativeCount%100 == 0 {
|
||||
r.logger.Warnw(
|
||||
"large sequence number gap negative", nil,
|
||||
append(getLoggingFields(), "count", r.largeJumpNegativeCount)...,
|
||||
)
|
||||
}
|
||||
r.largeJumpNegativeCount++
|
||||
}
|
||||
|
||||
if gapSN != 0 {
|
||||
@@ -212,26 +247,14 @@ func (r *RTPStatsReceiver) Update(
|
||||
flowState.ExtSequenceNumber = resSN.ExtendedVal
|
||||
flowState.ExtTimestamp = resTS.ExtendedVal
|
||||
} else { // in-order
|
||||
if gapSN >= cNumSequenceNumbers/2 {
|
||||
r.logger.Warnw(
|
||||
"large sequence number gap", nil,
|
||||
"extStartSN", r.sequenceNumber.GetExtendedStart(),
|
||||
"extHighestSN", r.sequenceNumber.GetExtendedHighest(),
|
||||
"extStartTS", r.timestamp.GetExtendedStart(),
|
||||
"extHighestTS", r.timestamp.GetExtendedHighest(),
|
||||
"firstTime", r.firstTime.String(),
|
||||
"highestTime", r.highestTime.String(),
|
||||
"prev", resSN.PreExtendedHighest,
|
||||
"curr", resSN.ExtendedVal,
|
||||
"gap", gapSN,
|
||||
"packetTime", packetTime.String(),
|
||||
"sequenceNumber", sequenceNumber,
|
||||
"timestamp", timestamp,
|
||||
"marker", marker,
|
||||
"hdrSize", hdrSize,
|
||||
"payloadSize", payloadSize,
|
||||
"paddingSize", paddingSize,
|
||||
)
|
||||
if gapSN >= cNumSequenceNumbers/2 || resTS.ExtendedVal < resTS.PreExtendedHighest {
|
||||
if r.largeJumpCount%100 == 0 {
|
||||
r.logger.Warnw(
|
||||
"large sequence number gap OR time reversed", nil,
|
||||
append(getLoggingFields(), "count", r.largeJumpCount)...,
|
||||
)
|
||||
}
|
||||
r.largeJumpCount++
|
||||
}
|
||||
|
||||
// update gap histogram
|
||||
@@ -277,12 +300,12 @@ func (r *RTPStatsReceiver) Update(
|
||||
return
|
||||
}
|
||||
|
||||
func (r *RTPStatsReceiver) SetRtcpSenderReportData(srData *RTCPSenderReportData) {
|
||||
func (r *RTPStatsReceiver) SetRtcpSenderReportData(srData *RTCPSenderReportData) bool {
|
||||
r.lock.Lock()
|
||||
defer r.lock.Unlock()
|
||||
|
||||
if srData == nil || !r.initialized {
|
||||
return
|
||||
return false
|
||||
}
|
||||
|
||||
// prevent against extreme case of anachronous sender reports
|
||||
@@ -293,7 +316,7 @@ func (r *RTPStatsReceiver) SetRtcpSenderReportData(srData *RTCPSenderReportData)
|
||||
"last", r.srNewest,
|
||||
"current", srData,
|
||||
)
|
||||
return
|
||||
return false
|
||||
}
|
||||
|
||||
tsCycles := uint64(0)
|
||||
@@ -342,17 +365,17 @@ func (r *RTPStatsReceiver) SetRtcpSenderReportData(srData *RTCPSenderReportData)
|
||||
// i. e. muting replacing with null and unmute restoring the original track.
|
||||
// Or it could be due bad report generation.
|
||||
// In any case, ignore out-of-order reports.
|
||||
if r.outOfOrderSsenderReportCount%10 == 0 {
|
||||
if r.outOfOrderSenderReportCount%10 == 0 {
|
||||
r.logger.Infow(
|
||||
"received sender report, out-of-order, skipping",
|
||||
"first", r.srFirst,
|
||||
"last", r.srNewest,
|
||||
"current", &srDataCopy,
|
||||
"count", r.outOfOrderSsenderReportCount,
|
||||
"count", r.outOfOrderSenderReportCount,
|
||||
)
|
||||
}
|
||||
r.outOfOrderSsenderReportCount++
|
||||
return
|
||||
r.outOfOrderSenderReportCount++
|
||||
return false
|
||||
}
|
||||
|
||||
if r.srNewest != nil {
|
||||
@@ -438,7 +461,7 @@ func (r *RTPStatsReceiver) SetRtcpSenderReportData(srData *RTCPSenderReportData)
|
||||
}
|
||||
|
||||
if r.propagationDelayDeltaHighCount >= cPropagationDelayDeltaHighResetNumReports && time.Since(r.propagationDelayDeltaHighStartTime) >= cPropagationDelayDeltaHighResetWait {
|
||||
r.logger.Debugw("re-initializing propagation delay", append(getPropagationFields(), "newPropagationDelay", propagationDelay.String())...)
|
||||
r.logger.Debugw("re-initializing propagation delay", append(getPropagationFields(), "newPropagationDelay", r.propagationDelaySpike.String())...)
|
||||
initPropagationDelay(r.propagationDelaySpike)
|
||||
}
|
||||
} else {
|
||||
@@ -471,6 +494,7 @@ func (r *RTPStatsReceiver) SetRtcpSenderReportData(srData *RTCPSenderReportData)
|
||||
r.srNewest = &srDataCopy
|
||||
|
||||
r.maybeAdjustFirstPacketTime(r.srNewest, 0, r.timestamp.GetExtendedStart())
|
||||
return true
|
||||
}
|
||||
|
||||
func (r *RTPStatsReceiver) GetRtcpSenderReportData() *RTCPSenderReportData {
|
||||
@@ -561,6 +585,22 @@ func (r *RTPStatsReceiver) DeltaInfo(snapshotID uint32) *RTPDeltaInfo {
|
||||
return r.deltaInfo(snapshotID, r.sequenceNumber.GetExtendedStart(), r.sequenceNumber.GetExtendedHighest())
|
||||
}
|
||||
|
||||
func (r *RTPStatsReceiver) MarshalLogObject(e zapcore.ObjectEncoder) error {
|
||||
if r == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
r.lock.RLock()
|
||||
defer r.lock.RUnlock()
|
||||
|
||||
e.AddObject("base", r.rtpStatsBase)
|
||||
e.AddUint64("extendedStartSN", r.sequenceNumber.GetExtendedStart())
|
||||
e.AddUint64("extHighestSN", r.sequenceNumber.GetExtendedHighest())
|
||||
e.AddUint64("extStartTS", r.timestamp.GetExtendedStart())
|
||||
e.AddUint64("extHighestTS", r.timestamp.GetExtendedHighest())
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *RTPStatsReceiver) String() string {
|
||||
r.lock.RLock()
|
||||
defer r.lock.RUnlock()
|
||||
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/pion/rtcp"
|
||||
"go.uber.org/zap/zapcore"
|
||||
|
||||
"github.com/livekit/mediatransportutil"
|
||||
"github.com/livekit/protocol/livekit"
|
||||
@@ -33,6 +34,8 @@ const (
|
||||
cSenderReportInitialWait = time.Second
|
||||
)
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
type snInfoFlag byte
|
||||
|
||||
const (
|
||||
@@ -161,6 +164,8 @@ type RTPStatsSender struct {
|
||||
|
||||
clockSkewCount int
|
||||
metadataCacheOverflowCount int
|
||||
largeJumpNegativeCount int
|
||||
largeJumpCount int
|
||||
}
|
||||
|
||||
func NewRTPStatsSender(params RTPStatsParams) *RTPStatsSender {
|
||||
@@ -282,33 +287,45 @@ func (r *RTPStatsSender) Update(
|
||||
pktSize := uint64(hdrSize + payloadSize + paddingSize)
|
||||
isDuplicate := false
|
||||
gapSN := int64(extSequenceNumber - r.extHighestSN)
|
||||
getLoggingFields := func() []interface{} {
|
||||
return []interface{}{
|
||||
"extStartSN", r.extStartSN,
|
||||
"extHighestSN", r.extHighestSN,
|
||||
"extStartTS", r.extStartTS,
|
||||
"extHighestTS", r.extHighestTS,
|
||||
"startTime", r.startTime.String(),
|
||||
"firstTime", r.firstTime.String(),
|
||||
"highestTime", r.highestTime.String(),
|
||||
"prevSN", r.extHighestSN,
|
||||
"currSN", extSequenceNumber,
|
||||
"gapSN", gapSN,
|
||||
"prevTS", r.extHighestTS,
|
||||
"currTS", extTimestamp,
|
||||
"gapTS", extTimestamp - r.extHighestTS,
|
||||
"packetTime", packetTime.String(),
|
||||
"sequenceNumber", extSequenceNumber,
|
||||
"timestamp", extTimestamp,
|
||||
"marker", marker,
|
||||
"hdrSize", hdrSize,
|
||||
"payloadSize", payloadSize,
|
||||
"paddingSize", paddingSize,
|
||||
"firstSR", r.srFirst,
|
||||
"lastSR", r.srNewest,
|
||||
}
|
||||
}
|
||||
if gapSN <= 0 { // duplicate OR out-of-order
|
||||
if payloadSize == 0 && extSequenceNumber < r.extStartSN {
|
||||
// do not start on a padding only packet
|
||||
return
|
||||
}
|
||||
if -gapSN >= cNumSequenceNumbers/2 {
|
||||
r.logger.Warnw(
|
||||
"large sequence number gap negative", nil,
|
||||
"extStartSN", r.extStartSN,
|
||||
"extHighestSN", r.extHighestSN,
|
||||
"extStartTS", r.extStartTS,
|
||||
"extHighestTS", r.extHighestTS,
|
||||
"firstTime", r.firstTime.String(),
|
||||
"highestTime", r.highestTime.String(),
|
||||
"prev", r.extHighestSN,
|
||||
"curr", extSequenceNumber,
|
||||
"gap", gapSN,
|
||||
"packetTime", packetTime.String(),
|
||||
"sequenceNumber", extSequenceNumber,
|
||||
"timestamp", extTimestamp,
|
||||
"marker", marker,
|
||||
"hdrSize", hdrSize,
|
||||
"payloadSize", payloadSize,
|
||||
"paddingSize", paddingSize,
|
||||
"firstSR", r.srFirst,
|
||||
"lastSR", r.srNewest,
|
||||
)
|
||||
if r.largeJumpNegativeCount%100 == 0 {
|
||||
r.logger.Warnw(
|
||||
"large sequence number gap negative", nil,
|
||||
append(getLoggingFields(), "count", r.largeJumpNegativeCount)...,
|
||||
)
|
||||
}
|
||||
r.largeJumpNegativeCount++
|
||||
}
|
||||
|
||||
if extSequenceNumber < r.extStartSN {
|
||||
@@ -333,10 +350,12 @@ func (r *RTPStatsSender) Update(
|
||||
|
||||
r.logger.Infow(
|
||||
"adjusting start sequence number",
|
||||
"snBefore", r.extStartSN,
|
||||
"snAfter", extSequenceNumber,
|
||||
"tsBefore", r.extStartTS,
|
||||
"tsAfter", extTimestamp,
|
||||
append(getLoggingFields(),
|
||||
"snBefore", r.extStartSN,
|
||||
"snAfter", extSequenceNumber,
|
||||
"tsBefore", r.extStartTS,
|
||||
"tsAfter", extTimestamp,
|
||||
)...,
|
||||
)
|
||||
r.extStartSN = extSequenceNumber
|
||||
}
|
||||
@@ -355,28 +374,14 @@ func (r *RTPStatsSender) Update(
|
||||
r.setSnInfo(extSequenceNumber, r.extHighestSN, uint16(pktSize), uint8(hdrSize), uint16(payloadSize), marker, true)
|
||||
}
|
||||
} else { // in-order
|
||||
if gapSN >= cNumSequenceNumbers/2 {
|
||||
r.logger.Warnw(
|
||||
"large sequence number gap", nil,
|
||||
"extStartSN", r.extStartSN,
|
||||
"extHighestSN", r.extHighestSN,
|
||||
"extStartTS", r.extStartTS,
|
||||
"extHighestTS", r.extHighestTS,
|
||||
"firstTime", r.firstTime.String(),
|
||||
"highestTime", r.highestTime.String(),
|
||||
"prev", r.extHighestSN,
|
||||
"curr", extSequenceNumber,
|
||||
"gap", gapSN,
|
||||
"packetTime", packetTime.String(),
|
||||
"sequenceNumber", extSequenceNumber,
|
||||
"timestamp", extTimestamp,
|
||||
"marker", marker,
|
||||
"hdrSize", hdrSize,
|
||||
"payloadSize", payloadSize,
|
||||
"paddingSize", paddingSize,
|
||||
"firstSR", r.srFirst,
|
||||
"lastSR", r.srNewest,
|
||||
)
|
||||
if gapSN >= cNumSequenceNumbers/2 || extTimestamp < r.extHighestTS {
|
||||
if r.largeJumpCount%100 == 0 {
|
||||
r.logger.Warnw(
|
||||
"large sequence number gap OR time reversed", nil,
|
||||
append(getLoggingFields(), "count", r.largeJumpCount)...,
|
||||
)
|
||||
}
|
||||
r.largeJumpCount++
|
||||
}
|
||||
|
||||
// update gap histogram
|
||||
@@ -394,10 +399,12 @@ func (r *RTPStatsSender) Update(
|
||||
if extTimestamp < r.extStartTS {
|
||||
r.logger.Infow(
|
||||
"adjusting start timestamp",
|
||||
"snBefore", r.extStartSN,
|
||||
"snAfter", extSequenceNumber,
|
||||
"tsBefore", r.extStartTS,
|
||||
"tsAfter", extTimestamp,
|
||||
append(getLoggingFields(),
|
||||
"snBefore", r.extStartSN,
|
||||
"snAfter", extSequenceNumber,
|
||||
"tsBefore", r.extStartTS,
|
||||
"tsAfter", extTimestamp,
|
||||
)...,
|
||||
)
|
||||
r.extStartTS = extTimestamp
|
||||
}
|
||||
@@ -484,9 +491,7 @@ func (r *RTPStatsSender) UpdateFromReceiverReport(rr rtcp.ReceptionReport) (rtt
|
||||
if err == nil {
|
||||
isRttChanged = rtt != r.rtt
|
||||
} else {
|
||||
if !errors.Is(err, mediatransportutil.ErrRttNotLastSenderReport) && !errors.Is(err, mediatransportutil.ErrRttNoLastSenderReport) {
|
||||
r.logger.Warnw("error getting rtt", err)
|
||||
}
|
||||
r.logger.Debugw("error getting rtt", "error", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -625,7 +630,7 @@ func (r *RTPStatsSender) GetExpectedRTPTimestamp(at time.Time) (expectedTSExt ui
|
||||
defer r.lock.RUnlock()
|
||||
|
||||
if !r.initialized {
|
||||
err = errors.New("uninitilaized")
|
||||
err = errors.New("uninitialized")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -643,10 +648,19 @@ func (r *RTPStatsSender) GetRtcpSenderReport(ssrc uint32, publisherSRData *RTCPS
|
||||
return nil
|
||||
}
|
||||
|
||||
timeSincePublisherSR := time.Since(publisherSRData.AtAdjusted)
|
||||
now := publisherSRData.AtAdjusted.Add(timeSincePublisherSR)
|
||||
nowNTP := mediatransportutil.ToNtpTime(now)
|
||||
nowRTPExt := publisherSRData.RTPTimestampExt - tsOffset + uint64(timeSincePublisherSR.Nanoseconds()*int64(r.params.ClockRate)/1e9)
|
||||
timeSincePublisherSRAdjusted := time.Since(publisherSRData.AtAdjusted)
|
||||
now := publisherSRData.AtAdjusted.Add(timeSincePublisherSRAdjusted)
|
||||
var (
|
||||
nowNTP mediatransportutil.NtpTime
|
||||
nowRTPExt uint64
|
||||
)
|
||||
if cPassthroughNTPTimestamp {
|
||||
nowNTP = publisherSRData.NTPTimestamp
|
||||
nowRTPExt = publisherSRData.RTPTimestampExt - tsOffset
|
||||
} else {
|
||||
nowNTP = mediatransportutil.ToNtpTime(now)
|
||||
nowRTPExt = publisherSRData.RTPTimestampExt - tsOffset + uint64(timeSincePublisherSRAdjusted.Nanoseconds()*int64(r.params.ClockRate)/1e9)
|
||||
}
|
||||
|
||||
srData := &RTCPSenderReportData{
|
||||
NTPTimestamp: nowNTP,
|
||||
@@ -664,13 +678,15 @@ func (r *RTPStatsSender) GetRtcpSenderReport(ssrc uint32, publisherSRData *RTCPS
|
||||
"feed", publisherSRData,
|
||||
"tsOffset", tsOffset,
|
||||
"timeNow", time.Now().String(),
|
||||
"now", now.String(),
|
||||
"extStartTS", r.extStartTS,
|
||||
"extHighestTS", r.extHighestTS,
|
||||
"highestTime", r.highestTime.String(),
|
||||
"timeSinceHighest", now.Sub(r.highestTime).String(),
|
||||
"firstTime", r.firstTime.String(),
|
||||
"timeSinceFirst", now.Sub(r.firstTime).String(),
|
||||
"timeSincePublisherSR", timeSincePublisherSR.String(),
|
||||
"timeSincePublisherSRAdjusted", timeSincePublisherSRAdjusted.String(),
|
||||
"timeSincePublisherSR", time.Since(publisherSRData.At).String(),
|
||||
"nowRTPExt", nowRTPExt,
|
||||
}
|
||||
}
|
||||
@@ -810,6 +826,28 @@ func (r *RTPStatsSender) DeltaInfoSender(senderSnapshotID uint32) *RTPDeltaInfo
|
||||
}
|
||||
}
|
||||
|
||||
func (r *RTPStatsSender) MarshalLogObject(e zapcore.ObjectEncoder) error {
|
||||
if r == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
r.lock.RLock()
|
||||
defer r.lock.RUnlock()
|
||||
|
||||
e.AddObject("base", r.rtpStatsBase)
|
||||
e.AddUint64("extStartSN", r.extStartSN)
|
||||
e.AddUint64("extHighestSN", r.extHighestSN)
|
||||
e.AddUint64("extStartTS", r.extStartTS)
|
||||
e.AddUint64("extHighestTS", r.extHighestTS)
|
||||
e.AddTime("lastRRTime", r.lastRRTime)
|
||||
e.AddReflected("lastRR", r.lastRR)
|
||||
e.AddUint64("extHighestSNFromRR", r.extHighestSNFromRR)
|
||||
e.AddUint64("packetsLostFromRR", r.packetsLostFromRR)
|
||||
e.AddFloat64("jitterFromRR", r.jitterFromRR)
|
||||
e.AddFloat64("maxJitterFromRR", r.maxJitterFromRR)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *RTPStatsSender) String() string {
|
||||
r.lock.RLock()
|
||||
defer r.lock.RUnlock()
|
||||
|
||||
@@ -124,18 +124,18 @@ func (w *windowStat) calculatePacketScore(plw float64, includeRTT bool, includeJ
|
||||
return score
|
||||
}
|
||||
|
||||
func (w *windowStat) calculateBitrateScore(expectedBitrate int64, isEnabled bool) float64 {
|
||||
if expectedBitrate == 0 || !isEnabled {
|
||||
func (w *windowStat) calculateBitrateScore(expectedBits int64, isEnabled bool) float64 {
|
||||
if expectedBits == 0 || !isEnabled {
|
||||
// unsupported mode OR all layers stopped
|
||||
return cMaxScore
|
||||
}
|
||||
|
||||
var score float64
|
||||
if w.bytes != 0 {
|
||||
// using the ratio of expectedBitrate / actualBitrate
|
||||
// using the ratio of expectedBits / actualBits
|
||||
// the quality inflection points are approximately
|
||||
// GOOD at ~2.7x, POOR at ~20.1x
|
||||
score = cMaxScore - 20*math.Log(float64(expectedBitrate)/float64(w.bytes*8))
|
||||
score = cMaxScore - 20*math.Log(float64(expectedBits)/float64(w.bytes*8))
|
||||
if score > cMaxScore {
|
||||
score = cMaxScore
|
||||
}
|
||||
@@ -369,7 +369,7 @@ func (q *qualityScorer) AddLayerTransition(distance float64) {
|
||||
|
||||
func (q *qualityScorer) updateAtLocked(stat *windowStat, at time.Time) {
|
||||
// always update transitions
|
||||
expectedBitrate, _, err := q.aggregateBitrate.GetAggregateAndRestartAt(at)
|
||||
expectedBits, _, err := q.aggregateBitrate.GetAggregateAndRestartAt(at)
|
||||
if err != nil {
|
||||
q.params.Logger.Warnw("error getting expected bitrate", err)
|
||||
}
|
||||
@@ -405,7 +405,7 @@ func (q *qualityScorer) updateAtLocked(stat *windowStat, at time.Time) {
|
||||
}
|
||||
} else {
|
||||
packetScore := stat.calculatePacketScore(plw, q.params.IncludeRTT, q.params.IncludeJitter)
|
||||
bitrateScore := stat.calculateBitrateScore(expectedBitrate, q.params.EnableBitrateScore)
|
||||
bitrateScore := stat.calculateBitrateScore(expectedBits, q.params.EnableBitrateScore)
|
||||
layerScore := math.Max(math.Min(cMaxScore, cMaxScore-(expectedDistance*distanceWeight)), 0.0)
|
||||
|
||||
minScore := math.Min(packetScore, bitrateScore)
|
||||
@@ -440,7 +440,7 @@ func (q *qualityScorer) updateAtLocked(stat *windowStat, at time.Time) {
|
||||
prevCQ := scoreToConnectionQuality(q.score)
|
||||
currCQ := scoreToConnectionQuality(score)
|
||||
if utils.IsConnectionQualityLower(prevCQ, currCQ) {
|
||||
q.params.Logger.Infow(
|
||||
q.params.Logger.Debugw(
|
||||
"quality drop",
|
||||
"reason", reason,
|
||||
"prevScore", q.score,
|
||||
@@ -451,7 +451,7 @@ func (q *qualityScorer) updateAtLocked(stat *windowStat, at time.Time) {
|
||||
"stat", stat,
|
||||
"packetLossWeight", plw,
|
||||
"maxPPS", q.maxPPS,
|
||||
"expectedBitrate", expectedBitrate,
|
||||
"expectedBits", expectedBits,
|
||||
"expectedDistance", expectedDistance,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -63,6 +63,7 @@ type TrackSender interface {
|
||||
layer int32,
|
||||
publisherSRData *buffer.RTCPSenderReportData,
|
||||
) error
|
||||
Resync()
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
@@ -86,8 +86,12 @@ func (d *DownTrackSpreader) HasDownTrack(subscriberID livekit.ParticipantID) boo
|
||||
return ok
|
||||
}
|
||||
|
||||
func (d *DownTrackSpreader) Broadcast(writer func(TrackSender)) {
|
||||
func (d *DownTrackSpreader) Broadcast(writer func(TrackSender)) int {
|
||||
downTracks := d.GetDownTracks()
|
||||
if len(downTracks) == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
threshold := uint64(d.params.Threshold)
|
||||
if threshold == 0 {
|
||||
threshold = 1000000
|
||||
@@ -97,6 +101,7 @@ func (d *DownTrackSpreader) Broadcast(writer func(TrackSender)) {
|
||||
// WriteRTP takes about 50µs on average, so we write to 2 down tracks per loop.
|
||||
step := uint64(2)
|
||||
utils.ParallelExec(downTracks, threshold, step, writer)
|
||||
return len(downTracks)
|
||||
}
|
||||
|
||||
func (d *DownTrackSpreader) DownTrackCount() int {
|
||||
|
||||
+160
-95
@@ -192,7 +192,7 @@ type ForwarderState struct {
|
||||
ReferenceLayerSpatial int32
|
||||
PreStartTime time.Time
|
||||
ExtFirstTS uint64
|
||||
RefTSOffset uint64
|
||||
DummyStartTSOffset uint64
|
||||
RTP RTPMungerState
|
||||
Codec interface{}
|
||||
}
|
||||
@@ -203,12 +203,12 @@ func (f ForwarderState) String() string {
|
||||
case codecmunger.VP8State:
|
||||
codecString = codecState.String()
|
||||
}
|
||||
return fmt.Sprintf("ForwarderState{started: %v, referenceLayerSpatial: %d, preStartTime: %s, extFirstTS: %d, refTSOffset: %d, rtp: %s, codec: %s}",
|
||||
return fmt.Sprintf("ForwarderState{started: %v, referenceLayerSpatial: %d, preStartTime: %s, extFirstTS: %d, dummyStartTSOffset: %d, rtp: %s, codec: %s}",
|
||||
f.Started,
|
||||
f.ReferenceLayerSpatial,
|
||||
f.PreStartTime.String(),
|
||||
f.ExtFirstTS,
|
||||
f.RefTSOffset,
|
||||
f.DummyStartTSOffset,
|
||||
f.RTP.String(),
|
||||
codecString,
|
||||
)
|
||||
@@ -216,6 +216,12 @@ func (f ForwarderState) String() string {
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
type refInfo struct {
|
||||
senderReport *buffer.RTCPSenderReportData
|
||||
tsOffset uint64
|
||||
isTSOffsetValid bool
|
||||
}
|
||||
|
||||
type Forwarder struct {
|
||||
lock sync.RWMutex
|
||||
codec webrtc.RTPCodecCapability
|
||||
@@ -233,8 +239,8 @@ type Forwarder struct {
|
||||
extFirstTS uint64
|
||||
lastSSRC uint32
|
||||
referenceLayerSpatial int32
|
||||
refTSOffset uint64
|
||||
refSenderReports [buffer.DefaultMaxLayerSpatial + 1]*buffer.RTCPSenderReportData
|
||||
dummyStartTSOffset uint64
|
||||
refInfos [buffer.DefaultMaxLayerSpatial + 1]refInfo
|
||||
refIsSVC bool
|
||||
|
||||
provisional *VideoAllocationProvisional
|
||||
@@ -327,15 +333,17 @@ func (f *Forwarder) DetermineCodec(codec webrtc.RTPCodecCapability, extensions [
|
||||
f.vls = videolayerselector.NewSimulcast(f.logger)
|
||||
}
|
||||
f.vls.SetTemporalLayerSelector(temporallayerselector.NewVP8(f.logger))
|
||||
|
||||
case "video/h264":
|
||||
if f.vls != nil {
|
||||
f.vls = videolayerselector.NewSimulcastFromNull(f.vls)
|
||||
} else {
|
||||
f.vls = videolayerselector.NewSimulcast(f.logger)
|
||||
}
|
||||
case "video/vp9":
|
||||
isDDAvailable := ddAvailable(extensions)
|
||||
|
||||
case "video/vp9":
|
||||
// DD-TODO : we only enable dd layer selector for av1/vp9 now, in the future we can enable it for vp8 too
|
||||
isDDAvailable := ddAvailable(extensions)
|
||||
if isDDAvailable {
|
||||
if f.vls != nil {
|
||||
f.vls = videolayerselector.NewDependencyDescriptorFromNull(f.vls)
|
||||
@@ -350,9 +358,9 @@ func (f *Forwarder) DetermineCodec(codec webrtc.RTPCodecCapability, extensions [
|
||||
}
|
||||
}
|
||||
// SVC-TODO: Support for VP9 simulcast. When DD is not available, have to pick selector based on VP9 SVC or Simulcast
|
||||
|
||||
case "video/av1":
|
||||
// DD-TODO : we only enable dd layer selector for av1/vp9 now, in the future we can enable it for vp8 too
|
||||
|
||||
isDDAvailable := ddAvailable(extensions)
|
||||
if isDDAvailable {
|
||||
if f.vls != nil {
|
||||
@@ -384,7 +392,7 @@ func (f *Forwarder) GetState() ForwarderState {
|
||||
ReferenceLayerSpatial: f.referenceLayerSpatial,
|
||||
PreStartTime: f.preStartTime,
|
||||
ExtFirstTS: f.extFirstTS,
|
||||
RefTSOffset: f.refTSOffset,
|
||||
DummyStartTSOffset: f.dummyStartTSOffset,
|
||||
RTP: f.rtpMunger.GetLast(),
|
||||
Codec: f.codecMunger.GetState(),
|
||||
}
|
||||
@@ -405,7 +413,7 @@ func (f *Forwarder) SeedState(state ForwarderState) {
|
||||
f.referenceLayerSpatial = state.ReferenceLayerSpatial
|
||||
f.preStartTime = state.PreStartTime
|
||||
f.extFirstTS = state.ExtFirstTS
|
||||
f.refTSOffset = state.RefTSOffset
|
||||
f.dummyStartTSOffset = state.DummyStartTSOffset
|
||||
}
|
||||
|
||||
func (f *Forwarder) Mute(muted bool, isSubscribeMutable bool) bool {
|
||||
@@ -561,16 +569,39 @@ func (f *Forwarder) GetMaxSubscribedSpatial() int32 {
|
||||
return layer
|
||||
}
|
||||
|
||||
func (f *Forwarder) getReferenceLayer() (int32, int32) {
|
||||
if f.lastSSRC == 0 {
|
||||
return buffer.InvalidLayerSpatial, buffer.InvalidLayerSpatial
|
||||
}
|
||||
|
||||
if f.kind == webrtc.RTPCodecTypeAudio {
|
||||
return 0, 0
|
||||
}
|
||||
|
||||
currentLayerSpatial := f.vls.GetCurrent().Spatial
|
||||
if currentLayerSpatial < 0 || currentLayerSpatial > buffer.DefaultMaxLayerSpatial {
|
||||
return buffer.InvalidLayerSpatial, buffer.InvalidLayerSpatial
|
||||
}
|
||||
|
||||
if f.refIsSVC {
|
||||
return 0, currentLayerSpatial
|
||||
}
|
||||
|
||||
return currentLayerSpatial, currentLayerSpatial
|
||||
}
|
||||
|
||||
func (f *Forwarder) SetRefSenderReport(isSVC bool, layer int32, srData *buffer.RTCPSenderReportData) {
|
||||
f.lock.Lock()
|
||||
defer f.lock.Unlock()
|
||||
|
||||
f.refIsSVC = isSVC
|
||||
if isSVC {
|
||||
layer = 0
|
||||
}
|
||||
if layer >= 0 && int(layer) < len(f.refSenderReports) {
|
||||
f.refSenderReports[layer] = srData
|
||||
refLayer, _ := f.getReferenceLayer()
|
||||
if layer >= 0 && int(layer) < len(f.refInfos) {
|
||||
f.refInfos[layer] = refInfo{srData, 0, false}
|
||||
if layer == refLayer {
|
||||
f.refInfos[layer].tsOffset = f.rtpMunger.GetTSOffset()
|
||||
f.refInfos[layer].isTSOffsetValid = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -579,7 +610,7 @@ func (f *Forwarder) clearRefSenderReportsLocked() {
|
||||
// This is done to prevent use of potentially stale publisher sender reports.
|
||||
//
|
||||
// It is possible to implement mute using pause/unpause
|
||||
// which can implemented using a replaceTrack(null)/replaceTrack(track).
|
||||
// which can be implemented using replaceTrack(null)/replaceTrack(track).
|
||||
// In those cases, the RTP time stamp may not jump across
|
||||
// the mute/pause valley (for the time it is replaced with null track).
|
||||
// So, relying on a report that happened before unmute/unpause
|
||||
@@ -592,7 +623,8 @@ func (f *Forwarder) clearRefSenderReportsLocked() {
|
||||
// 2. Publisher pauses: there are no more reports.
|
||||
// 3. When paused, subscriber can still use the publisher side sender
|
||||
// report to send reports. Although the time since last publisher
|
||||
// sender report is increasing, the reports are correct though.
|
||||
// sender report is increasing, the reports would still be correct
|
||||
// as they referencing a previous (albeit older) correct report.
|
||||
// 4. Publisher unpauses after 20 seconds. But, it may not have advanced
|
||||
// RTP Timestamp by that much. Let us say, it advances only by 5 seconds.
|
||||
// 5. When subscriber starts forwarding packets, it will calculate
|
||||
@@ -604,7 +636,7 @@ func (f *Forwarder) clearRefSenderReportsLocked() {
|
||||
// By clearing sender report on (re)start of a stream, subscribers will wait for a fresh report
|
||||
// after unmute to send sender report.
|
||||
for layer := int32(0); layer < buffer.DefaultMaxLayerSpatial+1; layer++ {
|
||||
f.refSenderReports[layer] = nil
|
||||
f.refInfos[layer] = refInfo{nil, 0, false}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -612,21 +644,12 @@ func (f *Forwarder) GetSenderReportParams() (int32, uint64, *buffer.RTCPSenderRe
|
||||
f.lock.RLock()
|
||||
defer f.lock.RUnlock()
|
||||
|
||||
if f.kind == webrtc.RTPCodecTypeAudio {
|
||||
return 0, f.rtpMunger.GetPinnedTSOffset(), f.refSenderReports[0]
|
||||
refLayer, currentLayerSpatial := f.getReferenceLayer()
|
||||
if refLayer == buffer.InvalidLayerSpatial || !f.refInfos[refLayer].isTSOffsetValid {
|
||||
return buffer.InvalidLayerSpatial, 0, nil
|
||||
}
|
||||
|
||||
currentLayerSpatial := f.vls.GetCurrent().Spatial
|
||||
if currentLayerSpatial < 0 || currentLayerSpatial > buffer.DefaultMaxLayerSpatial {
|
||||
return currentLayerSpatial, f.rtpMunger.GetPinnedTSOffset(), nil
|
||||
}
|
||||
|
||||
refSenderReport := f.refSenderReports[currentLayerSpatial]
|
||||
if f.refIsSVC {
|
||||
refSenderReport = f.refSenderReports[0]
|
||||
}
|
||||
|
||||
return currentLayerSpatial, f.rtpMunger.GetPinnedTSOffset(), refSenderReport
|
||||
return currentLayerSpatial, f.refInfos[refLayer].tsOffset, f.refInfos[refLayer].senderReport
|
||||
}
|
||||
|
||||
func (f *Forwarder) isDeficientLocked() bool {
|
||||
@@ -1534,6 +1557,7 @@ func (f *Forwarder) GetTranslationParams(extPkt *buffer.ExtPacket, layer int32)
|
||||
switch f.kind {
|
||||
case webrtc.RTPCodecTypeAudio:
|
||||
return f.getTranslationParamsAudio(extPkt, layer)
|
||||
|
||||
case webrtc.RTPCodecTypeVideo:
|
||||
return f.getTranslationParamsVideo(extPkt, layer)
|
||||
}
|
||||
@@ -1544,7 +1568,7 @@ func (f *Forwarder) GetTranslationParams(extPkt *buffer.ExtPacket, layer int32)
|
||||
}
|
||||
|
||||
func (f *Forwarder) getReferenceLayerRTPTimestamp(ts uint32, refLayer, targetLayer int32) (uint32, error) {
|
||||
if refLayer < 0 || int(refLayer) > len(f.refSenderReports) || targetLayer < 0 || int(targetLayer) > len(f.refSenderReports) {
|
||||
if refLayer < 0 || int(refLayer) > len(f.refInfos) || targetLayer < 0 || int(targetLayer) > len(f.refInfos) {
|
||||
return 0, fmt.Errorf("invalid layer(s), refLayer: %d, targetLayer: %d", refLayer, targetLayer)
|
||||
}
|
||||
|
||||
@@ -1552,8 +1576,8 @@ func (f *Forwarder) getReferenceLayerRTPTimestamp(ts uint32, refLayer, targetLay
|
||||
return ts, nil
|
||||
}
|
||||
|
||||
srRef := f.refSenderReports[refLayer]
|
||||
srTarget := f.refSenderReports[targetLayer]
|
||||
srRef := f.refInfos[refLayer].senderReport
|
||||
srTarget := f.refInfos[targetLayer].senderReport
|
||||
if srRef == nil || srRef.NTPTimestamp == 0 || srTarget == nil || srTarget.NTPTimestamp == 0 {
|
||||
return 0, fmt.Errorf("unavailable layer(s), refLayer: %d, targetLayer: %d", refLayer, targetLayer)
|
||||
}
|
||||
@@ -1576,7 +1600,9 @@ func (f *Forwarder) processSourceSwitch(extPkt *buffer.ExtPacket, layer int32) (
|
||||
f.referenceLayerSpatial = layer
|
||||
f.rtpMunger.SetLastSnTs(extPkt)
|
||||
f.codecMunger.SetLast(extPkt)
|
||||
|
||||
f.clearRefSenderReportsLocked()
|
||||
|
||||
f.logger.Debugw(
|
||||
"starting forwarding",
|
||||
"sequenceNumber", extPkt.Packet.SequenceNumber,
|
||||
@@ -1589,6 +1615,7 @@ func (f *Forwarder) processSourceSwitch(extPkt *buffer.ExtPacket, layer int32) (
|
||||
return nil, nil
|
||||
} else if f.referenceLayerSpatial == buffer.InvalidLayerSpatial {
|
||||
f.referenceLayerSpatial = layer
|
||||
f.codecMunger.SetLast(extPkt)
|
||||
f.logger.Debugw(
|
||||
"catch up forwarding",
|
||||
"sequenceNumber", extPkt.Packet.SequenceNumber,
|
||||
@@ -1605,6 +1632,21 @@ func (f *Forwarder) processSourceSwitch(extPkt *buffer.ExtPacket, layer int32) (
|
||||
message,
|
||||
"layer", layer,
|
||||
"extExpectedTS", extExpectedTS,
|
||||
"incomingTS", extPkt.Packet.Timestamp,
|
||||
"extIncomingTS", extPkt.ExtTimestamp,
|
||||
"extRefTS", extRefTS,
|
||||
"extLastTS", extLastTS,
|
||||
"diffSeconds", math.Abs(diffSeconds),
|
||||
)
|
||||
}
|
||||
// TODO-REMOVE-AFTER-DATA-COLLECTION
|
||||
logTransitionInfo := func(message string, extExpectedTS, extRefTS, extLastTS uint64, diffSeconds float64) {
|
||||
f.logger.Infow(
|
||||
message,
|
||||
"layer", layer,
|
||||
"extExpectedTS", extExpectedTS,
|
||||
"incomingTS", extPkt.Packet.Timestamp,
|
||||
"extIncomingTS", extPkt.ExtTimestamp,
|
||||
"extRefTS", extRefTS,
|
||||
"extLastTS", extLastTS,
|
||||
"diffSeconds", math.Abs(diffSeconds),
|
||||
@@ -1624,7 +1666,7 @@ func (f *Forwarder) processSourceSwitch(extPkt *buffer.ExtPacket, layer int32) (
|
||||
rtpMungerState := f.rtpMunger.GetLast()
|
||||
extLastTS := rtpMungerState.ExtLastTS
|
||||
extExpectedTS := extLastTS
|
||||
extRefTS := extExpectedTS
|
||||
extRefTS := extLastTS
|
||||
refTS := uint32(extRefTS)
|
||||
switchingAt := time.Now()
|
||||
if !f.skipReferenceTS {
|
||||
@@ -1640,13 +1682,14 @@ func (f *Forwarder) processSourceSwitch(extPkt *buffer.ExtPacket, layer int32) (
|
||||
}
|
||||
}
|
||||
|
||||
extRefTS = (extRefTS & 0xFFFF_FFFF_0000_0000) + uint64(refTS)
|
||||
|
||||
expectedTS := uint32(extExpectedTS)
|
||||
if (refTS-expectedTS) < 1<<31 && refTS < expectedTS {
|
||||
// adjust extRefTS to current packet's timestamp mapped to that of reference layer's
|
||||
extRefTS = (extRefTS & 0xFFFF_FFFF_0000_0000) + uint64(refTS) + f.dummyStartTSOffset
|
||||
lastTS := uint32(extLastTS)
|
||||
refTS = uint32(extRefTS)
|
||||
if (refTS-lastTS) < 1<<31 && refTS < lastTS {
|
||||
extRefTS += (1 << 32)
|
||||
}
|
||||
if (expectedTS-refTS) < 1<<31 && expectedTS < refTS && extRefTS >= 1<<32 {
|
||||
if (lastTS-refTS) < 1<<31 && lastTS < refTS && extRefTS >= 1<<32 {
|
||||
extRefTS -= (1 << 32)
|
||||
}
|
||||
|
||||
@@ -1659,23 +1702,26 @@ func (f *Forwarder) processSourceSwitch(extPkt *buffer.ExtPacket, layer int32) (
|
||||
timeSinceFirst := time.Since(f.preStartTime)
|
||||
rtpDiff := uint64(timeSinceFirst.Nanoseconds() * int64(f.codec.ClockRate) / 1e9)
|
||||
extExpectedTS = f.extFirstTS + rtpDiff
|
||||
if f.refTSOffset == 0 {
|
||||
f.refTSOffset = extExpectedTS - extRefTS
|
||||
if f.dummyStartTSOffset == 0 {
|
||||
f.dummyStartTSOffset = extExpectedTS - uint64(refTS)
|
||||
extRefTS = extExpectedTS
|
||||
f.logger.Infow(
|
||||
"calculating refTSOffset",
|
||||
"calculating dummyStartTSOffset",
|
||||
"preStartTime", f.preStartTime.String(),
|
||||
"extFirstTS", f.extFirstTS,
|
||||
"timeSinceFirst", timeSinceFirst,
|
||||
"timeSinceFirst", timeSinceFirst.String(),
|
||||
"rtpDiff", rtpDiff,
|
||||
"extRefTS", extRefTS,
|
||||
"refTSOffset", f.refTSOffset,
|
||||
"incomingTS", extPkt.Packet.Timestamp,
|
||||
"referenceLayerSpatial", f.referenceLayerSpatial,
|
||||
"dummyStartTSOffset", f.dummyStartTSOffset,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
extRefTS += f.refTSOffset
|
||||
|
||||
bigJump := false
|
||||
var extNextTS uint64
|
||||
if f.lastSSRC == 0 {
|
||||
// If resuming (e. g. on unmute), keep next timestamp close to expected timestamp.
|
||||
@@ -1690,7 +1736,7 @@ func (f *Forwarder) processSourceSwitch(extPkt *buffer.ExtPacket, layer int32) (
|
||||
// timestamp should be used as things will catch up to real time when channel capacity
|
||||
// increases and pacer starts sending at faster rate.
|
||||
//
|
||||
// But, the challenege is distinguishing between the two cases. As a compromise, the difference
|
||||
// But, the challenge is distinguishing between the two cases. As a compromise, the difference
|
||||
// between extExpectedTS and extRefTS is thresholded. Difference below the threshold is treated as Case 2
|
||||
// and above as Case 1.
|
||||
//
|
||||
@@ -1701,12 +1747,14 @@ func (f *Forwarder) processSourceSwitch(extPkt *buffer.ExtPacket, layer int32) (
|
||||
diffSeconds := float64(int64(extExpectedTS-extRefTS)) / float64(f.codec.ClockRate)
|
||||
if diffSeconds >= 0.0 {
|
||||
if f.resumeBehindThreshold > 0 && diffSeconds > f.resumeBehindThreshold {
|
||||
logTransition("resume, reference too far behind", extExpectedTS, extRefTS, extLastTS, diffSeconds)
|
||||
logTransitionInfo("resume, reference too far behind", extExpectedTS, extRefTS, extLastTS, diffSeconds)
|
||||
extNextTS = extExpectedTS
|
||||
bigJump = true
|
||||
} else if diffSeconds > ResumeBehindHighThresholdSeconds {
|
||||
// could be due to incorrect reference calculation
|
||||
logTransition("resume, reference very far behind", extExpectedTS, extRefTS, extLastTS, diffSeconds)
|
||||
// could be due to incoming time stamp lagging a lot, like an unpause of the track
|
||||
logTransitionInfo("resume, reference very far behind", extExpectedTS, extRefTS, extLastTS, diffSeconds)
|
||||
extNextTS = extExpectedTS
|
||||
bigJump = true
|
||||
} else {
|
||||
extNextTS = extRefTS
|
||||
}
|
||||
@@ -1735,14 +1783,16 @@ func (f *Forwarder) processSourceSwitch(extPkt *buffer.ExtPacket, layer int32) (
|
||||
logTransition("layer switch, reference too far behind", extExpectedTS, extRefTS, extLastTS, diffSeconds)
|
||||
return nil, errors.New("switch point too far behind")
|
||||
}
|
||||
|
||||
// use a nominal increase to ensure that timestamp is always moving forward
|
||||
logTransition("layer switch, reference is slightly behind", extExpectedTS, extRefTS, extLastTS, diffSeconds)
|
||||
extNextTS = extLastTS + 1
|
||||
} else {
|
||||
diffSeconds = float64(int64(extExpectedTS-extRefTS)) / float64(f.codec.ClockRate)
|
||||
if diffSeconds < 0.0 && math.Abs(diffSeconds) > SwitchAheadThresholdSeconds {
|
||||
diffSeconds = float64(int64(extRefTS-extExpectedTS)) / float64(f.codec.ClockRate)
|
||||
if diffSeconds > SwitchAheadThresholdSeconds {
|
||||
logTransition("layer switch, reference too far ahead", extExpectedTS, extRefTS, extLastTS, diffSeconds)
|
||||
}
|
||||
|
||||
extNextTS = extRefTS
|
||||
}
|
||||
}
|
||||
@@ -1764,26 +1814,49 @@ func (f *Forwarder) processSourceSwitch(extPkt *buffer.ExtPacket, layer int32) (
|
||||
tsOffset = uint64(f.codec.ClockRate * 33 / 1000)
|
||||
}
|
||||
}
|
||||
|
||||
if bigJump { // TODO-REMOVE-AFTER-DATA-COLLECTION
|
||||
f.logger.Infow(
|
||||
"next timestamp on switch",
|
||||
"switchingAt", switchingAt.String(),
|
||||
"layer", layer,
|
||||
"extLastTS", extLastTS,
|
||||
"lastMarker", rtpMungerState.LastMarker,
|
||||
"extRefTS", extRefTS,
|
||||
"dummyStartTSOffset", f.dummyStartTSOffset,
|
||||
"referenceLayerSpatial", f.referenceLayerSpatial,
|
||||
"extExpectedTS", extExpectedTS,
|
||||
"extNextTS", extNextTS,
|
||||
"tsJump", extNextTS-extLastTS,
|
||||
"nextSN", rtpMungerState.ExtLastSN+1,
|
||||
"snOffset", snOffset,
|
||||
"extIncomingSN", extPkt.ExtSequenceNumber,
|
||||
"incomingTS", extPkt.Packet.Timestamp,
|
||||
"extIncomingTS", extPkt.ExtTimestamp,
|
||||
)
|
||||
} else {
|
||||
f.logger.Debugw(
|
||||
"next timestamp on switch",
|
||||
"switchingAt", switchingAt.String(),
|
||||
"layer", layer,
|
||||
"extLastTS", extLastTS,
|
||||
"lastMarker", rtpMungerState.LastMarker,
|
||||
"extRefTS", extRefTS,
|
||||
"dummyStartTSOffset", f.dummyStartTSOffset,
|
||||
"referenceLayerSpatial", f.referenceLayerSpatial,
|
||||
"extExpectedTS", extExpectedTS,
|
||||
"extNextTS", extNextTS,
|
||||
"tsJump", extNextTS-extLastTS,
|
||||
"nextSN", rtpMungerState.ExtLastSN+1,
|
||||
"snOffset", snOffset,
|
||||
"extIncomingSN", extPkt.ExtSequenceNumber,
|
||||
"extIncomingTS", extPkt.ExtTimestamp,
|
||||
)
|
||||
}
|
||||
|
||||
f.rtpMunger.UpdateSnTsOffsets(extPkt, snOffset, tsOffset)
|
||||
f.codecMunger.UpdateOffsets(extPkt)
|
||||
|
||||
f.logger.Debugw(
|
||||
"source switch",
|
||||
"switchingAt", switchingAt.String(),
|
||||
"layer", layer,
|
||||
"extLastTS", extLastTS,
|
||||
"extRefTS", extRefTS,
|
||||
"refTSOffset", f.refTSOffset,
|
||||
"referenceLayerSpatial", f.referenceLayerSpatial,
|
||||
"extExpectedTS", extExpectedTS,
|
||||
"extNextTS", extNextTS,
|
||||
"tsJump", extNextTS-extLastTS,
|
||||
"nextSN", rtpMungerState.ExtLastSN+1,
|
||||
"snOffset", snOffset,
|
||||
"extIncomingSN", extPkt.ExtSequenceNumber,
|
||||
"extIncomingTS", extPkt.ExtTimestamp,
|
||||
)
|
||||
|
||||
var eof *SnTs
|
||||
if snOffset != 1 {
|
||||
eof = &SnTs{
|
||||
@@ -1795,12 +1868,13 @@ func (f *Forwarder) processSourceSwitch(extPkt *buffer.ExtPacket, layer int32) (
|
||||
}
|
||||
|
||||
// should be called with lock held
|
||||
func (f *Forwarder) getTranslationParamsCommon(extPkt *buffer.ExtPacket, layer int32, tp *TranslationParams) (bool, error) {
|
||||
func (f *Forwarder) getTranslationParamsCommon(extPkt *buffer.ExtPacket, layer int32, tp *TranslationParams) error {
|
||||
if f.lastSSRC != extPkt.Packet.SSRC {
|
||||
eof, err := f.processSourceSwitch(extPkt, layer)
|
||||
if err != nil {
|
||||
tp.shouldDrop = true
|
||||
return false, nil
|
||||
f.vls.Rollback()
|
||||
return nil
|
||||
}
|
||||
tp.eof = eof
|
||||
|
||||
@@ -1812,9 +1886,9 @@ func (f *Forwarder) getTranslationParamsCommon(extPkt *buffer.ExtPacket, layer i
|
||||
if err != nil {
|
||||
tp.shouldDrop = true
|
||||
if err == ErrPaddingOnlyPacket || err == ErrDuplicatePacket || err == ErrOutOfOrderSequenceNumberCacheMiss {
|
||||
return false, nil
|
||||
return nil
|
||||
}
|
||||
return false, err
|
||||
return err
|
||||
}
|
||||
|
||||
tp.rtp = tpRTP
|
||||
@@ -1823,13 +1897,13 @@ func (f *Forwarder) getTranslationParamsCommon(extPkt *buffer.ExtPacket, layer i
|
||||
return f.translateCodecHeader(extPkt, tp)
|
||||
}
|
||||
|
||||
return false, nil
|
||||
return nil
|
||||
}
|
||||
|
||||
// should be called with lock held
|
||||
func (f *Forwarder) getTranslationParamsAudio(extPkt *buffer.ExtPacket, layer int32) (TranslationParams, error) {
|
||||
tp := TranslationParams{}
|
||||
if _, err := f.getTranslationParamsCommon(extPkt, layer, &tp); err != nil {
|
||||
if err := f.getTranslationParamsCommon(extPkt, layer, &tp); err != nil {
|
||||
tp.shouldDrop = true
|
||||
return tp, err
|
||||
}
|
||||
@@ -1838,12 +1912,6 @@ func (f *Forwarder) getTranslationParamsAudio(extPkt *buffer.ExtPacket, layer in
|
||||
|
||||
// should be called with lock held
|
||||
func (f *Forwarder) getTranslationParamsVideo(extPkt *buffer.ExtPacket, layer int32) (TranslationParams, error) {
|
||||
maybeRollback := func(isSwitching bool) {
|
||||
if isSwitching {
|
||||
f.vls.Rollback()
|
||||
}
|
||||
}
|
||||
|
||||
tp := TranslationParams{}
|
||||
if !f.vls.GetTarget().IsValid() {
|
||||
// stream is paused by streamallocator
|
||||
@@ -1869,6 +1937,11 @@ func (f *Forwarder) getTranslationParamsVideo(extPkt *buffer.ExtPacket, layer in
|
||||
tp.ddBytes = result.DependencyDescriptorExtension
|
||||
tp.marker = result.RTPMarker
|
||||
|
||||
err := f.getTranslationParamsCommon(extPkt, layer, &tp)
|
||||
if tp.shouldDrop {
|
||||
return tp, err
|
||||
}
|
||||
|
||||
if FlagPauseOnDowngrade && f.isDeficientLocked() && f.vls.GetTarget().Spatial < f.vls.GetCurrent().Spatial {
|
||||
//
|
||||
// If target layer is lower than both the current and
|
||||
@@ -1889,22 +1962,15 @@ func (f *Forwarder) getTranslationParamsVideo(extPkt *buffer.ExtPacket, layer in
|
||||
// To differentiate between the two cases, drop only when in DEFICIENT state.
|
||||
//
|
||||
tp.shouldDrop = true
|
||||
maybeRollback(result.IsSwitching)
|
||||
return tp, nil
|
||||
}
|
||||
|
||||
isTemporalSwitching, err := f.getTranslationParamsCommon(extPkt, layer, &tp)
|
||||
if tp.shouldDrop {
|
||||
maybeRollback(result.IsSwitching || isTemporalSwitching)
|
||||
return tp, err
|
||||
}
|
||||
|
||||
return tp, err
|
||||
return tp, nil
|
||||
}
|
||||
|
||||
func (f *Forwarder) translateCodecHeader(extPkt *buffer.ExtPacket, tp *TranslationParams) (bool, error) {
|
||||
func (f *Forwarder) translateCodecHeader(extPkt *buffer.ExtPacket, tp *TranslationParams) error {
|
||||
// codec specific forwarding check and any needed packet munging
|
||||
tl, isSwitching := f.vls.SelectTemporal(extPkt)
|
||||
tl := f.vls.SelectTemporal(extPkt)
|
||||
inputSize, codecBytes, err := f.codecMunger.UpdateAndGet(
|
||||
extPkt,
|
||||
tp.rtp.snOrdering == SequenceNumberOrderingOutOfOrder,
|
||||
@@ -1918,15 +1984,14 @@ func (f *Forwarder) translateCodecHeader(extPkt *buffer.ExtPacket, tp *Translati
|
||||
// filtered temporal layer, update sequence number offset to prevent holes
|
||||
f.rtpMunger.PacketDropped(extPkt)
|
||||
}
|
||||
return isSwitching, nil
|
||||
return nil
|
||||
}
|
||||
|
||||
return isSwitching, err
|
||||
return err
|
||||
}
|
||||
tp.incomingHeaderSize = inputSize
|
||||
tp.codecBytes = codecBytes
|
||||
|
||||
return isSwitching, nil
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *Forwarder) maybeStart() {
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
package sfu
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/livekit/livekit-server/pkg/telemetry/prometheus"
|
||||
"github.com/livekit/protocol/logger"
|
||||
"github.com/livekit/protocol/utils"
|
||||
)
|
||||
|
||||
type ForwardStats struct {
|
||||
lock sync.Mutex
|
||||
lastLeftMs atomic.Int64
|
||||
latency *utils.LatencyAggregate
|
||||
closeCh chan struct{}
|
||||
}
|
||||
|
||||
func NewForwardStats(latencyUpdateInterval, reportInterval, latencyWindowLength time.Duration) *ForwardStats {
|
||||
s := &ForwardStats{
|
||||
latency: utils.NewLatencyAggregate(latencyUpdateInterval, latencyWindowLength),
|
||||
closeCh: make(chan struct{}),
|
||||
}
|
||||
|
||||
go s.report(reportInterval)
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *ForwardStats) Update(arrival, left time.Time) {
|
||||
transit := left.Sub(arrival)
|
||||
|
||||
// ignore if transit is too large or negative, this could happen if system time is adjusted
|
||||
if transit < 0 || transit > 5*time.Second {
|
||||
return
|
||||
}
|
||||
leftMs := left.UnixMilli()
|
||||
lastMs := s.lastLeftMs.Load()
|
||||
if leftMs < lastMs || !s.lastLeftMs.CompareAndSwap(lastMs, leftMs) {
|
||||
return
|
||||
}
|
||||
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
s.latency.Update(time.Duration(arrival.UnixNano()), float64(transit))
|
||||
}
|
||||
|
||||
func (s *ForwardStats) GetStats() (latency, jitter time.Duration) {
|
||||
s.lock.Lock()
|
||||
w := s.latency.Summarize()
|
||||
s.lock.Unlock()
|
||||
latency, jitter = time.Duration(w.Mean()), time.Duration(w.StdDev())
|
||||
// TODO: remove this check after debugging unexpected jitter issue
|
||||
if jitter > 10*time.Second {
|
||||
logger.Infow("unexpected forward jitter",
|
||||
"jitter", jitter,
|
||||
"stats", fmt.Sprintf("count %.2f, mean %.2f, stdDev %.2f", w.Count(), w.Mean(), w.StdDev()),
|
||||
)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *ForwardStats) GetLastStats(duration time.Duration) (latency, jitter time.Duration) {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
w := s.latency.SummarizeLast(duration)
|
||||
return time.Duration(w.Mean()), time.Duration(w.StdDev())
|
||||
}
|
||||
|
||||
func (s *ForwardStats) Stop() {
|
||||
close(s.closeCh)
|
||||
}
|
||||
|
||||
func (s *ForwardStats) report(reportInterval time.Duration) {
|
||||
ticker := time.NewTicker(reportInterval)
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-s.closeCh:
|
||||
return
|
||||
case <-ticker.C:
|
||||
latency, jitter := s.GetLastStats(reportInterval)
|
||||
latencySlow, jitterSlow := s.GetStats()
|
||||
prometheus.RecordForwardJitter(uint32(jitter/time.Millisecond), uint32(jitterSlow/time.Millisecond))
|
||||
prometheus.RecordForwardLatency(uint32(latency/time.Millisecond), uint32(latencySlow/time.Millisecond))
|
||||
}
|
||||
}
|
||||
}
|
||||
+16
-6
@@ -27,7 +27,6 @@ import (
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
"github.com/livekit/mediatransportutil/pkg/bucket"
|
||||
"github.com/livekit/mediatransportutil/pkg/twcc"
|
||||
"github.com/livekit/protocol/livekit"
|
||||
"github.com/livekit/protocol/logger"
|
||||
|
||||
@@ -108,8 +107,6 @@ type WebRTCReceiver struct {
|
||||
|
||||
onRTCP func([]rtcp.Packet)
|
||||
|
||||
twcc *twcc.Responder
|
||||
|
||||
bufferMu sync.RWMutex
|
||||
buffers [buffer.DefaultMaxLayerSpatial + 1]*buffer.Buffer
|
||||
upTracks [buffer.DefaultMaxLayerSpatial + 1]*webrtc.TrackRemote
|
||||
@@ -128,7 +125,9 @@ type WebRTCReceiver struct {
|
||||
|
||||
primaryReceiver atomic.Pointer[RedPrimaryReceiver]
|
||||
redReceiver atomic.Pointer[RedReceiver]
|
||||
redPktWriter func(pkt *buffer.ExtPacket, spatialLayer int32)
|
||||
redPktWriter func(pkt *buffer.ExtPacket, spatialLayer int32) int
|
||||
|
||||
forwardStats *ForwardStats
|
||||
}
|
||||
|
||||
// SVC-TODO: Have to use more conditions to differentiate between
|
||||
@@ -187,6 +186,13 @@ func WithLoadBalanceThreshold(downTracks int) ReceiverOpts {
|
||||
}
|
||||
}
|
||||
|
||||
func WithForwardStats(forwardStats *ForwardStats) ReceiverOpts {
|
||||
return func(w *WebRTCReceiver) *WebRTCReceiver {
|
||||
w.forwardStats = forwardStats
|
||||
return w
|
||||
}
|
||||
}
|
||||
|
||||
// NewWebRTCReceiver creates a new webrtc track receiver
|
||||
func NewWebRTCReceiver(
|
||||
receiver *webrtc.RTPReceiver,
|
||||
@@ -709,12 +715,16 @@ func (w *WebRTCReceiver) forwardRTP(layer int32) {
|
||||
}
|
||||
}
|
||||
|
||||
w.downTrackSpreader.Broadcast(func(dt TrackSender) {
|
||||
writeCount := w.downTrackSpreader.Broadcast(func(dt TrackSender) {
|
||||
_ = dt.WriteRTP(pkt, spatialLayer)
|
||||
})
|
||||
|
||||
if redPktWriter != nil {
|
||||
redPktWriter(pkt, spatialLayer)
|
||||
writeCount += redPktWriter(pkt, spatialLayer)
|
||||
}
|
||||
|
||||
if writeCount > 0 && w.forwardStats != nil {
|
||||
w.forwardStats.Update(pkt.Arrival, time.Now())
|
||||
}
|
||||
|
||||
if spatialTracker != nil {
|
||||
|
||||
@@ -57,18 +57,19 @@ func NewRedPrimaryReceiver(receiver TrackReceiver, dsp DownTrackSpreaderParams)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *RedPrimaryReceiver) ForwardRTP(pkt *buffer.ExtPacket, spatialLayer int32) {
|
||||
func (r *RedPrimaryReceiver) ForwardRTP(pkt *buffer.ExtPacket, spatialLayer int32) int {
|
||||
// extract primary payload from RED and forward to downtracks
|
||||
if r.downTrackSpreader.DownTrackCount() == 0 {
|
||||
return
|
||||
return 0
|
||||
}
|
||||
|
||||
pkts, err := r.getSendPktsFromRed(pkt.Packet)
|
||||
if err != nil {
|
||||
r.logger.Errorw("get encoding for red failed", err, "payloadtype", pkt.Packet.PayloadType)
|
||||
return
|
||||
return 0
|
||||
}
|
||||
|
||||
var count int
|
||||
for i, sendPkt := range pkts {
|
||||
pPkt := *pkt
|
||||
if i != len(pkts)-1 {
|
||||
@@ -81,10 +82,11 @@ func (r *RedPrimaryReceiver) ForwardRTP(pkt *buffer.ExtPacket, spatialLayer int3
|
||||
|
||||
// not modify the ExtPacket.RawPacket here for performance since it is not used by the DownTrack,
|
||||
// otherwise it should be set to the correct value (marshal the primary rtp packet)
|
||||
r.downTrackSpreader.Broadcast(func(dt TrackSender) {
|
||||
count += r.downTrackSpreader.Broadcast(func(dt TrackSender) {
|
||||
_ = dt.WriteRTP(&pPkt, spatialLayer)
|
||||
})
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
func (r *RedPrimaryReceiver) AddDownTrack(track TrackSender) error {
|
||||
|
||||
@@ -55,15 +55,15 @@ func NewRedReceiver(receiver TrackReceiver, dsp DownTrackSpreaderParams) *RedRec
|
||||
}
|
||||
}
|
||||
|
||||
func (r *RedReceiver) ForwardRTP(pkt *buffer.ExtPacket, spatialLayer int32) {
|
||||
func (r *RedReceiver) ForwardRTP(pkt *buffer.ExtPacket, spatialLayer int32) int {
|
||||
// extract primary payload from RED and forward to downtracks
|
||||
if r.downTrackSpreader.DownTrackCount() == 0 {
|
||||
return
|
||||
return 0
|
||||
}
|
||||
redLen, err := r.encodeRedForPrimary(pkt.Packet, r.redPayloadBuf[:])
|
||||
if err != nil {
|
||||
r.logger.Errorw("red encoding failed", err)
|
||||
return
|
||||
return 0
|
||||
}
|
||||
|
||||
pPkt := *pkt
|
||||
@@ -73,7 +73,7 @@ func (r *RedReceiver) ForwardRTP(pkt *buffer.ExtPacket, spatialLayer int32) {
|
||||
|
||||
// not modify the ExtPacket.RawPacket here for performance since it is not used by the DownTrack,
|
||||
// otherwise it should be set to the correct value (marshal the primary rtp packet)
|
||||
r.downTrackSpreader.Broadcast(func(dt TrackSender) {
|
||||
return r.downTrackSpreader.Broadcast(func(dt TrackSender) {
|
||||
_ = dt.WriteRTP(&pPkt, spatialLayer)
|
||||
})
|
||||
}
|
||||
|
||||
+17
-6
@@ -75,6 +75,7 @@ type RTPMunger struct {
|
||||
|
||||
extHighestIncomingSN uint64
|
||||
snRangeMap *utils.RangeMap[uint64, uint64]
|
||||
extHighestIncomingTS uint64 // TODO-REMOVE-AFTER-DATA-COLLECTION
|
||||
|
||||
extLastSN uint64
|
||||
extSecondLastSN uint64
|
||||
@@ -83,7 +84,6 @@ type RTPMunger struct {
|
||||
extLastTS uint64
|
||||
extSecondLastTS uint64
|
||||
tsOffset uint64
|
||||
pinnedTSOffset uint64
|
||||
|
||||
lastMarker bool
|
||||
secondLastMarker bool
|
||||
@@ -108,7 +108,6 @@ func (r *RTPMunger) DebugInfo() map[string]interface{} {
|
||||
"ExtLastTS": r.extLastTS,
|
||||
"ExtSecondLastTS": r.extSecondLastTS,
|
||||
"TSOffset": r.tsOffset,
|
||||
"PinnedTSOffset": r.pinnedTSOffset,
|
||||
"LastMarker": r.lastMarker,
|
||||
"SecondLastMarker": r.secondLastMarker,
|
||||
}
|
||||
@@ -125,8 +124,8 @@ func (r *RTPMunger) GetLast() RTPMungerState {
|
||||
}
|
||||
}
|
||||
|
||||
func (r *RTPMunger) GetPinnedTSOffset() uint64 {
|
||||
return r.pinnedTSOffset
|
||||
func (r *RTPMunger) GetTSOffset() uint64 {
|
||||
return r.tsOffset
|
||||
}
|
||||
|
||||
func (r *RTPMunger) SeedLast(state RTPMungerState) {
|
||||
@@ -140,6 +139,7 @@ func (r *RTPMunger) SeedLast(state RTPMungerState) {
|
||||
|
||||
func (r *RTPMunger) SetLastSnTs(extPkt *buffer.ExtPacket) {
|
||||
r.extHighestIncomingSN = extPkt.ExtSequenceNumber - 1
|
||||
r.extHighestIncomingTS = extPkt.ExtTimestamp - 1
|
||||
|
||||
r.extLastSN = extPkt.ExtSequenceNumber
|
||||
r.extSecondLastSN = r.extLastSN - 1
|
||||
@@ -149,17 +149,16 @@ func (r *RTPMunger) SetLastSnTs(extPkt *buffer.ExtPacket) {
|
||||
r.extLastTS = extPkt.ExtTimestamp
|
||||
r.extSecondLastTS = extPkt.ExtTimestamp
|
||||
r.tsOffset = 0
|
||||
r.pinnedTSOffset = r.tsOffset
|
||||
}
|
||||
|
||||
func (r *RTPMunger) UpdateSnTsOffsets(extPkt *buffer.ExtPacket, snAdjust uint64, tsAdjust uint64) {
|
||||
r.extHighestIncomingSN = extPkt.ExtSequenceNumber - 1
|
||||
r.extHighestIncomingTS = extPkt.ExtTimestamp - 1
|
||||
|
||||
r.snRangeMap.ClearAndResetValue(extPkt.ExtSequenceNumber, extPkt.ExtSequenceNumber-r.extLastSN-snAdjust)
|
||||
r.updateSnOffset()
|
||||
|
||||
r.tsOffset = extPkt.ExtTimestamp - r.extLastTS - tsAdjust
|
||||
r.pinnedTSOffset = r.tsOffset
|
||||
}
|
||||
|
||||
func (r *RTPMunger) PacketDropped(extPkt *buffer.ExtPacket) {
|
||||
@@ -195,6 +194,18 @@ func (r *RTPMunger) UpdateAndGetSnTs(extPkt *buffer.ExtPacket, marker bool) (Tra
|
||||
// in-order - either contiguous packet with payload OR packet following a gap, may or may not have payload
|
||||
r.extHighestIncomingSN = extPkt.ExtSequenceNumber
|
||||
|
||||
// TODO-REMOVE-AFTER-DATA-COLLECTION
|
||||
tsDiff := int64(extPkt.ExtTimestamp - r.extHighestIncomingTS)
|
||||
if tsDiff > 24000 { // 1/2 second at audio clock rate
|
||||
r.logger.Infow(
|
||||
"big jump in incoming timestamp",
|
||||
"last", r.extHighestIncomingTS,
|
||||
"current", extPkt.ExtTimestamp,
|
||||
"diff", tsDiff,
|
||||
)
|
||||
}
|
||||
r.extHighestIncomingTS = extPkt.ExtTimestamp
|
||||
|
||||
ordering := SequenceNumberOrderingContiguous
|
||||
if diff > 1 {
|
||||
ordering = SequenceNumberOrderingGap
|
||||
|
||||
@@ -237,7 +237,7 @@ func (s *sequencer) pushPadding(extStartSNInclusive uint64, extEndSNInclusive ui
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
|
||||
if s.snRangeMap == nil {
|
||||
if s.snRangeMap == nil || !s.initialized {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -65,6 +65,7 @@ func (p *ProbeController) Reset() {
|
||||
p.resetProbeIntervalLocked()
|
||||
p.resetProbeDurationLocked()
|
||||
|
||||
p.StopProbe()
|
||||
p.clearProbeLocked()
|
||||
}
|
||||
|
||||
@@ -73,7 +74,7 @@ func (p *ProbeController) ProbeClusterDone(info ProbeClusterInfo) {
|
||||
defer p.lock.Unlock()
|
||||
|
||||
if p.probeClusterId != info.Id {
|
||||
p.params.Logger.Infow("not expected probe cluster", "probeClusterId", p.probeClusterId, "resetProbeClusterId", info.Id)
|
||||
p.params.Logger.Debugw("not expected probe cluster", "probeClusterId", p.probeClusterId, "resetProbeClusterId", info.Id)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -99,12 +100,12 @@ func (p *ProbeController) CheckProbe(trend ChannelTrend, highestEstimate int64)
|
||||
// In rare cases, the estimate gets stuck. Prevent from probe running amok
|
||||
// STREAM-ALLOCATOR-TODO: Need more testing here to ensure that probe does not cause a lot of damage
|
||||
//
|
||||
p.params.Logger.Infow("stream allocator: probe: aborting, no trend", "cluster", p.probeClusterId)
|
||||
p.params.Logger.Debugw("stream allocator: probe: aborting, no trend", "cluster", p.probeClusterId)
|
||||
p.abortProbeLocked()
|
||||
|
||||
case trend == ChannelTrendCongesting:
|
||||
// stop immediately if the probe is congesting channel more
|
||||
p.params.Logger.Infow("stream allocator: probe: aborting, channel is congesting", "cluster", p.probeClusterId)
|
||||
p.params.Logger.Debugw("stream allocator: probe: aborting, channel is congesting", "cluster", p.probeClusterId)
|
||||
p.abortProbeLocked()
|
||||
|
||||
case highestEstimate > p.probeGoalBps:
|
||||
@@ -138,7 +139,9 @@ func (p *ProbeController) MaybeFinalizeProbe(
|
||||
return true, true, true
|
||||
}
|
||||
|
||||
if (isComplete || p.abortedProbeClusterId != ProbeClusterIdInvalid) && p.probeEndTime.IsZero() && p.doneProbeClusterInfo.Id != ProbeClusterIdInvalid && p.doneProbeClusterInfo.Id == p.probeClusterId {
|
||||
if (isComplete || p.abortedProbeClusterId != ProbeClusterIdInvalid) &&
|
||||
p.probeEndTime.IsZero() &&
|
||||
p.doneProbeClusterInfo.Id != ProbeClusterIdInvalid && p.doneProbeClusterInfo.Id == p.probeClusterId {
|
||||
// ensure any queueing due to probing is flushed
|
||||
// STREAM-ALLOCATOR-TODO: CongestionControlProbeConfig.SettleWait should actually be a certain number of RTTs.
|
||||
expectedDuration := float64(9.0)
|
||||
@@ -165,7 +168,7 @@ func (p *ProbeController) MaybeFinalizeProbe(
|
||||
}
|
||||
|
||||
if !p.probeEndTime.IsZero() && time.Now().After(p.probeEndTime) {
|
||||
// finalisze aborted or non-failing but non-goal-reached probe cluster
|
||||
// finalize aborted or non-failing but non-goal-reached probe cluster
|
||||
return true, p.finalizeProbeLocked(trend), false
|
||||
}
|
||||
|
||||
|
||||
@@ -189,7 +189,7 @@ func (p *Prober) Reset() {
|
||||
|
||||
p.clustersMu.Lock()
|
||||
if p.activeCluster != nil {
|
||||
p.logger.Infow("prober: resetting active cluster", "cluster", p.activeCluster.String())
|
||||
p.logger.Debugw("prober: resetting active cluster", "cluster", p.activeCluster.String())
|
||||
reset = true
|
||||
info = p.activeCluster.GetInfo()
|
||||
}
|
||||
|
||||
@@ -698,13 +698,12 @@ func (s *StreamAllocator) handleSignalProbeClusterDone(event Event) {
|
||||
func (s *StreamAllocator) handleSignalResume(event Event) {
|
||||
s.videoTracksMu.Lock()
|
||||
track := s.videoTracks[event.TrackID]
|
||||
updated := track != nil && track.SetStreamState(StreamStateActive)
|
||||
s.videoTracksMu.Unlock()
|
||||
|
||||
if track != nil {
|
||||
if updated {
|
||||
update := NewStreamStateUpdate()
|
||||
if track.SetStreamState(StreamStateActive) {
|
||||
update.HandleStreamingChange(track, StreamStateActive)
|
||||
}
|
||||
update.HandleStreamingChange(track, StreamStateActive)
|
||||
s.maybeSendUpdate(update)
|
||||
}
|
||||
}
|
||||
@@ -819,16 +818,29 @@ func (s *StreamAllocator) handleNewEstimateInNonProbe() {
|
||||
action = "skipping"
|
||||
}
|
||||
|
||||
s.params.Logger.Infow(
|
||||
fmt.Sprintf("stream allocator: channel congestion detected, %s channel capacity update", action),
|
||||
"reason", reason,
|
||||
"old(bps)", s.committedChannelCapacity,
|
||||
"new(bps)", estimateToCommit,
|
||||
"lastReceived(bps)", s.lastReceivedEstimate,
|
||||
"expectedUsage(bps)", expectedBandwidthUsage,
|
||||
"commitThreshold(bps)", commitThreshold,
|
||||
"channel", s.channelObserver.ToString(),
|
||||
)
|
||||
if action == "applying" {
|
||||
s.params.Logger.Infow(
|
||||
fmt.Sprintf("stream allocator: channel congestion detected, %s channel capacity update", action),
|
||||
"reason", reason,
|
||||
"old(bps)", s.committedChannelCapacity,
|
||||
"new(bps)", estimateToCommit,
|
||||
"lastReceived(bps)", s.lastReceivedEstimate,
|
||||
"expectedUsage(bps)", expectedBandwidthUsage,
|
||||
"commitThreshold(bps)", commitThreshold,
|
||||
"channel", s.channelObserver.ToString(),
|
||||
)
|
||||
} else {
|
||||
s.params.Logger.Debugw(
|
||||
fmt.Sprintf("stream allocator: channel congestion detected, %s channel capacity update", action),
|
||||
"reason", reason,
|
||||
"old(bps)", s.committedChannelCapacity,
|
||||
"new(bps)", estimateToCommit,
|
||||
"lastReceived(bps)", s.lastReceivedEstimate,
|
||||
"expectedUsage(bps)", expectedBandwidthUsage,
|
||||
"commitThreshold(bps)", commitThreshold,
|
||||
"channel", s.channelObserver.ToString(),
|
||||
)
|
||||
}
|
||||
/* STREAM-ALLOCATOR-DATA
|
||||
s.params.Logger.Debugw(
|
||||
fmt.Sprintf("stream allocator: channel congestion detected, %s channel capacity: experimental", action),
|
||||
|
||||
@@ -168,17 +168,28 @@ func (s *StreamTrackerManager) AddDependencyDescriptorTrackers() {
|
||||
}
|
||||
|
||||
func (s *StreamTrackerManager) AddTracker(layer int32) streamtracker.StreamTrackerWorker {
|
||||
bitrateInterval, ok := s.trackerConfig.BitrateReportInterval[layer]
|
||||
if !ok {
|
||||
if layer < 0 || int(layer) >= len(s.trackers) {
|
||||
return nil
|
||||
}
|
||||
|
||||
var tracker streamtracker.StreamTrackerWorker
|
||||
s.lock.Lock()
|
||||
tracker = s.trackers[layer]
|
||||
if tracker != nil {
|
||||
s.lock.Unlock()
|
||||
return tracker
|
||||
}
|
||||
|
||||
if s.ddTracker != nil {
|
||||
tracker = s.ddTracker.LayeredTracker(layer)
|
||||
}
|
||||
s.lock.Unlock()
|
||||
|
||||
bitrateInterval, ok := s.trackerConfig.BitrateReportInterval[layer]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
if tracker == nil {
|
||||
var trackerImpl streamtracker.StreamTrackerImpl
|
||||
switch s.trackerConfig.StreamTrackerType {
|
||||
@@ -318,20 +329,11 @@ func (s *StreamTrackerManager) SetMaxExpectedSpatialLayer(layer int32) int32 {
|
||||
//
|
||||
// Some higher layer is expected to start.
|
||||
// If the layer was not detected as stopped (i.e. it is still in available layers),
|
||||
// don't need to do anything. If not, reset the stream tracker so that
|
||||
// the layer is declared available on the first packet.
|
||||
//
|
||||
// NOTE: There may be a race between checking if a layer is available and
|
||||
// resetting the tracker, i.e. the track may stop just after checking.
|
||||
// But, those conditions should be rare. In those cases, the restart will
|
||||
// take longer.
|
||||
// resetting tracker will declare layer available afresh. That's fine as it will be
|
||||
// a no-op in available layers handling.
|
||||
//
|
||||
var trackersToReset []streamtracker.StreamTrackerWorker
|
||||
for l := s.maxExpectedLayer + 1; l <= layer; l++ {
|
||||
if s.hasSpatialLayerLocked(l) {
|
||||
continue
|
||||
}
|
||||
|
||||
if s.trackers[l] != nil {
|
||||
trackersToReset = append(trackersToReset, s.trackers[l])
|
||||
}
|
||||
|
||||
@@ -19,6 +19,8 @@ import (
|
||||
"fmt"
|
||||
"math"
|
||||
"unsafe"
|
||||
|
||||
"go.uber.org/zap/zapcore"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -40,12 +42,23 @@ type valueType interface {
|
||||
uint32 | uint64
|
||||
}
|
||||
|
||||
// ---------------------------------------------------
|
||||
|
||||
type rangeVal[RT rangeType, VT valueType] struct {
|
||||
start RT
|
||||
end RT
|
||||
value VT
|
||||
}
|
||||
|
||||
func (r rangeVal[RT, VT]) MarshalLogObject(e zapcore.ObjectEncoder) error {
|
||||
e.AddUint64("start", uint64(r.start))
|
||||
e.AddUint64("end", uint64(r.end))
|
||||
e.AddUint64("value", uint64(r.value))
|
||||
return nil
|
||||
}
|
||||
|
||||
// ---------------------------------------------------
|
||||
|
||||
type RangeMap[RT rangeType, VT valueType] struct {
|
||||
halfRange RT
|
||||
|
||||
@@ -63,6 +76,21 @@ func NewRangeMap[RT rangeType, VT valueType](size int) *RangeMap[RT, VT] {
|
||||
return r
|
||||
}
|
||||
|
||||
func (r *RangeMap[RT, VT]) MarshalLogObject(e zapcore.ObjectEncoder) error {
|
||||
e.AddInt("numRanges", len(r.ranges))
|
||||
|
||||
// just the last 10 ranges max
|
||||
startIdx := len(r.ranges) - 10
|
||||
if startIdx < 0 {
|
||||
startIdx = 0
|
||||
}
|
||||
for i := startIdx; i < len(r.ranges); i++ {
|
||||
e.AddObject(fmt.Sprintf("range[%d]", i), r.ranges[i])
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *RangeMap[RT, VT]) ClearAndResetValue(start RT, val VT) {
|
||||
r.initRanges(start, val)
|
||||
}
|
||||
|
||||
@@ -140,29 +140,25 @@ func (b *Base) Rollback() {
|
||||
b.targetLayer = b.previousTargetLayer
|
||||
}
|
||||
|
||||
func (b *Base) SelectTemporal(extPkt *buffer.ExtPacket) (int32, bool) {
|
||||
func (b *Base) SelectTemporal(extPkt *buffer.ExtPacket) int32 {
|
||||
if b.tls != nil {
|
||||
isSwitching := false
|
||||
this, next := b.tls.Select(extPkt, b.currentLayer.Temporal, b.targetLayer.Temporal)
|
||||
if next != b.currentLayer.Temporal {
|
||||
isSwitching = true
|
||||
|
||||
b.previousLayer = b.currentLayer
|
||||
previousLayer := b.currentLayer
|
||||
b.currentLayer.Temporal = next
|
||||
|
||||
b.logger.Debugw(
|
||||
"updating temporal layer",
|
||||
"previous", b.previousLayer,
|
||||
"previous", previousLayer,
|
||||
"current", b.currentLayer,
|
||||
"previousTarget", b.previousTargetLayer,
|
||||
"target", b.targetLayer,
|
||||
"max", b.maxLayer,
|
||||
"req", b.requestSpatial,
|
||||
"maxSeen", b.maxSeenLayer,
|
||||
)
|
||||
}
|
||||
return this, isSwitching
|
||||
return this
|
||||
}
|
||||
|
||||
return b.currentLayer.Temporal, false
|
||||
return b.currentLayer.Temporal
|
||||
}
|
||||
|
||||
@@ -55,6 +55,6 @@ type VideoLayerSelector interface {
|
||||
GetCurrent() buffer.VideoLayer
|
||||
|
||||
Select(extPkt *buffer.ExtPacket, layer int32) VideoLayerSelectorResult
|
||||
SelectTemporal(extPkt *buffer.ExtPacket) (int32, bool)
|
||||
SelectTemporal(extPkt *buffer.ExtPacket) int32
|
||||
Rollback()
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
|
||||
"github.com/livekit/protocol/livekit"
|
||||
"github.com/livekit/protocol/livekit/rpc"
|
||||
"github.com/livekit/protocol/logger"
|
||||
|
||||
"github.com/livekit/livekit-server/pkg/config"
|
||||
@@ -39,9 +40,9 @@ type analyticsService struct {
|
||||
nodeID string
|
||||
sequenceNumber atomic.Uint64
|
||||
|
||||
events livekit.AnalyticsRecorderService_IngestEventsClient
|
||||
stats livekit.AnalyticsRecorderService_IngestStatsClient
|
||||
nodeRooms livekit.AnalyticsRecorderService_IngestNodeRoomStatesClient
|
||||
events rpc.AnalyticsRecorderService_IngestEventsClient
|
||||
stats rpc.AnalyticsRecorderService_IngestStatsClient
|
||||
nodeRooms rpc.AnalyticsRecorderService_IngestNodeRoomStatesClient
|
||||
}
|
||||
|
||||
func NewAnalyticsService(_ *config.Config, currentNode routing.LocalNode) AnalyticsService {
|
||||
@@ -70,6 +71,7 @@ func (a *analyticsService) SendEvent(_ context.Context, event *livekit.Analytics
|
||||
return
|
||||
}
|
||||
|
||||
event.NodeId = a.nodeID
|
||||
event.AnalyticsKey = a.analyticsKey
|
||||
if err := a.events.Send(&livekit.AnalyticsEvents{
|
||||
Events: []*livekit.AnalyticsEvent{event},
|
||||
|
||||
@@ -23,7 +23,7 @@ import (
|
||||
"github.com/livekit/livekit-server/pkg/telemetry/prometheus"
|
||||
"github.com/livekit/protocol/livekit"
|
||||
"github.com/livekit/protocol/logger"
|
||||
"github.com/livekit/protocol/utils"
|
||||
"github.com/livekit/protocol/utils/guid"
|
||||
"github.com/livekit/protocol/webhook"
|
||||
)
|
||||
|
||||
@@ -33,7 +33,7 @@ func (t *telemetryService) NotifyEvent(ctx context.Context, event *livekit.Webho
|
||||
}
|
||||
|
||||
event.CreatedAt = time.Now().Unix()
|
||||
event.Id = utils.NewGuid("EV_")
|
||||
event.Id = guid.New("EV_")
|
||||
|
||||
if err := t.notifier.QueueNotify(ctx, event); err != nil {
|
||||
logger.Warnw("failed to notify webhook", err, "event", event.Event)
|
||||
@@ -163,10 +163,9 @@ func (t *telemetryService) ParticipantLeft(ctx context.Context,
|
||||
isConnected := false
|
||||
if worker, ok := t.getWorker(livekit.ParticipantID(participant.Sid)); ok {
|
||||
isConnected = worker.IsConnected()
|
||||
if worker.ClosedAt().IsZero() {
|
||||
if worker.Close() {
|
||||
prometheus.SubParticipant()
|
||||
}
|
||||
worker.Close()
|
||||
}
|
||||
|
||||
if isConnected && shouldSendEvent {
|
||||
|
||||
@@ -35,6 +35,7 @@ var (
|
||||
initialized atomic.Bool
|
||||
|
||||
MessageCounter *prometheus.CounterVec
|
||||
MessageBytes *prometheus.CounterVec
|
||||
ServiceOperationCounter *prometheus.CounterVec
|
||||
TwirpRequestStatusCounter *prometheus.CounterVec
|
||||
|
||||
@@ -61,6 +62,16 @@ func Init(nodeID string, nodeType livekit.NodeType) error {
|
||||
[]string{"type", "status"},
|
||||
)
|
||||
|
||||
MessageBytes = prometheus.NewCounterVec(
|
||||
prometheus.CounterOpts{
|
||||
Namespace: livekitNamespace,
|
||||
Subsystem: "node",
|
||||
Name: "message_bytes",
|
||||
ConstLabels: prometheus.Labels{"node_id": nodeID, "node_type": nodeType.String()},
|
||||
},
|
||||
[]string{"type", "message_type"},
|
||||
)
|
||||
|
||||
ServiceOperationCounter = prometheus.NewCounterVec(
|
||||
prometheus.CounterOpts{
|
||||
Namespace: livekitNamespace,
|
||||
@@ -103,6 +114,7 @@ func Init(nodeID string, nodeType livekit.NodeType) error {
|
||||
)
|
||||
|
||||
prometheus.MustRegister(MessageCounter)
|
||||
prometheus.MustRegister(MessageBytes)
|
||||
prometheus.MustRegister(ServiceOperationCounter)
|
||||
prometheus.MustRegister(TwirpRequestStatusCounter)
|
||||
prometheus.MustRegister(promSysPacketGauge)
|
||||
@@ -165,6 +177,8 @@ func GetUpdatedNodeStats(prev *livekit.NodeStats, prevAverage *livekit.NodeStats
|
||||
trackPublishSuccessNow := trackPublishSuccess.Load()
|
||||
trackSubscribeAttemptsNow := trackSubscribeAttempts.Load()
|
||||
trackSubscribeSuccessNow := trackSubscribeSuccess.Load()
|
||||
forwardLatencyNow := forwardLatency.Load()
|
||||
forwardJitterNow := forwardJitter.Load()
|
||||
|
||||
updatedAt := time.Now().Unix()
|
||||
elapsed := updatedAt - prevAverage.UpdatedAt
|
||||
@@ -207,6 +221,8 @@ func GetUpdatedNodeStats(prev *livekit.NodeStats, prevAverage *livekit.NodeStats
|
||||
RetransmitBytesOutPerSec: prevAverage.RetransmitBytesOutPerSec,
|
||||
RetransmitPacketsOutPerSec: prevAverage.RetransmitPacketsOutPerSec,
|
||||
NackPerSec: prevAverage.NackPerSec,
|
||||
ForwardLatency: forwardLatencyNow,
|
||||
ForwardJitter: forwardJitterNow,
|
||||
ParticipantSignalConnectedPerSec: prevAverage.ParticipantSignalConnectedPerSec,
|
||||
ParticipantRtcInitPerSec: prevAverage.ParticipantRtcInitPerSec,
|
||||
ParticipantRtcConnectedPerSec: prevAverage.ParticipantRtcConnectedPerSec,
|
||||
|
||||
@@ -41,6 +41,8 @@ var (
|
||||
participantSignalConnected atomic.Uint64
|
||||
participantRTCConnected atomic.Uint64
|
||||
participantRTCInit atomic.Uint64
|
||||
forwardLatency atomic.Uint32
|
||||
forwardJitter atomic.Uint32
|
||||
|
||||
promPacketLabels = []string{"direction", "transmission"}
|
||||
promPacketTotal *prometheus.CounterVec
|
||||
@@ -56,6 +58,8 @@ var (
|
||||
promRTT *prometheus.HistogramVec
|
||||
promParticipantJoin *prometheus.CounterVec
|
||||
promConnections *prometheus.GaugeVec
|
||||
promForwardLatency prometheus.Gauge
|
||||
promForwardJitter prometheus.Gauge
|
||||
|
||||
promPacketTotalIncomingInitial prometheus.Counter
|
||||
promPacketTotalIncomingRetransmit prometheus.Counter
|
||||
@@ -139,6 +143,18 @@ func initPacketStats(nodeID string, nodeType livekit.NodeType) {
|
||||
Name: "total",
|
||||
ConstLabels: prometheus.Labels{"node_id": nodeID, "node_type": nodeType.String()},
|
||||
}, []string{"kind"})
|
||||
promForwardLatency = prometheus.NewGauge(prometheus.GaugeOpts{
|
||||
Namespace: livekitNamespace,
|
||||
Subsystem: "forward",
|
||||
Name: "latency",
|
||||
ConstLabels: prometheus.Labels{"node_id": nodeID, "node_type": nodeType.String()},
|
||||
})
|
||||
promForwardJitter = prometheus.NewGauge(prometheus.GaugeOpts{
|
||||
Namespace: livekitNamespace,
|
||||
Subsystem: "forward",
|
||||
Name: "jitter",
|
||||
ConstLabels: prometheus.Labels{"node_id": nodeID, "node_type": nodeType.String()},
|
||||
})
|
||||
|
||||
prometheus.MustRegister(promPacketTotal)
|
||||
prometheus.MustRegister(promPacketBytes)
|
||||
@@ -151,6 +167,8 @@ func initPacketStats(nodeID string, nodeType livekit.NodeType) {
|
||||
prometheus.MustRegister(promRTT)
|
||||
prometheus.MustRegister(promParticipantJoin)
|
||||
prometheus.MustRegister(promConnections)
|
||||
prometheus.MustRegister(promForwardLatency)
|
||||
prometheus.MustRegister(promForwardJitter)
|
||||
|
||||
promPacketTotalIncomingInitial = promPacketTotal.WithLabelValues(string(Incoming), transmissionInitial)
|
||||
promPacketTotalIncomingRetransmit = promPacketTotal.WithLabelValues(string(Incoming), transmissionRetransmit)
|
||||
@@ -280,3 +298,13 @@ func AddConnection(direction Direction) {
|
||||
func SubConnection(direction Direction) {
|
||||
promConnections.WithLabelValues(string(direction)).Sub(1)
|
||||
}
|
||||
|
||||
func RecordForwardLatency(_, latencyAvg uint32) {
|
||||
forwardLatency.Store(latencyAvg)
|
||||
promForwardLatency.Set(float64(latencyAvg))
|
||||
}
|
||||
|
||||
func RecordForwardJitter(_, jitterAvg uint32) {
|
||||
forwardJitter.Store(jitterAvg)
|
||||
promForwardJitter.Set(float64(jitterAvg))
|
||||
}
|
||||
|
||||
@@ -29,6 +29,8 @@ import (
|
||||
|
||||
// StatsWorker handles participant stats
|
||||
type StatsWorker struct {
|
||||
next *StatsWorker
|
||||
|
||||
ctx context.Context
|
||||
t TelemetryService
|
||||
roomID livekit.RoomID
|
||||
@@ -91,8 +93,8 @@ func (s *StatsWorker) IsConnected() bool {
|
||||
return s.isConnected
|
||||
}
|
||||
|
||||
func (s *StatsWorker) Flush() {
|
||||
ts := timestamppb.Now()
|
||||
func (s *StatsWorker) Flush(now time.Time) bool {
|
||||
ts := timestamppb.New(now)
|
||||
|
||||
s.lock.Lock()
|
||||
stats := make([]*livekit.AnalyticsStat, 0, len(s.incomingPerTrack)+len(s.outgoingPerTrack))
|
||||
@@ -102,6 +104,8 @@ func (s *StatsWorker) Flush() {
|
||||
|
||||
outgoingPerTrack := s.outgoingPerTrack
|
||||
s.outgoingPerTrack = make(map[livekit.TrackID][]*livekit.AnalyticsStat)
|
||||
|
||||
closed := !s.closedAt.IsZero() && now.Sub(s.closedAt) > workerCleanupWait
|
||||
s.lock.Unlock()
|
||||
|
||||
stats = s.collectStats(ts, livekit.StreamType_UPSTREAM, incomingPerTrack, stats)
|
||||
@@ -109,21 +113,19 @@ func (s *StatsWorker) Flush() {
|
||||
if len(stats) > 0 {
|
||||
s.t.SendStats(s.ctx, stats)
|
||||
}
|
||||
|
||||
return closed
|
||||
}
|
||||
|
||||
func (s *StatsWorker) Close() {
|
||||
s.Flush()
|
||||
|
||||
func (s *StatsWorker) Close() bool {
|
||||
s.lock.Lock()
|
||||
s.closedAt = time.Now()
|
||||
s.lock.Unlock()
|
||||
}
|
||||
defer s.lock.Unlock()
|
||||
|
||||
func (s *StatsWorker) ClosedAt() time.Time {
|
||||
s.lock.RLock()
|
||||
defer s.lock.RUnlock()
|
||||
|
||||
return s.closedAt
|
||||
ok := s.closedAt.IsZero()
|
||||
if ok {
|
||||
s.closedAt = time.Now()
|
||||
}
|
||||
return ok
|
||||
}
|
||||
|
||||
func (s *StatsWorker) collectStats(
|
||||
|
||||
@@ -19,8 +19,6 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"golang.org/x/exp/maps"
|
||||
|
||||
"github.com/livekit/livekit-server/pkg/config"
|
||||
"github.com/livekit/livekit-server/pkg/utils"
|
||||
"github.com/livekit/protocol/livekit"
|
||||
@@ -95,9 +93,11 @@ type telemetryService struct {
|
||||
notifier webhook.QueuedNotifier
|
||||
jobsQueue *utils.OpsQueue
|
||||
|
||||
lock sync.RWMutex
|
||||
workers map[livekit.ParticipantID]*StatsWorker
|
||||
workersShadow []*StatsWorker
|
||||
workersMu sync.RWMutex
|
||||
workers map[livekit.ParticipantID]*StatsWorker
|
||||
workerList *StatsWorker
|
||||
|
||||
flushMu sync.Mutex
|
||||
}
|
||||
|
||||
func NewTelemetryService(notifier webhook.QueuedNotifier, analytics AnalyticsService) TelemetryService {
|
||||
@@ -121,29 +121,55 @@ func NewTelemetryService(notifier webhook.QueuedNotifier, analytics AnalyticsSer
|
||||
}
|
||||
|
||||
func (t *telemetryService) FlushStats() {
|
||||
t.lock.RLock()
|
||||
workersShadow := t.workersShadow
|
||||
t.lock.RUnlock()
|
||||
t.flushMu.Lock()
|
||||
defer t.flushMu.Unlock()
|
||||
|
||||
for _, worker := range workersShadow {
|
||||
worker.Flush()
|
||||
t.workersMu.RLock()
|
||||
worker := t.workerList
|
||||
t.workersMu.RUnlock()
|
||||
|
||||
now := time.Now()
|
||||
var prev, reap *StatsWorker
|
||||
for worker != nil {
|
||||
next := worker.next
|
||||
if closed := worker.Flush(now); closed {
|
||||
if prev == nil {
|
||||
// this worker was at the head of the list
|
||||
t.workersMu.Lock()
|
||||
p := &t.workerList
|
||||
for *p != worker {
|
||||
// new workers have been added. scan until we find the one
|
||||
// immediately before this
|
||||
prev = *p
|
||||
p = &prev.next
|
||||
}
|
||||
*p = worker.next
|
||||
t.workersMu.Unlock()
|
||||
} else {
|
||||
prev.next = worker.next
|
||||
}
|
||||
|
||||
worker.next = reap
|
||||
reap = worker
|
||||
} else {
|
||||
prev = worker
|
||||
}
|
||||
worker = next
|
||||
}
|
||||
|
||||
if reap != nil {
|
||||
t.workersMu.Lock()
|
||||
for reap != nil {
|
||||
delete(t.workers, reap.participantID)
|
||||
reap = reap.next
|
||||
}
|
||||
t.workersMu.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
func (t *telemetryService) run() {
|
||||
ticker := time.NewTicker(config.TelemetryStatsUpdateInterval)
|
||||
defer ticker.Stop()
|
||||
|
||||
cleanupTicker := time.NewTicker(time.Minute)
|
||||
defer cleanupTicker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
t.FlushStats()
|
||||
case <-cleanupTicker.C:
|
||||
t.cleanupWorkers()
|
||||
}
|
||||
for range time.Tick(config.TelemetryStatsUpdateInterval) {
|
||||
t.FlushStats()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,21 +178,22 @@ func (t *telemetryService) enqueue(op func()) {
|
||||
}
|
||||
|
||||
func (t *telemetryService) getWorker(participantID livekit.ParticipantID) (worker *StatsWorker, ok bool) {
|
||||
t.lock.RLock()
|
||||
defer t.lock.RUnlock()
|
||||
t.workersMu.RLock()
|
||||
defer t.workersMu.RUnlock()
|
||||
|
||||
worker, ok = t.workers[participantID]
|
||||
return
|
||||
}
|
||||
|
||||
func (t *telemetryService) getOrCreateWorker(ctx context.Context,
|
||||
func (t *telemetryService) getOrCreateWorker(
|
||||
ctx context.Context,
|
||||
roomID livekit.RoomID,
|
||||
roomName livekit.RoomName,
|
||||
participantID livekit.ParticipantID,
|
||||
participantIdentity livekit.ParticipantIdentity,
|
||||
) (*StatsWorker, bool) {
|
||||
t.lock.Lock()
|
||||
defer t.lock.Unlock()
|
||||
t.workersMu.Lock()
|
||||
defer t.workersMu.Unlock()
|
||||
|
||||
if worker, ok := t.workers[participantID]; ok {
|
||||
return worker, true
|
||||
@@ -182,39 +209,13 @@ func (t *telemetryService) getOrCreateWorker(ctx context.Context,
|
||||
)
|
||||
|
||||
t.workers[participantID] = worker
|
||||
t.workersShadow = maps.Values(t.workers)
|
||||
|
||||
worker.next = t.workerList
|
||||
t.workerList = worker
|
||||
|
||||
return worker, false
|
||||
}
|
||||
|
||||
func (t *telemetryService) cleanupWorkers() {
|
||||
t.lock.RLock()
|
||||
workersShadow := t.workersShadow
|
||||
t.lock.RUnlock()
|
||||
|
||||
toReap := make([]livekit.ParticipantID, 0, len(workersShadow))
|
||||
for _, worker := range workersShadow {
|
||||
closedAt := worker.ClosedAt()
|
||||
if !closedAt.IsZero() && time.Since(closedAt) > workerCleanupWait {
|
||||
worker.Flush()
|
||||
|
||||
toReap = append(toReap, worker.ParticipantID())
|
||||
}
|
||||
}
|
||||
|
||||
if len(toReap) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
t.lock.Lock()
|
||||
logger.Debugw("reaping analytics worker for participants", "pID", toReap)
|
||||
for _, pID := range toReap {
|
||||
delete(t.workers, pID)
|
||||
}
|
||||
t.workersShadow = maps.Values(t.workers)
|
||||
t.lock.Unlock()
|
||||
}
|
||||
|
||||
func (t *telemetryService) LocalRoomState(ctx context.Context, info *livekit.AnalyticsNodeRooms) {
|
||||
t.enqueue(func() {
|
||||
t.SendNodeRoomStates(ctx, info)
|
||||
|
||||
@@ -35,7 +35,7 @@ import (
|
||||
"github.com/livekit/protocol/auth"
|
||||
"github.com/livekit/protocol/livekit"
|
||||
"github.com/livekit/protocol/logger"
|
||||
"github.com/livekit/protocol/utils"
|
||||
"github.com/livekit/protocol/utils/guid"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -80,8 +80,8 @@ func setupSingleNodeTest(name string) (*service.LivekitServer, func()) {
|
||||
|
||||
func setupMultiNodeTest(name string) (*service.LivekitServer, *service.LivekitServer, func()) {
|
||||
logger.Infow("----------------STARTING TEST----------------", "test", name)
|
||||
s1 := createMultiNodeServer(utils.NewGuid(nodeID1), defaultServerPort)
|
||||
s2 := createMultiNodeServer(utils.NewGuid(nodeID2), secondServerPort)
|
||||
s1 := createMultiNodeServer(guid.New(nodeID1), defaultServerPort)
|
||||
s2 := createMultiNodeServer(guid.New(nodeID2), secondServerPort)
|
||||
go s1.Start()
|
||||
go s2.Start()
|
||||
|
||||
@@ -161,7 +161,7 @@ func createSingleNodeServer(configUpdater func(*config.Config)) *service.Livekit
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("could not create local node: %v", err))
|
||||
}
|
||||
currentNode.Id = utils.NewGuid(nodeID1)
|
||||
currentNode.Id = guid.New(nodeID1)
|
||||
|
||||
s, err := service.InitializeServer(conf, currentNode)
|
||||
if err != nil {
|
||||
|
||||
@@ -30,7 +30,7 @@ import (
|
||||
"github.com/livekit/protocol/auth"
|
||||
"github.com/livekit/protocol/livekit"
|
||||
"github.com/livekit/protocol/logger"
|
||||
"github.com/livekit/protocol/utils"
|
||||
"github.com/livekit/protocol/utils/guid"
|
||||
"github.com/livekit/protocol/webhook"
|
||||
|
||||
"github.com/livekit/livekit-server/pkg/config"
|
||||
@@ -137,7 +137,7 @@ func setupServerWithWebhook() (server *service.LivekitServer, testServer *webhoo
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
currentNode.Id = utils.NewGuid(nodeID1)
|
||||
currentNode.Id = guid.New(nodeID1)
|
||||
|
||||
server, err = service.InitializeServer(conf, currentNode)
|
||||
if err != nil {
|
||||
|
||||
+1
-1
@@ -14,4 +14,4 @@
|
||||
|
||||
package version
|
||||
|
||||
const Version = "1.6.0"
|
||||
const Version = "1.6.1"
|
||||
|
||||
Reference in New Issue
Block a user