use ion-sfu receiver/downtrack instead of our own.

This commit is contained in:
David Zhao authored and GitHub committed 2021-02-06 23:21:51 -08:00
commit 33787fd4f2
37 files changed
+1109 -2194

No files matched your search

+13 -12
View File
@@ -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
+5 -5
View File
@@ -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
+21 -2
View File
@@ -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=
+2 -1
View File
@@ -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{},
+1
View File
@@ -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)
+4
View File
@@ -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
+3
View File
@@ -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
View File
@@ -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
}
+65
View File
@@ -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()
+1 -1
View File
@@ -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))
+4
View File
@@ -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
}
+88
View File
@@ -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
View File
@@ -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)
}
}
-112
View File
@@ -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
View File
@@ -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)
//}
-99
View File
@@ -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
View File
@@ -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
}
+4 -4
View File
@@ -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)
+52
View File
@@ -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
View File
@@ -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
-755
View File
@@ -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)
+78 -79
View File
@@ -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{}{}
-191
View File
@@ -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)
+23
View File
@@ -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
}
+5 -5
View File
@@ -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
+4 -1
View File
@@ -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
}
+6 -1
View File
@@ -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
}
-5
View File
@@ -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
-271
View File
@@ -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
}
-19
View File
@@ -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")
)
-148
View File
@@ -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
}
-49
View File
@@ -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
}
+22 -6
View File
@@ -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))
+5 -6
View File
@@ -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
View File
@@ -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)