mirror of
https://github.com/livekit/livekit.git
synced 2026-10-22 12:12:30 +00:00
use ion-sfu receiver/downtrack instead of our own.
This commit is contained in:
37 files changed
+1109
-2194
No files matched your search
+13
-12
@@ -27,7 +27,6 @@ import (
|
||||
|
||||
type RTCClient struct {
|
||||
id string
|
||||
identity string
|
||||
conn *websocket.Conn
|
||||
PeerConn *webrtc.PeerConnection
|
||||
// sid => track
|
||||
@@ -128,7 +127,8 @@ func NewRTCClient(conn *websocket.Conn) (*RTCClient, error) {
|
||||
})
|
||||
|
||||
peerConn.OnTrack(func(track *webrtc.TrackRemote, rtpReceiver *webrtc.RTPReceiver) {
|
||||
logger.Debugw("track received", "label", track.StreamID(), "id", track.ID())
|
||||
logger.Debugw("track received", "label", track.StreamID(), "id", track.ID(),
|
||||
"participant", c.localParticipant.Identity)
|
||||
go c.processTrack(track)
|
||||
})
|
||||
|
||||
@@ -144,7 +144,7 @@ func NewRTCClient(conn *websocket.Conn) (*RTCClient, error) {
|
||||
|
||||
peerConn.OnICEConnectionStateChange(func(connectionState webrtc.ICEConnectionState) {
|
||||
logger.Debugw("ICE state has changed", "state", connectionState.String(),
|
||||
"participant", c.localParticipant.Sid)
|
||||
"participant", c.localParticipant.Identity)
|
||||
if connectionState == webrtc.ICEConnectionStateConnected {
|
||||
// flush peers
|
||||
c.lock.Lock()
|
||||
@@ -195,8 +195,8 @@ func (c *RTCClient) Run() error {
|
||||
}
|
||||
switch msg := res.Message.(type) {
|
||||
case *livekit.SignalResponse_Join:
|
||||
c.localParticipant = msg.Join.Participant
|
||||
c.id = msg.Join.Participant.Sid
|
||||
c.identity = msg.Join.Participant.Identity
|
||||
|
||||
c.lock.Lock()
|
||||
for _, p := range msg.Join.OtherParticipants {
|
||||
@@ -205,7 +205,6 @@ func (c *RTCClient) Run() error {
|
||||
c.lock.Unlock()
|
||||
|
||||
logger.Debugw("join accepted, sending offer..", "participant", msg.Join.Participant.Identity)
|
||||
c.localParticipant = msg.Join.Participant
|
||||
logger.Debugw("other participants", "count", len(msg.Join.OtherParticipants))
|
||||
|
||||
// Create an offer to send to the other process
|
||||
@@ -214,7 +213,7 @@ func (c *RTCClient) Run() error {
|
||||
return err
|
||||
}
|
||||
|
||||
logger.Debugw("created offer", "offer", offer.SDP)
|
||||
logger.Debugw("created offer", "participant", c.localParticipant.Identity)
|
||||
|
||||
// Sets the LocalDescription, and starts our UDP listeners
|
||||
// Note: this will start the gathering of ICE candidates
|
||||
@@ -228,7 +227,6 @@ func (c *RTCClient) Run() error {
|
||||
Offer: rtc.ToProtoSessionDescription(offer),
|
||||
},
|
||||
}
|
||||
logger.Debugw("connecting to remote...")
|
||||
if err = c.SendRequest(req); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -254,7 +252,6 @@ func (c *RTCClient) Run() error {
|
||||
c.lock.Lock()
|
||||
for _, p := range msg.Update.Participants {
|
||||
c.remoteParticipants[p.Sid] = p
|
||||
logger.Debugw("participant update", "id", p.Identity, "state", p.State.String())
|
||||
}
|
||||
c.lock.Unlock()
|
||||
case *livekit.SignalResponse_TrackPublished:
|
||||
@@ -277,7 +274,11 @@ func (c *RTCClient) WaitUntilConnected() error {
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return errors.New("could not connect after timeout")
|
||||
id := c.ID()
|
||||
if c.localParticipant != nil {
|
||||
id = c.localParticipant.Identity
|
||||
}
|
||||
return fmt.Errorf("%s could not connect after timeout", id)
|
||||
case <-time.After(10 * time.Millisecond):
|
||||
if c.iceConnected.Get() {
|
||||
return nil
|
||||
@@ -476,7 +477,7 @@ func (c *RTCClient) handleOffer(desc webrtc.SessionDescription) error {
|
||||
}
|
||||
|
||||
func (c *RTCClient) handleAnswer(desc webrtc.SessionDescription) error {
|
||||
logger.Debugw("handling server answer")
|
||||
logger.Debugw("handling server answer", "participant", c.localParticipant.Identity)
|
||||
// remote answered the offer, establish connection
|
||||
err := c.PeerConn.SetRemoteDescription(desc)
|
||||
if err != nil {
|
||||
@@ -499,7 +500,7 @@ func (c *RTCClient) handleAnswer(desc webrtc.SessionDescription) error {
|
||||
}
|
||||
|
||||
func (c *RTCClient) requestNegotiation() error {
|
||||
logger.Debugw("requesting negotiation")
|
||||
logger.Debugw("requesting negotiation", "participant", c.localParticipant.Identity)
|
||||
return c.SendRequest(&livekit.SignalRequest{
|
||||
Message: &livekit.SignalRequest_Negotiate{
|
||||
Negotiate: &livekit.NegotiationRequest{},
|
||||
@@ -508,7 +509,7 @@ func (c *RTCClient) requestNegotiation() error {
|
||||
}
|
||||
|
||||
func (c *RTCClient) negotiate() error {
|
||||
logger.Debugw("starting negotiation")
|
||||
logger.Debugw("starting negotiation", "participant", c.localParticipant.Identity)
|
||||
offer, err := c.PeerConn.CreateOffer(nil)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -4,7 +4,6 @@ go 1.15
|
||||
|
||||
require (
|
||||
github.com/bep/debounce v1.2.0
|
||||
github.com/gammazero/workerpool v1.1.1
|
||||
github.com/go-redis/redis/v8 v8.4.8
|
||||
github.com/golang/protobuf v1.4.3
|
||||
github.com/google/wire v0.4.0
|
||||
@@ -15,13 +14,12 @@ require (
|
||||
github.com/magefile/mage v1.10.0
|
||||
github.com/maxbrunsfeld/counterfeiter/v6 v6.3.0
|
||||
github.com/mitchellh/go-homedir v1.1.0
|
||||
github.com/pion/ion-log v1.0.0
|
||||
github.com/pion/ion-sfu v1.8.1
|
||||
github.com/pion/ion-sfu v1.8.3
|
||||
github.com/pion/rtcp v1.2.6
|
||||
github.com/pion/rtp v1.6.2
|
||||
github.com/pion/sdp/v3 v3.0.4
|
||||
github.com/pion/stun v0.3.5
|
||||
github.com/pion/transport v0.12.2
|
||||
github.com/pion/webrtc/v3 v3.0.4
|
||||
github.com/pion/webrtc/v3 v3.0.5
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/stretchr/testify v1.7.0
|
||||
github.com/thoas/go-funk v0.7.0
|
||||
@@ -35,3 +33,5 @@ require (
|
||||
gopkg.in/square/go-jose.v2 v2.5.1
|
||||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776
|
||||
)
|
||||
|
||||
replace github.com/pion/ion-sfu => github.com/pion/ion-sfu v1.8.4-0.20210205220645-c9ec7ad1db86
|
||||
@@ -38,6 +38,7 @@ github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN
|
||||
github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/bep/debounce v1.2.0 h1:wXds8Kq8qRfwAOpAxHrJDbCXgC5aHSzgQb/0gKsHQqo=
|
||||
github.com/bep/debounce v1.2.0/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0=
|
||||
@@ -70,6 +71,8 @@ github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7Do
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davidzhao/ion-sfu v1.8.3-0.20210205220645-c9ec7ad1db86 h1:POGjEQ8fyEQn96DMXMG7xhvWxHjNRMQ5aoiRzQOzOdY=
|
||||
github.com/davidzhao/ion-sfu v1.8.3-0.20210205220645-c9ec7ad1db86/go.mod h1:MKDh4JSLpBA/hFeYAYALb6nYpe6Ruknd3pmxPMbdpM8=
|
||||
github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f/go.mod h1:xH/i4TFMt8koVQZ6WFms69WAsDWr2XsYL3Hkl7jkoLE=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
@@ -158,6 +161,8 @@ github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+
|
||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y=
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.1.5 h1:kxhtnfFVi+rYdOALN0B3k9UT86zVJKfBimRaciULW4I=
|
||||
github.com/google/uuid v1.1.5/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/wire v0.4.0 h1:kXcsA/rIGzJImVqPdhfnr6q0xsS9gU0515q1EPpJ9fE=
|
||||
github.com/google/wire v0.4.0/go.mod h1:ngWDr9Qvq3yZA10YrxfyGELY/AFWGVpy9c1LTRi1EoU=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
@@ -233,6 +238,7 @@ github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-b
|
||||
github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4=
|
||||
github.com/lithammer/shortuuid/v3 v3.0.4 h1:uj4xhotfY92Y1Oa6n6HUiFn87CdoEHYUlTy0+IgbLrs=
|
||||
github.com/lithammer/shortuuid/v3 v3.0.4/go.mod h1:RviRjexKqIzx/7r1peoAITm6m7gnif/h+0zmolKJjzw=
|
||||
github.com/lucsky/cuid v1.0.2 h1:z4XlExeoderxoPj2/dxKOyPxe9RCOu7yNq9/XWxIUMQ=
|
||||
github.com/lucsky/cuid v1.0.2/go.mod h1:QaaJqckboimOmhRSJXSx/+IT+VTfxfPGSo/6mfgUfmE=
|
||||
github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ=
|
||||
github.com/lytics/base62 v0.0.0-20180808010106-0ee4de5a5d6d h1:HONlVR8gkr5QibYNVFtj8ajfqRAKt21DmdzeZXZl6VQ=
|
||||
@@ -247,6 +253,7 @@ github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNx
|
||||
github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs=
|
||||
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/maxbrunsfeld/counterfeiter/v6 v6.3.0 h1:8E6DrFvII6QR4eJ3PkFvV+lc03P+2qwqTPLm1ax7694=
|
||||
github.com/maxbrunsfeld/counterfeiter/v6 v6.3.0/go.mod h1:fcEyUyXZXoV4Abw8DX0t7wyL8mCDxXyU4iAFZfT3IHw=
|
||||
@@ -316,12 +323,16 @@ github.com/pion/dtls/v2 v2.0.4 h1:WuUcqi6oYMu/noNTz92QrF1DaFj4eXbhQ6dzaaAwOiI=
|
||||
github.com/pion/dtls/v2 v2.0.4/go.mod h1:qAkFscX0ZHoI1E07RfYPoRw3manThveu+mlTDdOxoGI=
|
||||
github.com/pion/ice/v2 v2.0.14 h1:FxXxauyykf89SWAtkQCfnHkno6G8+bhRkNguSh9zU+4=
|
||||
github.com/pion/ice/v2 v2.0.14/go.mod h1:wqaUbOq5ObDNU5ox1hRsEst0rWfsKuH1zXjQFEWiZwM=
|
||||
github.com/pion/ice/v2 v2.0.15 h1:KZrwa2ciL9od8+TUVJiYTNsCW9J5lktBjGwW1MacEnQ=
|
||||
github.com/pion/ice/v2 v2.0.15/go.mod h1:ZIiVGevpgAxF/cXiIVmuIUtCb3Xs4gCzCbXB6+nFkSI=
|
||||
github.com/pion/interceptor v0.0.9 h1:fk5hTdyLO3KURQsf/+RjMpEm4NE3yeTY9Kh97b5BvwA=
|
||||
github.com/pion/interceptor v0.0.9/go.mod h1:dHgEP5dtxOTf21MObuBAjJeAayPxLUAZjerGH8Xr07c=
|
||||
github.com/pion/ion-log v1.0.0 h1:2lJLImCmfCWCR38hLWsjQfBWe6NFz/htbqiYHwvOP/Q=
|
||||
github.com/pion/ion-log v1.0.0/go.mod h1:jwcla9KoB9bB/4FxYDSRJPcPYSLp5XiUUMnOLaqwl4E=
|
||||
github.com/pion/ion-sfu v1.8.1 h1:N3xaDZ2Om2pCzPLlusPmV7BJYVedE5VEx8um6d0Crns=
|
||||
github.com/pion/ion-sfu v1.8.1/go.mod h1:b6FI2WFTBsifw9EhxvfDks5/9SGYmVvm00IogZEgaik=
|
||||
github.com/pion/ion-sfu v1.8.3 h1:ZPmNgBRvrrYY5POjRYglCrradFMw/SBLPh0WXJYPm8E=
|
||||
github.com/pion/ion-sfu v1.8.3/go.mod h1:MKDh4JSLpBA/hFeYAYALb6nYpe6Ruknd3pmxPMbdpM8=
|
||||
github.com/pion/ion-sfu v1.8.4-0.20210205220645-c9ec7ad1db86 h1:HfWQIv0mJ5MYbKLaWlQ3nG/ToRTE96dpBmts97nra5o=
|
||||
github.com/pion/ion-sfu v1.8.4-0.20210205220645-c9ec7ad1db86/go.mod h1:MKDh4JSLpBA/hFeYAYALb6nYpe6Ruknd3pmxPMbdpM8=
|
||||
github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY=
|
||||
github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms=
|
||||
github.com/pion/mdns v0.0.4 h1:O4vvVqr4DGX63vzmO6Fw9vpy3lfztVWHGCQfyw0ZLSY=
|
||||
@@ -347,6 +358,7 @@ github.com/pion/transport v0.10.1 h1:2W+yJT+0mOQ160ThZYUx5Zp2skzshiNgxrNE9GUfhJM
|
||||
github.com/pion/transport v0.10.1/go.mod h1:PBis1stIILMiis0PewDw91WJeLJkyIMcEk+DwKOzf4A=
|
||||
github.com/pion/transport v0.12.0 h1:UFmOBBZkTZ3LgvLRf/NGrfWdZEubcU6zkLU3PsA9YvU=
|
||||
github.com/pion/transport v0.12.0/go.mod h1:N3+vZQD9HlDP5GWkZ85LohxNsDcNgofQmyL6ojX5d8Q=
|
||||
github.com/pion/transport v0.12.1/go.mod h1:N3+vZQD9HlDP5GWkZ85LohxNsDcNgofQmyL6ojX5d8Q=
|
||||
github.com/pion/transport v0.12.2 h1:WYEjhloRHt1R86LhUKjC5y+P52Y11/QqEUalvtzVoys=
|
||||
github.com/pion/transport v0.12.2/go.mod h1:N3+vZQD9HlDP5GWkZ85LohxNsDcNgofQmyL6ojX5d8Q=
|
||||
github.com/pion/turn/v2 v2.0.5 h1:iwMHqDfPEDEOFzwWKT56eFmh6DYC6o/+xnLAEzgISbA=
|
||||
@@ -355,6 +367,8 @@ github.com/pion/udp v0.1.0 h1:uGxQsNyrqG3GLINv36Ff60covYmfrLoxzwnCsIYspXI=
|
||||
github.com/pion/udp v0.1.0/go.mod h1:BPELIjbwE9PRbd/zxI/KYBnbo7B6+oA6YuEaNE8lths=
|
||||
github.com/pion/webrtc/v3 v3.0.4 h1:Tiw3H9fpfcwkvaxonB+Gv1DG9tmgYBQaM1vBagDHP40=
|
||||
github.com/pion/webrtc/v3 v3.0.4/go.mod h1:1TmFSLpPYFTFXFHPtoq9eGP1ASTa9LC6FBh7sUY8cd4=
|
||||
github.com/pion/webrtc/v3 v3.0.5 h1:utennp7RwX+2mtyMzoOXE03IUIckiHBigjarRJZ2DqY=
|
||||
github.com/pion/webrtc/v3 v3.0.5/go.mod h1:/EDCREM8y+JrJSkoCRHpoz//qtuBCOYV4E96vEK3bz0=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
@@ -369,12 +383,14 @@ github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDf
|
||||
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
||||
github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og=
|
||||
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
|
||||
github.com/prometheus/client_golang v1.9.0 h1:Rrch9mh17XcxvEu9D9DEpb4isxjGBtcevQjKvxPRQIU=
|
||||
github.com/prometheus/client_golang v1.9.0/go.mod h1:FqZLKOZnGdFAhOK4nqGHa7D66IdsO+O441Eve7ptJDU=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
|
||||
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
||||
github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
@@ -390,6 +406,7 @@ github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7z
|
||||
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
|
||||
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
|
||||
github.com/prometheus/procfs v0.2.0 h1:wH4vA7pcjKuZzjF7lM8awk4fnuJO6idemZXoKnULUx4=
|
||||
github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
|
||||
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
|
||||
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
|
||||
@@ -548,6 +565,8 @@ golang.org/x/net v0.0.0-20201201195509-5d6afe98e0b7/go.mod h1:sp8m0HH+o8qH0wwXwY
|
||||
golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201224014010-6772e930b67b h1:iFwSg7t5GZmB/Q5TjiEAsdoLDrdJRC1RiF2WhuV29Qw=
|
||||
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210119194325-5f4716e94777 h1:003p0dJM77cxMSyCPFphvZf/Y5/NXf5fzg6ufd1/Oew=
|
||||
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
|
||||
@@ -25,7 +25,7 @@ type RTCConfig struct {
|
||||
StunServers []string `yaml:"stun_servers"`
|
||||
UseExternalIP bool `yaml:"use_external_ip"`
|
||||
|
||||
MaxBitrate uint64 `yaml:"max_bandwidth"`
|
||||
MaxBitrate uint64 `yaml:"max_bitrate"`
|
||||
MaxBufferTime int `yaml:"max_buffer_time"`
|
||||
}
|
||||
|
||||
@@ -44,6 +44,7 @@ func NewConfig(confString string) (*Config, error) {
|
||||
StunServers: []string{
|
||||
"stun.l.google.com:19302",
|
||||
},
|
||||
MaxBitrate: 2 * 1024 * 1024, // 3 mbps
|
||||
},
|
||||
Redis: RedisConfig{},
|
||||
Keys: map[string]string{},
|
||||
|
||||
@@ -33,6 +33,7 @@ type Router interface {
|
||||
ClearRoomState(roomName string) error
|
||||
RegisterNode() error
|
||||
UnregisterNode() error
|
||||
RemoveDeadNodes() error
|
||||
GetNode(nodeId string) (*livekit.Node, error)
|
||||
ListNodes() ([]*livekit.Node, error)
|
||||
|
||||
|
||||
@@ -46,6 +46,10 @@ func (r *LocalRouter) UnregisterNode() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *LocalRouter) RemoveDeadNodes() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *LocalRouter) GetNode(nodeId string) (*livekit.Node, error) {
|
||||
if nodeId == r.currentNode.Id {
|
||||
return r.currentNode, nil
|
||||
|
||||
@@ -58,6 +58,9 @@ func publishRTCMessage(rc *redis.Client, nodeId string, participantKey string, m
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
//logger.Debugw("publishing to", "rtcChannel", rtcNodeChannel(nodeId),
|
||||
// "message", rm.Message)
|
||||
return rc.Publish(redisCtx, rtcNodeChannel(nodeId), data).Err()
|
||||
}
|
||||
|
||||
|
||||
+113
-118
@@ -2,7 +2,6 @@ package routing
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/go-redis/redis/v8"
|
||||
@@ -17,7 +16,7 @@ import (
|
||||
const (
|
||||
// expire participant mappings after a day
|
||||
participantMappingTTL = 24 * time.Hour
|
||||
statsUpdateInterval = 10 * time.Second
|
||||
statsUpdateInterval = 2 * time.Second
|
||||
)
|
||||
|
||||
// RedisRouter uses Redis pub/sub to route signaling messages across different nodes
|
||||
@@ -25,28 +24,27 @@ const (
|
||||
// Because
|
||||
type RedisRouter struct {
|
||||
LocalRouter
|
||||
rc *redis.Client
|
||||
cr *utils.CachedRedis
|
||||
ctx context.Context
|
||||
once sync.Once
|
||||
rc *redis.Client
|
||||
ctx context.Context
|
||||
isStarted utils.AtomicFlag
|
||||
|
||||
// map of participantKey => RTCNodeSink
|
||||
rtcSinks map[string]*RTCNodeSink
|
||||
// map of connectionId => SignalNodeSink
|
||||
signalSinks map[string]*SignalNodeSink
|
||||
cancel func()
|
||||
|
||||
pubsub *redis.PubSub
|
||||
cancel func()
|
||||
}
|
||||
|
||||
func NewRedisRouter(currentNode LocalNode, rc *redis.Client) *RedisRouter {
|
||||
rr := &RedisRouter{
|
||||
LocalRouter: *NewLocalRouter(currentNode),
|
||||
rc: rc,
|
||||
once: sync.Once{},
|
||||
rtcSinks: make(map[string]*RTCNodeSink),
|
||||
signalSinks: make(map[string]*SignalNodeSink),
|
||||
}
|
||||
rr.ctx, rr.cancel = context.WithCancel(context.Background())
|
||||
rr.cr = utils.NewCachedRedis(rr.ctx, rr.rc)
|
||||
return rr
|
||||
}
|
||||
|
||||
@@ -55,7 +53,6 @@ func (r *RedisRouter) RegisterNode() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
r.cr.ExpireHash(NodesKey, r.currentNode.Id)
|
||||
if err := r.rc.HSet(r.ctx, NodesKey, r.currentNode.Id, data).Err(); err != nil {
|
||||
return errors.Wrap(err, "could not register node")
|
||||
}
|
||||
@@ -67,8 +64,23 @@ func (r *RedisRouter) UnregisterNode() error {
|
||||
return r.rc.HDel(context.Background(), NodesKey, r.currentNode.Id).Err()
|
||||
}
|
||||
|
||||
func (r *RedisRouter) RemoveDeadNodes() error {
|
||||
nodes, err := r.ListNodes()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, n := range nodes {
|
||||
if !IsAvailable(n) {
|
||||
if err := r.rc.HDel(context.Background(), NodesKey, n.Id).Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *RedisRouter) GetNodeForRoom(roomName string) (string, error) {
|
||||
val, err := r.cr.CachedHGet(NodeRoomKey, roomName)
|
||||
val, err := r.rc.HGet(r.ctx, NodeRoomKey, roomName).Result()
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, "could not get node for room")
|
||||
}
|
||||
@@ -87,7 +99,7 @@ func (r *RedisRouter) ClearRoomState(roomName string) error {
|
||||
}
|
||||
|
||||
func (r *RedisRouter) GetNode(nodeId string) (*livekit.Node, error) {
|
||||
data, err := r.cr.CachedHGet(NodesKey, nodeId)
|
||||
data, err := r.rc.HGet(r.ctx, NodesKey, nodeId).Result()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -203,20 +215,24 @@ func (r *RedisRouter) startParticipantRTC(ss *livekit.StartSession, participantK
|
||||
}
|
||||
|
||||
func (r *RedisRouter) Start() error {
|
||||
r.once.Do(func() {
|
||||
go r.statsWorker()
|
||||
go r.rtcWorker()
|
||||
go r.signalWorker()
|
||||
})
|
||||
if !r.isStarted.TrySet(true) {
|
||||
return nil
|
||||
}
|
||||
go r.statsWorker()
|
||||
go r.redisWorker()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *RedisRouter) Stop() {
|
||||
if !r.isStarted.TrySet(false) {
|
||||
return
|
||||
}
|
||||
logger.Debugw("stopping RedisRouter")
|
||||
r.pubsub.Close()
|
||||
r.cancel()
|
||||
}
|
||||
|
||||
func (r *RedisRouter) setParticipantRTCNode(participantKey, nodeId string) error {
|
||||
r.cr.Expire(participantRTCKey(participantKey))
|
||||
err := r.rc.Set(r.ctx, participantRTCKey(participantKey), nodeId, participantMappingTTL).Err()
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, "could not set rtc node")
|
||||
@@ -225,7 +241,6 @@ func (r *RedisRouter) setParticipantRTCNode(participantKey, nodeId string) error
|
||||
}
|
||||
|
||||
func (r *RedisRouter) setParticipantSignalNode(connectionId, nodeId string) error {
|
||||
r.cr.Expire(participantSignalKey(connectionId))
|
||||
if err := r.rc.Set(r.ctx, participantSignalKey(connectionId), nodeId, participantMappingTTL).Err(); err != nil {
|
||||
return errors.Wrap(err, "could not set signal node")
|
||||
}
|
||||
@@ -271,126 +286,106 @@ func (r *RedisRouter) getOrCreateSignalSink(nodeId string, connectionId string)
|
||||
}
|
||||
|
||||
func (r *RedisRouter) getParticipantRTCNode(participantKey string) (string, error) {
|
||||
return r.cr.CachedGet(participantRTCKey(participantKey))
|
||||
return r.rc.Get(r.ctx, participantRTCKey(participantKey)).Result()
|
||||
}
|
||||
|
||||
func (r *RedisRouter) getParticipantSignalNode(connectionId string) (nodeId string, err error) {
|
||||
return r.cr.CachedGet(participantSignalKey(connectionId))
|
||||
return r.rc.Get(r.ctx, participantSignalKey(connectionId)).Result()
|
||||
}
|
||||
|
||||
// update node stats and cleanup
|
||||
func (r *RedisRouter) statsWorker() {
|
||||
for r.ctx.Err() == nil {
|
||||
// update every 10 seconds
|
||||
<-time.After(statsUpdateInterval)
|
||||
r.currentNode.Stats.UpdatedAt = time.Now().Unix()
|
||||
if err := r.RegisterNode(); err != nil {
|
||||
logger.Errorw("could not update node", "error", err)
|
||||
// update periodically seconds
|
||||
select {
|
||||
case <-time.After(statsUpdateInterval):
|
||||
r.currentNode.Stats.UpdatedAt = time.Now().Unix()
|
||||
if err := r.RegisterNode(); err != nil {
|
||||
logger.Errorw("could not update node", "error", err)
|
||||
}
|
||||
case <-r.ctx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// worker that consumes signal channel and processes
|
||||
func (r *RedisRouter) signalWorker() {
|
||||
sub := r.rc.Subscribe(redisCtx, signalNodeChannel(r.currentNode.Id))
|
||||
// worker that consumes redis messages intended for this node
|
||||
func (r *RedisRouter) redisWorker() {
|
||||
defer func() {
|
||||
logger.Debugw("finishing redis signalWorker", "node", r.currentNode.Id)
|
||||
logger.Debugw("finishing redisWorker", "node", r.currentNode.Id)
|
||||
}()
|
||||
logger.Debugw("starting redis signalWorker", "node", r.currentNode.Id)
|
||||
for r.ctx.Err() == nil {
|
||||
obj, err := sub.Receive(r.ctx)
|
||||
if err != nil {
|
||||
logger.Warnw("error receiving redis message", "error", err)
|
||||
// TODO: retry? ignore? at a minimum need to sleep here to retry
|
||||
time.Sleep(time.Second)
|
||||
continue
|
||||
}
|
||||
if obj == nil {
|
||||
logger.Debugw("starting redisWorker", "node", r.currentNode.Id)
|
||||
|
||||
sigChannel := signalNodeChannel(r.currentNode.Id)
|
||||
rtcChannel := rtcNodeChannel(r.currentNode.Id)
|
||||
r.pubsub = r.rc.Subscribe(r.ctx, sigChannel, rtcChannel)
|
||||
for msg := range r.pubsub.Channel() {
|
||||
if msg == nil {
|
||||
return
|
||||
}
|
||||
|
||||
msg, ok := obj.(*redis.Message)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
rm := livekit.SignalNodeMessage{}
|
||||
err = proto.Unmarshal([]byte(msg.Payload), &rm)
|
||||
connectionId := rm.ConnectionId
|
||||
|
||||
switch rmb := rm.Message.(type) {
|
||||
|
||||
case *livekit.SignalNodeMessage_Response:
|
||||
// in the event the current node is an Signal node, push to response channels
|
||||
resSink := r.getOrCreateMessageChannel(r.responseChannels, connectionId)
|
||||
err = resSink.WriteMessage(rmb.Response)
|
||||
if err != nil {
|
||||
logger.Errorw("could not write to response channel",
|
||||
"connectionId", connectionId,
|
||||
"error", err)
|
||||
}
|
||||
|
||||
case *livekit.SignalNodeMessage_EndSession:
|
||||
//signalNode, err := r.getParticipantSignalNode(connectionId)
|
||||
if err != nil {
|
||||
logger.Errorw("could not get participant RTC node",
|
||||
"error", err)
|
||||
if msg.Channel == sigChannel {
|
||||
sm := livekit.SignalNodeMessage{}
|
||||
if err := proto.Unmarshal([]byte(msg.Payload), &sm); err != nil {
|
||||
logger.Errorw("could not unmarshal signal message on sigchan", "error", err)
|
||||
continue
|
||||
}
|
||||
// EndSession can only be initiated on an RTC node, is handled on the signal node
|
||||
//if signalNode == r.currentNode.Id {
|
||||
resSink := r.getOrCreateMessageChannel(r.responseChannels, connectionId)
|
||||
resSink.Close()
|
||||
//}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// worker that consumes RTC channel and processes
|
||||
func (r *RedisRouter) rtcWorker() {
|
||||
sub := r.rc.Subscribe(redisCtx, rtcNodeChannel(r.currentNode.Id))
|
||||
|
||||
defer func() {
|
||||
logger.Debugw("finishing redis rtcWorker", "node", r.currentNode.Id)
|
||||
}()
|
||||
logger.Debugw("starting redis rtcWorker", "node", r.currentNode.Id)
|
||||
for r.ctx.Err() == nil {
|
||||
obj, err := sub.Receive(r.ctx)
|
||||
if err != nil {
|
||||
logger.Warnw("error receiving redis message", "error", err)
|
||||
// TODO: retry? ignore? at a minimum need to sleep here to retry
|
||||
time.Sleep(time.Second)
|
||||
continue
|
||||
}
|
||||
if obj == nil {
|
||||
return
|
||||
}
|
||||
|
||||
msg, ok := obj.(*redis.Message)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
rm := livekit.RTCNodeMessage{}
|
||||
err = proto.Unmarshal([]byte(msg.Payload), &rm)
|
||||
pKey := rm.ParticipantKey
|
||||
|
||||
switch rmb := rm.Message.(type) {
|
||||
case *livekit.RTCNodeMessage_StartSession:
|
||||
// RTC session should start on this node
|
||||
err = r.startParticipantRTC(rmb.StartSession, pKey)
|
||||
if err != nil {
|
||||
logger.Errorw("could not start participant", "error", err)
|
||||
if err := r.handleSignalMessage(&sm); err != nil {
|
||||
logger.Errorw("error processing signal message", "error", err)
|
||||
continue
|
||||
}
|
||||
|
||||
case *livekit.RTCNodeMessage_Request:
|
||||
requestChan := r.getOrCreateMessageChannel(r.requestChannels, pKey)
|
||||
err = requestChan.WriteMessage(rmb.Request)
|
||||
if err != nil {
|
||||
logger.Errorw("could not write to request channel",
|
||||
"participant", pKey,
|
||||
"error", err)
|
||||
} else if msg.Channel == rtcChannel {
|
||||
rm := livekit.RTCNodeMessage{}
|
||||
if err := proto.Unmarshal([]byte(msg.Payload), &rm); err != nil {
|
||||
logger.Errorw("could not unmarshal RTC message on rtcchan", "error", err)
|
||||
continue
|
||||
}
|
||||
if err := r.handleRTCMessage(&rm); err != nil {
|
||||
logger.Errorw("error processing RTC message", "error", err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (r *RedisRouter) handleSignalMessage(sm *livekit.SignalNodeMessage) error {
|
||||
connectionId := sm.ConnectionId
|
||||
|
||||
switch rmb := sm.Message.(type) {
|
||||
case *livekit.SignalNodeMessage_Response:
|
||||
//logger.Debugw("forwarding signal message",
|
||||
// "connectionId", connectionId,
|
||||
// "type", fmt.Sprintf("%T", rmb.Response.Message))
|
||||
// in the event the current node is an Signal node, push to response channels
|
||||
resSink := r.getOrCreateMessageChannel(r.responseChannels, connectionId)
|
||||
if err := resSink.WriteMessage(rmb.Response); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case *livekit.SignalNodeMessage_EndSession:
|
||||
logger.Debugw("received EndSession, closing signal connection",
|
||||
"connectionId", connectionId)
|
||||
resSink := r.getOrCreateMessageChannel(r.responseChannels, connectionId)
|
||||
resSink.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *RedisRouter) handleRTCMessage(rm *livekit.RTCNodeMessage) error {
|
||||
pKey := rm.ParticipantKey
|
||||
|
||||
switch rmb := rm.Message.(type) {
|
||||
case *livekit.RTCNodeMessage_StartSession:
|
||||
// RTC session should start on this node
|
||||
if err := r.startParticipantRTC(rmb.StartSession, pKey); err != nil {
|
||||
return errors.Wrap(err, "could not start participant")
|
||||
}
|
||||
|
||||
case *livekit.RTCNodeMessage_Request:
|
||||
requestChan := r.getOrCreateMessageChannel(r.requestChannels, pKey)
|
||||
if err := requestChan.WriteMessage(rmb.Request); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -73,6 +73,16 @@ type FakeRouter struct {
|
||||
registerNodeReturnsOnCall map[int]struct {
|
||||
result1 error
|
||||
}
|
||||
RemoveDeadNodesStub func() error
|
||||
removeDeadNodesMutex sync.RWMutex
|
||||
removeDeadNodesArgsForCall []struct {
|
||||
}
|
||||
removeDeadNodesReturns struct {
|
||||
result1 error
|
||||
}
|
||||
removeDeadNodesReturnsOnCall map[int]struct {
|
||||
result1 error
|
||||
}
|
||||
SetNodeForRoomStub func(string, string) error
|
||||
setNodeForRoomMutex sync.RWMutex
|
||||
setNodeForRoomArgsForCall []struct {
|
||||
@@ -460,6 +470,59 @@ func (fake *FakeRouter) RegisterNodeReturnsOnCall(i int, result1 error) {
|
||||
}{result1}
|
||||
}
|
||||
|
||||
func (fake *FakeRouter) RemoveDeadNodes() error {
|
||||
fake.removeDeadNodesMutex.Lock()
|
||||
ret, specificReturn := fake.removeDeadNodesReturnsOnCall[len(fake.removeDeadNodesArgsForCall)]
|
||||
fake.removeDeadNodesArgsForCall = append(fake.removeDeadNodesArgsForCall, struct {
|
||||
}{})
|
||||
stub := fake.RemoveDeadNodesStub
|
||||
fakeReturns := fake.removeDeadNodesReturns
|
||||
fake.recordInvocation("RemoveDeadNodes", []interface{}{})
|
||||
fake.removeDeadNodesMutex.Unlock()
|
||||
if stub != nil {
|
||||
return stub()
|
||||
}
|
||||
if specificReturn {
|
||||
return ret.result1
|
||||
}
|
||||
return fakeReturns.result1
|
||||
}
|
||||
|
||||
func (fake *FakeRouter) RemoveDeadNodesCallCount() int {
|
||||
fake.removeDeadNodesMutex.RLock()
|
||||
defer fake.removeDeadNodesMutex.RUnlock()
|
||||
return len(fake.removeDeadNodesArgsForCall)
|
||||
}
|
||||
|
||||
func (fake *FakeRouter) RemoveDeadNodesCalls(stub func() error) {
|
||||
fake.removeDeadNodesMutex.Lock()
|
||||
defer fake.removeDeadNodesMutex.Unlock()
|
||||
fake.RemoveDeadNodesStub = stub
|
||||
}
|
||||
|
||||
func (fake *FakeRouter) RemoveDeadNodesReturns(result1 error) {
|
||||
fake.removeDeadNodesMutex.Lock()
|
||||
defer fake.removeDeadNodesMutex.Unlock()
|
||||
fake.RemoveDeadNodesStub = nil
|
||||
fake.removeDeadNodesReturns = struct {
|
||||
result1 error
|
||||
}{result1}
|
||||
}
|
||||
|
||||
func (fake *FakeRouter) RemoveDeadNodesReturnsOnCall(i int, result1 error) {
|
||||
fake.removeDeadNodesMutex.Lock()
|
||||
defer fake.removeDeadNodesMutex.Unlock()
|
||||
fake.RemoveDeadNodesStub = nil
|
||||
if fake.removeDeadNodesReturnsOnCall == nil {
|
||||
fake.removeDeadNodesReturnsOnCall = make(map[int]struct {
|
||||
result1 error
|
||||
})
|
||||
}
|
||||
fake.removeDeadNodesReturnsOnCall[i] = struct {
|
||||
result1 error
|
||||
}{result1}
|
||||
}
|
||||
|
||||
func (fake *FakeRouter) SetNodeForRoom(arg1 string, arg2 string) error {
|
||||
fake.setNodeForRoomMutex.Lock()
|
||||
ret, specificReturn := fake.setNodeForRoomReturnsOnCall[len(fake.setNodeForRoomArgsForCall)]
|
||||
@@ -736,6 +799,8 @@ func (fake *FakeRouter) Invocations() map[string][][]interface{} {
|
||||
defer fake.onNewParticipantRTCMutex.RUnlock()
|
||||
fake.registerNodeMutex.RLock()
|
||||
defer fake.registerNodeMutex.RUnlock()
|
||||
fake.removeDeadNodesMutex.RLock()
|
||||
defer fake.removeDeadNodesMutex.RUnlock()
|
||||
fake.setNodeForRoomMutex.RLock()
|
||||
defer fake.setNodeForRoomMutex.RUnlock()
|
||||
fake.startMutex.RLock()
|
||||
|
||||
@@ -14,7 +14,7 @@ func TestIsAvailable(t *testing.T) {
|
||||
t.Run("still available", func(t *testing.T) {
|
||||
n := &livekit.Node{
|
||||
Stats: &livekit.NodeStats{
|
||||
UpdatedAt: time.Now().Unix() - 10,
|
||||
UpdatedAt: time.Now().Unix() - 3,
|
||||
},
|
||||
}
|
||||
assert.True(t, routing.IsAvailable(n))
|
||||
|
||||
@@ -80,6 +80,10 @@ func (t *DataTrack) IsMuted() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (t *DataTrack) SetMuted(muted bool) {
|
||||
|
||||
}
|
||||
|
||||
func (t *DataTrack) OnClose(f func()) {
|
||||
t.onClose = f
|
||||
}
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
package rtc
|
||||
|
||||
import (
|
||||
"github.com/pion/sdp/v3"
|
||||
"github.com/pion/webrtc/v3"
|
||||
)
|
||||
|
||||
const (
|
||||
repairedRTP = "urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id"
|
||||
frameMarking = "urn:ietf:params:rtp-hdrext:framemarking"
|
||||
)
|
||||
|
||||
func createMediaEngine() (*webrtc.MediaEngine, error) {
|
||||
me := &webrtc.MediaEngine{}
|
||||
if err := me.RegisterCodec(webrtc.RTPCodecParameters{
|
||||
RTPCodecCapability: webrtc.RTPCodecCapability{MimeType: webrtc.MimeTypeOpus, ClockRate: 48000, Channels: 2, SDPFmtpLine: "minptime=10;useinbandfec=1", RTCPFeedback: nil},
|
||||
PayloadType: 111,
|
||||
}, webrtc.RTPCodecTypeAudio); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
videoRTCPFeedback := []webrtc.RTCPFeedback{
|
||||
{webrtc.TypeRTCPFBGoogREMB, ""},
|
||||
//{webrtc.TypeRTCPFBCCM, "fir"},
|
||||
{webrtc.TypeRTCPFBNACK, ""},
|
||||
{webrtc.TypeRTCPFBNACK, "pli"}}
|
||||
for _, codec := range []webrtc.RTPCodecParameters{
|
||||
{
|
||||
RTPCodecCapability: webrtc.RTPCodecCapability{MimeType: webrtc.MimeTypeVP8, ClockRate: 90000, RTCPFeedback: videoRTCPFeedback},
|
||||
PayloadType: 96,
|
||||
},
|
||||
{
|
||||
RTPCodecCapability: webrtc.RTPCodecCapability{MimeType: webrtc.MimeTypeVP9, ClockRate: 90000, SDPFmtpLine: "profile-id=0", RTCPFeedback: videoRTCPFeedback},
|
||||
PayloadType: 98,
|
||||
},
|
||||
{
|
||||
RTPCodecCapability: webrtc.RTPCodecCapability{MimeType: webrtc.MimeTypeVP9, ClockRate: 90000, SDPFmtpLine: "profile-id=1", RTCPFeedback: videoRTCPFeedback},
|
||||
PayloadType: 100,
|
||||
},
|
||||
{
|
||||
RTPCodecCapability: webrtc.RTPCodecCapability{MimeType: webrtc.MimeTypeH264, ClockRate: 90000, SDPFmtpLine: "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f", RTCPFeedback: videoRTCPFeedback},
|
||||
PayloadType: 102,
|
||||
},
|
||||
{
|
||||
RTPCodecCapability: webrtc.RTPCodecCapability{MimeType: webrtc.MimeTypeH264, ClockRate: 90000, SDPFmtpLine: "level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42001f", RTCPFeedback: videoRTCPFeedback},
|
||||
PayloadType: 127,
|
||||
},
|
||||
{
|
||||
RTPCodecCapability: webrtc.RTPCodecCapability{MimeType: webrtc.MimeTypeH264, ClockRate: 90000, SDPFmtpLine: "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f", RTCPFeedback: videoRTCPFeedback},
|
||||
PayloadType: 125,
|
||||
},
|
||||
{
|
||||
RTPCodecCapability: webrtc.RTPCodecCapability{MimeType: webrtc.MimeTypeH264, ClockRate: 90000, SDPFmtpLine: "level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42e01f", RTCPFeedback: videoRTCPFeedback},
|
||||
PayloadType: 108,
|
||||
},
|
||||
{
|
||||
RTPCodecCapability: webrtc.RTPCodecCapability{MimeType: webrtc.MimeTypeH264, ClockRate: 90000, SDPFmtpLine: "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=640032", RTCPFeedback: videoRTCPFeedback},
|
||||
PayloadType: 123,
|
||||
},
|
||||
} {
|
||||
if err := me.RegisterCodec(codec, webrtc.RTPCodecTypeVideo); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
for _, extension := range []string{
|
||||
sdp.SDESMidURI,
|
||||
sdp.SDESRTPStreamIDURI,
|
||||
repairedRTP,
|
||||
//sdp.TransportCCURI,
|
||||
frameMarking,
|
||||
} {
|
||||
if err := me.RegisterHeaderExtension(webrtc.RTPHeaderExtensionCapability{URI: extension}, webrtc.RTPCodecTypeVideo); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
for _, extension := range []string{
|
||||
sdp.SDESMidURI,
|
||||
sdp.SDESRTPStreamIDURI,
|
||||
sdp.AudioLevelURI,
|
||||
} {
|
||||
if err := me.RegisterHeaderExtension(webrtc.RTPHeaderExtensionCapability{URI: extension}, webrtc.RTPCodecTypeAudio); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return me, nil
|
||||
}
|
||||
+153
-210
@@ -1,20 +1,18 @@
|
||||
package rtc
|
||||
|
||||
import (
|
||||
"io"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/gammazero/workerpool"
|
||||
"github.com/pion/ion-sfu/pkg/buffer"
|
||||
"github.com/pion/ion-sfu/pkg/sfu"
|
||||
"github.com/pion/ion-sfu/pkg/twcc"
|
||||
"github.com/pion/rtcp"
|
||||
"github.com/pion/transport/packetio"
|
||||
"github.com/pion/webrtc/v3"
|
||||
"github.com/pion/webrtc/v3/pkg/rtcerr"
|
||||
|
||||
"github.com/livekit/livekit-server/pkg/logger"
|
||||
"github.com/livekit/livekit-server/pkg/rtc/types"
|
||||
"github.com/livekit/livekit-server/pkg/sfu"
|
||||
"github.com/livekit/livekit-server/pkg/utils"
|
||||
"github.com/livekit/livekit-server/proto/livekit"
|
||||
)
|
||||
@@ -23,7 +21,6 @@ var (
|
||||
maxPLIFrequency = 1 * time.Second
|
||||
feedbackTypes = []webrtc.RTCPFeedback{
|
||||
{webrtc.TypeRTCPFBGoogREMB, ""},
|
||||
{webrtc.TypeRTCPFBTransportCC, ""},
|
||||
{webrtc.TypeRTCPFBNACK, ""},
|
||||
{webrtc.TypeRTCPFBNACK, "pli"}}
|
||||
)
|
||||
@@ -40,44 +37,38 @@ type MediaTrack struct {
|
||||
streamID string
|
||||
kind livekit.TrackType
|
||||
codec webrtc.RTPCodecParameters
|
||||
conf ReceiverConfig
|
||||
onClose func()
|
||||
|
||||
// channel to send RTCP packets to the source
|
||||
rtcpCh *utils.CalmChannel
|
||||
rtcpCh chan []rtcp.Packet
|
||||
lock sync.RWMutex
|
||||
once sync.Once
|
||||
// map of target participantId -> DownTrack
|
||||
downtracks map[string]types.DownTrack
|
||||
receiver types.Receiver
|
||||
nackWorker *workerpool.WorkerPool
|
||||
// map of target participantId -> *SubscribedTrack
|
||||
subscribedTracks map[string]*SubscribedTrack
|
||||
twcc *twcc.Responder
|
||||
receiver sfu.Receiver
|
||||
//lastNack int64
|
||||
lastPLI time.Time
|
||||
}
|
||||
|
||||
func NewMediaTrack(trackId string, pId string, rtcpCh *utils.CalmChannel, track *webrtc.TrackRemote, receiver types.Receiver) *MediaTrack {
|
||||
func NewMediaTrack(trackId string, pId string, rtcpCh chan []rtcp.Packet, conf ReceiverConfig, track *webrtc.TrackRemote) *MediaTrack {
|
||||
t := &MediaTrack{
|
||||
id: trackId,
|
||||
participantId: pId,
|
||||
ssrc: track.SSRC(),
|
||||
streamID: track.StreamID(),
|
||||
kind: ToProtoTrackKind(track.Kind()),
|
||||
codec: track.Codec(),
|
||||
rtcpCh: rtcpCh,
|
||||
lock: sync.RWMutex{},
|
||||
once: sync.Once{},
|
||||
downtracks: make(map[string]types.DownTrack),
|
||||
receiver: receiver,
|
||||
nackWorker: workerpool.New(1),
|
||||
id: trackId,
|
||||
participantId: pId,
|
||||
ssrc: track.SSRC(),
|
||||
streamID: track.StreamID(),
|
||||
kind: ToProtoTrackKind(track.Kind()),
|
||||
codec: track.Codec(),
|
||||
conf: conf,
|
||||
rtcpCh: rtcpCh,
|
||||
lock: sync.RWMutex{},
|
||||
subscribedTracks: make(map[string]*SubscribedTrack),
|
||||
}
|
||||
|
||||
return t
|
||||
}
|
||||
|
||||
func (t *MediaTrack) Start() {
|
||||
t.once.Do(func() {
|
||||
// start worker
|
||||
go t.forwardRTPWorker()
|
||||
})
|
||||
}
|
||||
|
||||
func (t *MediaTrack) ID() string {
|
||||
@@ -96,98 +87,171 @@ func (t *MediaTrack) IsMuted() bool {
|
||||
return t.muted.Get()
|
||||
}
|
||||
|
||||
func (t *MediaTrack) SetMuted(muted bool) {
|
||||
t.muted.TrySet(muted)
|
||||
|
||||
// mute all of the subscribedtracks
|
||||
t.lock.RLock()
|
||||
for id, st := range t.subscribedTracks {
|
||||
logger.Debugw("setting muted", "dstParticipant", id, "muted", muted, "track", t.ID())
|
||||
st.SetPublisherMuted(muted)
|
||||
}
|
||||
t.lock.RUnlock()
|
||||
}
|
||||
|
||||
func (t *MediaTrack) OnClose(f func()) {
|
||||
t.onClose = f
|
||||
}
|
||||
|
||||
// subscribes participant to current remoteTrack
|
||||
// creates and add necessary forwarders and starts them
|
||||
func (t *MediaTrack) AddSubscriber(participant types.Participant) error {
|
||||
t.lock.RLock()
|
||||
existingDt := t.downtracks[participant.ID()]
|
||||
t.lock.RUnlock()
|
||||
func (t *MediaTrack) AddSubscriber(sub types.Participant) error {
|
||||
t.lock.Lock()
|
||||
defer t.lock.Unlock()
|
||||
existingSt := t.subscribedTracks[sub.ID()]
|
||||
|
||||
// don't subscribe to the same track multiple times
|
||||
if existingDt != nil {
|
||||
if existingSt != nil {
|
||||
logger.Warnw("participant already subscribed to track",
|
||||
"participant", participant.Identity(),
|
||||
"track", existingDt.ID())
|
||||
"sub", sub.Identity(),
|
||||
"track", t.ID())
|
||||
return nil
|
||||
}
|
||||
|
||||
codec := t.codec
|
||||
// pack ID to identify all publishedTracks
|
||||
packedId := PackTrackId(t.participantId, t.id)
|
||||
codec := t.receiver.Codec()
|
||||
|
||||
// using DownTrack from ion-sfu
|
||||
outTrack, err := sfu.NewDownTrack(webrtc.RTPCodecCapability{
|
||||
downTrack, err := sfu.NewDownTrack(webrtc.RTPCodecCapability{
|
||||
MimeType: codec.MimeType,
|
||||
ClockRate: codec.ClockRate,
|
||||
Channels: codec.Channels,
|
||||
SDPFmtpLine: codec.SDPFmtpLine,
|
||||
RTCPFeedback: feedbackTypes,
|
||||
}, packedId, t.streamID)
|
||||
}, t.receiver, sub.ID())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
subTrack := NewSubscribedTrack(downTrack)
|
||||
|
||||
transceiver, err := participant.PeerConnection().AddTransceiverFromTrack(outTrack, webrtc.RTPTransceiverInit{
|
||||
transceiver, err := sub.PeerConnection().AddTransceiverFromTrack(downTrack, webrtc.RTPTransceiverInit{
|
||||
Direction: webrtc.RTPTransceiverDirectionSendrecv,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
outTrack.SetTransceiver(transceiver)
|
||||
downTrack.SetTransceiver(transceiver)
|
||||
// when outtrack is bound, start loop to send reports
|
||||
outTrack.OnBind(func() {
|
||||
if rr := bufferFactory.GetOrNew(packetio.RTCPBufferPacket, outTrack.SSRC()).(*buffer.RTCPReader); rr != nil {
|
||||
rr.OnPacket(func(pkt []byte) {
|
||||
t.handleRTCP(outTrack, participant.Identity(), pkt)
|
||||
})
|
||||
}
|
||||
t.sendDownTrackBindingReports(participant.ID(), participant.RTCPChan())
|
||||
downTrack.OnBind(func() {
|
||||
subTrack.SetPublisherMuted(t.IsMuted())
|
||||
t.sendDownTrackBindingReports(sub.ID(), sub.RTCPChan())
|
||||
})
|
||||
outTrack.OnCloseHandler(func() {
|
||||
downTrack.OnCloseHandler(func() {
|
||||
t.lock.Lock()
|
||||
delete(t.downtracks, participant.ID())
|
||||
delete(t.subscribedTracks, sub.ID())
|
||||
t.lock.Unlock()
|
||||
|
||||
// ignore if the subscribing participant is not connected
|
||||
if participant.PeerConnection().ConnectionState() != webrtc.PeerConnectionStateConnected {
|
||||
// ignore if the subscribing sub is not connected
|
||||
if sub.PeerConnection().ConnectionState() == webrtc.PeerConnectionStateClosed {
|
||||
return
|
||||
}
|
||||
|
||||
// if the source has been terminated, we'll need to terminate all of the downtracks
|
||||
// however, if the dest participant has disconnected, then we can skip
|
||||
// if the source has been terminated, we'll need to terminate all of the subscribedtracks
|
||||
// however, if the dest sub has disconnected, then we can skip
|
||||
sender := transceiver.Sender()
|
||||
if sender != nil {
|
||||
logger.Debugw("removing peerconnection track",
|
||||
"track", t.id,
|
||||
"participantId", t.participantId,
|
||||
"destParticipant", participant.Identity())
|
||||
if err := participant.PeerConnection().RemoveTrack(sender); err != nil {
|
||||
if err == webrtc.ErrConnectionClosed {
|
||||
// participant closing, can skip removing downtracks
|
||||
return
|
||||
}
|
||||
if _, ok := err.(*rtcerr.InvalidStateError); !ok {
|
||||
logger.Warnw("could not remove remoteTrack from forwarder",
|
||||
"participant", participant.Identity(),
|
||||
"err", err)
|
||||
}
|
||||
if sender == nil {
|
||||
return
|
||||
}
|
||||
logger.Debugw("removing peerconnection track",
|
||||
"track", t.id,
|
||||
"participantId", t.participantId,
|
||||
"destParticipant", sub.Identity())
|
||||
if err := sub.PeerConnection().RemoveTrack(sender); err != nil {
|
||||
if err == webrtc.ErrConnectionClosed {
|
||||
// sub closing, can skip removing subscribedtracks
|
||||
return
|
||||
}
|
||||
if _, ok := err.(*rtcerr.InvalidStateError); !ok {
|
||||
logger.Warnw("could not remove remoteTrack from forwarder",
|
||||
"sub", sub.Identity(),
|
||||
"err", err)
|
||||
}
|
||||
}
|
||||
|
||||
participant.RemoveDownTrack(t.streamID, outTrack)
|
||||
sub.RemoveSubscribedTrack(t.participantId, subTrack)
|
||||
})
|
||||
|
||||
t.subscribedTracks[sub.ID()] = subTrack
|
||||
|
||||
sub.AddSubscribedTrack(t.participantId, subTrack)
|
||||
t.receiver.AddDownTrack(downTrack, true)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// adds a new RTP receiver to the track, returns true if this is a new track
|
||||
func (t *MediaTrack) AddReceiver(receiver *webrtc.RTPReceiver, track *webrtc.TrackRemote) {
|
||||
//rid := track.RID()
|
||||
|
||||
buff, rtcpReader := bufferFactory.GetBufferPair(uint32(track.SSRC()))
|
||||
buff.OnFeedback(func(fb []rtcp.Packet) {
|
||||
t.rtcpCh <- fb
|
||||
})
|
||||
|
||||
if t.Kind() == livekit.TrackType_AUDIO {
|
||||
// TODO: audio level stuff
|
||||
} else if t.Kind() == livekit.TrackType_VIDEO {
|
||||
//if t.twcc == nil {
|
||||
// t.twcc = twcc.NewTransportWideCCResponder(uint32(track.SSRC()))
|
||||
// t.twcc.OnFeedback(func(p rtcp.RawPacket) {
|
||||
// t.rtcpCh <- []rtcp.Packet{&p}
|
||||
// })
|
||||
//}
|
||||
buff.OnTransportWideCC(func(sn uint16, timeNS int64, marker bool) {
|
||||
//t.twcc.Push(sn, timeNS, marker)
|
||||
})
|
||||
}
|
||||
|
||||
rtcpReader.OnPacket(func(bytes []byte) {
|
||||
pkts, err := rtcp.Unmarshal(bytes)
|
||||
if err != nil {
|
||||
logger.Errorw("could not unmarshal RTCP", "error", err)
|
||||
return
|
||||
}
|
||||
|
||||
for _, pkt := range pkts {
|
||||
switch pkt := pkt.(type) {
|
||||
case *rtcp.SourceDescription:
|
||||
// do nothing for now
|
||||
case *rtcp.SenderReport:
|
||||
buff.SetSenderReportData(pkt.RTPTime, pkt.NTPTime)
|
||||
}
|
||||
}
|
||||
})
|
||||
participant.AddDownTrack(t.streamID, outTrack)
|
||||
|
||||
t.lock.Lock()
|
||||
defer t.lock.Unlock()
|
||||
t.downtracks[participant.ID()] = outTrack
|
||||
if t.receiver == nil {
|
||||
// pack ID to identify all publishedTracks
|
||||
packedId := PackTrackId(t.participantId, track.ID())
|
||||
t.receiver = NewWrappedReceiver(sfu.NewWebRTCReceiver(receiver, track, t.participantId), packedId)
|
||||
t.receiver.SetRTCPCh(t.rtcpCh)
|
||||
t.receiver.OnCloseHandler(func() {
|
||||
t.lock.Lock()
|
||||
defer t.lock.Unlock()
|
||||
// source track closed
|
||||
if t.Kind() == livekit.TrackType_AUDIO {
|
||||
// TODO: remove audio level observer
|
||||
}
|
||||
t.receiver = nil
|
||||
})
|
||||
}
|
||||
t.receiver.AddUpTrack(track, buff)
|
||||
|
||||
return nil
|
||||
buff.Bind(receiver.GetParameters(), buffer.Options{
|
||||
BufferTime: t.conf.maxBufferTime,
|
||||
MaxBitRate: t.conf.maxBitrate,
|
||||
})
|
||||
}
|
||||
|
||||
// removes peer from subscription
|
||||
@@ -196,8 +260,8 @@ func (t *MediaTrack) RemoveSubscriber(participantId string) {
|
||||
t.lock.RLock()
|
||||
defer t.lock.RUnlock()
|
||||
|
||||
if dt := t.downtracks[participantId]; dt != nil {
|
||||
go dt.Close()
|
||||
if subTrack := t.subscribedTracks[participantId]; subTrack != nil {
|
||||
go subTrack.DownTrack().Close()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -205,22 +269,27 @@ func (t *MediaTrack) RemoveAllSubscribers() {
|
||||
logger.Debugw("removing all subscribers", "track", t.id)
|
||||
t.lock.RLock()
|
||||
defer t.lock.RUnlock()
|
||||
for _, dt := range t.downtracks {
|
||||
go dt.Close()
|
||||
for _, subTrack := range t.subscribedTracks {
|
||||
go subTrack.DownTrack().Close()
|
||||
}
|
||||
t.downtracks = make(map[string]types.DownTrack)
|
||||
t.subscribedTracks = make(map[string]*SubscribedTrack)
|
||||
}
|
||||
|
||||
func (t *MediaTrack) sendDownTrackBindingReports(participantId string, rtcpCh *utils.CalmChannel) {
|
||||
func (t *MediaTrack) sendDownTrackBindingReports(participantId string, rtcpCh chan []rtcp.Packet) {
|
||||
var sd []rtcp.SourceDescriptionChunk
|
||||
|
||||
t.lock.RLock()
|
||||
dt := t.downtracks[participantId]
|
||||
subTrack := t.subscribedTracks[participantId]
|
||||
t.lock.RUnlock()
|
||||
if !dt.IsBound() {
|
||||
|
||||
if subTrack == nil {
|
||||
return
|
||||
}
|
||||
|
||||
chunks := subTrack.DownTrack().CreateSourceDescriptionChunks()
|
||||
if chunks == nil {
|
||||
return
|
||||
}
|
||||
chunks := dt.CreateSourceDescriptionChunks()
|
||||
if chunks != nil {
|
||||
sd = append(sd, chunks...)
|
||||
}
|
||||
@@ -234,7 +303,7 @@ func (t *MediaTrack) sendDownTrackBindingReports(participantId string, rtcpCh *u
|
||||
batch := pkts
|
||||
i := 0
|
||||
for {
|
||||
rtcpCh.Write(batch)
|
||||
rtcpCh <- batch
|
||||
if i > 5 {
|
||||
return
|
||||
}
|
||||
@@ -243,129 +312,3 @@ func (t *MediaTrack) sendDownTrackBindingReports(participantId string, rtcpCh *u
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// b reads from the receiver and writes to each sender
|
||||
func (t *MediaTrack) forwardRTPWorker() {
|
||||
defer Recover()
|
||||
defer func() {
|
||||
t.RemoveAllSubscribers()
|
||||
// TODO: send unpublished events?
|
||||
t.nackWorker.Stop()
|
||||
|
||||
if t.onClose != nil {
|
||||
t.onClose()
|
||||
}
|
||||
}()
|
||||
|
||||
for pkt := range t.receiver.RTPChan() {
|
||||
//logger.Debugw("read packet from remoteTrack",
|
||||
// "participant", t.participantId,
|
||||
// "track", t.ID())
|
||||
// when track is muted, it's "disabled" on the client side, and will still be sending black frames
|
||||
// when our metadata is updated as such, we shortcircuit forwarding of black frames
|
||||
if t.muted.Get() {
|
||||
continue
|
||||
}
|
||||
|
||||
t.lock.RLock()
|
||||
for dstId, dt := range t.downtracks {
|
||||
//logger.Debugw("read packet from remoteTrack",
|
||||
// "srcParticipant", t.participantId,
|
||||
// "destParticipant", dstId,
|
||||
// "track", t.ID())
|
||||
err := dt.WriteRTP(pkt)
|
||||
if IsEOF(err) {
|
||||
// this participant unsubscribed, remove it
|
||||
t.RemoveSubscriber(dstId)
|
||||
continue
|
||||
}
|
||||
|
||||
if err == sfu.ErrRequiresKeyFrame {
|
||||
delta := time.Now().Sub(t.lastPLI)
|
||||
if delta < maxPLIFrequency {
|
||||
continue
|
||||
}
|
||||
logger.Infow("keyframe required, sending PLI",
|
||||
"srcParticipant", t.participantId)
|
||||
rtcpPkts := []rtcp.Packet{
|
||||
&rtcp.PictureLossIndication{SenderSSRC: uint32(t.ssrc), MediaSSRC: pkt.SSRC},
|
||||
}
|
||||
t.rtcpCh.Write(rtcpPkts)
|
||||
t.lastPLI = time.Now()
|
||||
} else if err != nil {
|
||||
logger.Warnw("could not forward packet to participant",
|
||||
"src", t.participantId,
|
||||
"dest", dstId,
|
||||
"remoteTrack", t.id,
|
||||
"err", err)
|
||||
}
|
||||
}
|
||||
t.lock.RUnlock()
|
||||
}
|
||||
}
|
||||
|
||||
func (t *MediaTrack) handleRTCP(dt *sfu.DownTrack, identity string, rtcpBuf []byte) {
|
||||
defer Recover()
|
||||
pkts, err := rtcp.Unmarshal(rtcpBuf)
|
||||
if err != nil {
|
||||
logger.Warnw("could not decode RTCP packet", "err", err)
|
||||
}
|
||||
|
||||
var fwdPkts []rtcp.Packet
|
||||
// sufficient to send this only once
|
||||
pliOnce := true
|
||||
firOnce := true
|
||||
for _, pkt := range pkts {
|
||||
switch p := pkt.(type) {
|
||||
case *rtcp.PictureLossIndication:
|
||||
if pliOnce {
|
||||
p.MediaSSRC = dt.LastSSRC()
|
||||
p.SenderSSRC = dt.SSRC()
|
||||
fwdPkts = append(fwdPkts, p)
|
||||
pliOnce = false
|
||||
}
|
||||
case *rtcp.FullIntraRequest:
|
||||
if firOnce {
|
||||
p.MediaSSRC = dt.LastSSRC()
|
||||
p.SenderSSRC = dt.SSRC()
|
||||
fwdPkts = append(fwdPkts, p)
|
||||
firOnce = false
|
||||
}
|
||||
case *rtcp.ReceiverReport:
|
||||
if len(p.Reports) > 0 && p.Reports[0].FractionLost > 25 {
|
||||
//log.Tracef("Slow link for sender %s, fraction packet lost %.2f", f.track.peerID, float64(p.Reports[0].FractionLost)/256)
|
||||
}
|
||||
case *rtcp.TransportLayerNack:
|
||||
logger.Debugw("forwarder got nack",
|
||||
"participant", identity,
|
||||
"packet", p)
|
||||
var nackedPackets []uint16
|
||||
for _, pair := range p.Nacks {
|
||||
nackedPackets = append(nackedPackets, dt.GetNACKSeqNo(pair.PacketList())...)
|
||||
}
|
||||
|
||||
if t.nackWorker.Stopped() {
|
||||
return
|
||||
}
|
||||
t.nackWorker.Submit(func() {
|
||||
pktBuf := packetFactory.Get().([]byte)
|
||||
for _, sn := range nackedPackets {
|
||||
pkt, err := t.receiver.GetBufferedPacket(pktBuf, sn, dt.SnOffset())
|
||||
if err == io.EOF {
|
||||
break
|
||||
} else if err != nil {
|
||||
logger.Warnw("error getting buffered packet", "error", err)
|
||||
continue
|
||||
}
|
||||
// what about handling write errors such as no packet found
|
||||
dt.WriteRTP(pkt)
|
||||
}
|
||||
packetFactory.Put(pktBuf)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if len(fwdPkts) > 0 {
|
||||
t.rtcpCh.Write(fwdPkts)
|
||||
}
|
||||
}
|
||||
@@ -1,112 +0,0 @@
|
||||
package rtc
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gammazero/workerpool"
|
||||
"github.com/pion/rtcp"
|
||||
"github.com/pion/rtp"
|
||||
"github.com/pion/webrtc/v3"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/livekit/livekit-server/pkg/rtc/types"
|
||||
"github.com/livekit/livekit-server/pkg/rtc/types/typesfakes"
|
||||
"github.com/livekit/livekit-server/pkg/sfu"
|
||||
"github.com/livekit/livekit-server/pkg/utils"
|
||||
"github.com/livekit/livekit-server/proto/livekit"
|
||||
)
|
||||
|
||||
const (
|
||||
testWaitDuration = 10 * time.Millisecond
|
||||
)
|
||||
|
||||
func TestForwardRTP(t *testing.T) {
|
||||
t.Run("ensure that forwarders are getting packets", func(t *testing.T) {
|
||||
mt := newMediaTrackWithReceiver()
|
||||
receiver := mt.receiver.(*typesfakes.FakeReceiver)
|
||||
packet := rtp.Packet{}
|
||||
receiver.RTPChanReturns(packetGenerator(packet))
|
||||
|
||||
dt := &typesfakes.FakeDownTrack{}
|
||||
mt.downtracks["test"] = dt
|
||||
|
||||
mt.Start()
|
||||
time.Sleep(testWaitDuration)
|
||||
|
||||
assert.Equal(t, 1, dt.WriteRTPCallCount(), "WriteRTP wasn't called on Forwarder")
|
||||
assert.EqualValues(t, packet, dt.WriteRTPArgsForCall(0))
|
||||
})
|
||||
|
||||
t.Run("muted tracks do not forward data", func(t *testing.T) {
|
||||
mt := newMediaTrackWithReceiver()
|
||||
mt.muted.TrySet(true)
|
||||
|
||||
dt := &typesfakes.FakeDownTrack{}
|
||||
mt.downtracks["test"] = dt
|
||||
|
||||
mt.Start()
|
||||
time.Sleep(testWaitDuration)
|
||||
assert.Zero(t, dt.WriteRTPCallCount())
|
||||
})
|
||||
}
|
||||
|
||||
func TestMissingKeyFrames(t *testing.T) {
|
||||
t.Run("PLI packet is sent when forwarder misses keyframe", func(t *testing.T) {
|
||||
mt := newMediaTrackWithReceiver()
|
||||
|
||||
dt := &typesfakes.FakeDownTrack{}
|
||||
mt.downtracks["test"] = dt
|
||||
dt.WriteRTPReturns(sfu.ErrRequiresKeyFrame)
|
||||
|
||||
mt.Start()
|
||||
time.Sleep(testWaitDuration)
|
||||
|
||||
select {
|
||||
case o := <-mt.rtcpCh.ReadChan():
|
||||
if o == nil {
|
||||
return
|
||||
}
|
||||
pkts, ok := o.([]rtcp.Packet)
|
||||
assert.True(t, ok)
|
||||
assert.Len(t, pkts, 1, "a single RTCP packet should be returned")
|
||||
assert.IsType(t, &rtcp.PictureLossIndication{}, pkts[0])
|
||||
default:
|
||||
t.Fatalf("did not receive RTCP packets")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// returns a receiver that reads a packet then returns EOF
|
||||
func newMediaTrackWithReceiver() *MediaTrack {
|
||||
packet := rtp.Packet{}
|
||||
receiver := &typesfakes.FakeReceiver{
|
||||
RTPChanStub: func() <-chan rtp.Packet {
|
||||
return packetGenerator(packet)
|
||||
},
|
||||
}
|
||||
return &MediaTrack{
|
||||
id: utils.NewGuid(utils.TrackPrefix),
|
||||
participantId: "PAtest",
|
||||
kind: livekit.TrackType_VIDEO,
|
||||
codec: webrtc.RTPCodecParameters{},
|
||||
rtcpCh: utils.NewCalmChannel(5),
|
||||
lock: sync.RWMutex{},
|
||||
once: sync.Once{},
|
||||
downtracks: map[string]types.DownTrack{},
|
||||
receiver: receiver,
|
||||
nackWorker: workerpool.New(1),
|
||||
}
|
||||
}
|
||||
|
||||
func packetGenerator(packets ...rtp.Packet) <-chan rtp.Packet {
|
||||
pc := make(chan rtp.Packet)
|
||||
go func() {
|
||||
defer close(pc)
|
||||
for _, p := range packets {
|
||||
pc <- p
|
||||
}
|
||||
}()
|
||||
return pc
|
||||
}
|
||||
+101
-59
@@ -15,7 +15,6 @@ import (
|
||||
"github.com/livekit/livekit-server/pkg/logger"
|
||||
"github.com/livekit/livekit-server/pkg/routing"
|
||||
"github.com/livekit/livekit-server/pkg/rtc/types"
|
||||
"github.com/livekit/livekit-server/pkg/sfu"
|
||||
"github.com/livekit/livekit-server/pkg/utils"
|
||||
"github.com/livekit/livekit-server/proto/livekit"
|
||||
)
|
||||
@@ -33,16 +32,17 @@ const (
|
||||
)
|
||||
|
||||
type ParticipantImpl struct {
|
||||
id string
|
||||
peerConn types.PeerConnection
|
||||
responseSink routing.MessageSink
|
||||
receiverConfig ReceiverConfig
|
||||
isClosed utils.AtomicFlag
|
||||
mediaEngine *webrtc.MediaEngine
|
||||
identity string
|
||||
state atomic.Value // livekit.ParticipantInfo_State
|
||||
rtcpCh *utils.CalmChannel
|
||||
subscribedTracks map[string][]*sfu.DownTrack
|
||||
id string
|
||||
peerConn types.PeerConnection
|
||||
responseSink routing.MessageSink
|
||||
receiverConfig ReceiverConfig
|
||||
isClosed utils.AtomicFlag
|
||||
mediaEngine *webrtc.MediaEngine
|
||||
identity string
|
||||
state atomic.Value // livekit.ParticipantInfo_State
|
||||
rtcpCh chan []rtcp.Packet
|
||||
// tracks the current participant is subscribed to, map of otherParticipantId => []DownTrack
|
||||
subscribedTracks map[string][]types.SubscribedTrack
|
||||
// publishedTracks that participant is publishing
|
||||
publishedTracks map[string]types.PublishedTrack
|
||||
// client intended to publish, yet to be reconciled
|
||||
@@ -64,8 +64,10 @@ type ParticipantImpl struct {
|
||||
}
|
||||
|
||||
func NewPeerConnection(conf *WebRTCConfig) (*webrtc.PeerConnection, error) {
|
||||
me := &webrtc.MediaEngine{}
|
||||
me.RegisterDefaultCodecs()
|
||||
me, err := createMediaEngine()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
se := conf.SettingEngine
|
||||
se.BufferFactory = bufferFactory.GetOrNew
|
||||
|
||||
@@ -76,8 +78,6 @@ func NewPeerConnection(conf *WebRTCConfig) (*webrtc.PeerConnection, error) {
|
||||
|
||||
func NewParticipant(identity string, pc types.PeerConnection, rs routing.MessageSink, receiverConfig ReceiverConfig) (*ParticipantImpl, error) {
|
||||
// TODO: check to ensure params are valid, id and identity can't be empty
|
||||
me := &webrtc.MediaEngine{}
|
||||
me.RegisterDefaultCodecs()
|
||||
|
||||
participant := &ParticipantImpl{
|
||||
id: utils.NewGuid(utils.ParticipantPrefix),
|
||||
@@ -85,13 +85,12 @@ func NewParticipant(identity string, pc types.PeerConnection, rs routing.Message
|
||||
peerConn: pc,
|
||||
responseSink: rs,
|
||||
receiverConfig: receiverConfig,
|
||||
rtcpCh: utils.NewCalmChannel(50),
|
||||
subscribedTracks: make(map[string][]*sfu.DownTrack),
|
||||
rtcpCh: make(chan []rtcp.Packet, 50),
|
||||
subscribedTracks: make(map[string][]types.SubscribedTrack),
|
||||
lock: sync.RWMutex{},
|
||||
negotiationCond: sync.NewCond(&sync.Mutex{}),
|
||||
publishedTracks: make(map[string]types.PublishedTrack, 0),
|
||||
pendingTracks: make(map[string]*livekit.TrackInfo),
|
||||
mediaEngine: me,
|
||||
debouncedNegotiate: debounce.New(negotiationFrequency),
|
||||
}
|
||||
participant.state.Store(livekit.ParticipantInfo_JOINING)
|
||||
@@ -113,7 +112,8 @@ func NewParticipant(identity string, pc types.PeerConnection, rs routing.Message
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
logger.Errorw("could not send trickle", "err", err)
|
||||
logger.Errorw("could not send trickle", "err", err,
|
||||
"participant", identity)
|
||||
}
|
||||
|
||||
if participant.onICECandidate != nil {
|
||||
@@ -162,7 +162,7 @@ func (p *ParticipantImpl) IsReady() bool {
|
||||
return state == livekit.ParticipantInfo_JOINED || state == livekit.ParticipantInfo_ACTIVE
|
||||
}
|
||||
|
||||
func (p *ParticipantImpl) RTCPChan() *utils.CalmChannel {
|
||||
func (p *ParticipantImpl) RTCPChan() chan []rtcp.Packet {
|
||||
return p.rtcpCh
|
||||
}
|
||||
|
||||
@@ -246,9 +246,9 @@ func (p *ParticipantImpl) Answer(sdp webrtc.SessionDescription) (answer webrtc.S
|
||||
}
|
||||
p.negotiationCond.L.Unlock()
|
||||
|
||||
logger.Debugw("sending to client answer",
|
||||
logger.Debugw("sending answer to client",
|
||||
"participant", p.Identity(),
|
||||
//"sdp", sdp.SDP,
|
||||
//"sdp", sdp.SDP,
|
||||
)
|
||||
err = p.responseSink.WriteMessage(&livekit.SignalResponse{
|
||||
Message: &livekit.SignalResponse_Answer{
|
||||
@@ -279,7 +279,7 @@ func (p *ParticipantImpl) AddTrack(clientId, name string, trackType livekit.Trac
|
||||
}
|
||||
p.pendingTracks[clientId] = ti
|
||||
|
||||
p.responseSink.WriteMessage(&livekit.SignalResponse{
|
||||
err := p.responseSink.WriteMessage(&livekit.SignalResponse{
|
||||
Message: &livekit.SignalResponse_TrackPublished{
|
||||
TrackPublished: &livekit.TrackPublishedResponse{
|
||||
Cid: clientId,
|
||||
@@ -287,6 +287,10 @@ func (p *ParticipantImpl) AddTrack(clientId, name string, trackType livekit.Trac
|
||||
},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
logger.Errorw("could not write message", "error", err,
|
||||
"participant", p.identity)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *ParticipantImpl) HandleAnswer(sdp webrtc.SessionDescription) error {
|
||||
@@ -295,7 +299,7 @@ func (p *ParticipantImpl) HandleAnswer(sdp webrtc.SessionDescription) error {
|
||||
}
|
||||
logger.Debugw("setting participant answer",
|
||||
"participant", p.Identity(),
|
||||
//"sdp", sdp.SDP,
|
||||
//"sdp", sdp.SDP,
|
||||
)
|
||||
if err := p.peerConn.SetRemoteDescription(sdp); err != nil {
|
||||
return errors.Wrap(err, "could not set remote description")
|
||||
@@ -323,11 +327,15 @@ func (p *ParticipantImpl) HandleClientNegotiation() {
|
||||
|
||||
logger.Debugw("allowing participant to negotiate",
|
||||
"participant", p.Identity())
|
||||
p.responseSink.WriteMessage(&livekit.SignalResponse{
|
||||
err := p.responseSink.WriteMessage(&livekit.SignalResponse{
|
||||
Message: &livekit.SignalResponse_Negotiate{
|
||||
Negotiate: &livekit.NegotiationResponse{},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
logger.Errorw("could not write message", "error", err,
|
||||
"participant", p.identity)
|
||||
}
|
||||
}
|
||||
|
||||
// AddICECandidate adds candidates for remote peer
|
||||
@@ -361,7 +369,7 @@ func (p *ParticipantImpl) Close() error {
|
||||
p.onClose(p)
|
||||
}
|
||||
p.peerConn.Close()
|
||||
p.rtcpCh.Close()
|
||||
close(p.rtcpCh)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -369,11 +377,16 @@ func (p *ParticipantImpl) Close() error {
|
||||
func (p *ParticipantImpl) AddSubscriber(op types.Participant) error {
|
||||
p.lock.RLock()
|
||||
tracks := funk.Values(p.publishedTracks).([]types.PublishedTrack)
|
||||
defer p.lock.RUnlock()
|
||||
p.lock.RUnlock()
|
||||
|
||||
if len(tracks) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
logger.Debugw("subscribing new participant to tracks",
|
||||
"srcParticipant", p.Identity(),
|
||||
"dstParticipant", op.Identity())
|
||||
"newParticipant", op.Identity(),
|
||||
"numTracks", len(tracks))
|
||||
|
||||
for _, track := range tracks {
|
||||
if err := track.AddSubscriber(op); err != nil {
|
||||
@@ -442,23 +455,28 @@ func (p *ParticipantImpl) PeerConnection() types.PeerConnection {
|
||||
return p.peerConn
|
||||
}
|
||||
|
||||
func (p *ParticipantImpl) AddDownTrack(streamId string, dt *sfu.DownTrack) {
|
||||
// add a track to the participant's subscribed list
|
||||
func (p *ParticipantImpl) AddSubscribedTrack(pubId string, subTrack types.SubscribedTrack) {
|
||||
logger.Debugw("added subscribedTrack", "srcParticipant", pubId,
|
||||
"participant", p.Identity())
|
||||
p.lock.Lock()
|
||||
p.subscribedTracks[streamId] = append(p.subscribedTracks[streamId], dt)
|
||||
p.subscribedTracks[pubId] = append(p.subscribedTracks[pubId], subTrack)
|
||||
p.lock.Unlock()
|
||||
}
|
||||
|
||||
func (p *ParticipantImpl) RemoveDownTrack(streamId string, dt *sfu.DownTrack) {
|
||||
// remove a track to the participant's subscribed list
|
||||
func (p *ParticipantImpl) RemoveSubscribedTrack(pubId string, subTrack types.SubscribedTrack) {
|
||||
logger.Debugw("removed subscribedTrack", "srcParticipant", pubId,
|
||||
"participant", p.Identity())
|
||||
p.lock.Lock()
|
||||
defer p.lock.Unlock()
|
||||
tracks := p.subscribedTracks[streamId]
|
||||
newTracks := make([]*sfu.DownTrack, 0, len(tracks))
|
||||
for _, track := range tracks {
|
||||
if track != dt {
|
||||
newTracks = append(newTracks, track)
|
||||
tracks := make([]types.SubscribedTrack, 0, len(p.subscribedTracks[pubId]))
|
||||
for _, tr := range p.subscribedTracks[pubId] {
|
||||
if tr != subTrack {
|
||||
tracks = append(tracks, tr)
|
||||
}
|
||||
}
|
||||
p.subscribedTracks[streamId] = newTracks
|
||||
p.subscribedTracks[pubId] = tracks
|
||||
}
|
||||
|
||||
func (p *ParticipantImpl) scheduleNegotiate() {
|
||||
@@ -472,13 +490,13 @@ func (p *ParticipantImpl) negotiate() {
|
||||
return
|
||||
}
|
||||
|
||||
logger.Debugw("starting server negotiation", "participant", p.Identity())
|
||||
p.negotiationCond.L.Lock()
|
||||
for p.negotiationState != negotiationStateNone {
|
||||
p.negotiationCond.Wait()
|
||||
p.negotiationState = negotiationStateServer
|
||||
}
|
||||
p.negotiationState = negotiationStateServer
|
||||
p.negotiationCond.L.Unlock()
|
||||
logger.Debugw("starting server negotiation", "participant", p.Identity())
|
||||
|
||||
offer, err := p.peerConn.CreateOffer(nil)
|
||||
if err != nil {
|
||||
@@ -508,8 +526,9 @@ func (p *ParticipantImpl) negotiate() {
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
logger.Errorw("could not send offer to peer",
|
||||
"err", err)
|
||||
logger.Errorw("could not send offer to participant",
|
||||
"err", err,
|
||||
"participant", p.identity)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -538,11 +557,39 @@ func (p *ParticipantImpl) onMediaTrack(track *webrtc.TrackRemote, rtpReceiver *w
|
||||
}
|
||||
|
||||
// create ReceiverImpl
|
||||
receiver := NewReceiver(p.rtcpCh, rtpReceiver, track, p.receiverConfig)
|
||||
mt := NewMediaTrack(ti.Sid, p.id, p.rtcpCh, track, receiver)
|
||||
mt.name = ti.Name
|
||||
//receiver := NewReceiver(p.rtcpCh, rtpReceiver, track, p.receiverConfig)
|
||||
|
||||
p.handleTrackPublished(mt)
|
||||
// use existing mediatrack to handle simulcast
|
||||
p.lock.RLock()
|
||||
ptrack := p.publishedTracks[ti.Sid]
|
||||
p.lock.RUnlock()
|
||||
|
||||
var mt *MediaTrack
|
||||
var newTrack bool
|
||||
if trk, ok := ptrack.(*MediaTrack); ok {
|
||||
logger.Debugw("using existing mediatrack, simulcast", "rid", track.RID())
|
||||
mt = trk
|
||||
} else {
|
||||
mt = NewMediaTrack(ti.Sid, p.id, p.rtcpCh, p.receiverConfig, track)
|
||||
mt.name = ti.Name
|
||||
newTrack = true
|
||||
}
|
||||
|
||||
mt.AddReceiver(rtpReceiver, track)
|
||||
|
||||
if newTrack {
|
||||
p.handleTrackPublished(mt)
|
||||
}
|
||||
|
||||
// TODO: video tracks the current participant is subscribed to tends to freeze when the participant adds tracks
|
||||
// to get around this, we'll trigger a resync on all tracks it's subscribed to
|
||||
p.lock.RLock()
|
||||
defer p.lock.RUnlock()
|
||||
for _, tracks := range p.subscribedTracks {
|
||||
for _, subTrack := range tracks {
|
||||
subTrack.Resync()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *ParticipantImpl) onDataChannel(dc *webrtc.DataChannel) {
|
||||
@@ -623,16 +670,15 @@ func (p *ParticipantImpl) downTracksRTCPWorker() {
|
||||
var pkts []rtcp.Packet
|
||||
var sd []rtcp.SourceDescriptionChunk
|
||||
p.lock.RLock()
|
||||
for _, dts := range p.subscribedTracks {
|
||||
for _, dt := range dts {
|
||||
if !dt.IsBound() {
|
||||
for _, tracks := range p.subscribedTracks {
|
||||
for _, subTrack := range tracks {
|
||||
sr := subTrack.DownTrack().CreateSenderReport()
|
||||
chunks := subTrack.DownTrack().CreateSourceDescriptionChunks()
|
||||
if sr == nil || chunks == nil {
|
||||
continue
|
||||
}
|
||||
pkts = append(pkts, dt.CreateSenderReport())
|
||||
chunks := dt.CreateSourceDescriptionChunks()
|
||||
if chunks != nil {
|
||||
sd = append(sd, chunks...)
|
||||
}
|
||||
pkts = append(pkts, sr)
|
||||
sd = append(sd, chunks...)
|
||||
}
|
||||
}
|
||||
p.lock.RUnlock()
|
||||
@@ -664,14 +710,10 @@ func (p *ParticipantImpl) downTracksRTCPWorker() {
|
||||
func (p *ParticipantImpl) rtcpSendWorker() {
|
||||
defer Recover()
|
||||
// read from rtcpChan
|
||||
for o := range p.rtcpCh.ReadChan() {
|
||||
if o == nil {
|
||||
for pkts := range p.rtcpCh {
|
||||
if pkts == nil {
|
||||
return
|
||||
}
|
||||
pkts, ok := o.([]rtcp.Packet)
|
||||
if !ok {
|
||||
logger.Errorw("unexpected obj from rtcpCh", "object", o)
|
||||
}
|
||||
//for _, pkt := range pkts {
|
||||
// logger.Debugw("writing RTCP", "packet", pkt)
|
||||
//}
|
||||
|
||||
@@ -1,99 +0,0 @@
|
||||
package rtc
|
||||
|
||||
import (
|
||||
"github.com/pion/ion-sfu/pkg/buffer"
|
||||
"github.com/pion/rtcp"
|
||||
"github.com/pion/rtp"
|
||||
"github.com/pion/webrtc/v3"
|
||||
|
||||
"github.com/livekit/livekit-server/pkg/logger"
|
||||
"github.com/livekit/livekit-server/pkg/utils"
|
||||
)
|
||||
|
||||
const (
|
||||
// TODO: could probably increase this depending on Configuration/memory
|
||||
maxChanSize = 1024
|
||||
)
|
||||
|
||||
// A receiver is responsible for pulling from a single logical track
|
||||
type ReceiverImpl struct {
|
||||
rtpReceiver *webrtc.RTPReceiver
|
||||
track *webrtc.TrackRemote
|
||||
buffer *buffer.Buffer
|
||||
rtcpReader *buffer.RTCPReader
|
||||
rtcpChan *utils.CalmChannel
|
||||
}
|
||||
|
||||
func NewReceiver(rtcpCh *utils.CalmChannel, rtpReceiver *webrtc.RTPReceiver, track *webrtc.TrackRemote, config ReceiverConfig) *ReceiverImpl {
|
||||
r := &ReceiverImpl{
|
||||
rtpReceiver: rtpReceiver,
|
||||
rtcpChan: rtcpCh,
|
||||
track: track,
|
||||
}
|
||||
|
||||
r.buffer, r.rtcpReader = bufferFactory.GetBufferPair(uint32(track.SSRC()))
|
||||
|
||||
// when we have feedback for the sender, send through the rtcp channel
|
||||
r.buffer.OnFeedback(func(fb []rtcp.Packet) {
|
||||
RecoverSilent()
|
||||
// rtcpChan could be closed
|
||||
if r.rtcpChan != nil {
|
||||
r.rtcpChan.Write(fb)
|
||||
}
|
||||
})
|
||||
|
||||
r.buffer.OnTransportWideCC(func(sn uint16, timeNS int64, marker bool) {
|
||||
// TODO: figure out how to handle this
|
||||
})
|
||||
|
||||
r.buffer.Bind(rtpReceiver.GetParameters(), buffer.Options{
|
||||
BufferTime: config.maxBufferTime,
|
||||
MaxBitRate: config.maxBitrate,
|
||||
})
|
||||
|
||||
// received sender updates
|
||||
r.rtcpReader.OnPacket(func(bytes []byte) {
|
||||
pkts, err := rtcp.Unmarshal(bytes)
|
||||
if err != nil {
|
||||
logger.Warnw("could not unmarshal RTCP packet")
|
||||
return
|
||||
}
|
||||
for _, pkt := range pkts {
|
||||
switch p := pkt.(type) {
|
||||
case *rtcp.SenderReport:
|
||||
r.buffer.SetSenderReportData(p.RTPTime, p.NTPTime)
|
||||
case *rtcp.SourceDescription:
|
||||
// TODO: see what we'd want to do with this
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
func (r *ReceiverImpl) Close() {
|
||||
r.rtcpChan = nil
|
||||
r.buffer.OnFeedback(nil)
|
||||
r.buffer.Close()
|
||||
r.rtpReceiver.Stop()
|
||||
r.rtcpReader.Close()
|
||||
}
|
||||
|
||||
// PacketBuffer interface, retrieves a packet from buffer and deserializes
|
||||
// it's possible that the packet can't be found, or the connection has been closed (io.EOF)
|
||||
func (r *ReceiverImpl) GetBufferedPacket(pktBuf []byte, sn uint16, snOffset uint16) (p rtp.Packet, err error) {
|
||||
if pktBuf == nil {
|
||||
pktBuf = make([]byte, rtpPacketMaxSize)
|
||||
}
|
||||
size, err := r.buffer.GetPacket(pktBuf, sn+snOffset)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = p.Unmarshal(pktBuf[:size])
|
||||
return
|
||||
}
|
||||
|
||||
func (r *ReceiverImpl) RTPChan() <-chan rtp.Packet {
|
||||
return r.buffer.PacketChan()
|
||||
}
|
||||
+2
-2
@@ -102,7 +102,7 @@ func (r *Room) Join(participant types.Participant) error {
|
||||
"oldState", oldState)
|
||||
r.broadcastParticipantState(p)
|
||||
|
||||
if oldState == livekit.ParticipantInfo_JOINING && p.State() == livekit.ParticipantInfo_JOINED {
|
||||
if p.State() == livekit.ParticipantInfo_ACTIVE {
|
||||
// subscribe participant to existing publishedTracks
|
||||
for _, op := range r.GetParticipants() {
|
||||
if p.ID() == op.ID() {
|
||||
@@ -215,7 +215,7 @@ func (r *Room) onTrackAdded(participant types.Participant, track types.Published
|
||||
// skip publishing participant
|
||||
continue
|
||||
}
|
||||
if !existingParticipant.IsReady() {
|
||||
if existingParticipant.State() != livekit.ParticipantInfo_ACTIVE {
|
||||
// not fully joined. don't subscribe yet
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -76,8 +76,8 @@ func TestRoomJoin(t *testing.T) {
|
||||
|
||||
stateChangeCB := p.OnStateChangeArgsForCall(0)
|
||||
assert.NotNil(t, stateChangeCB)
|
||||
p.StateReturns(livekit.ParticipantInfo_JOINED)
|
||||
stateChangeCB(p, livekit.ParticipantInfo_JOINING)
|
||||
p.StateReturns(livekit.ParticipantInfo_ACTIVE)
|
||||
stateChangeCB(p, livekit.ParticipantInfo_JOINED)
|
||||
|
||||
// it should become a subscriber when connectivity changes
|
||||
for _, op := range rm.GetParticipants() {
|
||||
@@ -176,9 +176,9 @@ func TestNewTrack(t *testing.T) {
|
||||
rm := newRoomWithParticipants(t, 3)
|
||||
participants := rm.GetParticipants()
|
||||
p0 := participants[0].(*typesfakes.FakeParticipant)
|
||||
p0.IsReadyReturns(false)
|
||||
p0.StateReturns(livekit.ParticipantInfo_JOINED)
|
||||
p1 := participants[1].(*typesfakes.FakeParticipant)
|
||||
p1.IsReadyReturns(true)
|
||||
p1.StateReturns(livekit.ParticipantInfo_ACTIVE)
|
||||
|
||||
pub := participants[2].(*typesfakes.FakeParticipant)
|
||||
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
package rtc
|
||||
|
||||
import (
|
||||
"github.com/pion/ion-sfu/pkg/sfu"
|
||||
|
||||
"github.com/livekit/livekit-server/pkg/utils"
|
||||
)
|
||||
|
||||
type SubscribedTrack struct {
|
||||
dt *sfu.DownTrack
|
||||
subMuted utils.AtomicFlag
|
||||
pubMuted utils.AtomicFlag
|
||||
}
|
||||
|
||||
func NewSubscribedTrack(dt *sfu.DownTrack) *SubscribedTrack {
|
||||
return &SubscribedTrack{
|
||||
dt: dt,
|
||||
}
|
||||
}
|
||||
|
||||
func (t *SubscribedTrack) DownTrack() *sfu.DownTrack {
|
||||
return t.dt
|
||||
}
|
||||
|
||||
// has subscriber indicated it wants to mute this track
|
||||
func (t *SubscribedTrack) IsMuted() bool {
|
||||
return t.subMuted.Get()
|
||||
}
|
||||
|
||||
// set subscriber mute preference
|
||||
func (t *SubscribedTrack) SetMuted(muted bool) {
|
||||
t.subMuted.TrySet(muted)
|
||||
t.updateDownTrackMute()
|
||||
}
|
||||
|
||||
func (t *SubscribedTrack) SetPublisherMuted(muted bool) {
|
||||
t.pubMuted.TrySet(muted)
|
||||
t.updateDownTrackMute()
|
||||
}
|
||||
|
||||
// cause downtrack to resync and request a keyframe
|
||||
// this is sort of a hack to prevent a subscriber from having tracks freeze on the client due to missing
|
||||
// keyframes
|
||||
func (t *SubscribedTrack) Resync() {
|
||||
t.dt.Mute(true)
|
||||
t.updateDownTrackMute()
|
||||
}
|
||||
|
||||
func (t *SubscribedTrack) updateDownTrackMute() {
|
||||
muted := t.subMuted.Get() || t.pubMuted.Get()
|
||||
t.dt.Mute(muted)
|
||||
}
|
||||
+12
-28
@@ -3,13 +3,11 @@ package types
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/pion/ion-sfu/pkg/sfu"
|
||||
"github.com/pion/rtcp"
|
||||
"github.com/pion/rtp"
|
||||
"github.com/pion/webrtc/v3"
|
||||
|
||||
"github.com/livekit/livekit-server/pkg/routing"
|
||||
"github.com/livekit/livekit-server/pkg/sfu"
|
||||
"github.com/livekit/livekit-server/pkg/utils"
|
||||
"github.com/livekit/livekit-server/proto/livekit"
|
||||
)
|
||||
|
||||
@@ -53,7 +51,7 @@ type Participant interface {
|
||||
State() livekit.ParticipantInfo_State
|
||||
IsReady() bool
|
||||
ToProto() *livekit.ParticipantInfo
|
||||
RTCPChan() *utils.CalmChannel
|
||||
RTCPChan() chan []rtcp.Packet
|
||||
GetResponseSink() routing.MessageSink
|
||||
SetResponseSink(sink routing.MessageSink)
|
||||
|
||||
@@ -82,8 +80,8 @@ type Participant interface {
|
||||
OnClose(func(Participant))
|
||||
|
||||
// package methods
|
||||
AddDownTrack(streamId string, dt *sfu.DownTrack)
|
||||
RemoveDownTrack(streamId string, dt *sfu.DownTrack)
|
||||
AddSubscribedTrack(participantId string, st SubscribedTrack)
|
||||
RemoveSubscribedTrack(participantId string, st SubscribedTrack)
|
||||
PeerConnection() PeerConnection
|
||||
}
|
||||
|
||||
@@ -96,6 +94,7 @@ type PublishedTrack interface {
|
||||
Kind() livekit.TrackType
|
||||
Name() string
|
||||
IsMuted() bool
|
||||
SetMuted(muted bool)
|
||||
AddSubscriber(participant Participant) error
|
||||
RemoveSubscriber(participantId string)
|
||||
RemoveAllSubscribers()
|
||||
@@ -104,28 +103,13 @@ type PublishedTrack interface {
|
||||
OnClose(func())
|
||||
}
|
||||
|
||||
//counterfeiter:generate . Receiver
|
||||
type Receiver interface {
|
||||
RTPChan() <-chan rtp.Packet
|
||||
GetBufferedPacket(pktBuf []byte, sn uint16, snOffset uint16) (rtp.Packet, error)
|
||||
}
|
||||
|
||||
// DownTrack publishes data to a target participant
|
||||
// using this interface to make testing more practical
|
||||
//counterfeiter:generate . DownTrack
|
||||
type DownTrack interface {
|
||||
ID() string
|
||||
WriteRTP(p rtp.Packet) error
|
||||
IsBound() bool
|
||||
Close()
|
||||
OnCloseHandler(fn func())
|
||||
OnBind(fn func())
|
||||
SSRC() uint32
|
||||
LastSSRC() uint32
|
||||
SnOffset() uint16
|
||||
TsOffset() uint32
|
||||
GetNACKSeqNo(seqNo []uint16) []uint16
|
||||
CreateSourceDescriptionChunks() []rtcp.SourceDescriptionChunk
|
||||
//counterfeiter:generate . SubscribedTrack
|
||||
type SubscribedTrack interface {
|
||||
DownTrack() *sfu.DownTrack
|
||||
IsMuted() bool
|
||||
SetMuted(muted bool)
|
||||
SetPublisherMuted(muted bool)
|
||||
Resync()
|
||||
}
|
||||
|
||||
// interface for properties of webrtc.TrackRemote
|
||||
|
||||
@@ -1,755 +0,0 @@
|
||||
// Code generated by counterfeiter. DO NOT EDIT.
|
||||
package typesfakes
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/livekit/livekit-server/pkg/rtc/types"
|
||||
"github.com/pion/rtcp"
|
||||
"github.com/pion/rtp"
|
||||
)
|
||||
|
||||
type FakeDownTrack struct {
|
||||
CloseStub func()
|
||||
closeMutex sync.RWMutex
|
||||
closeArgsForCall []struct {
|
||||
}
|
||||
CreateSourceDescriptionChunksStub func() []rtcp.SourceDescriptionChunk
|
||||
createSourceDescriptionChunksMutex sync.RWMutex
|
||||
createSourceDescriptionChunksArgsForCall []struct {
|
||||
}
|
||||
createSourceDescriptionChunksReturns struct {
|
||||
result1 []rtcp.SourceDescriptionChunk
|
||||
}
|
||||
createSourceDescriptionChunksReturnsOnCall map[int]struct {
|
||||
result1 []rtcp.SourceDescriptionChunk
|
||||
}
|
||||
GetNACKSeqNoStub func([]uint16) []uint16
|
||||
getNACKSeqNoMutex sync.RWMutex
|
||||
getNACKSeqNoArgsForCall []struct {
|
||||
arg1 []uint16
|
||||
}
|
||||
getNACKSeqNoReturns struct {
|
||||
result1 []uint16
|
||||
}
|
||||
getNACKSeqNoReturnsOnCall map[int]struct {
|
||||
result1 []uint16
|
||||
}
|
||||
IDStub func() string
|
||||
iDMutex sync.RWMutex
|
||||
iDArgsForCall []struct {
|
||||
}
|
||||
iDReturns struct {
|
||||
result1 string
|
||||
}
|
||||
iDReturnsOnCall map[int]struct {
|
||||
result1 string
|
||||
}
|
||||
IsBoundStub func() bool
|
||||
isBoundMutex sync.RWMutex
|
||||
isBoundArgsForCall []struct {
|
||||
}
|
||||
isBoundReturns struct {
|
||||
result1 bool
|
||||
}
|
||||
isBoundReturnsOnCall map[int]struct {
|
||||
result1 bool
|
||||
}
|
||||
LastSSRCStub func() uint32
|
||||
lastSSRCMutex sync.RWMutex
|
||||
lastSSRCArgsForCall []struct {
|
||||
}
|
||||
lastSSRCReturns struct {
|
||||
result1 uint32
|
||||
}
|
||||
lastSSRCReturnsOnCall map[int]struct {
|
||||
result1 uint32
|
||||
}
|
||||
OnBindStub func(func())
|
||||
onBindMutex sync.RWMutex
|
||||
onBindArgsForCall []struct {
|
||||
arg1 func()
|
||||
}
|
||||
OnCloseHandlerStub func(func())
|
||||
onCloseHandlerMutex sync.RWMutex
|
||||
onCloseHandlerArgsForCall []struct {
|
||||
arg1 func()
|
||||
}
|
||||
SSRCStub func() uint32
|
||||
sSRCMutex sync.RWMutex
|
||||
sSRCArgsForCall []struct {
|
||||
}
|
||||
sSRCReturns struct {
|
||||
result1 uint32
|
||||
}
|
||||
sSRCReturnsOnCall map[int]struct {
|
||||
result1 uint32
|
||||
}
|
||||
SnOffsetStub func() uint16
|
||||
snOffsetMutex sync.RWMutex
|
||||
snOffsetArgsForCall []struct {
|
||||
}
|
||||
snOffsetReturns struct {
|
||||
result1 uint16
|
||||
}
|
||||
snOffsetReturnsOnCall map[int]struct {
|
||||
result1 uint16
|
||||
}
|
||||
TsOffsetStub func() uint32
|
||||
tsOffsetMutex sync.RWMutex
|
||||
tsOffsetArgsForCall []struct {
|
||||
}
|
||||
tsOffsetReturns struct {
|
||||
result1 uint32
|
||||
}
|
||||
tsOffsetReturnsOnCall map[int]struct {
|
||||
result1 uint32
|
||||
}
|
||||
WriteRTPStub func(rtp.Packet) error
|
||||
writeRTPMutex sync.RWMutex
|
||||
writeRTPArgsForCall []struct {
|
||||
arg1 rtp.Packet
|
||||
}
|
||||
writeRTPReturns struct {
|
||||
result1 error
|
||||
}
|
||||
writeRTPReturnsOnCall map[int]struct {
|
||||
result1 error
|
||||
}
|
||||
invocations map[string][][]interface{}
|
||||
invocationsMutex sync.RWMutex
|
||||
}
|
||||
|
||||
func (fake *FakeDownTrack) Close() {
|
||||
fake.closeMutex.Lock()
|
||||
fake.closeArgsForCall = append(fake.closeArgsForCall, struct {
|
||||
}{})
|
||||
stub := fake.CloseStub
|
||||
fake.recordInvocation("Close", []interface{}{})
|
||||
fake.closeMutex.Unlock()
|
||||
if stub != nil {
|
||||
fake.CloseStub()
|
||||
}
|
||||
}
|
||||
|
||||
func (fake *FakeDownTrack) CloseCallCount() int {
|
||||
fake.closeMutex.RLock()
|
||||
defer fake.closeMutex.RUnlock()
|
||||
return len(fake.closeArgsForCall)
|
||||
}
|
||||
|
||||
func (fake *FakeDownTrack) CloseCalls(stub func()) {
|
||||
fake.closeMutex.Lock()
|
||||
defer fake.closeMutex.Unlock()
|
||||
fake.CloseStub = stub
|
||||
}
|
||||
|
||||
func (fake *FakeDownTrack) CreateSourceDescriptionChunks() []rtcp.SourceDescriptionChunk {
|
||||
fake.createSourceDescriptionChunksMutex.Lock()
|
||||
ret, specificReturn := fake.createSourceDescriptionChunksReturnsOnCall[len(fake.createSourceDescriptionChunksArgsForCall)]
|
||||
fake.createSourceDescriptionChunksArgsForCall = append(fake.createSourceDescriptionChunksArgsForCall, struct {
|
||||
}{})
|
||||
stub := fake.CreateSourceDescriptionChunksStub
|
||||
fakeReturns := fake.createSourceDescriptionChunksReturns
|
||||
fake.recordInvocation("CreateSourceDescriptionChunks", []interface{}{})
|
||||
fake.createSourceDescriptionChunksMutex.Unlock()
|
||||
if stub != nil {
|
||||
return stub()
|
||||
}
|
||||
if specificReturn {
|
||||
return ret.result1
|
||||
}
|
||||
return fakeReturns.result1
|
||||
}
|
||||
|
||||
func (fake *FakeDownTrack) CreateSourceDescriptionChunksCallCount() int {
|
||||
fake.createSourceDescriptionChunksMutex.RLock()
|
||||
defer fake.createSourceDescriptionChunksMutex.RUnlock()
|
||||
return len(fake.createSourceDescriptionChunksArgsForCall)
|
||||
}
|
||||
|
||||
func (fake *FakeDownTrack) CreateSourceDescriptionChunksCalls(stub func() []rtcp.SourceDescriptionChunk) {
|
||||
fake.createSourceDescriptionChunksMutex.Lock()
|
||||
defer fake.createSourceDescriptionChunksMutex.Unlock()
|
||||
fake.CreateSourceDescriptionChunksStub = stub
|
||||
}
|
||||
|
||||
func (fake *FakeDownTrack) CreateSourceDescriptionChunksReturns(result1 []rtcp.SourceDescriptionChunk) {
|
||||
fake.createSourceDescriptionChunksMutex.Lock()
|
||||
defer fake.createSourceDescriptionChunksMutex.Unlock()
|
||||
fake.CreateSourceDescriptionChunksStub = nil
|
||||
fake.createSourceDescriptionChunksReturns = struct {
|
||||
result1 []rtcp.SourceDescriptionChunk
|
||||
}{result1}
|
||||
}
|
||||
|
||||
func (fake *FakeDownTrack) CreateSourceDescriptionChunksReturnsOnCall(i int, result1 []rtcp.SourceDescriptionChunk) {
|
||||
fake.createSourceDescriptionChunksMutex.Lock()
|
||||
defer fake.createSourceDescriptionChunksMutex.Unlock()
|
||||
fake.CreateSourceDescriptionChunksStub = nil
|
||||
if fake.createSourceDescriptionChunksReturnsOnCall == nil {
|
||||
fake.createSourceDescriptionChunksReturnsOnCall = make(map[int]struct {
|
||||
result1 []rtcp.SourceDescriptionChunk
|
||||
})
|
||||
}
|
||||
fake.createSourceDescriptionChunksReturnsOnCall[i] = struct {
|
||||
result1 []rtcp.SourceDescriptionChunk
|
||||
}{result1}
|
||||
}
|
||||
|
||||
func (fake *FakeDownTrack) GetNACKSeqNo(arg1 []uint16) []uint16 {
|
||||
var arg1Copy []uint16
|
||||
if arg1 != nil {
|
||||
arg1Copy = make([]uint16, len(arg1))
|
||||
copy(arg1Copy, arg1)
|
||||
}
|
||||
fake.getNACKSeqNoMutex.Lock()
|
||||
ret, specificReturn := fake.getNACKSeqNoReturnsOnCall[len(fake.getNACKSeqNoArgsForCall)]
|
||||
fake.getNACKSeqNoArgsForCall = append(fake.getNACKSeqNoArgsForCall, struct {
|
||||
arg1 []uint16
|
||||
}{arg1Copy})
|
||||
stub := fake.GetNACKSeqNoStub
|
||||
fakeReturns := fake.getNACKSeqNoReturns
|
||||
fake.recordInvocation("GetNACKSeqNo", []interface{}{arg1Copy})
|
||||
fake.getNACKSeqNoMutex.Unlock()
|
||||
if stub != nil {
|
||||
return stub(arg1)
|
||||
}
|
||||
if specificReturn {
|
||||
return ret.result1
|
||||
}
|
||||
return fakeReturns.result1
|
||||
}
|
||||
|
||||
func (fake *FakeDownTrack) GetNACKSeqNoCallCount() int {
|
||||
fake.getNACKSeqNoMutex.RLock()
|
||||
defer fake.getNACKSeqNoMutex.RUnlock()
|
||||
return len(fake.getNACKSeqNoArgsForCall)
|
||||
}
|
||||
|
||||
func (fake *FakeDownTrack) GetNACKSeqNoCalls(stub func([]uint16) []uint16) {
|
||||
fake.getNACKSeqNoMutex.Lock()
|
||||
defer fake.getNACKSeqNoMutex.Unlock()
|
||||
fake.GetNACKSeqNoStub = stub
|
||||
}
|
||||
|
||||
func (fake *FakeDownTrack) GetNACKSeqNoArgsForCall(i int) []uint16 {
|
||||
fake.getNACKSeqNoMutex.RLock()
|
||||
defer fake.getNACKSeqNoMutex.RUnlock()
|
||||
argsForCall := fake.getNACKSeqNoArgsForCall[i]
|
||||
return argsForCall.arg1
|
||||
}
|
||||
|
||||
func (fake *FakeDownTrack) GetNACKSeqNoReturns(result1 []uint16) {
|
||||
fake.getNACKSeqNoMutex.Lock()
|
||||
defer fake.getNACKSeqNoMutex.Unlock()
|
||||
fake.GetNACKSeqNoStub = nil
|
||||
fake.getNACKSeqNoReturns = struct {
|
||||
result1 []uint16
|
||||
}{result1}
|
||||
}
|
||||
|
||||
func (fake *FakeDownTrack) GetNACKSeqNoReturnsOnCall(i int, result1 []uint16) {
|
||||
fake.getNACKSeqNoMutex.Lock()
|
||||
defer fake.getNACKSeqNoMutex.Unlock()
|
||||
fake.GetNACKSeqNoStub = nil
|
||||
if fake.getNACKSeqNoReturnsOnCall == nil {
|
||||
fake.getNACKSeqNoReturnsOnCall = make(map[int]struct {
|
||||
result1 []uint16
|
||||
})
|
||||
}
|
||||
fake.getNACKSeqNoReturnsOnCall[i] = struct {
|
||||
result1 []uint16
|
||||
}{result1}
|
||||
}
|
||||
|
||||
func (fake *FakeDownTrack) ID() string {
|
||||
fake.iDMutex.Lock()
|
||||
ret, specificReturn := fake.iDReturnsOnCall[len(fake.iDArgsForCall)]
|
||||
fake.iDArgsForCall = append(fake.iDArgsForCall, struct {
|
||||
}{})
|
||||
stub := fake.IDStub
|
||||
fakeReturns := fake.iDReturns
|
||||
fake.recordInvocation("ID", []interface{}{})
|
||||
fake.iDMutex.Unlock()
|
||||
if stub != nil {
|
||||
return stub()
|
||||
}
|
||||
if specificReturn {
|
||||
return ret.result1
|
||||
}
|
||||
return fakeReturns.result1
|
||||
}
|
||||
|
||||
func (fake *FakeDownTrack) IDCallCount() int {
|
||||
fake.iDMutex.RLock()
|
||||
defer fake.iDMutex.RUnlock()
|
||||
return len(fake.iDArgsForCall)
|
||||
}
|
||||
|
||||
func (fake *FakeDownTrack) IDCalls(stub func() string) {
|
||||
fake.iDMutex.Lock()
|
||||
defer fake.iDMutex.Unlock()
|
||||
fake.IDStub = stub
|
||||
}
|
||||
|
||||
func (fake *FakeDownTrack) IDReturns(result1 string) {
|
||||
fake.iDMutex.Lock()
|
||||
defer fake.iDMutex.Unlock()
|
||||
fake.IDStub = nil
|
||||
fake.iDReturns = struct {
|
||||
result1 string
|
||||
}{result1}
|
||||
}
|
||||
|
||||
func (fake *FakeDownTrack) IDReturnsOnCall(i int, result1 string) {
|
||||
fake.iDMutex.Lock()
|
||||
defer fake.iDMutex.Unlock()
|
||||
fake.IDStub = nil
|
||||
if fake.iDReturnsOnCall == nil {
|
||||
fake.iDReturnsOnCall = make(map[int]struct {
|
||||
result1 string
|
||||
})
|
||||
}
|
||||
fake.iDReturnsOnCall[i] = struct {
|
||||
result1 string
|
||||
}{result1}
|
||||
}
|
||||
|
||||
func (fake *FakeDownTrack) IsBound() bool {
|
||||
fake.isBoundMutex.Lock()
|
||||
ret, specificReturn := fake.isBoundReturnsOnCall[len(fake.isBoundArgsForCall)]
|
||||
fake.isBoundArgsForCall = append(fake.isBoundArgsForCall, struct {
|
||||
}{})
|
||||
stub := fake.IsBoundStub
|
||||
fakeReturns := fake.isBoundReturns
|
||||
fake.recordInvocation("IsBound", []interface{}{})
|
||||
fake.isBoundMutex.Unlock()
|
||||
if stub != nil {
|
||||
return stub()
|
||||
}
|
||||
if specificReturn {
|
||||
return ret.result1
|
||||
}
|
||||
return fakeReturns.result1
|
||||
}
|
||||
|
||||
func (fake *FakeDownTrack) IsBoundCallCount() int {
|
||||
fake.isBoundMutex.RLock()
|
||||
defer fake.isBoundMutex.RUnlock()
|
||||
return len(fake.isBoundArgsForCall)
|
||||
}
|
||||
|
||||
func (fake *FakeDownTrack) IsBoundCalls(stub func() bool) {
|
||||
fake.isBoundMutex.Lock()
|
||||
defer fake.isBoundMutex.Unlock()
|
||||
fake.IsBoundStub = stub
|
||||
}
|
||||
|
||||
func (fake *FakeDownTrack) IsBoundReturns(result1 bool) {
|
||||
fake.isBoundMutex.Lock()
|
||||
defer fake.isBoundMutex.Unlock()
|
||||
fake.IsBoundStub = nil
|
||||
fake.isBoundReturns = struct {
|
||||
result1 bool
|
||||
}{result1}
|
||||
}
|
||||
|
||||
func (fake *FakeDownTrack) IsBoundReturnsOnCall(i int, result1 bool) {
|
||||
fake.isBoundMutex.Lock()
|
||||
defer fake.isBoundMutex.Unlock()
|
||||
fake.IsBoundStub = nil
|
||||
if fake.isBoundReturnsOnCall == nil {
|
||||
fake.isBoundReturnsOnCall = make(map[int]struct {
|
||||
result1 bool
|
||||
})
|
||||
}
|
||||
fake.isBoundReturnsOnCall[i] = struct {
|
||||
result1 bool
|
||||
}{result1}
|
||||
}
|
||||
|
||||
func (fake *FakeDownTrack) LastSSRC() uint32 {
|
||||
fake.lastSSRCMutex.Lock()
|
||||
ret, specificReturn := fake.lastSSRCReturnsOnCall[len(fake.lastSSRCArgsForCall)]
|
||||
fake.lastSSRCArgsForCall = append(fake.lastSSRCArgsForCall, struct {
|
||||
}{})
|
||||
stub := fake.LastSSRCStub
|
||||
fakeReturns := fake.lastSSRCReturns
|
||||
fake.recordInvocation("LastSSRC", []interface{}{})
|
||||
fake.lastSSRCMutex.Unlock()
|
||||
if stub != nil {
|
||||
return stub()
|
||||
}
|
||||
if specificReturn {
|
||||
return ret.result1
|
||||
}
|
||||
return fakeReturns.result1
|
||||
}
|
||||
|
||||
func (fake *FakeDownTrack) LastSSRCCallCount() int {
|
||||
fake.lastSSRCMutex.RLock()
|
||||
defer fake.lastSSRCMutex.RUnlock()
|
||||
return len(fake.lastSSRCArgsForCall)
|
||||
}
|
||||
|
||||
func (fake *FakeDownTrack) LastSSRCCalls(stub func() uint32) {
|
||||
fake.lastSSRCMutex.Lock()
|
||||
defer fake.lastSSRCMutex.Unlock()
|
||||
fake.LastSSRCStub = stub
|
||||
}
|
||||
|
||||
func (fake *FakeDownTrack) LastSSRCReturns(result1 uint32) {
|
||||
fake.lastSSRCMutex.Lock()
|
||||
defer fake.lastSSRCMutex.Unlock()
|
||||
fake.LastSSRCStub = nil
|
||||
fake.lastSSRCReturns = struct {
|
||||
result1 uint32
|
||||
}{result1}
|
||||
}
|
||||
|
||||
func (fake *FakeDownTrack) LastSSRCReturnsOnCall(i int, result1 uint32) {
|
||||
fake.lastSSRCMutex.Lock()
|
||||
defer fake.lastSSRCMutex.Unlock()
|
||||
fake.LastSSRCStub = nil
|
||||
if fake.lastSSRCReturnsOnCall == nil {
|
||||
fake.lastSSRCReturnsOnCall = make(map[int]struct {
|
||||
result1 uint32
|
||||
})
|
||||
}
|
||||
fake.lastSSRCReturnsOnCall[i] = struct {
|
||||
result1 uint32
|
||||
}{result1}
|
||||
}
|
||||
|
||||
func (fake *FakeDownTrack) OnBind(arg1 func()) {
|
||||
fake.onBindMutex.Lock()
|
||||
fake.onBindArgsForCall = append(fake.onBindArgsForCall, struct {
|
||||
arg1 func()
|
||||
}{arg1})
|
||||
stub := fake.OnBindStub
|
||||
fake.recordInvocation("OnBind", []interface{}{arg1})
|
||||
fake.onBindMutex.Unlock()
|
||||
if stub != nil {
|
||||
fake.OnBindStub(arg1)
|
||||
}
|
||||
}
|
||||
|
||||
func (fake *FakeDownTrack) OnBindCallCount() int {
|
||||
fake.onBindMutex.RLock()
|
||||
defer fake.onBindMutex.RUnlock()
|
||||
return len(fake.onBindArgsForCall)
|
||||
}
|
||||
|
||||
func (fake *FakeDownTrack) OnBindCalls(stub func(func())) {
|
||||
fake.onBindMutex.Lock()
|
||||
defer fake.onBindMutex.Unlock()
|
||||
fake.OnBindStub = stub
|
||||
}
|
||||
|
||||
func (fake *FakeDownTrack) OnBindArgsForCall(i int) func() {
|
||||
fake.onBindMutex.RLock()
|
||||
defer fake.onBindMutex.RUnlock()
|
||||
argsForCall := fake.onBindArgsForCall[i]
|
||||
return argsForCall.arg1
|
||||
}
|
||||
|
||||
func (fake *FakeDownTrack) OnCloseHandler(arg1 func()) {
|
||||
fake.onCloseHandlerMutex.Lock()
|
||||
fake.onCloseHandlerArgsForCall = append(fake.onCloseHandlerArgsForCall, struct {
|
||||
arg1 func()
|
||||
}{arg1})
|
||||
stub := fake.OnCloseHandlerStub
|
||||
fake.recordInvocation("OnCloseHandler", []interface{}{arg1})
|
||||
fake.onCloseHandlerMutex.Unlock()
|
||||
if stub != nil {
|
||||
fake.OnCloseHandlerStub(arg1)
|
||||
}
|
||||
}
|
||||
|
||||
func (fake *FakeDownTrack) OnCloseHandlerCallCount() int {
|
||||
fake.onCloseHandlerMutex.RLock()
|
||||
defer fake.onCloseHandlerMutex.RUnlock()
|
||||
return len(fake.onCloseHandlerArgsForCall)
|
||||
}
|
||||
|
||||
func (fake *FakeDownTrack) OnCloseHandlerCalls(stub func(func())) {
|
||||
fake.onCloseHandlerMutex.Lock()
|
||||
defer fake.onCloseHandlerMutex.Unlock()
|
||||
fake.OnCloseHandlerStub = stub
|
||||
}
|
||||
|
||||
func (fake *FakeDownTrack) OnCloseHandlerArgsForCall(i int) func() {
|
||||
fake.onCloseHandlerMutex.RLock()
|
||||
defer fake.onCloseHandlerMutex.RUnlock()
|
||||
argsForCall := fake.onCloseHandlerArgsForCall[i]
|
||||
return argsForCall.arg1
|
||||
}
|
||||
|
||||
func (fake *FakeDownTrack) SSRC() uint32 {
|
||||
fake.sSRCMutex.Lock()
|
||||
ret, specificReturn := fake.sSRCReturnsOnCall[len(fake.sSRCArgsForCall)]
|
||||
fake.sSRCArgsForCall = append(fake.sSRCArgsForCall, struct {
|
||||
}{})
|
||||
stub := fake.SSRCStub
|
||||
fakeReturns := fake.sSRCReturns
|
||||
fake.recordInvocation("SSRC", []interface{}{})
|
||||
fake.sSRCMutex.Unlock()
|
||||
if stub != nil {
|
||||
return stub()
|
||||
}
|
||||
if specificReturn {
|
||||
return ret.result1
|
||||
}
|
||||
return fakeReturns.result1
|
||||
}
|
||||
|
||||
func (fake *FakeDownTrack) SSRCCallCount() int {
|
||||
fake.sSRCMutex.RLock()
|
||||
defer fake.sSRCMutex.RUnlock()
|
||||
return len(fake.sSRCArgsForCall)
|
||||
}
|
||||
|
||||
func (fake *FakeDownTrack) SSRCCalls(stub func() uint32) {
|
||||
fake.sSRCMutex.Lock()
|
||||
defer fake.sSRCMutex.Unlock()
|
||||
fake.SSRCStub = stub
|
||||
}
|
||||
|
||||
func (fake *FakeDownTrack) SSRCReturns(result1 uint32) {
|
||||
fake.sSRCMutex.Lock()
|
||||
defer fake.sSRCMutex.Unlock()
|
||||
fake.SSRCStub = nil
|
||||
fake.sSRCReturns = struct {
|
||||
result1 uint32
|
||||
}{result1}
|
||||
}
|
||||
|
||||
func (fake *FakeDownTrack) SSRCReturnsOnCall(i int, result1 uint32) {
|
||||
fake.sSRCMutex.Lock()
|
||||
defer fake.sSRCMutex.Unlock()
|
||||
fake.SSRCStub = nil
|
||||
if fake.sSRCReturnsOnCall == nil {
|
||||
fake.sSRCReturnsOnCall = make(map[int]struct {
|
||||
result1 uint32
|
||||
})
|
||||
}
|
||||
fake.sSRCReturnsOnCall[i] = struct {
|
||||
result1 uint32
|
||||
}{result1}
|
||||
}
|
||||
|
||||
func (fake *FakeDownTrack) SnOffset() uint16 {
|
||||
fake.snOffsetMutex.Lock()
|
||||
ret, specificReturn := fake.snOffsetReturnsOnCall[len(fake.snOffsetArgsForCall)]
|
||||
fake.snOffsetArgsForCall = append(fake.snOffsetArgsForCall, struct {
|
||||
}{})
|
||||
stub := fake.SnOffsetStub
|
||||
fakeReturns := fake.snOffsetReturns
|
||||
fake.recordInvocation("SnOffset", []interface{}{})
|
||||
fake.snOffsetMutex.Unlock()
|
||||
if stub != nil {
|
||||
return stub()
|
||||
}
|
||||
if specificReturn {
|
||||
return ret.result1
|
||||
}
|
||||
return fakeReturns.result1
|
||||
}
|
||||
|
||||
func (fake *FakeDownTrack) SnOffsetCallCount() int {
|
||||
fake.snOffsetMutex.RLock()
|
||||
defer fake.snOffsetMutex.RUnlock()
|
||||
return len(fake.snOffsetArgsForCall)
|
||||
}
|
||||
|
||||
func (fake *FakeDownTrack) SnOffsetCalls(stub func() uint16) {
|
||||
fake.snOffsetMutex.Lock()
|
||||
defer fake.snOffsetMutex.Unlock()
|
||||
fake.SnOffsetStub = stub
|
||||
}
|
||||
|
||||
func (fake *FakeDownTrack) SnOffsetReturns(result1 uint16) {
|
||||
fake.snOffsetMutex.Lock()
|
||||
defer fake.snOffsetMutex.Unlock()
|
||||
fake.SnOffsetStub = nil
|
||||
fake.snOffsetReturns = struct {
|
||||
result1 uint16
|
||||
}{result1}
|
||||
}
|
||||
|
||||
func (fake *FakeDownTrack) SnOffsetReturnsOnCall(i int, result1 uint16) {
|
||||
fake.snOffsetMutex.Lock()
|
||||
defer fake.snOffsetMutex.Unlock()
|
||||
fake.SnOffsetStub = nil
|
||||
if fake.snOffsetReturnsOnCall == nil {
|
||||
fake.snOffsetReturnsOnCall = make(map[int]struct {
|
||||
result1 uint16
|
||||
})
|
||||
}
|
||||
fake.snOffsetReturnsOnCall[i] = struct {
|
||||
result1 uint16
|
||||
}{result1}
|
||||
}
|
||||
|
||||
func (fake *FakeDownTrack) TsOffset() uint32 {
|
||||
fake.tsOffsetMutex.Lock()
|
||||
ret, specificReturn := fake.tsOffsetReturnsOnCall[len(fake.tsOffsetArgsForCall)]
|
||||
fake.tsOffsetArgsForCall = append(fake.tsOffsetArgsForCall, struct {
|
||||
}{})
|
||||
stub := fake.TsOffsetStub
|
||||
fakeReturns := fake.tsOffsetReturns
|
||||
fake.recordInvocation("TsOffset", []interface{}{})
|
||||
fake.tsOffsetMutex.Unlock()
|
||||
if stub != nil {
|
||||
return stub()
|
||||
}
|
||||
if specificReturn {
|
||||
return ret.result1
|
||||
}
|
||||
return fakeReturns.result1
|
||||
}
|
||||
|
||||
func (fake *FakeDownTrack) TsOffsetCallCount() int {
|
||||
fake.tsOffsetMutex.RLock()
|
||||
defer fake.tsOffsetMutex.RUnlock()
|
||||
return len(fake.tsOffsetArgsForCall)
|
||||
}
|
||||
|
||||
func (fake *FakeDownTrack) TsOffsetCalls(stub func() uint32) {
|
||||
fake.tsOffsetMutex.Lock()
|
||||
defer fake.tsOffsetMutex.Unlock()
|
||||
fake.TsOffsetStub = stub
|
||||
}
|
||||
|
||||
func (fake *FakeDownTrack) TsOffsetReturns(result1 uint32) {
|
||||
fake.tsOffsetMutex.Lock()
|
||||
defer fake.tsOffsetMutex.Unlock()
|
||||
fake.TsOffsetStub = nil
|
||||
fake.tsOffsetReturns = struct {
|
||||
result1 uint32
|
||||
}{result1}
|
||||
}
|
||||
|
||||
func (fake *FakeDownTrack) TsOffsetReturnsOnCall(i int, result1 uint32) {
|
||||
fake.tsOffsetMutex.Lock()
|
||||
defer fake.tsOffsetMutex.Unlock()
|
||||
fake.TsOffsetStub = nil
|
||||
if fake.tsOffsetReturnsOnCall == nil {
|
||||
fake.tsOffsetReturnsOnCall = make(map[int]struct {
|
||||
result1 uint32
|
||||
})
|
||||
}
|
||||
fake.tsOffsetReturnsOnCall[i] = struct {
|
||||
result1 uint32
|
||||
}{result1}
|
||||
}
|
||||
|
||||
func (fake *FakeDownTrack) WriteRTP(arg1 rtp.Packet) error {
|
||||
fake.writeRTPMutex.Lock()
|
||||
ret, specificReturn := fake.writeRTPReturnsOnCall[len(fake.writeRTPArgsForCall)]
|
||||
fake.writeRTPArgsForCall = append(fake.writeRTPArgsForCall, struct {
|
||||
arg1 rtp.Packet
|
||||
}{arg1})
|
||||
stub := fake.WriteRTPStub
|
||||
fakeReturns := fake.writeRTPReturns
|
||||
fake.recordInvocation("WriteRTP", []interface{}{arg1})
|
||||
fake.writeRTPMutex.Unlock()
|
||||
if stub != nil {
|
||||
return stub(arg1)
|
||||
}
|
||||
if specificReturn {
|
||||
return ret.result1
|
||||
}
|
||||
return fakeReturns.result1
|
||||
}
|
||||
|
||||
func (fake *FakeDownTrack) WriteRTPCallCount() int {
|
||||
fake.writeRTPMutex.RLock()
|
||||
defer fake.writeRTPMutex.RUnlock()
|
||||
return len(fake.writeRTPArgsForCall)
|
||||
}
|
||||
|
||||
func (fake *FakeDownTrack) WriteRTPCalls(stub func(rtp.Packet) error) {
|
||||
fake.writeRTPMutex.Lock()
|
||||
defer fake.writeRTPMutex.Unlock()
|
||||
fake.WriteRTPStub = stub
|
||||
}
|
||||
|
||||
func (fake *FakeDownTrack) WriteRTPArgsForCall(i int) rtp.Packet {
|
||||
fake.writeRTPMutex.RLock()
|
||||
defer fake.writeRTPMutex.RUnlock()
|
||||
argsForCall := fake.writeRTPArgsForCall[i]
|
||||
return argsForCall.arg1
|
||||
}
|
||||
|
||||
func (fake *FakeDownTrack) WriteRTPReturns(result1 error) {
|
||||
fake.writeRTPMutex.Lock()
|
||||
defer fake.writeRTPMutex.Unlock()
|
||||
fake.WriteRTPStub = nil
|
||||
fake.writeRTPReturns = struct {
|
||||
result1 error
|
||||
}{result1}
|
||||
}
|
||||
|
||||
func (fake *FakeDownTrack) WriteRTPReturnsOnCall(i int, result1 error) {
|
||||
fake.writeRTPMutex.Lock()
|
||||
defer fake.writeRTPMutex.Unlock()
|
||||
fake.WriteRTPStub = nil
|
||||
if fake.writeRTPReturnsOnCall == nil {
|
||||
fake.writeRTPReturnsOnCall = make(map[int]struct {
|
||||
result1 error
|
||||
})
|
||||
}
|
||||
fake.writeRTPReturnsOnCall[i] = struct {
|
||||
result1 error
|
||||
}{result1}
|
||||
}
|
||||
|
||||
func (fake *FakeDownTrack) Invocations() map[string][][]interface{} {
|
||||
fake.invocationsMutex.RLock()
|
||||
defer fake.invocationsMutex.RUnlock()
|
||||
fake.closeMutex.RLock()
|
||||
defer fake.closeMutex.RUnlock()
|
||||
fake.createSourceDescriptionChunksMutex.RLock()
|
||||
defer fake.createSourceDescriptionChunksMutex.RUnlock()
|
||||
fake.getNACKSeqNoMutex.RLock()
|
||||
defer fake.getNACKSeqNoMutex.RUnlock()
|
||||
fake.iDMutex.RLock()
|
||||
defer fake.iDMutex.RUnlock()
|
||||
fake.isBoundMutex.RLock()
|
||||
defer fake.isBoundMutex.RUnlock()
|
||||
fake.lastSSRCMutex.RLock()
|
||||
defer fake.lastSSRCMutex.RUnlock()
|
||||
fake.onBindMutex.RLock()
|
||||
defer fake.onBindMutex.RUnlock()
|
||||
fake.onCloseHandlerMutex.RLock()
|
||||
defer fake.onCloseHandlerMutex.RUnlock()
|
||||
fake.sSRCMutex.RLock()
|
||||
defer fake.sSRCMutex.RUnlock()
|
||||
fake.snOffsetMutex.RLock()
|
||||
defer fake.snOffsetMutex.RUnlock()
|
||||
fake.tsOffsetMutex.RLock()
|
||||
defer fake.tsOffsetMutex.RUnlock()
|
||||
fake.writeRTPMutex.RLock()
|
||||
defer fake.writeRTPMutex.RUnlock()
|
||||
copiedInvocations := map[string][][]interface{}{}
|
||||
for key, value := range fake.invocations {
|
||||
copiedInvocations[key] = value
|
||||
}
|
||||
return copiedInvocations
|
||||
}
|
||||
|
||||
func (fake *FakeDownTrack) recordInvocation(key string, args []interface{}) {
|
||||
fake.invocationsMutex.Lock()
|
||||
defer fake.invocationsMutex.Unlock()
|
||||
if fake.invocations == nil {
|
||||
fake.invocations = map[string][][]interface{}{}
|
||||
}
|
||||
if fake.invocations[key] == nil {
|
||||
fake.invocations[key] = [][]interface{}{}
|
||||
}
|
||||
fake.invocations[key] = append(fake.invocations[key], args)
|
||||
}
|
||||
|
||||
var _ types.DownTrack = new(FakeDownTrack)
|
||||
@@ -6,19 +6,12 @@ import (
|
||||
|
||||
"github.com/livekit/livekit-server/pkg/routing"
|
||||
"github.com/livekit/livekit-server/pkg/rtc/types"
|
||||
"github.com/livekit/livekit-server/pkg/sfu"
|
||||
"github.com/livekit/livekit-server/pkg/utils"
|
||||
"github.com/livekit/livekit-server/proto/livekit"
|
||||
"github.com/pion/rtcp"
|
||||
webrtc "github.com/pion/webrtc/v3"
|
||||
)
|
||||
|
||||
type FakeParticipant struct {
|
||||
AddDownTrackStub func(string, *sfu.DownTrack)
|
||||
addDownTrackMutex sync.RWMutex
|
||||
addDownTrackArgsForCall []struct {
|
||||
arg1 string
|
||||
arg2 *sfu.DownTrack
|
||||
}
|
||||
AddICECandidateStub func(webrtc.ICECandidateInit) error
|
||||
addICECandidateMutex sync.RWMutex
|
||||
addICECandidateArgsForCall []struct {
|
||||
@@ -30,6 +23,12 @@ type FakeParticipant struct {
|
||||
addICECandidateReturnsOnCall map[int]struct {
|
||||
result1 error
|
||||
}
|
||||
AddSubscribedTrackStub func(string, types.SubscribedTrack)
|
||||
addSubscribedTrackMutex sync.RWMutex
|
||||
addSubscribedTrackArgsForCall []struct {
|
||||
arg1 string
|
||||
arg2 types.SubscribedTrack
|
||||
}
|
||||
AddSubscriberStub func(types.Participant) error
|
||||
addSubscriberMutex sync.RWMutex
|
||||
addSubscriberArgsForCall []struct {
|
||||
@@ -161,21 +160,21 @@ type FakeParticipant struct {
|
||||
peerConnectionReturnsOnCall map[int]struct {
|
||||
result1 types.PeerConnection
|
||||
}
|
||||
RTCPChanStub func() *utils.CalmChannel
|
||||
RTCPChanStub func() chan []rtcp.Packet
|
||||
rTCPChanMutex sync.RWMutex
|
||||
rTCPChanArgsForCall []struct {
|
||||
}
|
||||
rTCPChanReturns struct {
|
||||
result1 *utils.CalmChannel
|
||||
result1 chan []rtcp.Packet
|
||||
}
|
||||
rTCPChanReturnsOnCall map[int]struct {
|
||||
result1 *utils.CalmChannel
|
||||
result1 chan []rtcp.Packet
|
||||
}
|
||||
RemoveDownTrackStub func(string, *sfu.DownTrack)
|
||||
removeDownTrackMutex sync.RWMutex
|
||||
removeDownTrackArgsForCall []struct {
|
||||
RemoveSubscribedTrackStub func(string, types.SubscribedTrack)
|
||||
removeSubscribedTrackMutex sync.RWMutex
|
||||
removeSubscribedTrackArgsForCall []struct {
|
||||
arg1 string
|
||||
arg2 *sfu.DownTrack
|
||||
arg2 types.SubscribedTrack
|
||||
}
|
||||
RemoveSubscriberStub func(string)
|
||||
removeSubscriberMutex sync.RWMutex
|
||||
@@ -244,39 +243,6 @@ type FakeParticipant struct {
|
||||
invocationsMutex sync.RWMutex
|
||||
}
|
||||
|
||||
func (fake *FakeParticipant) AddDownTrack(arg1 string, arg2 *sfu.DownTrack) {
|
||||
fake.addDownTrackMutex.Lock()
|
||||
fake.addDownTrackArgsForCall = append(fake.addDownTrackArgsForCall, struct {
|
||||
arg1 string
|
||||
arg2 *sfu.DownTrack
|
||||
}{arg1, arg2})
|
||||
stub := fake.AddDownTrackStub
|
||||
fake.recordInvocation("AddDownTrack", []interface{}{arg1, arg2})
|
||||
fake.addDownTrackMutex.Unlock()
|
||||
if stub != nil {
|
||||
fake.AddDownTrackStub(arg1, arg2)
|
||||
}
|
||||
}
|
||||
|
||||
func (fake *FakeParticipant) AddDownTrackCallCount() int {
|
||||
fake.addDownTrackMutex.RLock()
|
||||
defer fake.addDownTrackMutex.RUnlock()
|
||||
return len(fake.addDownTrackArgsForCall)
|
||||
}
|
||||
|
||||
func (fake *FakeParticipant) AddDownTrackCalls(stub func(string, *sfu.DownTrack)) {
|
||||
fake.addDownTrackMutex.Lock()
|
||||
defer fake.addDownTrackMutex.Unlock()
|
||||
fake.AddDownTrackStub = stub
|
||||
}
|
||||
|
||||
func (fake *FakeParticipant) AddDownTrackArgsForCall(i int) (string, *sfu.DownTrack) {
|
||||
fake.addDownTrackMutex.RLock()
|
||||
defer fake.addDownTrackMutex.RUnlock()
|
||||
argsForCall := fake.addDownTrackArgsForCall[i]
|
||||
return argsForCall.arg1, argsForCall.arg2
|
||||
}
|
||||
|
||||
func (fake *FakeParticipant) AddICECandidate(arg1 webrtc.ICECandidateInit) error {
|
||||
fake.addICECandidateMutex.Lock()
|
||||
ret, specificReturn := fake.addICECandidateReturnsOnCall[len(fake.addICECandidateArgsForCall)]
|
||||
@@ -338,6 +304,39 @@ func (fake *FakeParticipant) AddICECandidateReturnsOnCall(i int, result1 error)
|
||||
}{result1}
|
||||
}
|
||||
|
||||
func (fake *FakeParticipant) AddSubscribedTrack(arg1 string, arg2 types.SubscribedTrack) {
|
||||
fake.addSubscribedTrackMutex.Lock()
|
||||
fake.addSubscribedTrackArgsForCall = append(fake.addSubscribedTrackArgsForCall, struct {
|
||||
arg1 string
|
||||
arg2 types.SubscribedTrack
|
||||
}{arg1, arg2})
|
||||
stub := fake.AddSubscribedTrackStub
|
||||
fake.recordInvocation("AddSubscribedTrack", []interface{}{arg1, arg2})
|
||||
fake.addSubscribedTrackMutex.Unlock()
|
||||
if stub != nil {
|
||||
fake.AddSubscribedTrackStub(arg1, arg2)
|
||||
}
|
||||
}
|
||||
|
||||
func (fake *FakeParticipant) AddSubscribedTrackCallCount() int {
|
||||
fake.addSubscribedTrackMutex.RLock()
|
||||
defer fake.addSubscribedTrackMutex.RUnlock()
|
||||
return len(fake.addSubscribedTrackArgsForCall)
|
||||
}
|
||||
|
||||
func (fake *FakeParticipant) AddSubscribedTrackCalls(stub func(string, types.SubscribedTrack)) {
|
||||
fake.addSubscribedTrackMutex.Lock()
|
||||
defer fake.addSubscribedTrackMutex.Unlock()
|
||||
fake.AddSubscribedTrackStub = stub
|
||||
}
|
||||
|
||||
func (fake *FakeParticipant) AddSubscribedTrackArgsForCall(i int) (string, types.SubscribedTrack) {
|
||||
fake.addSubscribedTrackMutex.RLock()
|
||||
defer fake.addSubscribedTrackMutex.RUnlock()
|
||||
argsForCall := fake.addSubscribedTrackArgsForCall[i]
|
||||
return argsForCall.arg1, argsForCall.arg2
|
||||
}
|
||||
|
||||
func (fake *FakeParticipant) AddSubscriber(arg1 types.Participant) error {
|
||||
fake.addSubscriberMutex.Lock()
|
||||
ret, specificReturn := fake.addSubscriberReturnsOnCall[len(fake.addSubscriberArgsForCall)]
|
||||
@@ -1060,7 +1059,7 @@ func (fake *FakeParticipant) PeerConnectionReturnsOnCall(i int, result1 types.Pe
|
||||
}{result1}
|
||||
}
|
||||
|
||||
func (fake *FakeParticipant) RTCPChan() *utils.CalmChannel {
|
||||
func (fake *FakeParticipant) RTCPChan() chan []rtcp.Packet {
|
||||
fake.rTCPChanMutex.Lock()
|
||||
ret, specificReturn := fake.rTCPChanReturnsOnCall[len(fake.rTCPChanArgsForCall)]
|
||||
fake.rTCPChanArgsForCall = append(fake.rTCPChanArgsForCall, struct {
|
||||
@@ -1084,65 +1083,65 @@ func (fake *FakeParticipant) RTCPChanCallCount() int {
|
||||
return len(fake.rTCPChanArgsForCall)
|
||||
}
|
||||
|
||||
func (fake *FakeParticipant) RTCPChanCalls(stub func() *utils.CalmChannel) {
|
||||
func (fake *FakeParticipant) RTCPChanCalls(stub func() chan []rtcp.Packet) {
|
||||
fake.rTCPChanMutex.Lock()
|
||||
defer fake.rTCPChanMutex.Unlock()
|
||||
fake.RTCPChanStub = stub
|
||||
}
|
||||
|
||||
func (fake *FakeParticipant) RTCPChanReturns(result1 *utils.CalmChannel) {
|
||||
func (fake *FakeParticipant) RTCPChanReturns(result1 chan []rtcp.Packet) {
|
||||
fake.rTCPChanMutex.Lock()
|
||||
defer fake.rTCPChanMutex.Unlock()
|
||||
fake.RTCPChanStub = nil
|
||||
fake.rTCPChanReturns = struct {
|
||||
result1 *utils.CalmChannel
|
||||
result1 chan []rtcp.Packet
|
||||
}{result1}
|
||||
}
|
||||
|
||||
func (fake *FakeParticipant) RTCPChanReturnsOnCall(i int, result1 *utils.CalmChannel) {
|
||||
func (fake *FakeParticipant) RTCPChanReturnsOnCall(i int, result1 chan []rtcp.Packet) {
|
||||
fake.rTCPChanMutex.Lock()
|
||||
defer fake.rTCPChanMutex.Unlock()
|
||||
fake.RTCPChanStub = nil
|
||||
if fake.rTCPChanReturnsOnCall == nil {
|
||||
fake.rTCPChanReturnsOnCall = make(map[int]struct {
|
||||
result1 *utils.CalmChannel
|
||||
result1 chan []rtcp.Packet
|
||||
})
|
||||
}
|
||||
fake.rTCPChanReturnsOnCall[i] = struct {
|
||||
result1 *utils.CalmChannel
|
||||
result1 chan []rtcp.Packet
|
||||
}{result1}
|
||||
}
|
||||
|
||||
func (fake *FakeParticipant) RemoveDownTrack(arg1 string, arg2 *sfu.DownTrack) {
|
||||
fake.removeDownTrackMutex.Lock()
|
||||
fake.removeDownTrackArgsForCall = append(fake.removeDownTrackArgsForCall, struct {
|
||||
func (fake *FakeParticipant) RemoveSubscribedTrack(arg1 string, arg2 types.SubscribedTrack) {
|
||||
fake.removeSubscribedTrackMutex.Lock()
|
||||
fake.removeSubscribedTrackArgsForCall = append(fake.removeSubscribedTrackArgsForCall, struct {
|
||||
arg1 string
|
||||
arg2 *sfu.DownTrack
|
||||
arg2 types.SubscribedTrack
|
||||
}{arg1, arg2})
|
||||
stub := fake.RemoveDownTrackStub
|
||||
fake.recordInvocation("RemoveDownTrack", []interface{}{arg1, arg2})
|
||||
fake.removeDownTrackMutex.Unlock()
|
||||
stub := fake.RemoveSubscribedTrackStub
|
||||
fake.recordInvocation("RemoveSubscribedTrack", []interface{}{arg1, arg2})
|
||||
fake.removeSubscribedTrackMutex.Unlock()
|
||||
if stub != nil {
|
||||
fake.RemoveDownTrackStub(arg1, arg2)
|
||||
fake.RemoveSubscribedTrackStub(arg1, arg2)
|
||||
}
|
||||
}
|
||||
|
||||
func (fake *FakeParticipant) RemoveDownTrackCallCount() int {
|
||||
fake.removeDownTrackMutex.RLock()
|
||||
defer fake.removeDownTrackMutex.RUnlock()
|
||||
return len(fake.removeDownTrackArgsForCall)
|
||||
func (fake *FakeParticipant) RemoveSubscribedTrackCallCount() int {
|
||||
fake.removeSubscribedTrackMutex.RLock()
|
||||
defer fake.removeSubscribedTrackMutex.RUnlock()
|
||||
return len(fake.removeSubscribedTrackArgsForCall)
|
||||
}
|
||||
|
||||
func (fake *FakeParticipant) RemoveDownTrackCalls(stub func(string, *sfu.DownTrack)) {
|
||||
fake.removeDownTrackMutex.Lock()
|
||||
defer fake.removeDownTrackMutex.Unlock()
|
||||
fake.RemoveDownTrackStub = stub
|
||||
func (fake *FakeParticipant) RemoveSubscribedTrackCalls(stub func(string, types.SubscribedTrack)) {
|
||||
fake.removeSubscribedTrackMutex.Lock()
|
||||
defer fake.removeSubscribedTrackMutex.Unlock()
|
||||
fake.RemoveSubscribedTrackStub = stub
|
||||
}
|
||||
|
||||
func (fake *FakeParticipant) RemoveDownTrackArgsForCall(i int) (string, *sfu.DownTrack) {
|
||||
fake.removeDownTrackMutex.RLock()
|
||||
defer fake.removeDownTrackMutex.RUnlock()
|
||||
argsForCall := fake.removeDownTrackArgsForCall[i]
|
||||
func (fake *FakeParticipant) RemoveSubscribedTrackArgsForCall(i int) (string, types.SubscribedTrack) {
|
||||
fake.removeSubscribedTrackMutex.RLock()
|
||||
defer fake.removeSubscribedTrackMutex.RUnlock()
|
||||
argsForCall := fake.removeSubscribedTrackArgsForCall[i]
|
||||
return argsForCall.arg1, argsForCall.arg2
|
||||
}
|
||||
|
||||
@@ -1509,10 +1508,10 @@ func (fake *FakeParticipant) ToProtoReturnsOnCall(i int, result1 *livekit.Partic
|
||||
func (fake *FakeParticipant) Invocations() map[string][][]interface{} {
|
||||
fake.invocationsMutex.RLock()
|
||||
defer fake.invocationsMutex.RUnlock()
|
||||
fake.addDownTrackMutex.RLock()
|
||||
defer fake.addDownTrackMutex.RUnlock()
|
||||
fake.addICECandidateMutex.RLock()
|
||||
defer fake.addICECandidateMutex.RUnlock()
|
||||
fake.addSubscribedTrackMutex.RLock()
|
||||
defer fake.addSubscribedTrackMutex.RUnlock()
|
||||
fake.addSubscriberMutex.RLock()
|
||||
defer fake.addSubscriberMutex.RUnlock()
|
||||
fake.addTrackMutex.RLock()
|
||||
@@ -1547,8 +1546,8 @@ func (fake *FakeParticipant) Invocations() map[string][][]interface{} {
|
||||
defer fake.peerConnectionMutex.RUnlock()
|
||||
fake.rTCPChanMutex.RLock()
|
||||
defer fake.rTCPChanMutex.RUnlock()
|
||||
fake.removeDownTrackMutex.RLock()
|
||||
defer fake.removeDownTrackMutex.RUnlock()
|
||||
fake.removeSubscribedTrackMutex.RLock()
|
||||
defer fake.removeSubscribedTrackMutex.RUnlock()
|
||||
fake.removeSubscriberMutex.RLock()
|
||||
defer fake.removeSubscriberMutex.RUnlock()
|
||||
fake.sendJoinResponseMutex.RLock()
|
||||
|
||||
@@ -74,6 +74,11 @@ type FakePublishedTrack struct {
|
||||
removeSubscriberArgsForCall []struct {
|
||||
arg1 string
|
||||
}
|
||||
SetMutedStub func(bool)
|
||||
setMutedMutex sync.RWMutex
|
||||
setMutedArgsForCall []struct {
|
||||
arg1 bool
|
||||
}
|
||||
StartStub func()
|
||||
startMutex sync.RWMutex
|
||||
startArgsForCall []struct {
|
||||
@@ -443,6 +448,38 @@ func (fake *FakePublishedTrack) RemoveSubscriberArgsForCall(i int) string {
|
||||
return argsForCall.arg1
|
||||
}
|
||||
|
||||
func (fake *FakePublishedTrack) SetMuted(arg1 bool) {
|
||||
fake.setMutedMutex.Lock()
|
||||
fake.setMutedArgsForCall = append(fake.setMutedArgsForCall, struct {
|
||||
arg1 bool
|
||||
}{arg1})
|
||||
stub := fake.SetMutedStub
|
||||
fake.recordInvocation("SetMuted", []interface{}{arg1})
|
||||
fake.setMutedMutex.Unlock()
|
||||
if stub != nil {
|
||||
fake.SetMutedStub(arg1)
|
||||
}
|
||||
}
|
||||
|
||||
func (fake *FakePublishedTrack) SetMutedCallCount() int {
|
||||
fake.setMutedMutex.RLock()
|
||||
defer fake.setMutedMutex.RUnlock()
|
||||
return len(fake.setMutedArgsForCall)
|
||||
}
|
||||
|
||||
func (fake *FakePublishedTrack) SetMutedCalls(stub func(bool)) {
|
||||
fake.setMutedMutex.Lock()
|
||||
defer fake.setMutedMutex.Unlock()
|
||||
fake.SetMutedStub = stub
|
||||
}
|
||||
|
||||
func (fake *FakePublishedTrack) SetMutedArgsForCall(i int) bool {
|
||||
fake.setMutedMutex.RLock()
|
||||
defer fake.setMutedMutex.RUnlock()
|
||||
argsForCall := fake.setMutedArgsForCall[i]
|
||||
return argsForCall.arg1
|
||||
}
|
||||
|
||||
func (fake *FakePublishedTrack) Start() {
|
||||
fake.startMutex.Lock()
|
||||
fake.startArgsForCall = append(fake.startArgsForCall, struct {
|
||||
@@ -486,6 +523,8 @@ func (fake *FakePublishedTrack) Invocations() map[string][][]interface{} {
|
||||
defer fake.removeAllSubscribersMutex.RUnlock()
|
||||
fake.removeSubscriberMutex.RLock()
|
||||
defer fake.removeSubscriberMutex.RUnlock()
|
||||
fake.setMutedMutex.RLock()
|
||||
defer fake.setMutedMutex.RUnlock()
|
||||
fake.startMutex.RLock()
|
||||
defer fake.startMutex.RUnlock()
|
||||
copiedInvocations := map[string][][]interface{}{}
|
||||
|
||||
@@ -1,191 +0,0 @@
|
||||
// Code generated by counterfeiter. DO NOT EDIT.
|
||||
package typesfakes
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/livekit/livekit-server/pkg/rtc/types"
|
||||
"github.com/pion/rtp"
|
||||
)
|
||||
|
||||
type FakeReceiver struct {
|
||||
GetBufferedPacketStub func([]byte, uint16, uint16) (rtp.Packet, error)
|
||||
getBufferedPacketMutex sync.RWMutex
|
||||
getBufferedPacketArgsForCall []struct {
|
||||
arg1 []byte
|
||||
arg2 uint16
|
||||
arg3 uint16
|
||||
}
|
||||
getBufferedPacketReturns struct {
|
||||
result1 rtp.Packet
|
||||
result2 error
|
||||
}
|
||||
getBufferedPacketReturnsOnCall map[int]struct {
|
||||
result1 rtp.Packet
|
||||
result2 error
|
||||
}
|
||||
RTPChanStub func() <-chan rtp.Packet
|
||||
rTPChanMutex sync.RWMutex
|
||||
rTPChanArgsForCall []struct {
|
||||
}
|
||||
rTPChanReturns struct {
|
||||
result1 <-chan rtp.Packet
|
||||
}
|
||||
rTPChanReturnsOnCall map[int]struct {
|
||||
result1 <-chan rtp.Packet
|
||||
}
|
||||
invocations map[string][][]interface{}
|
||||
invocationsMutex sync.RWMutex
|
||||
}
|
||||
|
||||
func (fake *FakeReceiver) GetBufferedPacket(arg1 []byte, arg2 uint16, arg3 uint16) (rtp.Packet, error) {
|
||||
var arg1Copy []byte
|
||||
if arg1 != nil {
|
||||
arg1Copy = make([]byte, len(arg1))
|
||||
copy(arg1Copy, arg1)
|
||||
}
|
||||
fake.getBufferedPacketMutex.Lock()
|
||||
ret, specificReturn := fake.getBufferedPacketReturnsOnCall[len(fake.getBufferedPacketArgsForCall)]
|
||||
fake.getBufferedPacketArgsForCall = append(fake.getBufferedPacketArgsForCall, struct {
|
||||
arg1 []byte
|
||||
arg2 uint16
|
||||
arg3 uint16
|
||||
}{arg1Copy, arg2, arg3})
|
||||
stub := fake.GetBufferedPacketStub
|
||||
fakeReturns := fake.getBufferedPacketReturns
|
||||
fake.recordInvocation("GetBufferedPacket", []interface{}{arg1Copy, arg2, arg3})
|
||||
fake.getBufferedPacketMutex.Unlock()
|
||||
if stub != nil {
|
||||
return stub(arg1, arg2, arg3)
|
||||
}
|
||||
if specificReturn {
|
||||
return ret.result1, ret.result2
|
||||
}
|
||||
return fakeReturns.result1, fakeReturns.result2
|
||||
}
|
||||
|
||||
func (fake *FakeReceiver) GetBufferedPacketCallCount() int {
|
||||
fake.getBufferedPacketMutex.RLock()
|
||||
defer fake.getBufferedPacketMutex.RUnlock()
|
||||
return len(fake.getBufferedPacketArgsForCall)
|
||||
}
|
||||
|
||||
func (fake *FakeReceiver) GetBufferedPacketCalls(stub func([]byte, uint16, uint16) (rtp.Packet, error)) {
|
||||
fake.getBufferedPacketMutex.Lock()
|
||||
defer fake.getBufferedPacketMutex.Unlock()
|
||||
fake.GetBufferedPacketStub = stub
|
||||
}
|
||||
|
||||
func (fake *FakeReceiver) GetBufferedPacketArgsForCall(i int) ([]byte, uint16, uint16) {
|
||||
fake.getBufferedPacketMutex.RLock()
|
||||
defer fake.getBufferedPacketMutex.RUnlock()
|
||||
argsForCall := fake.getBufferedPacketArgsForCall[i]
|
||||
return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3
|
||||
}
|
||||
|
||||
func (fake *FakeReceiver) GetBufferedPacketReturns(result1 rtp.Packet, result2 error) {
|
||||
fake.getBufferedPacketMutex.Lock()
|
||||
defer fake.getBufferedPacketMutex.Unlock()
|
||||
fake.GetBufferedPacketStub = nil
|
||||
fake.getBufferedPacketReturns = struct {
|
||||
result1 rtp.Packet
|
||||
result2 error
|
||||
}{result1, result2}
|
||||
}
|
||||
|
||||
func (fake *FakeReceiver) GetBufferedPacketReturnsOnCall(i int, result1 rtp.Packet, result2 error) {
|
||||
fake.getBufferedPacketMutex.Lock()
|
||||
defer fake.getBufferedPacketMutex.Unlock()
|
||||
fake.GetBufferedPacketStub = nil
|
||||
if fake.getBufferedPacketReturnsOnCall == nil {
|
||||
fake.getBufferedPacketReturnsOnCall = make(map[int]struct {
|
||||
result1 rtp.Packet
|
||||
result2 error
|
||||
})
|
||||
}
|
||||
fake.getBufferedPacketReturnsOnCall[i] = struct {
|
||||
result1 rtp.Packet
|
||||
result2 error
|
||||
}{result1, result2}
|
||||
}
|
||||
|
||||
func (fake *FakeReceiver) RTPChan() <-chan rtp.Packet {
|
||||
fake.rTPChanMutex.Lock()
|
||||
ret, specificReturn := fake.rTPChanReturnsOnCall[len(fake.rTPChanArgsForCall)]
|
||||
fake.rTPChanArgsForCall = append(fake.rTPChanArgsForCall, struct {
|
||||
}{})
|
||||
stub := fake.RTPChanStub
|
||||
fakeReturns := fake.rTPChanReturns
|
||||
fake.recordInvocation("RTPChan", []interface{}{})
|
||||
fake.rTPChanMutex.Unlock()
|
||||
if stub != nil {
|
||||
return stub()
|
||||
}
|
||||
if specificReturn {
|
||||
return ret.result1
|
||||
}
|
||||
return fakeReturns.result1
|
||||
}
|
||||
|
||||
func (fake *FakeReceiver) RTPChanCallCount() int {
|
||||
fake.rTPChanMutex.RLock()
|
||||
defer fake.rTPChanMutex.RUnlock()
|
||||
return len(fake.rTPChanArgsForCall)
|
||||
}
|
||||
|
||||
func (fake *FakeReceiver) RTPChanCalls(stub func() <-chan rtp.Packet) {
|
||||
fake.rTPChanMutex.Lock()
|
||||
defer fake.rTPChanMutex.Unlock()
|
||||
fake.RTPChanStub = stub
|
||||
}
|
||||
|
||||
func (fake *FakeReceiver) RTPChanReturns(result1 <-chan rtp.Packet) {
|
||||
fake.rTPChanMutex.Lock()
|
||||
defer fake.rTPChanMutex.Unlock()
|
||||
fake.RTPChanStub = nil
|
||||
fake.rTPChanReturns = struct {
|
||||
result1 <-chan rtp.Packet
|
||||
}{result1}
|
||||
}
|
||||
|
||||
func (fake *FakeReceiver) RTPChanReturnsOnCall(i int, result1 <-chan rtp.Packet) {
|
||||
fake.rTPChanMutex.Lock()
|
||||
defer fake.rTPChanMutex.Unlock()
|
||||
fake.RTPChanStub = nil
|
||||
if fake.rTPChanReturnsOnCall == nil {
|
||||
fake.rTPChanReturnsOnCall = make(map[int]struct {
|
||||
result1 <-chan rtp.Packet
|
||||
})
|
||||
}
|
||||
fake.rTPChanReturnsOnCall[i] = struct {
|
||||
result1 <-chan rtp.Packet
|
||||
}{result1}
|
||||
}
|
||||
|
||||
func (fake *FakeReceiver) Invocations() map[string][][]interface{} {
|
||||
fake.invocationsMutex.RLock()
|
||||
defer fake.invocationsMutex.RUnlock()
|
||||
fake.getBufferedPacketMutex.RLock()
|
||||
defer fake.getBufferedPacketMutex.RUnlock()
|
||||
fake.rTPChanMutex.RLock()
|
||||
defer fake.rTPChanMutex.RUnlock()
|
||||
copiedInvocations := map[string][][]interface{}{}
|
||||
for key, value := range fake.invocations {
|
||||
copiedInvocations[key] = value
|
||||
}
|
||||
return copiedInvocations
|
||||
}
|
||||
|
||||
func (fake *FakeReceiver) recordInvocation(key string, args []interface{}) {
|
||||
fake.invocationsMutex.Lock()
|
||||
defer fake.invocationsMutex.Unlock()
|
||||
if fake.invocations == nil {
|
||||
fake.invocations = map[string][][]interface{}{}
|
||||
}
|
||||
if fake.invocations[key] == nil {
|
||||
fake.invocations[key] = [][]interface{}{}
|
||||
}
|
||||
fake.invocations[key] = append(fake.invocations[key], args)
|
||||
}
|
||||
|
||||
var _ types.Receiver = new(FakeReceiver)
|
||||
@@ -0,0 +1,276 @@
|
||||
// Code generated by counterfeiter. DO NOT EDIT.
|
||||
package typesfakes
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/livekit/livekit-server/pkg/rtc/types"
|
||||
"github.com/pion/ion-sfu/pkg/sfu"
|
||||
)
|
||||
|
||||
type FakeSubscribedTrack struct {
|
||||
DownTrackStub func() *sfu.DownTrack
|
||||
downTrackMutex sync.RWMutex
|
||||
downTrackArgsForCall []struct {
|
||||
}
|
||||
downTrackReturns struct {
|
||||
result1 *sfu.DownTrack
|
||||
}
|
||||
downTrackReturnsOnCall map[int]struct {
|
||||
result1 *sfu.DownTrack
|
||||
}
|
||||
IsMutedStub func() bool
|
||||
isMutedMutex sync.RWMutex
|
||||
isMutedArgsForCall []struct {
|
||||
}
|
||||
isMutedReturns struct {
|
||||
result1 bool
|
||||
}
|
||||
isMutedReturnsOnCall map[int]struct {
|
||||
result1 bool
|
||||
}
|
||||
ResyncStub func()
|
||||
resyncMutex sync.RWMutex
|
||||
resyncArgsForCall []struct {
|
||||
}
|
||||
SetMutedStub func(bool)
|
||||
setMutedMutex sync.RWMutex
|
||||
setMutedArgsForCall []struct {
|
||||
arg1 bool
|
||||
}
|
||||
SetPublisherMutedStub func(bool)
|
||||
setPublisherMutedMutex sync.RWMutex
|
||||
setPublisherMutedArgsForCall []struct {
|
||||
arg1 bool
|
||||
}
|
||||
invocations map[string][][]interface{}
|
||||
invocationsMutex sync.RWMutex
|
||||
}
|
||||
|
||||
func (fake *FakeSubscribedTrack) DownTrack() *sfu.DownTrack {
|
||||
fake.downTrackMutex.Lock()
|
||||
ret, specificReturn := fake.downTrackReturnsOnCall[len(fake.downTrackArgsForCall)]
|
||||
fake.downTrackArgsForCall = append(fake.downTrackArgsForCall, struct {
|
||||
}{})
|
||||
stub := fake.DownTrackStub
|
||||
fakeReturns := fake.downTrackReturns
|
||||
fake.recordInvocation("DownTrack", []interface{}{})
|
||||
fake.downTrackMutex.Unlock()
|
||||
if stub != nil {
|
||||
return stub()
|
||||
}
|
||||
if specificReturn {
|
||||
return ret.result1
|
||||
}
|
||||
return fakeReturns.result1
|
||||
}
|
||||
|
||||
func (fake *FakeSubscribedTrack) DownTrackCallCount() int {
|
||||
fake.downTrackMutex.RLock()
|
||||
defer fake.downTrackMutex.RUnlock()
|
||||
return len(fake.downTrackArgsForCall)
|
||||
}
|
||||
|
||||
func (fake *FakeSubscribedTrack) DownTrackCalls(stub func() *sfu.DownTrack) {
|
||||
fake.downTrackMutex.Lock()
|
||||
defer fake.downTrackMutex.Unlock()
|
||||
fake.DownTrackStub = stub
|
||||
}
|
||||
|
||||
func (fake *FakeSubscribedTrack) DownTrackReturns(result1 *sfu.DownTrack) {
|
||||
fake.downTrackMutex.Lock()
|
||||
defer fake.downTrackMutex.Unlock()
|
||||
fake.DownTrackStub = nil
|
||||
fake.downTrackReturns = struct {
|
||||
result1 *sfu.DownTrack
|
||||
}{result1}
|
||||
}
|
||||
|
||||
func (fake *FakeSubscribedTrack) DownTrackReturnsOnCall(i int, result1 *sfu.DownTrack) {
|
||||
fake.downTrackMutex.Lock()
|
||||
defer fake.downTrackMutex.Unlock()
|
||||
fake.DownTrackStub = nil
|
||||
if fake.downTrackReturnsOnCall == nil {
|
||||
fake.downTrackReturnsOnCall = make(map[int]struct {
|
||||
result1 *sfu.DownTrack
|
||||
})
|
||||
}
|
||||
fake.downTrackReturnsOnCall[i] = struct {
|
||||
result1 *sfu.DownTrack
|
||||
}{result1}
|
||||
}
|
||||
|
||||
func (fake *FakeSubscribedTrack) IsMuted() bool {
|
||||
fake.isMutedMutex.Lock()
|
||||
ret, specificReturn := fake.isMutedReturnsOnCall[len(fake.isMutedArgsForCall)]
|
||||
fake.isMutedArgsForCall = append(fake.isMutedArgsForCall, struct {
|
||||
}{})
|
||||
stub := fake.IsMutedStub
|
||||
fakeReturns := fake.isMutedReturns
|
||||
fake.recordInvocation("IsMuted", []interface{}{})
|
||||
fake.isMutedMutex.Unlock()
|
||||
if stub != nil {
|
||||
return stub()
|
||||
}
|
||||
if specificReturn {
|
||||
return ret.result1
|
||||
}
|
||||
return fakeReturns.result1
|
||||
}
|
||||
|
||||
func (fake *FakeSubscribedTrack) IsMutedCallCount() int {
|
||||
fake.isMutedMutex.RLock()
|
||||
defer fake.isMutedMutex.RUnlock()
|
||||
return len(fake.isMutedArgsForCall)
|
||||
}
|
||||
|
||||
func (fake *FakeSubscribedTrack) IsMutedCalls(stub func() bool) {
|
||||
fake.isMutedMutex.Lock()
|
||||
defer fake.isMutedMutex.Unlock()
|
||||
fake.IsMutedStub = stub
|
||||
}
|
||||
|
||||
func (fake *FakeSubscribedTrack) IsMutedReturns(result1 bool) {
|
||||
fake.isMutedMutex.Lock()
|
||||
defer fake.isMutedMutex.Unlock()
|
||||
fake.IsMutedStub = nil
|
||||
fake.isMutedReturns = struct {
|
||||
result1 bool
|
||||
}{result1}
|
||||
}
|
||||
|
||||
func (fake *FakeSubscribedTrack) IsMutedReturnsOnCall(i int, result1 bool) {
|
||||
fake.isMutedMutex.Lock()
|
||||
defer fake.isMutedMutex.Unlock()
|
||||
fake.IsMutedStub = nil
|
||||
if fake.isMutedReturnsOnCall == nil {
|
||||
fake.isMutedReturnsOnCall = make(map[int]struct {
|
||||
result1 bool
|
||||
})
|
||||
}
|
||||
fake.isMutedReturnsOnCall[i] = struct {
|
||||
result1 bool
|
||||
}{result1}
|
||||
}
|
||||
|
||||
func (fake *FakeSubscribedTrack) Resync() {
|
||||
fake.resyncMutex.Lock()
|
||||
fake.resyncArgsForCall = append(fake.resyncArgsForCall, struct {
|
||||
}{})
|
||||
stub := fake.ResyncStub
|
||||
fake.recordInvocation("Resync", []interface{}{})
|
||||
fake.resyncMutex.Unlock()
|
||||
if stub != nil {
|
||||
fake.ResyncStub()
|
||||
}
|
||||
}
|
||||
|
||||
func (fake *FakeSubscribedTrack) ResyncCallCount() int {
|
||||
fake.resyncMutex.RLock()
|
||||
defer fake.resyncMutex.RUnlock()
|
||||
return len(fake.resyncArgsForCall)
|
||||
}
|
||||
|
||||
func (fake *FakeSubscribedTrack) ResyncCalls(stub func()) {
|
||||
fake.resyncMutex.Lock()
|
||||
defer fake.resyncMutex.Unlock()
|
||||
fake.ResyncStub = stub
|
||||
}
|
||||
|
||||
func (fake *FakeSubscribedTrack) SetMuted(arg1 bool) {
|
||||
fake.setMutedMutex.Lock()
|
||||
fake.setMutedArgsForCall = append(fake.setMutedArgsForCall, struct {
|
||||
arg1 bool
|
||||
}{arg1})
|
||||
stub := fake.SetMutedStub
|
||||
fake.recordInvocation("SetMuted", []interface{}{arg1})
|
||||
fake.setMutedMutex.Unlock()
|
||||
if stub != nil {
|
||||
fake.SetMutedStub(arg1)
|
||||
}
|
||||
}
|
||||
|
||||
func (fake *FakeSubscribedTrack) SetMutedCallCount() int {
|
||||
fake.setMutedMutex.RLock()
|
||||
defer fake.setMutedMutex.RUnlock()
|
||||
return len(fake.setMutedArgsForCall)
|
||||
}
|
||||
|
||||
func (fake *FakeSubscribedTrack) SetMutedCalls(stub func(bool)) {
|
||||
fake.setMutedMutex.Lock()
|
||||
defer fake.setMutedMutex.Unlock()
|
||||
fake.SetMutedStub = stub
|
||||
}
|
||||
|
||||
func (fake *FakeSubscribedTrack) SetMutedArgsForCall(i int) bool {
|
||||
fake.setMutedMutex.RLock()
|
||||
defer fake.setMutedMutex.RUnlock()
|
||||
argsForCall := fake.setMutedArgsForCall[i]
|
||||
return argsForCall.arg1
|
||||
}
|
||||
|
||||
func (fake *FakeSubscribedTrack) SetPublisherMuted(arg1 bool) {
|
||||
fake.setPublisherMutedMutex.Lock()
|
||||
fake.setPublisherMutedArgsForCall = append(fake.setPublisherMutedArgsForCall, struct {
|
||||
arg1 bool
|
||||
}{arg1})
|
||||
stub := fake.SetPublisherMutedStub
|
||||
fake.recordInvocation("SetPublisherMuted", []interface{}{arg1})
|
||||
fake.setPublisherMutedMutex.Unlock()
|
||||
if stub != nil {
|
||||
fake.SetPublisherMutedStub(arg1)
|
||||
}
|
||||
}
|
||||
|
||||
func (fake *FakeSubscribedTrack) SetPublisherMutedCallCount() int {
|
||||
fake.setPublisherMutedMutex.RLock()
|
||||
defer fake.setPublisherMutedMutex.RUnlock()
|
||||
return len(fake.setPublisherMutedArgsForCall)
|
||||
}
|
||||
|
||||
func (fake *FakeSubscribedTrack) SetPublisherMutedCalls(stub func(bool)) {
|
||||
fake.setPublisherMutedMutex.Lock()
|
||||
defer fake.setPublisherMutedMutex.Unlock()
|
||||
fake.SetPublisherMutedStub = stub
|
||||
}
|
||||
|
||||
func (fake *FakeSubscribedTrack) SetPublisherMutedArgsForCall(i int) bool {
|
||||
fake.setPublisherMutedMutex.RLock()
|
||||
defer fake.setPublisherMutedMutex.RUnlock()
|
||||
argsForCall := fake.setPublisherMutedArgsForCall[i]
|
||||
return argsForCall.arg1
|
||||
}
|
||||
|
||||
func (fake *FakeSubscribedTrack) Invocations() map[string][][]interface{} {
|
||||
fake.invocationsMutex.RLock()
|
||||
defer fake.invocationsMutex.RUnlock()
|
||||
fake.downTrackMutex.RLock()
|
||||
defer fake.downTrackMutex.RUnlock()
|
||||
fake.isMutedMutex.RLock()
|
||||
defer fake.isMutedMutex.RUnlock()
|
||||
fake.resyncMutex.RLock()
|
||||
defer fake.resyncMutex.RUnlock()
|
||||
fake.setMutedMutex.RLock()
|
||||
defer fake.setMutedMutex.RUnlock()
|
||||
fake.setPublisherMutedMutex.RLock()
|
||||
defer fake.setPublisherMutedMutex.RUnlock()
|
||||
copiedInvocations := map[string][][]interface{}{}
|
||||
for key, value := range fake.invocations {
|
||||
copiedInvocations[key] = value
|
||||
}
|
||||
return copiedInvocations
|
||||
}
|
||||
|
||||
func (fake *FakeSubscribedTrack) recordInvocation(key string, args []interface{}) {
|
||||
fake.invocationsMutex.Lock()
|
||||
defer fake.invocationsMutex.Unlock()
|
||||
if fake.invocations == nil {
|
||||
fake.invocations = map[string][][]interface{}{}
|
||||
}
|
||||
if fake.invocations[key] == nil {
|
||||
fake.invocations[key] = [][]interface{}{}
|
||||
}
|
||||
fake.invocations[key] = append(fake.invocations[key], args)
|
||||
}
|
||||
|
||||
var _ types.SubscribedTrack = new(FakeSubscribedTrack)
|
||||
@@ -0,0 +1,23 @@
|
||||
package rtc
|
||||
|
||||
import (
|
||||
"github.com/pion/ion-sfu/pkg/sfu"
|
||||
)
|
||||
|
||||
// wrapper around WebRTC receiver, overriding its ID
|
||||
|
||||
type WrappedReceiver struct {
|
||||
sfu.Receiver
|
||||
trackId string
|
||||
}
|
||||
|
||||
func NewWrappedReceiver(receiver sfu.Receiver, trackId string) WrappedReceiver {
|
||||
return WrappedReceiver{
|
||||
Receiver: receiver,
|
||||
trackId: trackId,
|
||||
}
|
||||
}
|
||||
|
||||
func (r WrappedReceiver) TrackID() string {
|
||||
return r.trackId
|
||||
}
|
||||
@@ -253,7 +253,7 @@ func (r *RoomManager) rtcSessionWorker(room *rtc.Room, participant types.Partici
|
||||
case *livekit.SignalRequest_Offer:
|
||||
_, err := participant.Answer(rtc.FromProtoSessionDescription(msg.Offer))
|
||||
if err != nil {
|
||||
logger.Errorw("could not handle join", "err", err, "participant", participant.ID())
|
||||
logger.Errorw("could not handle offer", "err", err, "participant", participant.Identity())
|
||||
return
|
||||
}
|
||||
case *livekit.SignalRequest_AddTrack:
|
||||
@@ -262,13 +262,13 @@ func (r *RoomManager) rtcSessionWorker(room *rtc.Room, participant types.Partici
|
||||
participant.AddTrack(msg.AddTrack.Cid, msg.AddTrack.Name, msg.AddTrack.Type)
|
||||
case *livekit.SignalRequest_Answer:
|
||||
if participant.State() == livekit.ParticipantInfo_JOINING {
|
||||
logger.Errorw("cannot negotiate before peer offer", "participant", participant.ID())
|
||||
logger.Errorw("cannot negotiate before peer offer", "participant", participant.Identity())
|
||||
//conn.WriteJSON(jsonError(http.StatusNotAcceptable, "cannot negotiate before peer offer"))
|
||||
return
|
||||
}
|
||||
sd := rtc.FromProtoSessionDescription(msg.Answer)
|
||||
if err := participant.HandleAnswer(sd); err != nil {
|
||||
logger.Errorw("could not handle answer", "participant", participant.ID(), "err", err)
|
||||
logger.Errorw("could not handle answer", "participant", participant.Identity(), "err", err)
|
||||
//conn.WriteJSON(
|
||||
// jsonError(http.StatusInternalServerError, "could not handle negotiate", err.Error()))
|
||||
return
|
||||
@@ -277,7 +277,7 @@ func (r *RoomManager) rtcSessionWorker(room *rtc.Room, participant types.Partici
|
||||
participant.HandleClientNegotiation()
|
||||
case *livekit.SignalRequest_Trickle:
|
||||
if participant.State() == livekit.ParticipantInfo_JOINING {
|
||||
logger.Errorw("cannot trickle before peer offer", "participant", participant.ID())
|
||||
logger.Errorw("cannot trickle before offer", "participant", participant.Identity())
|
||||
//conn.WriteJSON(jsonError(http.StatusNotAcceptable, "cannot trickle before peer offer"))
|
||||
return
|
||||
}
|
||||
@@ -285,7 +285,7 @@ func (r *RoomManager) rtcSessionWorker(room *rtc.Room, participant types.Partici
|
||||
candidateInit := rtc.FromProtoTrickle(msg.Trickle)
|
||||
//logger.Debugw("adding peer candidate", "participant", participant.ID())
|
||||
if err := participant.AddICECandidate(candidateInit); err != nil {
|
||||
logger.Errorw("could not handle trickle", "participant", participant.ID(), "err", err)
|
||||
logger.Errorw("could not handle trickle", "participant", participant.Identity(), "err", err)
|
||||
//conn.WriteJSON(
|
||||
// jsonError(http.StatusInternalServerError, "could not handle trickle", err.Error()))
|
||||
return
|
||||
|
||||
@@ -118,11 +118,14 @@ func (s *RTCService) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
case msg := <-resSource.ReadChan():
|
||||
if msg == nil {
|
||||
logger.Errorw("source closed connection", "participant", identity)
|
||||
return
|
||||
}
|
||||
res, ok := msg.(*livekit.SignalResponse)
|
||||
if !ok {
|
||||
logger.Errorw("unexpected message type", "type", fmt.Sprintf("%T", msg))
|
||||
logger.Errorw("unexpected message type",
|
||||
"type", fmt.Sprintf("%T", msg),
|
||||
"participant", identity)
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
@@ -69,7 +69,12 @@ func NewLivekitServer(conf *config.Config,
|
||||
router.OnNewParticipantRTC(roomManager.StartSession)
|
||||
|
||||
// clean up old rooms on startup
|
||||
err = roomManager.CleanupRooms()
|
||||
if err = roomManager.CleanupRooms(); err != nil {
|
||||
return
|
||||
}
|
||||
if err = router.RemoveDeadNodes(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
This package is largely files from the wonderful ion-sfu project.
|
||||
|
||||
https://github.com/pion/ion-sfu
|
||||
|
||||
It's duplicated here with modifications to reference only classes that we need
|
||||
@@ -1,271 +0,0 @@
|
||||
package sfu
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
log "github.com/pion/ion-log"
|
||||
"github.com/pion/rtcp"
|
||||
"github.com/pion/rtp"
|
||||
"github.com/pion/webrtc/v3"
|
||||
)
|
||||
|
||||
// DownTrack implements TrackLocal, is the track used to write packets
|
||||
// to SFU Subscriber, the track handle the packets for simple, simulcast
|
||||
// and SVC Publisher.
|
||||
type DownTrack struct {
|
||||
id string
|
||||
bound atomicBool
|
||||
mime string
|
||||
nList *nackList
|
||||
ssrc uint32
|
||||
payload uint8
|
||||
streamID string
|
||||
|
||||
//enabled atomicBool
|
||||
reSync atomicBool
|
||||
snOffset uint16
|
||||
tsOffset uint32
|
||||
lastSSRC uint32
|
||||
lastSN uint16
|
||||
lastTS uint32
|
||||
|
||||
codec webrtc.RTPCodecCapability
|
||||
transceiver *webrtc.RTPTransceiver
|
||||
writeStream webrtc.TrackLocalWriter
|
||||
onCloseHandler func()
|
||||
onBind func()
|
||||
closeOnce sync.Once
|
||||
|
||||
// Report helpers
|
||||
octetCount uint32
|
||||
packetCount uint32
|
||||
maxPacketTs uint32
|
||||
lastPacketMs int64
|
||||
}
|
||||
|
||||
// NewDownTrack returns a DownTrack.
|
||||
func NewDownTrack(c webrtc.RTPCodecCapability, id, streamID string) (*DownTrack, error) {
|
||||
return &DownTrack{
|
||||
id: id,
|
||||
nList: newNACKList(),
|
||||
codec: c,
|
||||
streamID: streamID,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Bind is called by the PeerConnection after negotiation is complete
|
||||
// This asserts that the code requested is supported by the remote peer.
|
||||
// If so it setups all the state (SSRC and PayloadType) to have a call
|
||||
func (d *DownTrack) Bind(t webrtc.TrackLocalContext) (webrtc.RTPCodecParameters, error) {
|
||||
parameters := webrtc.RTPCodecParameters{RTPCodecCapability: d.codec}
|
||||
if codec, err := codecParametersFuzzySearch(parameters, t.CodecParameters()); err == nil {
|
||||
d.ssrc = uint32(t.SSRC())
|
||||
d.payload = uint8(codec.PayloadType)
|
||||
d.writeStream = t.WriteStream()
|
||||
d.mime = strings.ToLower(codec.MimeType)
|
||||
d.bound.set(true)
|
||||
d.reSync.set(true)
|
||||
if d.onBind != nil {
|
||||
d.onBind()
|
||||
}
|
||||
return codec, nil
|
||||
}
|
||||
return webrtc.RTPCodecParameters{}, webrtc.ErrUnsupportedCodec
|
||||
}
|
||||
|
||||
// Unbind implements the teardown logic when the track is no longer needed. This happens
|
||||
// because a track has been stopped.
|
||||
func (d *DownTrack) Unbind(_ webrtc.TrackLocalContext) error {
|
||||
d.bound.set(false)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DownTrack) IsBound() bool {
|
||||
return d.bound.get()
|
||||
}
|
||||
|
||||
// ID is the unique identifier for this Track. This should be unique for the
|
||||
// stream, but doesn't have to globally unique. A common example would be 'audio' or 'video'
|
||||
// and StreamID would be 'desktop' or 'webcam'
|
||||
func (d *DownTrack) ID() string { return d.id }
|
||||
|
||||
// Codec returns current track codec capability
|
||||
func (d *DownTrack) Codec() webrtc.RTPCodecCapability { return d.codec }
|
||||
|
||||
// StreamID is the group this track belongs too. This must be unique
|
||||
func (d *DownTrack) StreamID() string { return d.streamID }
|
||||
|
||||
// Kind controls if this TrackLocal is audio or video
|
||||
func (d *DownTrack) Kind() webrtc.RTPCodecType {
|
||||
switch {
|
||||
case strings.HasPrefix(d.codec.MimeType, "audio/"):
|
||||
return webrtc.RTPCodecTypeAudio
|
||||
case strings.HasPrefix(d.codec.MimeType, "video/"):
|
||||
return webrtc.RTPCodecTypeVideo
|
||||
default:
|
||||
return webrtc.RTPCodecType(0)
|
||||
}
|
||||
}
|
||||
|
||||
// WriteRTP writes a RTP Packet to the DownTrack
|
||||
func (d *DownTrack) WriteRTP(p rtp.Packet) error {
|
||||
if !d.IsBound() {
|
||||
return nil
|
||||
}
|
||||
return d.writeSimpleRTP(p)
|
||||
}
|
||||
|
||||
func (d *DownTrack) CreateSourceDescriptionChunks() []rtcp.SourceDescriptionChunk {
|
||||
if !d.IsBound() {
|
||||
return nil
|
||||
}
|
||||
return []rtcp.SourceDescriptionChunk{
|
||||
{
|
||||
Source: d.ssrc,
|
||||
Items: []rtcp.SourceDescriptionItem{{
|
||||
Type: rtcp.SDESCNAME,
|
||||
Text: d.streamID,
|
||||
}},
|
||||
}, {
|
||||
Source: d.ssrc,
|
||||
Items: []rtcp.SourceDescriptionItem{{
|
||||
Type: rtcp.SDESType(15),
|
||||
Text: d.transceiver.Mid(),
|
||||
}},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (d *DownTrack) CreateSenderReport() *rtcp.SenderReport {
|
||||
now := time.Now().UnixNano()
|
||||
nowNTP := timeToNtp(now)
|
||||
lastPktMs := atomic.LoadInt64(&d.lastPacketMs)
|
||||
maxPktTs := atomic.LoadUint32(&d.lastTS)
|
||||
diffTs := uint32((now/1e6)-lastPktMs) * d.codec.ClockRate / 1000
|
||||
octets, packets := d.getSRStats()
|
||||
return &rtcp.SenderReport{
|
||||
SSRC: d.ssrc,
|
||||
NTPTime: nowNTP,
|
||||
RTPTime: maxPktTs + diffTs,
|
||||
PacketCount: packets,
|
||||
OctetCount: octets,
|
||||
}
|
||||
}
|
||||
|
||||
// Close track
|
||||
func (d *DownTrack) Close() {
|
||||
d.closeOnce.Do(func() {
|
||||
if d.onCloseHandler != nil {
|
||||
d.onCloseHandler()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// OnCloseHandler method to be called on remote tracked removed
|
||||
func (d *DownTrack) OnCloseHandler(fn func()) {
|
||||
d.onCloseHandler = fn
|
||||
}
|
||||
|
||||
func (d *DownTrack) OnBind(fn func()) {
|
||||
d.onBind = fn
|
||||
}
|
||||
|
||||
// -- start -- accessors to avoid accessing private vars
|
||||
|
||||
func (d *DownTrack) SetTransceiver(transceiver *webrtc.RTPTransceiver) {
|
||||
d.transceiver = transceiver
|
||||
}
|
||||
|
||||
func (d *DownTrack) RTPSender() *webrtc.RTPSender {
|
||||
return d.transceiver.Sender()
|
||||
}
|
||||
|
||||
func (d *DownTrack) SSRC() uint32 {
|
||||
return d.ssrc
|
||||
}
|
||||
|
||||
func (d *DownTrack) LastSSRC() uint32 {
|
||||
return d.lastSSRC
|
||||
}
|
||||
|
||||
func (d *DownTrack) SnOffset() uint16 {
|
||||
return d.snOffset
|
||||
}
|
||||
|
||||
func (d *DownTrack) TsOffset() uint32 {
|
||||
return d.tsOffset
|
||||
}
|
||||
|
||||
func (d *DownTrack) GetNACKSeqNo(seqNo []uint16) []uint16 {
|
||||
return d.nList.getNACKSeqNo(seqNo)
|
||||
}
|
||||
|
||||
// -- end -- accessors to avoid accessing private vars
|
||||
|
||||
func (d *DownTrack) writeSimpleRTP(pkt rtp.Packet) error {
|
||||
if d.reSync.get() {
|
||||
if d.Kind() == webrtc.RTPCodecTypeVideo {
|
||||
relay := false
|
||||
// Wait for a keyframe to sync new source
|
||||
switch d.mime {
|
||||
case "video/vp8":
|
||||
vp8Packet := VP8Helper{}
|
||||
if err := vp8Packet.Unmarshal(pkt.Payload); err == nil {
|
||||
relay = vp8Packet.IsKeyFrame
|
||||
}
|
||||
case "video/h264":
|
||||
var word uint32
|
||||
payload := bytes.NewReader(pkt.Payload)
|
||||
err := binary.Read(payload, binary.BigEndian, &word)
|
||||
if err != nil || (word&0x1F000000)>>24 != 24 {
|
||||
relay = false
|
||||
} else {
|
||||
relay = word&0x1F == 7
|
||||
}
|
||||
}
|
||||
if !relay {
|
||||
// when we are writing to a new client and there isn't a keyframe, it makes it impossible
|
||||
// for clients to render a frame. we'll send an error for the track writer.
|
||||
return ErrRequiresKeyFrame
|
||||
}
|
||||
}
|
||||
d.snOffset = pkt.SequenceNumber - d.lastSN - 1
|
||||
d.tsOffset = pkt.Timestamp - d.lastTS - 1
|
||||
d.lastSSRC = pkt.SSRC
|
||||
d.reSync.set(false)
|
||||
}
|
||||
|
||||
atomic.AddUint32(&d.octetCount, uint32(len(pkt.Payload)))
|
||||
atomic.AddUint32(&d.packetCount, 1)
|
||||
|
||||
d.lastSSRC = pkt.SSRC
|
||||
newSN := pkt.SequenceNumber - d.snOffset
|
||||
newTS := pkt.Timestamp - d.tsOffset
|
||||
if (newSN-d.lastSN)&0x8000 == 0 || d.lastSN == 0 {
|
||||
d.lastSN = newSN
|
||||
atomic.StoreInt64(&d.lastPacketMs, time.Now().UnixNano()/1e6)
|
||||
atomic.StoreUint32(&d.lastTS, newTS)
|
||||
}
|
||||
pkt.PayloadType = d.payload
|
||||
pkt.Timestamp = d.lastTS
|
||||
pkt.SequenceNumber = d.lastSN
|
||||
pkt.SSRC = d.ssrc
|
||||
|
||||
_, err := d.writeStream.WriteRTP(&pkt.Header, pkt.Payload)
|
||||
if err != nil {
|
||||
log.Errorf("Write packet err %v", err)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *DownTrack) getSRStats() (octets, packets uint32) {
|
||||
octets = atomic.LoadUint32(&d.octetCount)
|
||||
packets = atomic.LoadUint32(&d.packetCount)
|
||||
return
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
package sfu
|
||||
|
||||
import "errors"
|
||||
|
||||
var (
|
||||
errPeerConnectionInitFailed = errors.New("pc init failed")
|
||||
errPtNotSupported = errors.New("payload type not supported")
|
||||
errCreatingDataChannel = errors.New("failed to create data channel")
|
||||
// routing errors
|
||||
errNoReceiverFound = errors.New("no receiver found")
|
||||
// Helpers errors
|
||||
errShortPacket = errors.New("packet is not large enough")
|
||||
errNilPacket = errors.New("invalid nil packet")
|
||||
// buffer errors
|
||||
errPacketNotFound = errors.New("packet not found in cache")
|
||||
errPacketTooOld = errors.New("packet not found in cache, too old")
|
||||
|
||||
ErrRequiresKeyFrame = errors.New("requires a keyframe to send down first")
|
||||
)
|
||||
@@ -1,148 +0,0 @@
|
||||
package sfu
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/pion/webrtc/v3"
|
||||
)
|
||||
|
||||
const ntpEpoch = 2208988800
|
||||
|
||||
type atomicBool struct {
|
||||
val int32
|
||||
}
|
||||
|
||||
func (b *atomicBool) set(value bool) { // nolint: unparam
|
||||
var i int32
|
||||
if value {
|
||||
i = 1
|
||||
}
|
||||
|
||||
atomic.StoreInt32(&(b.val), i)
|
||||
}
|
||||
|
||||
func (b *atomicBool) get() bool {
|
||||
return atomic.LoadInt32(&(b.val)) != 0
|
||||
}
|
||||
|
||||
// VP8Helper is a helper to get temporal data from VP8 packet header
|
||||
/*
|
||||
VP8Helper Payload Descriptor
|
||||
0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
|
||||
+-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+
|
||||
|X|R|N|S|R| PID | (REQUIRED) |X|R|N|S|R| PID | (REQUIRED)
|
||||
+-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+
|
||||
X: |I|L|T|K| RSV | (OPTIONAL) X: |I|L|T|K| RSV | (OPTIONAL)
|
||||
+-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+
|
||||
I: |M| PictureID | (OPTIONAL) I: |M| PictureID | (OPTIONAL)
|
||||
+-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+
|
||||
L: | TL0PICIDX | (OPTIONAL) | PictureID |
|
||||
+-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+
|
||||
T/K:|TID|Y| KEYIDX | (OPTIONAL) L: | TL0PICIDX | (OPTIONAL)
|
||||
+-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+
|
||||
T/K:|TID|Y| KEYIDX | (OPTIONAL)
|
||||
+-+-+-+-+-+-+-+-+
|
||||
*/
|
||||
type VP8Helper struct {
|
||||
TemporalSupported bool
|
||||
// Optional Header
|
||||
PictureID uint16 /* 8 or 16 bits, picture ID */
|
||||
picIDIdx uint8
|
||||
mBit bool
|
||||
TL0PICIDX uint8 /* 8 bits temporal level zero index */
|
||||
tlzIdx uint8
|
||||
|
||||
// Optional Header If either of the T or K bits are set to 1,
|
||||
// the TID/Y/KEYIDX extension field MUST be present.
|
||||
TID uint8 /* 2 bits temporal layer idx*/
|
||||
// IsKeyFrame is a helper to detect if current packet is a keyframe
|
||||
IsKeyFrame bool
|
||||
}
|
||||
|
||||
// Unmarshal parses the passed byte slice and stores the result in the VP8Helper this method is called upon
|
||||
func (p *VP8Helper) Unmarshal(payload []byte) error {
|
||||
if payload == nil {
|
||||
return errNilPacket
|
||||
}
|
||||
|
||||
payloadLen := len(payload)
|
||||
|
||||
if payloadLen < 4 {
|
||||
return errShortPacket
|
||||
}
|
||||
|
||||
var idx uint8
|
||||
S := payload[idx]&0x10 > 0
|
||||
// Check for extended bit control
|
||||
if payload[idx]&0x80 > 0 {
|
||||
idx++
|
||||
// Check if T is present, if not, no temporal layer is available
|
||||
p.TemporalSupported = payload[idx]&0x20 > 0
|
||||
K := payload[idx]&0x10 > 0
|
||||
L := payload[idx]&0x40 > 0
|
||||
// Check for PictureID
|
||||
if payload[idx]&0x80 > 0 {
|
||||
idx++
|
||||
p.picIDIdx = idx
|
||||
pid := payload[idx] & 0x7f
|
||||
// Check if m is 1, then Picture ID is 15 bits
|
||||
if payload[idx]&0x80 > 0 {
|
||||
idx++
|
||||
p.mBit = true
|
||||
p.PictureID = binary.BigEndian.Uint16([]byte{pid, payload[idx]})
|
||||
} else {
|
||||
p.PictureID = uint16(pid)
|
||||
}
|
||||
}
|
||||
// Check if TL0PICIDX is present
|
||||
if L {
|
||||
idx++
|
||||
p.tlzIdx = idx
|
||||
p.TL0PICIDX = payload[idx]
|
||||
}
|
||||
if p.TemporalSupported || K {
|
||||
idx++
|
||||
p.TID = (payload[idx] & 0xc0) >> 6
|
||||
}
|
||||
if int(idx) >= payloadLen {
|
||||
return errShortPacket
|
||||
}
|
||||
idx++
|
||||
// Check is packet is a keyframe by looking at P bit in vp8 payload
|
||||
p.IsKeyFrame = payload[idx]&0x01 == 0 && S
|
||||
} else {
|
||||
idx++
|
||||
// Check is packet is a keyframe by looking at P bit in vp8 payload
|
||||
p.IsKeyFrame = payload[idx]&0x01 == 0 && S
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func timeToNtp(ns int64) uint64 {
|
||||
seconds := uint64(ns/1e9 + ntpEpoch)
|
||||
fraction := uint64(((ns % 1e9) << 32) / 1e9)
|
||||
return seconds<<32 | fraction
|
||||
}
|
||||
|
||||
// Do a fuzzy find for a codec in the list of codecs
|
||||
// Used for lookup up a codec in an existing list to find a match
|
||||
func codecParametersFuzzySearch(needle webrtc.RTPCodecParameters, haystack []webrtc.RTPCodecParameters) (webrtc.RTPCodecParameters, error) {
|
||||
// First attempt to match on MimeType + SDPFmtpLine
|
||||
for _, c := range haystack {
|
||||
if strings.EqualFold(c.RTPCodecCapability.MimeType, needle.RTPCodecCapability.MimeType) &&
|
||||
c.RTPCodecCapability.SDPFmtpLine == needle.RTPCodecCapability.SDPFmtpLine {
|
||||
return c, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to just MimeType
|
||||
for _, c := range haystack {
|
||||
if strings.EqualFold(c.RTPCodecCapability.MimeType, needle.RTPCodecCapability.MimeType) {
|
||||
return c, nil
|
||||
}
|
||||
}
|
||||
|
||||
return webrtc.RTPCodecParameters{}, webrtc.ErrCodecNotFound
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
package sfu
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
ignoreRetransmission = 5e8 // Ignore packet retransmission after ignoreRetransmission milliseconds
|
||||
maxNackQueue = 100
|
||||
)
|
||||
|
||||
type NACK struct {
|
||||
SN uint16
|
||||
LRX int64
|
||||
}
|
||||
|
||||
type nackList struct {
|
||||
nacks map[uint16]*list.Element
|
||||
ll *list.List
|
||||
}
|
||||
|
||||
func newNACKList() *nackList {
|
||||
return &nackList{
|
||||
nacks: make(map[uint16]*list.Element),
|
||||
ll: list.New(),
|
||||
}
|
||||
}
|
||||
|
||||
func (n *nackList) getNACKSeqNo(seqno []uint16) []uint16 {
|
||||
packets := make([]uint16, 0, 17)
|
||||
for _, sn := range seqno {
|
||||
if nack, ok := n.nacks[sn]; !ok {
|
||||
n.nacks[sn] = n.ll.PushBack(NACK{sn, time.Now().UnixNano()})
|
||||
packets = append(packets, sn)
|
||||
} else if time.Now().UnixNano()-nack.Value.(NACK).LRX > ignoreRetransmission {
|
||||
nack.Value = NACK{sn, time.Now().UnixNano()}
|
||||
packets = append(packets, sn)
|
||||
}
|
||||
}
|
||||
|
||||
for len(n.nacks) > maxNackQueue {
|
||||
el := n.ll.Back()
|
||||
delete(n.nacks, el.Value.(NACK).SN)
|
||||
n.ll.Remove(el)
|
||||
}
|
||||
|
||||
return packets
|
||||
}
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/go-redis/redis/v8"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/twitchtv/twirp"
|
||||
|
||||
"github.com/livekit/livekit-server/cmd/cli/client"
|
||||
@@ -18,6 +17,7 @@ import (
|
||||
"github.com/livekit/livekit-server/pkg/logger"
|
||||
"github.com/livekit/livekit-server/pkg/routing"
|
||||
"github.com/livekit/livekit-server/pkg/service"
|
||||
"github.com/livekit/livekit-server/pkg/utils"
|
||||
"github.com/livekit/livekit-server/proto/livekit"
|
||||
)
|
||||
|
||||
@@ -62,8 +62,8 @@ func setupSingleNodeTest(roomName string) *service.LivekitServer {
|
||||
}
|
||||
|
||||
func setupMultiNodeTest() (*service.LivekitServer, *service.LivekitServer) {
|
||||
s1 := createMultiNodeServer(nodeId1, defaultServerPort)
|
||||
s2 := createMultiNodeServer(nodeId2, secondServerPort)
|
||||
s1 := createMultiNodeServer(utils.NewGuid(nodeId1), defaultServerPort)
|
||||
s2 := createMultiNodeServer(utils.NewGuid(nodeId2), secondServerPort)
|
||||
go s1.Start()
|
||||
go s2.Start()
|
||||
|
||||
@@ -122,17 +122,28 @@ func withTimeout(t *testing.T, description string, f func() bool) bool {
|
||||
func waitUntilConnected(t *testing.T, clients ...*client.RTCClient) {
|
||||
logger.Infow("waiting for clients to become connected")
|
||||
wg := sync.WaitGroup{}
|
||||
errChan := make(chan error, len(clients))
|
||||
for i := range clients {
|
||||
c := clients[i]
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
if !assert.NoError(t, c.WaitUntilConnected()) {
|
||||
t.Fatal("client could not connect", c.ID())
|
||||
err := c.WaitUntilConnected()
|
||||
if err != nil {
|
||||
errChan <- err
|
||||
}
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
close(errChan)
|
||||
hasError := false
|
||||
for err := range errChan {
|
||||
t.Fatal(err)
|
||||
hasError = true
|
||||
}
|
||||
if hasError {
|
||||
t.FailNow()
|
||||
}
|
||||
}
|
||||
|
||||
func createSingleNodeServer() *service.LivekitServer {
|
||||
@@ -143,7 +154,7 @@ func createSingleNodeServer() *service.LivekitServer {
|
||||
}
|
||||
|
||||
currentNode, err := routing.NewLocalNode(conf)
|
||||
currentNode.Id = nodeId1
|
||||
currentNode.Id = utils.NewGuid(nodeId1)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -151,6 +162,7 @@ func createSingleNodeServer() *service.LivekitServer {
|
||||
// local routing and store
|
||||
router := routing.NewLocalRouter(currentNode)
|
||||
roomStore := service.NewLocalRoomStore()
|
||||
roomStore.DeleteRoom(testRoom)
|
||||
s, err := service.InitializeServer(conf, &StaticKeyProvider{}, roomStore, router, currentNode, &routing.RandomSelector{})
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("could not create server: %v", err))
|
||||
@@ -180,9 +192,13 @@ func createMultiNodeServer(nodeId string, port uint32) *service.LivekitServer {
|
||||
if err = rc.Ping(context.Background()).Err(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err = rc.Del(context.Background(), routing.NodesKey).Err(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
router := routing.NewRedisRouter(currentNode, rc)
|
||||
roomStore := service.NewRedisRoomStore(rc)
|
||||
roomStore.DeleteRoom(testRoom)
|
||||
s, err := service.InitializeServer(conf, &StaticKeyProvider{}, roomStore, router, currentNode, &routing.RandomSelector{})
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("could not create server: %v", err))
|
||||
|
||||
@@ -15,7 +15,7 @@ func TestMultiNodeRouting(t *testing.T) {
|
||||
return
|
||||
}
|
||||
|
||||
logger.Infow("---Starting TestMultiNodeRouting---")
|
||||
logger.Infow("\n\n---Starting TestMultiNodeRouting---")
|
||||
defer logger.Infow("---Finishing TestMultiNodeRouting---")
|
||||
|
||||
s1, s2 := setupMultiNodeTest()
|
||||
@@ -24,8 +24,7 @@ func TestMultiNodeRouting(t *testing.T) {
|
||||
|
||||
// creating room on node 1
|
||||
_, err := roomClient.CreateRoom(contextWithCreateRoomToken(), &livekit.CreateRoomRequest{
|
||||
Name: testRoom,
|
||||
NodeId: nodeId1,
|
||||
Name: testRoom,
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
|
||||
@@ -80,7 +79,7 @@ func TestConnectWithoutCreation(t *testing.T) {
|
||||
t.SkipNow()
|
||||
return
|
||||
}
|
||||
logger.Infow("---Starting TestConnectWithoutCreation---")
|
||||
logger.Infow("\n\n---Starting TestConnectWithoutCreation---")
|
||||
defer logger.Infow("---Finishing TestConnectWithoutCreation---")
|
||||
|
||||
s1, s2 := setupMultiNodeTest()
|
||||
@@ -100,7 +99,7 @@ func TestMultinodePublishingUponJoining(t *testing.T) {
|
||||
return
|
||||
}
|
||||
|
||||
logger.Infow("---Starting TestMultinodePublishingUponJoining---")
|
||||
logger.Infow("\n\n---Starting TestMultinodePublishingUponJoining---")
|
||||
defer logger.Infow("---Finishing TestMultinodePublishingUponJoining---")
|
||||
|
||||
s1, s2 := setupMultiNodeTest()
|
||||
@@ -116,7 +115,7 @@ func TestMultinodeReceiveBeforePublish(t *testing.T) {
|
||||
return
|
||||
}
|
||||
|
||||
logger.Infow("---Starting TestMultinodeReceiveBeforePublish---")
|
||||
logger.Infow("\n\n---Starting TestMultinodeReceiveBeforePublish---")
|
||||
defer logger.Infow("---Finishing TestMultinodeReceiveBeforePublish---")
|
||||
|
||||
s1, s2 := setupMultiNodeTest()
|
||||
|
||||
+7
-5
@@ -1,7 +1,6 @@
|
||||
package test
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -13,9 +12,11 @@ import (
|
||||
|
||||
// a scenario with lots of clients connecting, publishing, and leaving at random periods
|
||||
func scenarioPublishingUponJoining(t *testing.T, ports ...int) {
|
||||
c1 := createRTCClient("puj_1", ports[rand.Intn(len(ports))])
|
||||
c2 := createRTCClient("puj_2", ports[rand.Intn(len(ports))])
|
||||
c3 := createRTCClient("puj_3", ports[rand.Intn(len(ports))])
|
||||
firstPort := ports[0]
|
||||
lastPort := ports[len(ports)-1]
|
||||
c1 := createRTCClient("puj_1", firstPort)
|
||||
c2 := createRTCClient("puj_2", lastPort)
|
||||
c3 := createRTCClient("puj_3", firstPort)
|
||||
defer stopClients(c1, c2, c3)
|
||||
|
||||
waitUntilConnected(t, c1, c2, c3)
|
||||
@@ -63,7 +64,8 @@ func scenarioPublishingUponJoining(t *testing.T, ports ...int) {
|
||||
}
|
||||
|
||||
logger.Infow("c2 reconnecting")
|
||||
c2 = createRTCClient("puj_2", ports[rand.Intn(len(ports))])
|
||||
// connect to a diff port
|
||||
c2 = createRTCClient("puj_2", firstPort)
|
||||
defer c2.Stop()
|
||||
waitUntilConnected(t, c2)
|
||||
writers = publishTracksForClients(t, c2)
|
||||
|
||||
Reference in new issue
Block a user