From 7954748d7a288c339ce99919962de8d77f863ba5 Mon Sep 17 00:00:00 2001 From: Raja Subramanian Date: Thu, 4 Dec 2025 10:44:34 +0530 Subject: [PATCH] Data tracks (#4089) * WIP * WIP * Starting to add some signalling integration testing. * Working tests. * fix tests * Forward data packets (#4096) * WIP commit * WIP * WIP * fix forwarding * address PR comments * move some methods from LocalParticipant to Participant interface * handle subscription update * add extensions and tests * more packet tests * add test for replace extension and fix a bug * update protocol and add config --- go.mod | 12 +- go.sum | 24 +- pkg/config/config.go | 2 + pkg/rtc/datadowntrack.go | 104 +++ pkg/rtc/datatrack.go | 152 +++ .../datatrack/extension_participant_sid.go | 59 ++ .../extension_participant_sid_test.go | 46 + pkg/rtc/datatrack/packet.go | 269 ++++++ pkg/rtc/datatrack/packet_test.go | 265 ++++++ pkg/rtc/datatrack/testutils.go | 74 ++ pkg/rtc/participant.go | 70 +- pkg/rtc/participant_data_track.go | 158 ++++ pkg/rtc/participant_internal_test.go | 4 +- pkg/rtc/participant_signal.go | 18 + pkg/rtc/room.go | 147 ++- pkg/rtc/room_test.go | 2 +- pkg/rtc/roomtrackmanager.go | 70 ++ pkg/rtc/signalling/interfaces.go | 3 + pkg/rtc/signalling/signalhandler.go | 9 + pkg/rtc/signalling/signalling.go | 24 + pkg/rtc/signalling/signallingunimplemented.go | 12 + pkg/rtc/subscriptionmanager.go | 875 ++++++++++++------ pkg/rtc/subscriptionmanager_test.go | 20 +- pkg/rtc/testutils.go | 2 +- pkg/rtc/transport.go | 106 ++- pkg/rtc/transport/handler.go | 2 + .../transport/transportfakes/fake_handler.go | 42 + pkg/rtc/transportmanager.go | 59 +- pkg/rtc/types/interfaces.go | 106 ++- .../types/typesfakes/fake_data_down_track.go | 201 ++++ pkg/rtc/types/typesfakes/fake_data_track.go | 658 +++++++++++++ .../typesfakes/fake_data_track_sender.go | 146 +++ .../typesfakes/fake_data_track_transport.go | 114 +++ .../typesfakes/fake_local_participant.go | 803 +++++++++++++++- .../fake_local_participant_helper.go | 74 ++ pkg/rtc/types/typesfakes/fake_participant.go | 743 ++++++++++++++- pkg/rtc/types/typesfakes/fake_room.go | 74 ++ pkg/rtc/updatatrackmanager.go | 137 +++ pkg/service/roommanager.go | 7 +- pkg/sfu/buffer/buffer_test.go | 4 +- pkg/sfu/receiver.go | 9 +- pkg/sfu/redprimaryreceiver.go | 7 +- pkg/sfu/redreceiver.go | 7 +- pkg/sfu/{ => utils}/downtrackspreader.go | 40 +- test/client/client.go | 242 ++++- test/client/datatrack_remote.go | 86 ++ test/client/datatrack_writer.go | 79 ++ test/client/trackwriter.go | 26 +- test/integration_helpers.go | 4 +- test/multinode_test.go | 2 +- test/scenarios.go | 113 ++- test/singlenode_test.go | 84 +- 52 files changed, 5897 insertions(+), 499 deletions(-) create mode 100644 pkg/rtc/datadowntrack.go create mode 100644 pkg/rtc/datatrack.go create mode 100644 pkg/rtc/datatrack/extension_participant_sid.go create mode 100644 pkg/rtc/datatrack/extension_participant_sid_test.go create mode 100644 pkg/rtc/datatrack/packet.go create mode 100644 pkg/rtc/datatrack/packet_test.go create mode 100644 pkg/rtc/datatrack/testutils.go create mode 100644 pkg/rtc/participant_data_track.go create mode 100644 pkg/rtc/types/typesfakes/fake_data_down_track.go create mode 100644 pkg/rtc/types/typesfakes/fake_data_track.go create mode 100644 pkg/rtc/types/typesfakes/fake_data_track_sender.go create mode 100644 pkg/rtc/types/typesfakes/fake_data_track_transport.go create mode 100644 pkg/rtc/updatatrackmanager.go rename pkg/sfu/{ => utils}/downtrackspreader.go (66%) create mode 100644 test/client/datatrack_remote.go create mode 100644 test/client/datatrack_writer.go diff --git a/go.mod b/go.mod index bc3a8d80d..82446e7ff 100644 --- a/go.mod +++ b/go.mod @@ -23,7 +23,7 @@ require ( github.com/jxskiss/base62 v1.1.0 github.com/livekit/mageutil v0.0.0-20250511045019-0f1ff63f7731 github.com/livekit/mediatransportutil v0.0.0-20251201055132-772504244e19 - github.com/livekit/protocol v1.43.3-0.20251125082437-3bc7a74d3d5e + github.com/livekit/protocol v1.43.3-0.20251204035522-21f690495229 github.com/livekit/psrpc v0.7.1 github.com/mackerelio/go-osstat v0.2.6 github.com/magefile/mage v1.15.0 @@ -32,7 +32,7 @@ require ( github.com/olekukonko/tablewriter v0.0.5 github.com/ory/dockertest/v3 v3.12.0 github.com/pion/datachannel v1.5.10 - github.com/pion/dtls/v3 v3.0.7 + github.com/pion/dtls/v3 v3.0.8 github.com/pion/ice/v4 v4.0.12 github.com/pion/interceptor v0.1.42 github.com/pion/rtcp v1.2.16 @@ -44,7 +44,7 @@ require ( github.com/pion/webrtc/v4 v4.1.6 github.com/pkg/errors v0.9.1 github.com/prometheus/client_golang v1.23.0 - github.com/redis/go-redis/v9 v9.17.0 + github.com/redis/go-redis/v9 v9.17.2 github.com/rs/cors v1.11.1 github.com/stretchr/testify v1.11.1 github.com/thoas/go-funk v0.9.3 @@ -55,7 +55,7 @@ require ( go.uber.org/atomic v1.11.0 go.uber.org/multierr v1.11.0 go.uber.org/zap v1.27.1 - golang.org/x/exp v0.0.0-20251113190631-e25ba8c21ef6 + golang.org/x/exp v0.0.0-20251125195548-87e1e737ad39 golang.org/x/mod v0.30.0 golang.org/x/sync v0.18.0 google.golang.org/protobuf v1.36.10 @@ -106,7 +106,7 @@ require ( github.com/hashicorp/go-retryablehttp v0.7.7 // indirect github.com/hashicorp/golang-lru v1.0.2 // indirect github.com/josharian/native v1.1.0 // indirect - github.com/klauspost/compress v1.18.1 // indirect + github.com/klauspost/compress v1.18.2 // indirect github.com/klauspost/cpuid/v2 v2.3.0 // indirect github.com/lithammer/shortuuid/v4 v4.2.0 // indirect github.com/mattn/go-runewidth v0.0.9 // indirect @@ -124,7 +124,7 @@ require ( github.com/pion/logging v0.2.4 // indirect github.com/pion/mdns/v2 v2.1.0 // indirect github.com/pion/randutil v0.1.0 // indirect - github.com/pion/srtp/v3 v3.0.8 // indirect + github.com/pion/srtp/v3 v3.0.9 // indirect github.com/pion/stun/v3 v3.0.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/prometheus/client_model v0.6.2 // indirect diff --git a/go.sum b/go.sum index f36a3c16c..146794b78 100644 --- a/go.sum +++ b/go.sum @@ -150,8 +150,8 @@ github.com/jxskiss/base62 v1.1.0 h1:A5zbF8v8WXx2xixnAKD2w+abC+sIzYJX+nxmhA6HWFw= github.com/jxskiss/base62 v1.1.0/go.mod h1:HhWAlUXvxKThfOlZbcuFzsqwtF5TcqS9ru3y5GfjWAc= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.18.1 h1:bcSGx7UbpBqMChDtsF28Lw6v/G94LPrrbMbdC3JH2co= -github.com/klauspost/compress v1.18.1/go.mod h1:ZQFFVG+MdnR0P+l6wpXgIL4NTtwiKIdBnrBd8Nrxr+0= +github.com/klauspost/compress v1.18.2 h1:iiPHWW0YrcFgpBYhsA6D1+fqHssJscY/Tm/y2Uqnapk= +github.com/klauspost/compress v1.18.2/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4= github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= @@ -173,8 +173,8 @@ github.com/livekit/mageutil v0.0.0-20250511045019-0f1ff63f7731 h1:9x+U2HGLrSw5AT github.com/livekit/mageutil v0.0.0-20250511045019-0f1ff63f7731/go.mod h1:Rs3MhFwutWhGwmY1VQsygw28z5bWcnEYmS1OG9OxjOQ= github.com/livekit/mediatransportutil v0.0.0-20251201055132-772504244e19 h1:JLLiGw3xtmLPzncsxe8wxvNQu91de+IV3BHIG5KlGoQ= github.com/livekit/mediatransportutil v0.0.0-20251201055132-772504244e19/go.mod h1:mSNtYzSf6iY9xM3UX42VEI+STHvMgHmrYzEHPcdhB8A= -github.com/livekit/protocol v1.43.3-0.20251125082437-3bc7a74d3d5e h1:Fsdj1LoPI5LrzGwOxTGfP7SbGsK+/4SgFCbt83NAiws= -github.com/livekit/protocol v1.43.3-0.20251125082437-3bc7a74d3d5e/go.mod h1:yjkL2/HcaCRyHykP9rLgKST2099AGd8laaU8EuHMnfw= +github.com/livekit/protocol v1.43.3-0.20251204035522-21f690495229 h1:vY7Y24LPMSt8t8FndopzaNF+vn3+SkzIr6toMu2bBxQ= +github.com/livekit/protocol v1.43.3-0.20251204035522-21f690495229/go.mod h1:n00Ul4P6o2YILGhxw+O57B0h/bF3Je9PzRN36fElCmw= github.com/livekit/psrpc v0.7.1 h1:ms37az0QTD3UXIWuUC5D/SkmKOlRMVRsI261eBWu/Vw= github.com/livekit/psrpc v0.7.1/go.mod h1:bZ4iHFQptTkbPnB0LasvRNu/OBYXEu1NA6O5BMFo9kk= github.com/mackerelio/go-osstat v0.2.6 h1:gs4U8BZeS1tjrL08tt5VUliVvSWP26Ai2Ob8Lr7f2i0= @@ -240,8 +240,8 @@ github.com/ory/dockertest/v3 v3.12.0 h1:3oV9d0sDzlSQfHtIaB5k6ghUCVMVLpAY8hwrqoCy github.com/ory/dockertest/v3 v3.12.0/go.mod h1:aKNDTva3cp8dwOWwb9cWuX84aH5akkxXRvO7KCwWVjE= github.com/pion/datachannel v1.5.10 h1:ly0Q26K1i6ZkGf42W7D4hQYR90pZwzFOjTq5AuCKk4o= github.com/pion/datachannel v1.5.10/go.mod h1:p/jJfC9arb29W7WrxyKbepTU20CFgyx5oLo8Rs4Py/M= -github.com/pion/dtls/v3 v3.0.7 h1:bItXtTYYhZwkPFk4t1n3Kkf5TDrfj6+4wG+CZR8uI9Q= -github.com/pion/dtls/v3 v3.0.7/go.mod h1:uDlH5VPrgOQIw59irKYkMudSFprY9IEFCqz/eTz16f8= +github.com/pion/dtls/v3 v3.0.8 h1:ZrPUrvPVDaTJDM8Vu1veatzXebLlsIWeT7Vaate/zwM= +github.com/pion/dtls/v3 v3.0.8/go.mod h1:abApPjgadS/ra1wvUzHLc3o2HvoxppAh+NZkyApL4Os= github.com/pion/ice/v4 v4.0.12 h1:vuI3h9OD5M0Z+V304qLC1/o16ahHVnDgqNCWbONv6s0= github.com/pion/ice/v4 v4.0.12/go.mod h1:tAp574oAufhHRHr8EO1xgPmVKVDBROX+708WggYD6NE= github.com/pion/interceptor v0.1.42 h1:0/4tvNtruXflBxLfApMVoMubUMik57VZ+94U0J7cmkQ= @@ -260,8 +260,8 @@ github.com/pion/sctp v1.8.41 h1:20R4OHAno4Vky3/iE4xccInAScAa83X6nWUfyc65MIs= github.com/pion/sctp v1.8.41/go.mod h1:2wO6HBycUH7iCssuGyc2e9+0giXVW0pyCv3ZuL8LiyY= github.com/pion/sdp/v3 v3.0.16 h1:0dKzYO6gTAvuLaAKQkC02eCPjMIi4NuAr/ibAwrGDCo= github.com/pion/sdp/v3 v3.0.16/go.mod h1:9tyKzznud3qiweZcD86kS0ff1pGYB3VX+Bcsmkx6IXo= -github.com/pion/srtp/v3 v3.0.8 h1:RjRrjcIeQsilPzxvdaElN0CpuQZdMvcl9VZ5UY9suUM= -github.com/pion/srtp/v3 v3.0.8/go.mod h1:2Sq6YnDH7/UDCvkSoHSDNDeyBcFgWL0sAVycVbAsXFg= +github.com/pion/srtp/v3 v3.0.9 h1:lRGF4G61xxj+m/YluB3ZnBpiALSri2lTzba0kGZMrQY= +github.com/pion/srtp/v3 v3.0.9/go.mod h1:E+AuWd7Ug2Fp5u38MKnhduvpVkveXJX6J4Lq4rxUYt8= github.com/pion/stun/v3 v3.0.1 h1:jx1uUq6BdPihF0yF33Jj2mh+C9p0atY94IkdnW174kA= github.com/pion/stun/v3 v3.0.1/go.mod h1:RHnvlKFg+qHgoKIqtQWMOJF52wsImCAf/Jh5GjX+4Tw= github.com/pion/transport/v3 v3.1.1 h1:Tr684+fnnKlhPceU+ICdrw6KKkTms+5qHMgw6bIkYOM= @@ -284,8 +284,8 @@ github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzM github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= github.com/puzpuzpuz/xsync/v3 v3.5.1 h1:GJYJZwO6IdxN/IKbneznS6yPkVC+c3zyY/j19c++5Fg= github.com/puzpuzpuz/xsync/v3 v3.5.1/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA= -github.com/redis/go-redis/v9 v9.17.0 h1:K6E+ZlYN95KSMmZeEQPbU/c++wfmEvfFB17yEAq/VhM= -github.com/redis/go-redis/v9 v9.17.0/go.mod h1:u410H11HMLoB+TP67dz8rL9s6QW2j76l0//kSOd3370= +github.com/redis/go-redis/v9 v9.17.2 h1:P2EGsA4qVIM3Pp+aPocCJ7DguDHhqrXNhVcEp4ViluI= +github.com/redis/go-redis/v9 v9.17.2/go.mod h1:u410H11HMLoB+TP67dz8rL9s6QW2j76l0//kSOd3370= github.com/rodaine/protogofakeit v0.1.1 h1:ZKouljuRM3A+TArppfBqnH8tGZHOwM/pjvtXe9DaXH8= github.com/rodaine/protogofakeit v0.1.1/go.mod h1:pXn/AstBYMaSfc1/RqH3N82pBuxtWgejz1AlYpY1mI0= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= @@ -373,8 +373,8 @@ golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1m golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= -golang.org/x/exp v0.0.0-20251113190631-e25ba8c21ef6 h1:zfMcR1Cs4KNuomFFgGefv5N0czO2XZpUbxGUy8i8ug0= -golang.org/x/exp v0.0.0-20251113190631-e25ba8c21ef6/go.mod h1:46edojNIoXTNOhySWIWdix628clX9ODXwPsQuG6hsK0= +golang.org/x/exp v0.0.0-20251125195548-87e1e737ad39 h1:DHNhtq3sNNzrvduZZIiFyXWOL9IWaDPHqTnLJp+rCBY= +golang.org/x/exp v0.0.0-20251125195548-87e1e737ad39/go.mod h1:46edojNIoXTNOhySWIWdix628clX9ODXwPsQuG6hsK0= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= diff --git a/pkg/config/config.go b/pkg/config/config.go index f35507a84..67e8470e1 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -83,6 +83,8 @@ type Config struct { Metric metric.MetricConfig `yaml:"metric,omitempty"` NodeStats NodeStatsConfig `yaml:"node_stats,omitempty"` + + EnableDataTracks bool `yaml:"enable_data_tracks,omitempty"` } type RTCConfig struct { diff --git a/pkg/rtc/datadowntrack.go b/pkg/rtc/datadowntrack.go new file mode 100644 index 000000000..84b095f9d --- /dev/null +++ b/pkg/rtc/datadowntrack.go @@ -0,0 +1,104 @@ +// Copyright 2023 LiveKit, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package rtc + +import ( + "fmt" + "math/rand" + "time" + + "github.com/livekit/livekit-server/pkg/rtc/datatrack" + "github.com/livekit/livekit-server/pkg/rtc/types" + "github.com/livekit/protocol/livekit" + "github.com/livekit/protocol/logger" +) + +type DataDownTrackParams struct { + Logger logger.Logger + SubscriberID livekit.ParticipantID + PublishDataTrack types.DataTrack + Transport types.DataTrackTransport +} + +type DataDownTrack struct { + params DataDownTrackParams + dti *livekit.DataTrackInfo + handle uint16 + createdAt int64 +} + +func NewDataDownTrack(params DataDownTrackParams, dti *livekit.DataTrackInfo) (*DataDownTrack, error) { + d := &DataDownTrack{ + params: params, + dti: dti, + handle: uint16(rand.Intn(256)), + createdAt: time.Now().UnixNano(), + } + + if err := d.params.PublishDataTrack.AddDataDownTrack(d); err != nil { + d.params.Logger.Warnw("could not add data down track", err) + return nil, err + } + + d.params.Logger.Infow("created data down track", "id", d.ID(), "name", d.Name()) + return d, nil +} + +func (d *DataDownTrack) Close() { + d.params.Logger.Infow("closing data down track", "id", d.ID(), "name", d.Name()) + d.params.PublishDataTrack.DeleteDataDownTrack(d.SubscriberID()) +} + +func (d *DataDownTrack) Handle() uint16 { + return d.handle +} + +func (d *DataDownTrack) PublishDataTrack() types.DataTrack { + return d.params.PublishDataTrack +} + +func (d *DataDownTrack) PubHandle() uint16 { + return uint16(d.dti.PubHandle) +} + +func (d *DataDownTrack) ID() livekit.TrackID { + return livekit.TrackID(d.dti.Sid) +} + +func (d *DataDownTrack) Name() string { + return d.dti.Name +} + +func (d *DataDownTrack) SubscriberID() livekit.ParticipantID { + // add `createdAt` to ensure repeated subscriptions from same subscriber to same publisher does not collide + return livekit.ParticipantID(fmt.Sprintf("%s:%d", d.params.SubscriberID, d.createdAt)) +} + +func (d *DataDownTrack) WritePacket(data []byte, packet *datatrack.Packet) { + forwardedPacket := *packet + forwardedPacket.Handle = d.handle + buf, err := forwardedPacket.Marshal() + if err != nil { + d.params.Logger.Warnw("could not marshal data track message", err) + return + } + if err := d.params.Transport.SendDataTrackMessage(buf); err != nil { + d.params.Logger.Warnw("could not send data track message", err, "handle", d.handle) + } +} + +func (d *DataDownTrack) UpdateSubscriptionOptions(subscriptionOptions *livekit.DataTrackSubscriptionOptions) { + // DT-TODO +} diff --git a/pkg/rtc/datatrack.go b/pkg/rtc/datatrack.go new file mode 100644 index 000000000..f00ae2a90 --- /dev/null +++ b/pkg/rtc/datatrack.go @@ -0,0 +1,152 @@ +// Copyright 2023 LiveKit, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package rtc + +import ( + "errors" + "sync" + + "github.com/frostbyte73/core" + "github.com/livekit/livekit-server/pkg/rtc/datatrack" + "github.com/livekit/livekit-server/pkg/rtc/types" + sfuutils "github.com/livekit/livekit-server/pkg/sfu/utils" + "github.com/livekit/protocol/livekit" + "github.com/livekit/protocol/logger" + "github.com/livekit/protocol/utils" +) + +var ( + errReceiverClosed = errors.New("datatrack is closed") +) + +type DataTrackParams struct { + Logger logger.Logger +} + +type DataTrack struct { + params DataTrackParams + + lock sync.Mutex + dti *livekit.DataTrackInfo + subscribedTracks map[livekit.ParticipantID]*DataDownTrack + + downTrackSpreader *sfuutils.DownTrackSpreader[types.DataTrackSender] + + closed core.Fuse +} + +func NewDataTrack(params DataTrackParams, dti *livekit.DataTrackInfo) *DataTrack { + d := &DataTrack{ + params: params, + dti: dti, + subscribedTracks: make(map[livekit.ParticipantID]*DataDownTrack), + downTrackSpreader: sfuutils.NewDownTrackSpreader[types.DataTrackSender](sfuutils.DownTrackSpreaderParams{ + Threshold: 20, + Logger: params.Logger, + }), + } + d.params.Logger.Infow("created data track", "id", d.ID(), "name", d.Name()) + return d +} + +func (d *DataTrack) Close() { + d.params.Logger.Infow("closing data track", "id", d.ID(), "name", d.Name()) + d.closed.Break() +} + +func (d *DataTrack) ToProto() *livekit.DataTrackInfo { + return utils.CloneProto(d.dti) +} + +func (d *DataTrack) PubHandle() uint16 { + return uint16(d.dti.PubHandle) +} + +func (d *DataTrack) ID() livekit.TrackID { + return livekit.TrackID(d.dti.Sid) +} + +func (d *DataTrack) Name() string { + return d.dti.Name +} + +func (d *DataTrack) AddSubscriber(sub types.LocalParticipant) (types.DataDownTrack, error) { + d.lock.Lock() + defer d.lock.Unlock() + + if _, ok := d.subscribedTracks[sub.ID()]; ok { + return nil, errAlreadySubscribed + } + + dataDownTrack, err := NewDataDownTrack( + DataDownTrackParams{ + Logger: sub.GetLogger().WithValues("trackID", d.ID()), + SubscriberID: sub.ID(), + PublishDataTrack: d, + Transport: sub.GetDataTrackTransport(), + }, + d.dti, + ) + if err != nil { + return nil, err + } + + d.subscribedTracks[sub.ID()] = dataDownTrack + return dataDownTrack, nil +} + +func (d *DataTrack) RemoveSubscriber(subID livekit.ParticipantID) { + d.lock.Lock() + dataDownTrack, ok := d.subscribedTracks[subID] + delete(d.subscribedTracks, subID) + d.lock.Unlock() + + if ok { + dataDownTrack.Close() + } +} + +func (d *DataTrack) IsSubscriber(subID livekit.ParticipantID) bool { + d.lock.Lock() + defer d.lock.Unlock() + + _, ok := d.subscribedTracks[subID] + return ok +} + +func (d *DataTrack) AddDataDownTrack(dts types.DataTrackSender) error { + if d.closed.IsBroken() { + return errReceiverClosed + } + + if d.downTrackSpreader.HasDownTrack(dts.SubscriberID()) { + d.params.Logger.Infow("subscriberID already exists, replacing data downtrack", "subscriberID", dts.SubscriberID()) + } + + d.downTrackSpreader.Store(dts) + d.params.Logger.Infow("data downtrack added", "subscriberID", dts.SubscriberID()) + return nil +} + +func (d *DataTrack) DeleteDataDownTrack(subscriberID livekit.ParticipantID) { + d.downTrackSpreader.Free(subscriberID) + d.params.Logger.Infow("data downtrack deleted", "subscriberID", subscriberID) +} + +func (d *DataTrack) HandlePacket(data []byte, packet *datatrack.Packet) { + d.downTrackSpreader.Broadcast(func(dts types.DataTrackSender) { + dts.WritePacket(data, packet) + }) +} diff --git a/pkg/rtc/datatrack/extension_participant_sid.go b/pkg/rtc/datatrack/extension_participant_sid.go new file mode 100644 index 000000000..bfad08a78 --- /dev/null +++ b/pkg/rtc/datatrack/extension_participant_sid.go @@ -0,0 +1,59 @@ +// Copyright 2023 LiveKit, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package datatrack + +import ( + "errors" + + "github.com/livekit/protocol/livekit" +) + +type ExtensionParticipantSid struct { + participantID livekit.ParticipantID +} + +func NewExtensionParticipantSid(participantID livekit.ParticipantID) (*ExtensionParticipantSid, error) { + if len(participantID) >= 65536 { + return nil, errors.New("participantID too long") + } + + return &ExtensionParticipantSid{participantID}, nil +} + +func (e *ExtensionParticipantSid) ParticipantID() livekit.ParticipantID { + return e.participantID +} + +func (e *ExtensionParticipantSid) Marshal() (Extension, error) { + data := make([]byte, len(e.participantID)) + copy(data, e.participantID) + return Extension{ + id: uint16(livekit.DataTrackExtensionID_DTEI_PARTICIPANT_SID), + data: data, + }, nil +} + +func (e *ExtensionParticipantSid) Unmarshal(ext Extension) error { + if ext.id != uint16(livekit.DataTrackExtensionID_DTEI_PARTICIPANT_SID) { + return errors.New("invalid extension ID") + } + + if len(ext.data) == 0 { + return errors.New("empty extension data") + } + + e.participantID = livekit.ParticipantID(ext.data) + return nil +} diff --git a/pkg/rtc/datatrack/extension_participant_sid_test.go b/pkg/rtc/datatrack/extension_participant_sid_test.go new file mode 100644 index 000000000..243bfc6d3 --- /dev/null +++ b/pkg/rtc/datatrack/extension_participant_sid_test.go @@ -0,0 +1,46 @@ +// Copyright 2023 LiveKit, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package datatrack + +import ( + "testing" + + "github.com/livekit/protocol/livekit" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestExtensionParticipantSid(t *testing.T) { + longTestParticipantID := livekit.ParticipantID(make([]byte, 65536)) + extParticipantSid, err := NewExtensionParticipantSid(longTestParticipantID) + require.Error(t, err) + + testParticipantID := livekit.ParticipantID("test") + extParticipantSid, err = NewExtensionParticipantSid(testParticipantID) + require.NoError(t, err) + + expectedExt := Extension{ + id: uint16(livekit.DataTrackExtensionID_DTEI_PARTICIPANT_SID), + data: []byte{'t', 'e', 's', 't'}, + } + ext, err := extParticipantSid.Marshal() + require.NoError(t, err) + require.Equal(t, expectedExt, ext) + + var unmarshaled ExtensionParticipantSid + err = unmarshaled.Unmarshal(ext) + require.NoError(t, err) + assert.Equal(t, testParticipantID, unmarshaled.ParticipantID()) +} diff --git a/pkg/rtc/datatrack/packet.go b/pkg/rtc/datatrack/packet.go new file mode 100644 index 000000000..e2dc670c8 --- /dev/null +++ b/pkg/rtc/datatrack/packet.go @@ -0,0 +1,269 @@ +// Copyright 2023 LiveKit, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package datatrack + +import ( + "encoding/binary" + "errors" + "fmt" +) + +var ( + errHeaderSizeInsufficient = errors.New("data track packet header size insufficient") + errBufferSizeInsufficient = errors.New("data track packet buffer size insufficient") + errExtensionSizeInsufficient = errors.New("data track packet extension size insufficient") + errExtensionNotFound = errors.New("data track packet extension not found") +) + +const ( + headerLength = 12 + + versionShift = 5 + versionMask = (1 << 3) - 1 + + firstOfFrameShift = 4 + firstOfFrameMask = (1 << 1) - 1 + + lastOfFrameShift = 3 + lastOfFrameMask = (1 << 1) - 1 + + extensionsShift = 2 + extensionsMask = (1 << 1) - 1 + + handleOffset = 2 + handleLength = 2 + + seqNumOffset = 4 + seqNumLength = 2 + + frameNumOffset = 6 + frameNumLength = 2 + + timestampOffset = 8 + timestampLength = 4 + + extensionsSizeOffset = headerLength + extensionsSizeLength = 2 + + extensionIDLength = 2 + extensionSizeLength = 2 +) + +type Extension struct { + id uint16 + data []byte +} + +type Header struct { + Version uint8 + IsFirstOfFrame bool + IsLastOfFrame bool + HasExtensions bool + Handle uint16 + SequenceNumber uint16 + FrameNumber uint16 + Timestamp uint32 + ExtensionsSize uint16 + Extensions []Extension +} + +/* + ┆* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + ┆* 0 1 2 3 + ┆* 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + ┆* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + ┆* |V |F|L|X| reserved | handle | + ┆* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + ┆* | sequence number | frame number | + ┆* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + ┆* | timestamp | + ┆* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |* Extensions Size if X=1 | Extensions... | + ┆* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + Each extension + ┆* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + ┆* 0 1 2 3 + ┆* 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + ┆* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + ┆* | Extension ID | Extension size | + ┆* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |* Extension payload (padded to 4 byte boundary) | + ┆* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +*/ + +func (h *Header) Unmarshal(buf []byte) (int, error) { + if len(buf) < headerLength { + return 0, fmt.Errorf("%w: %d < %d", errHeaderSizeInsufficient, len(buf), headerLength) + } + + hdrSize := headerLength + h.Version = buf[0] >> versionShift & versionMask + h.IsFirstOfFrame = (buf[0] >> firstOfFrameShift & firstOfFrameMask) > 0 + h.IsLastOfFrame = (buf[0] >> lastOfFrameShift & lastOfFrameMask) > 0 + h.HasExtensions = (buf[0] >> extensionsShift & extensionsMask) > 0 + + h.Handle = binary.BigEndian.Uint16(buf[handleOffset : handleOffset+handleLength]) + h.SequenceNumber = binary.BigEndian.Uint16(buf[seqNumOffset : seqNumOffset+seqNumLength]) + h.FrameNumber = binary.BigEndian.Uint16(buf[frameNumOffset : frameNumOffset+frameNumLength]) + h.Timestamp = binary.BigEndian.Uint32(buf[timestampOffset : timestampOffset+timestampLength]) + + if h.HasExtensions { + h.ExtensionsSize = (binary.BigEndian.Uint16(buf[extensionsSizeOffset:extensionsSizeOffset+extensionsSizeLength]) + 1) * 4 + hdrSize += extensionsSizeLength + + remainingSize := int(h.ExtensionsSize) + idx := extensionsSizeOffset + extensionsSizeLength + for remainingSize != 0 { + if len(buf[idx:]) < 4 || remainingSize < 4 { + return 0, fmt.Errorf("%w: %d/%d < %d", errExtensionSizeInsufficient, remainingSize, len(buf[idx:]), 4) + } + + id := binary.BigEndian.Uint16(buf[idx : idx+2]) + size := int(binary.BigEndian.Uint16(buf[idx+2 : idx+4])) + remainingSize -= 4 + idx += 4 + hdrSize += 4 + + if len(buf[idx:]) < size || remainingSize < size { + return 0, fmt.Errorf("%w: %d/%d < %d", errExtensionSizeInsufficient, remainingSize, len(buf[idx:]), size) + } + h.Extensions = append(h.Extensions, Extension{id: id, data: buf[idx : idx+size]}) + + size = ((size + 3) / 4) * 4 + remainingSize -= size + idx += size + hdrSize += size + } + } + + return hdrSize, nil +} + +func (h *Header) MarshalSize() int { + size := headerLength + if h.HasExtensions { + size += 2 // extensions size field + for _, ext := range h.Extensions { + size += ((len(ext.data)+3)/4)*4 + 2 /* extension ID field */ + 2 /* extension length field */ + } + } + return size +} + +func (h *Header) MarshalTo(buf []byte) (int, error) { + if len(buf) < headerLength { + return 0, fmt.Errorf("%w: %d < %d", errHeaderSizeInsufficient, len(buf), headerLength) + } + + hdrSize := headerLength + buf[0] = h.Version << versionShift + if h.IsFirstOfFrame { + buf[0] |= (1 << firstOfFrameShift) + } + if h.IsLastOfFrame { + buf[0] |= (1 << lastOfFrameShift) + } + if h.HasExtensions { + buf[0] |= (1 << extensionsShift) + } + + binary.BigEndian.PutUint16(buf[handleOffset:handleOffset+handleLength], h.Handle) + binary.BigEndian.PutUint16(buf[seqNumOffset:seqNumOffset+seqNumLength], h.SequenceNumber) + binary.BigEndian.PutUint16(buf[frameNumOffset:frameNumOffset+frameNumLength], h.FrameNumber) + binary.BigEndian.PutUint32(buf[timestampOffset:timestampOffset+timestampLength], h.Timestamp) + + if h.HasExtensions { + binary.BigEndian.PutUint16(buf[extensionsSizeOffset:extensionsSizeOffset+extensionsSizeLength], (h.ExtensionsSize/4)-1) + hdrSize += extensionsSizeLength + + idx := extensionsSizeOffset + extensionsSizeLength + for _, ext := range h.Extensions { + binary.BigEndian.PutUint16(buf[idx:idx+extensionIDLength], ext.id) + binary.BigEndian.PutUint16(buf[idx+extensionIDLength:idx+extensionIDLength+extensionSizeLength], uint16(len(ext.data))) + copy(buf[idx+extensionIDLength+extensionSizeLength:], ext.data) + + idx += ((len(ext.data)+3)/4)*4 + 2 /* extension ID field */ + 2 /* extension length field */ + hdrSize += ((len(ext.data)+3)/4)*4 + 2 /* extension ID field */ + 2 /* extension length field */ + } + } + + return hdrSize, nil +} + +func (h *Header) AddExtension(ext Extension) { + for i, existingExt := range h.Extensions { + if existingExt.id == ext.id { + h.ExtensionsSize -= uint16((len(existingExt.data)+3)/4*4 + 2 /* extension ID field */ + 2 /* extension length field */) + h.Extensions[i].data = ext.data + h.ExtensionsSize += uint16((len(h.Extensions[i].data)+3)/4*4 + 2 /* extension ID field */ + 2 /* extension length field */) + return + } + } + + h.Extensions = append(h.Extensions, ext) + h.ExtensionsSize += uint16((len(ext.data)+3)/4*4 + 2 /* extension ID field */ + 2 /* extension length field */) + h.HasExtensions = true +} + +func (h *Header) GetExtension(id uint16) (Extension, error) { + for _, ext := range h.Extensions { + if ext.id == id { + return ext, nil + } + } + return Extension{}, fmt.Errorf("%w, id: %d", errExtensionNotFound, id) +} + +// ---------------------------------------------------- + +type Packet struct { + Header + Payload []byte +} + +func (p *Packet) Unmarshal(buf []byte) error { + hdrSize, err := p.Header.Unmarshal(buf) + if err != nil { + return err + } + + p.Payload = buf[hdrSize:] + return nil +} + +func (p *Packet) Marshal() ([]byte, error) { + buf := make([]byte, p.Header.MarshalSize()+len(p.Payload)) + if err := p.MarshalTo(buf); err != nil { + return nil, err + } + + return buf, nil +} + +func (p *Packet) MarshalTo(buf []byte) error { + size := p.Header.MarshalSize() + len(p.Payload) + if len(buf) < size { + return fmt.Errorf("%w: %d < %d", errBufferSizeInsufficient, len(buf), size) + } + + hdrSize, err := p.Header.MarshalTo(buf) + if err != nil { + return err + } + + copy(buf[hdrSize:], p.Payload) + return nil +} diff --git a/pkg/rtc/datatrack/packet_test.go b/pkg/rtc/datatrack/packet_test.go new file mode 100644 index 000000000..6074ca548 --- /dev/null +++ b/pkg/rtc/datatrack/packet_test.go @@ -0,0 +1,265 @@ +// Copyright 2023 LiveKit, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package datatrack + +import ( + "testing" + + "github.com/livekit/protocol/livekit" + "github.com/stretchr/testify/require" +) + +func TestPacket(t *testing.T) { + t.Run("without extension", func(t *testing.T) { + payload := make([]byte, 6) + for i := range len(payload) { + payload[i] = byte(255 - i) + } + packet := &Packet{ + Header: Header{ + Version: 0, + IsFirstOfFrame: true, + IsLastOfFrame: true, + Handle: 3333, + SequenceNumber: 6666, + FrameNumber: 9999, + Timestamp: 0xdeadbeef, + }, + Payload: payload, + } + rawPacket, err := packet.Marshal() + require.NoError(t, err) + + expectedRawPacket := []byte{ + 0x18, 0x00, 0x0d, 0x05, 0x1a, 0x0a, 0x27, 0x0f, + 0xde, 0xad, 0xbe, 0xef, 0xff, 0xfe, 0xfd, 0xfc, + 0xfb, 0xfa, + } + require.Equal(t, expectedRawPacket, rawPacket) + + var unmarshaled Packet + err = unmarshaled.Unmarshal(rawPacket) + require.NoError(t, err) + require.Equal(t, packet, &unmarshaled) + }) + + t.Run("with extension", func(t *testing.T) { + payload := make([]byte, 4) + for i := range len(payload) { + payload[i] = byte(255 - i) + } + packet := &Packet{ + Header: Header{ + Version: 0, + IsFirstOfFrame: true, + IsLastOfFrame: false, + Handle: 3333, + SequenceNumber: 6666, + FrameNumber: 9999, + Timestamp: 0xdeadbeef, + }, + Payload: payload, + } + if extParticipantSid, err := NewExtensionParticipantSid("test_participant"); err == nil { + if ext, err := extParticipantSid.Marshal(); err == nil { + packet.AddExtension(ext) + } + } + rawPacket, err := packet.Marshal() + require.NoError(t, err) + + expectedRawPacket := []byte{ + 0x14, 0x00, 0x0d, 0x05, 0x1a, 0x0a, 0x27, 0x0f, + 0xde, 0xad, 0xbe, 0xef, 0x00, 0x04, 0x00, 0x01, + 0x00, 0x10, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x70, + 0x61, 0x72, 0x74, 0x69, 0x63, 0x69, 0x70, 0x61, + 0x6e, 0x74, 0xff, 0xfe, 0xfd, 0xfc, + } + require.Equal(t, expectedRawPacket, rawPacket) + + var unmarshaled Packet + err = unmarshaled.Unmarshal(rawPacket) + require.NoError(t, err) + require.Equal(t, packet, &unmarshaled) + + ext, err := unmarshaled.GetExtension(uint16(livekit.DataTrackExtensionID_DTEI_PARTICIPANT_SID)) + require.NoError(t, err) + + var extParticipantSid ExtensionParticipantSid + require.NoError(t, extParticipantSid.Unmarshal(ext)) + require.Equal(t, livekit.ParticipantID("test_participant"), extParticipantSid.ParticipantID()) + }) + + t.Run("with extension padding", func(t *testing.T) { + payload := make([]byte, 4) + for i := range len(payload) { + payload[i] = byte(255 - i) + } + packet := &Packet{ + Header: Header{ + Version: 0, + IsFirstOfFrame: true, + IsLastOfFrame: false, + Handle: 3333, + SequenceNumber: 6666, + FrameNumber: 9999, + Timestamp: 0xdeadbeef, + }, + Payload: payload, + } + if extParticipantSid, err := NewExtensionParticipantSid("participant"); err == nil { + if ext, err := extParticipantSid.Marshal(); err == nil { + packet.AddExtension(ext) + } + } + rawPacket, err := packet.Marshal() + require.NoError(t, err) + + expectedRawPacket := []byte{ + 0x14, 0x00, 0x0d, 0x05, 0x1a, 0x0a, 0x27, 0x0f, + 0xde, 0xad, 0xbe, 0xef, 0x00, 0x03, 0x00, 0x01, + 0x00, 0x0b, 0x70, 0x61, 0x72, 0x74, 0x69, 0x63, + 0x69, 0x70, 0x61, 0x6e, 0x74, 0x00, 0xff, 0xfe, + 0xfd, 0xfc, + } + require.Equal(t, expectedRawPacket, rawPacket) + + var unmarshaled Packet + err = unmarshaled.Unmarshal(rawPacket) + require.NoError(t, err) + require.Equal(t, packet, &unmarshaled) + + ext, err := unmarshaled.GetExtension(uint16(livekit.DataTrackExtensionID_DTEI_PARTICIPANT_SID)) + require.NoError(t, err) + + var extParticipantSid ExtensionParticipantSid + require.NoError(t, extParticipantSid.Unmarshal(ext)) + require.Equal(t, livekit.ParticipantID("participant"), extParticipantSid.ParticipantID()) + }) + + t.Run("replace extension", func(t *testing.T) { + payload := make([]byte, 4) + for i := range len(payload) { + payload[i] = byte(255 - i) + } + packet := &Packet{ + Header: Header{ + Version: 0, + IsFirstOfFrame: true, + IsLastOfFrame: false, + Handle: 3333, + SequenceNumber: 6666, + FrameNumber: 9999, + Timestamp: 0xdeadbeef, + }, + Payload: payload, + } + if extParticipantSid, err := NewExtensionParticipantSid("participant"); err == nil { + if ext, err := extParticipantSid.Marshal(); err == nil { + packet.AddExtension(ext) + } + } + rawPacket, err := packet.Marshal() + require.NoError(t, err) + + expectedRawPacket := []byte{ + 0x14, 0x00, 0x0d, 0x05, 0x1a, 0x0a, 0x27, 0x0f, + 0xde, 0xad, 0xbe, 0xef, 0x00, 0x03, 0x00, 0x01, + 0x00, 0x0b, 0x70, 0x61, 0x72, 0x74, 0x69, 0x63, + 0x69, 0x70, 0x61, 0x6e, 0x74, 0x00, 0xff, 0xfe, + 0xfd, 0xfc, + } + require.Equal(t, expectedRawPacket, rawPacket) + + // replace existing extension ID and ensure that marshalled packet is updated + if extParticipantSid, err := NewExtensionParticipantSid("test_participant"); err == nil { + if ext, err := extParticipantSid.Marshal(); err == nil { + packet.AddExtension(ext) + } + } + rawPacket, err = packet.Marshal() + require.NoError(t, err) + + expectedRawPacket = []byte{ + 0x14, 0x00, 0x0d, 0x05, 0x1a, 0x0a, 0x27, 0x0f, + 0xde, 0xad, 0xbe, 0xef, 0x00, 0x04, 0x00, 0x01, + 0x00, 0x10, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x70, + 0x61, 0x72, 0x74, 0x69, 0x63, 0x69, 0x70, 0x61, + 0x6e, 0x74, 0xff, 0xfe, 0xfd, 0xfc, + } + require.Equal(t, expectedRawPacket, rawPacket) + + var unmarshaled Packet + err = unmarshaled.Unmarshal(rawPacket) + require.NoError(t, err) + require.Equal(t, packet, &unmarshaled) + + ext, err := unmarshaled.GetExtension(uint16(livekit.DataTrackExtensionID_DTEI_PARTICIPANT_SID)) + require.NoError(t, err) + + var extParticipantSid ExtensionParticipantSid + require.NoError(t, extParticipantSid.Unmarshal(ext)) + require.Equal(t, livekit.ParticipantID("test_participant"), extParticipantSid.ParticipantID()) + }) + + t.Run("bad pcaket", func(t *testing.T) { + var unmarshaled Packet + // extensions size too small + badPacket := []byte{ + 0x14, 0x00, 0x0d, 0x05, 0x1a, 0x0a, 0x27, 0x0f, + 0xde, 0xad, 0xbe, 0xef, 0x00, 0x02, 0x00, 0x01, + 0x00, 0x0b, 0x70, 0x61, 0x72, 0x74, 0x69, 0x63, + 0x69, 0x70, 0x61, 0x6e, 0x74, 0x00, 0xff, 0xfe, + 0xfd, 0xfc, + } + err := unmarshaled.Unmarshal(badPacket) + require.Error(t, err) + + // get an invalid extension id + badPacket = []byte{ + 0x14, 0x00, 0x0d, 0x05, 0x1a, 0x0a, 0x27, 0x0f, + 0xde, 0xad, 0xbe, 0xef, 0x00, 0x03, 0x00, 0x02, + 0x00, 0x0b, 0x70, 0x61, 0x72, 0x74, 0x69, 0x63, + 0x69, 0x70, 0x61, 0x6e, 0x74, 0x00, 0xff, 0xfe, + 0xfd, 0xfc, + } + err = unmarshaled.Unmarshal(badPacket) + require.NoError(t, err) + _, err = unmarshaled.GetExtension(uint16(livekit.DataTrackExtensionID_DTEI_PARTICIPANT_SID)) + require.Error(t, err) + + // extension payload size bigger than payload + badPacket = []byte{ + 0x14, 0x00, 0x0d, 0x05, 0x1a, 0x0a, 0x27, 0x0f, + 0xde, 0xad, 0xbe, 0xef, 0x00, 0x03, 0x00, 0x01, + 0x00, 0x0d, 0x70, 0x61, 0x72, 0x74, 0x69, 0x63, + 0x69, 0x70, 0x61, 0x6e, 0x74, 0x00, 0xff, 0xfe, + 0xfd, 0xfc, + } + err = unmarshaled.Unmarshal(badPacket) + require.Error(t, err) + + // extension payload size smaller than payload + badPacket = []byte{ + 0x14, 0x00, 0x0d, 0x05, 0x1a, 0x0a, 0x27, 0x0f, + 0xde, 0xad, 0xbe, 0xef, 0x00, 0x03, 0x00, 0x01, + 0x00, 0x07, 0x70, 0x61, 0x72, 0x74, 0x69, 0x63, + 0x69, 0x70, 0x61, 0x6e, 0x74, 0x00, 0xff, 0xfe, + 0xfd, 0xfc, + } + err = unmarshaled.Unmarshal(badPacket) + require.Error(t, err) + }) +} diff --git a/pkg/rtc/datatrack/testutils.go b/pkg/rtc/datatrack/testutils.go new file mode 100644 index 000000000..6cdbdd8f2 --- /dev/null +++ b/pkg/rtc/datatrack/testutils.go @@ -0,0 +1,74 @@ +// Copyright 2023 LiveKit, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package datatrack + +import ( + "math/rand" + "time" +) + +func GenerateRawDataPackets(handle uint16, seqNum uint16, frameNum uint16, numFrames int, frameSize int, frameDuration time.Duration) [][]byte { + if seqNum == 0 { + seqNum = uint16(rand.Intn(256) + 1) + } + if frameNum == 0 { + frameNum = uint16(rand.Intn(256) + 1) + } + timestamp := uint32(rand.Intn(1024)) + + packetsPerFrame := (frameSize + 255) / 256 // using 256 bytes of payload per packet + if packetsPerFrame == 0 { + return nil + } + numPackets := packetsPerFrame * numFrames + rawPackets := make([][]byte, 0, numPackets) + for range numFrames { + remainingSize := frameSize + for packetIdx := range packetsPerFrame { + payloadSize := min(remainingSize, 256) + payload := make([]byte, payloadSize) + for i := range len(payload) { + payload[i] = byte(255 - i) + } + packet := &Packet{ + Header: Header{ + Version: 0, + IsFirstOfFrame: packetIdx == 0, + IsLastOfFrame: packetIdx == packetsPerFrame-1, + Handle: handle, + SequenceNumber: seqNum, + FrameNumber: frameNum, + Timestamp: timestamp, + }, + Payload: payload, + } + if extParticipantSid, err := NewExtensionParticipantSid("test_participant"); err == nil { + if ext, err := extParticipantSid.Marshal(); err == nil { + packet.AddExtension(ext) + } + } + rawPacket, err := packet.Marshal() + if err == nil { + rawPackets = append(rawPackets, rawPacket) + } + seqNum++ + remainingSize -= payloadSize + } + frameNum++ + timestamp += uint32(90000 * frameDuration.Seconds()) + } + + return rawPackets +} diff --git a/pkg/rtc/participant.go b/pkg/rtc/participant.go index 8f501a54e..e5c2f2cf3 100644 --- a/pkg/rtc/participant.go +++ b/pkg/rtc/participant.go @@ -52,6 +52,7 @@ import ( "github.com/livekit/livekit-server/pkg/config" "github.com/livekit/livekit-server/pkg/metric" "github.com/livekit/livekit-server/pkg/routing" + "github.com/livekit/livekit-server/pkg/rtc/datatrack" "github.com/livekit/livekit-server/pkg/rtc/signalling" "github.com/livekit/livekit-server/pkg/rtc/supervisor" "github.com/livekit/livekit-server/pkg/rtc/transport" @@ -217,6 +218,7 @@ type ParticipantParams struct { Country string PreferVideoSizeFromMedia bool UseSinglePeerConnection bool + EnableDataTracks bool } type ParticipantImpl struct { @@ -264,6 +266,7 @@ type ParticipantImpl struct { *TransportManager *UpTrackManager + *UpDataTrackManager *SubscriptionManager icQueue [2]atomic.Pointer[webrtc.ICECandidate] @@ -293,18 +296,20 @@ type ParticipantImpl struct { version atomic.Uint32 // callbacks & handlers - onTrackPublished func(types.LocalParticipant, types.MediaTrack) - onTrackUpdated func(types.LocalParticipant, types.MediaTrack) - onTrackUnpublished func(types.LocalParticipant, types.MediaTrack) + onTrackPublished func(types.Participant, types.MediaTrack) + onTrackUpdated func(types.Participant, types.MediaTrack) + onTrackUnpublished func(types.Participant, types.MediaTrack) onStateChange func(p types.LocalParticipant) onSubscriberReady func(p types.LocalParticipant) onMigrateStateChange func(p types.LocalParticipant, migrateState types.MigrateState) onParticipantUpdate func(types.LocalParticipant) onDataPacket func(types.LocalParticipant, livekit.DataPacket_Kind, *livekit.DataPacket) onDataMessage func(types.LocalParticipant, []byte) + onDataTrackMessage func(types.LocalParticipant, []byte, *datatrack.Packet) onMetrics func(types.Participant, *livekit.DataPacket) onUpdateSubscriptions func(types.LocalParticipant, []livekit.TrackID, []*livekit.ParticipantTracks, bool) onUpdateSubscriptionPermission func(types.LocalParticipant, *livekit.SubscriptionPermission) error + onUpdateDataSubscriptions func(types.LocalParticipant, *livekit.UpdateDataSubscription) onSyncState func(types.LocalParticipant, *livekit.SyncState) error onSimulateScenario func(types.LocalParticipant, *livekit.SimulateScenario) error onLeave func(types.LocalParticipant, types.ParticipantCloseReason) @@ -431,6 +436,7 @@ func NewParticipant(params ParticipantParams) (*ParticipantImpl, error) { } p.setupUpTrackManager() + p.setupUpDataTrackManager() p.setupSubscriptionManager() p.setupMetrics() @@ -788,6 +794,12 @@ func (p *ParticipantImpl) SetPermission(permission *livekit.ParticipantPermissio } } + if !grants.Video.GetCanPublishData() { + for _, dt := range p.UpDataTrackManager.GetPublishedDataTracks() { + p.UpDataTrackManager.RemovePublishedDataTrack(dt) + } + } + // update isPublisher attribute p.isPublisher.Store(isPublisher) @@ -882,6 +894,8 @@ func (p *ParticipantImpl) ToProtoWithVersion() (*livekit.ParticipantInfo, utils. } p.pendingTracksLock.RUnlock() + pi.DataTracks = p.UpDataTrackManager.ToProto() + return pi, piv } @@ -896,25 +910,25 @@ func (p *ParticipantImpl) TelemetryGuard() *telemetry.ReferenceGuard { // callbacks for clients -func (p *ParticipantImpl) OnTrackPublished(callback func(types.LocalParticipant, types.MediaTrack)) { +func (p *ParticipantImpl) OnTrackPublished(callback func(types.Participant, types.MediaTrack)) { p.lock.Lock() p.onTrackPublished = callback p.lock.Unlock() } -func (p *ParticipantImpl) getOnTrackPublished() func(types.LocalParticipant, types.MediaTrack) { +func (p *ParticipantImpl) getOnTrackPublished() func(types.Participant, types.MediaTrack) { p.lock.RLock() defer p.lock.RUnlock() return p.onTrackPublished } -func (p *ParticipantImpl) OnTrackUnpublished(callback func(types.LocalParticipant, types.MediaTrack)) { +func (p *ParticipantImpl) OnTrackUnpublished(callback func(types.Participant, types.MediaTrack)) { p.lock.Lock() p.onTrackUnpublished = callback p.lock.Unlock() } -func (p *ParticipantImpl) getOnTrackUnpublished() func(types.LocalParticipant, types.MediaTrack) { +func (p *ParticipantImpl) getOnTrackUnpublished() func(types.Participant, types.MediaTrack) { p.lock.RLock() defer p.lock.RUnlock() return p.onTrackUnpublished @@ -956,13 +970,13 @@ func (p *ParticipantImpl) getOnMigrateStateChange() func(p types.LocalParticipan return p.onMigrateStateChange } -func (p *ParticipantImpl) OnTrackUpdated(callback func(types.LocalParticipant, types.MediaTrack)) { +func (p *ParticipantImpl) OnTrackUpdated(callback func(types.Participant, types.MediaTrack)) { p.lock.Lock() p.onTrackUpdated = callback p.lock.Unlock() } -func (p *ParticipantImpl) getOnTrackUpdated() func(types.LocalParticipant, types.MediaTrack) { +func (p *ParticipantImpl) getOnTrackUpdated() func(types.Participant, types.MediaTrack) { p.lock.RLock() defer p.lock.RUnlock() return p.onTrackUpdated @@ -998,6 +1012,18 @@ func (p *ParticipantImpl) getOnDataMessage() func(types.LocalParticipant, []byte return p.onDataMessage } +func (p *ParticipantImpl) OnDataTrackMessage(callback func(types.LocalParticipant, []byte, *datatrack.Packet)) { + p.lock.Lock() + p.onDataTrackMessage = callback + p.lock.Unlock() +} + +func (p *ParticipantImpl) getOnDataTrackMessage() func(types.LocalParticipant, []byte, *datatrack.Packet) { + p.lock.RLock() + defer p.lock.RUnlock() + return p.onDataTrackMessage +} + func (p *ParticipantImpl) OnMetrics(callback func(types.Participant, *livekit.DataPacket)) { p.lock.Lock() p.onMetrics = callback @@ -1169,7 +1195,7 @@ func (p *ParticipantImpl) updateRidsFromSDP(parsed *sdp.SessionDescription, unma rids, ok := protosdp.GetSimulcastRids(m) if ok { n := min(len(rids), len(inRids)) - for i := 0; i < n; i++ { + for i := range n { // disabled layers will have a `~` prefix, remove it while determining actual rid if len(rids[i]) != 0 && rids[i][0] == '~' { outRids[i] = rids[i][1:] @@ -1454,6 +1480,7 @@ func (p *ParticipantImpl) SetMigrateInfo( mediaTracks []*livekit.TrackPublishedResponse, dataChannels []*livekit.DataChannelInfo, dataChannelReceiveState []*livekit.DataChannelReceiveState, + dataTracks []*livekit.PublishDataTrackResponse, ) { p.pendingTracksLock.Lock() for _, t := range mediaTracks { @@ -1478,7 +1505,13 @@ func (p *ParticipantImpl) SetMigrateInfo( } p.pendingTracksLock.Unlock() - if len(mediaTracks) != 0 { + for _, t := range dataTracks { + dti := t.GetInfo() + dt := NewDataTrack(DataTrackParams{Logger: p.params.Logger.WithValues("trackID", dti.Sid)}, dti) + p.UpDataTrackManager.AddPublishedDataTrack(dt) + } + + if len(mediaTracks) != 0 || len(dataTracks) != 0 { p.setIsPublisher(true) } @@ -1983,6 +2016,10 @@ func (h PublisherTransportHandler) OnDataMessageUnlabeled(data []byte) { h.p.onReceivedDataMessageUnlabeled(data) } +func (h PublisherTransportHandler) OnDataTrackMessage(data []byte) { + h.p.onReceivedDataTrackMessage(data) +} + func (h PublisherTransportHandler) OnDataSendError(err error) { h.p.onDataSendError(err) } @@ -2089,6 +2126,7 @@ func (p *ParticipantImpl) setupTransportManager() error { DataChannelStats: p.dataChannelStats, UseOneShotSignallingMode: p.params.UseOneShotSignallingMode, FireOnTrackBySdp: p.params.FireOnTrackBySdp, + EnableDataTracks: p.params.EnableDataTracks, } if p.params.SyncStreams && p.params.PlayoutDelay.GetEnabled() && p.params.ClientInfo.isFirefox() { // we will disable playout delay for Firefox if the user is expecting @@ -2141,6 +2179,13 @@ func (p *ParticipantImpl) setupUpTrackManager() { p.UpTrackManager.OnUpTrackManagerClose(p.onUpTrackManagerClose) } +func (p *ParticipantImpl) setupUpDataTrackManager() { + p.UpDataTrackManager = NewUpDataTrackManager(UpDataTrackManagerParams{ + Logger: p.pubLogger, + Participant: p, + }) +} + func (p *ParticipantImpl) setupSubscriptionManager() { p.SubscriptionManager = NewSubscriptionManager(SubscriptionManagerParams{ Participant: p, @@ -2148,6 +2193,9 @@ func (p *ParticipantImpl) setupSubscriptionManager() { TrackResolver: func(lp types.LocalParticipant, ti livekit.TrackID) types.MediaResolverResult { return p.helper().ResolveMediaTrack(lp, ti) }, + DataTrackResolver: func(lp types.LocalParticipant, ti livekit.TrackID) types.DataResolverResult { + return p.helper().ResolveDataTrack(lp, ti) + }, Telemetry: p.params.Telemetry, OnTrackSubscribed: p.onTrackSubscribed, OnTrackUnsubscribed: p.onTrackUnsubscribed, diff --git a/pkg/rtc/participant_data_track.go b/pkg/rtc/participant_data_track.go new file mode 100644 index 000000000..63e6e1a36 --- /dev/null +++ b/pkg/rtc/participant_data_track.go @@ -0,0 +1,158 @@ +// Copyright 2023 LiveKit, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package rtc + +import ( + "github.com/livekit/livekit-server/pkg/rtc/datatrack" + "github.com/livekit/livekit-server/pkg/rtc/types" + "github.com/livekit/protocol/livekit" + "github.com/livekit/protocol/logger" + "github.com/livekit/protocol/utils" + "github.com/livekit/protocol/utils/guid" +) + +func (p *ParticipantImpl) OnUpdateDataSubscriptions(callback func(types.LocalParticipant, *livekit.UpdateDataSubscription)) { + p.lock.Lock() + p.onUpdateDataSubscriptions = callback + p.lock.Unlock() +} + +func (p *ParticipantImpl) getOnUpdateDataSubscriptions() func(types.LocalParticipant, *livekit.UpdateDataSubscription) { + p.lock.RLock() + defer p.lock.RUnlock() + return p.onUpdateDataSubscriptions +} + +func (p *ParticipantImpl) HandlePublishDataTrackRequest(req *livekit.PublishDataTrackRequest) { + if !p.CanPublishData() || !p.params.EnableDataTracks { + p.pubLogger.Warnw("no permission to publish data track", nil, "req", logger.Proto(req)) + p.sendRequestResponse(&livekit.RequestResponse{ + Reason: livekit.RequestResponse_NOT_ALLOWED, + Message: "does not have permission to publish data", + Request: &livekit.RequestResponse_PublishDataTrack{ + PublishDataTrack: utils.CloneProto(req), + }, + }) + return + } + + if req.PubHandle == 0 || req.PubHandle > 65535 { + p.pubLogger.Warnw("invalid data track handle", nil, "req", logger.Proto(req)) + p.sendRequestResponse(&livekit.RequestResponse{ + Reason: livekit.RequestResponse_INVALID_HANDLE, + Message: "handle should be > 0 AND < 65536", + Request: &livekit.RequestResponse_PublishDataTrack{ + PublishDataTrack: utils.CloneProto(req), + }, + }) + return + } + + if len(req.Name) == 0 || len(req.Name) > 256 { + p.pubLogger.Warnw("invalid data track name", nil, "req", logger.Proto(req)) + p.sendRequestResponse(&livekit.RequestResponse{ + Reason: livekit.RequestResponse_INVALID_NAME, + Message: "name should not be empty and should not exceed 256 characters", + Request: &livekit.RequestResponse_PublishDataTrack{ + PublishDataTrack: utils.CloneProto(req), + }, + }) + return + } + + publishedDataTracks := p.UpDataTrackManager.GetPublishedDataTracks() + for _, dt := range publishedDataTracks { + message := "" + reason := livekit.RequestResponse_OK + switch { + case dt.PubHandle() == uint16(req.PubHandle): + message = "a data track with same handle already exists" + reason = livekit.RequestResponse_DUPLICATE_HANDLE + case dt.Name() == req.Name: + message = "a data track with same name already exists" + reason = livekit.RequestResponse_DUPLICATE_NAME + } + if message != "" { + p.pubLogger.Warnw( + "cannot publish duplicate data track", nil, + "req", logger.Proto(req), + "existing", logger.Proto(dt.ToProto()), + ) + p.sendRequestResponse(&livekit.RequestResponse{ + Reason: reason, + Message: message, + Request: &livekit.RequestResponse_PublishDataTrack{ + PublishDataTrack: utils.CloneProto(req), + }, + }) + return + } + } + + dti := &livekit.DataTrackInfo{ + PubHandle: req.PubHandle, + Sid: guid.New(utils.DataTrackPrefix), + Name: req.Name, + Encryption: req.Encryption, + } + dt := NewDataTrack(DataTrackParams{Logger: p.params.Logger.WithValues("trackID", dti.Sid)}, dti) + + p.UpDataTrackManager.AddPublishedDataTrack(dt) + + p.sendPublishDataTrackResponse(dti) + + p.setIsPublisher(true) + p.dirty.Store(true) +} + +func (p *ParticipantImpl) HandleUnpublishDataTrackRequest(req *livekit.UnpublishDataTrackRequest) { + dt := p.UpDataTrackManager.GetPublishedDataTrack(uint16(req.PubHandle)) + if dt == nil { + p.pubLogger.Warnw("unpublish data track not found", nil, "req", logger.Proto(req)) + p.sendRequestResponse(&livekit.RequestResponse{ + Reason: livekit.RequestResponse_NOT_FOUND, + Request: &livekit.RequestResponse_UnpublishDataTrack{ + UnpublishDataTrack: utils.CloneProto(req), + }, + }) + return + } + + p.UpDataTrackManager.RemovePublishedDataTrack(dt) + + p.sendUnpublishDataTrackResponse(dt.ToProto()) + + p.dirty.Store(true) +} + +func (p *ParticipantImpl) HandleUpdateDataSubscription(req *livekit.UpdateDataSubscription) { + if onUpdateDataSubscriptions := p.getOnUpdateDataSubscriptions(); onUpdateDataSubscriptions != nil { + onUpdateDataSubscriptions(p, req) + } +} + +func (p *ParticipantImpl) onReceivedDataTrackMessage(data []byte) { + var packet datatrack.Packet + if err := packet.Unmarshal(data); err != nil { + p.params.Logger.Errorw("could not unmarshal data track message", err) + return + } + + p.UpDataTrackManager.HandleReceivedDataTrackMessage(data, &packet) + + if onDataTrackMessage := p.getOnDataTrackMessage(); onDataTrackMessage != nil { + onDataTrackMessage(p, data, &packet) + } +} diff --git a/pkg/rtc/participant_internal_test.go b/pkg/rtc/participant_internal_test.go index 0e7d50946..bf99850aa 100644 --- a/pkg/rtc/participant_internal_test.go +++ b/pkg/rtc/participant_internal_test.go @@ -84,10 +84,10 @@ func TestTrackPublishing(t *testing.T) { track.IDReturns("id") published := false updated := false - p.OnTrackUpdated(func(p types.LocalParticipant, track types.MediaTrack) { + p.OnTrackUpdated(func(p types.Participant, track types.MediaTrack) { updated = true }) - p.OnTrackPublished(func(p types.LocalParticipant, track types.MediaTrack) { + p.OnTrackPublished(func(p types.Participant, track types.MediaTrack) { published = true }) p.UpTrackManager.AddPublishedTrack(track) diff --git a/pkg/rtc/participant_signal.go b/pkg/rtc/participant_signal.go index ae7d8bbf8..ad08ba0f7 100644 --- a/pkg/rtc/participant_signal.go +++ b/pkg/rtc/participant_signal.go @@ -350,3 +350,21 @@ func (p *ParticipantImpl) sendMediaSectionsRequirement(numAudios uint32, numVide } return err } + +func (p *ParticipantImpl) sendPublishDataTrackResponse(dti *livekit.DataTrackInfo) error { + return p.signaller.WriteMessage(p.signalling.SignalPublishDataTrackResponse(&livekit.PublishDataTrackResponse{ + Info: dti, + })) +} + +func (p *ParticipantImpl) sendUnpublishDataTrackResponse(dti *livekit.DataTrackInfo) error { + return p.signaller.WriteMessage(p.signalling.SignalUnpublishDataTrackResponse(&livekit.UnpublishDataTrackResponse{ + Info: dti, + })) +} + +func (p *ParticipantImpl) SendDataTrackSubscriberHandles(handles map[uint32]*livekit.DataTrackSubscriberHandles_PublishedDataTrack) error { + return p.signaller.WriteMessage(p.signalling.SignalDataTrackSubscriberHandles(&livekit.DataTrackSubscriberHandles{ + SubHandles: handles, + })) +} diff --git a/pkg/rtc/room.go b/pkg/rtc/room.go index 7ce0d8e25..1fe8e10f5 100644 --- a/pkg/rtc/room.go +++ b/pkg/rtc/room.go @@ -134,7 +134,7 @@ type Room struct { trailer []byte - onParticipantChanged func(p types.LocalParticipant) + onParticipantChanged func(p types.Participant) onRoomUpdated func() onClose func() @@ -518,6 +518,8 @@ func (r *Room) Join( participant.OnTrackPublished(r.onTrackPublished) participant.OnTrackUpdated(r.onTrackUpdated) participant.OnTrackUnpublished(r.onTrackUnpublished) + participant.OnDataTrackPublished(r.onDataTrackPublished) + participant.OnDataTrackUnpublished(r.onDataTrackUnpublished) participant.OnParticipantUpdate(r.onParticipantUpdate) participant.OnDataPacket(r.onDataPacket) participant.OnDataMessage(r.onDataMessage) @@ -558,6 +560,7 @@ func (r *Room) Join( }) participant.OnUpdateSubscriptions(r.onUpdateSubscriptions) participant.OnUpdateSubscriptionPermission(r.onUpdateSubscriptionPermission) + participant.OnUpdateDataSubscriptions(r.onUpdateDataSubscriptions) participant.OnSyncState(r.onSyncState) participant.OnSimulateScenario(r.onSimulateScenario) participant.OnLeave(r.onLeave) @@ -745,6 +748,32 @@ func (r *Room) onSyncState(participant types.LocalParticipant, state *livekit.Sy break } } + + pubDataTracks := state.GetPublishDataTracks() + existingPubDataTracks := participant.GetPublishedDataTracks() + for _, pubDataTrack := range pubDataTracks { + // client may not have sent DataTrackInfo for each published data track + dti := pubDataTrack.Info + if dti == nil { + pLogger.Warnw("DataTrackInfo not sent during resume", nil) + shouldReconnect = true + break + } + + found := false + for _, existingPubDataTrack := range existingPubDataTracks { + if existingPubDataTrack.ID() == livekit.TrackID(dti.Sid) { + found = true + break + } + } + if !found { + pLogger.Warnw("unknown data track during resume", nil, "trackID", dti.Sid) + shouldReconnect = true + break + } + } + if shouldReconnect { pLogger.Warnw("unable to resume due to missing published tracks, starting full reconnect", nil) participant.IssueFullReconnect(types.ParticipantCloseReasonPublicationError) @@ -800,6 +829,23 @@ func (r *Room) ResolveMediaTrackForSubscriber(sub types.LocalParticipant, trackI return res } +func (r *Room) ResolveDataTrackForSubscriber(sub types.LocalParticipant, trackID livekit.TrackID) types.DataResolverResult { + res := types.DataResolverResult{} + + info := r.trackManager.GetDataTrackInfo(trackID) + res.TrackChangedNotifier = r.trackManager.GetOrCreateTrackChangeNotifier(trackID) + + if info == nil { + return res + } + + res.DataTrack = info.DataTrack + res.TrackRemovedNotifier = r.trackManager.GetOrCreateTrackRemoveNotifier(trackID) + res.PublisherIdentity = info.PublisherIdentity + res.PublisherID = info.PublisherID + return res +} + func (r *Room) IsClosed() bool { select { case <-r.closed: @@ -874,7 +920,7 @@ func (r *Room) OnClose(f func()) { r.onClose = f } -func (r *Room) OnParticipantChanged(f func(participant types.LocalParticipant)) { +func (r *Room) OnParticipantChanged(f func(participant types.Participant)) { r.onParticipantChanged = f } @@ -1101,7 +1147,9 @@ func (r *Room) createJoinResponseLocked( } // a ParticipantImpl in the room added a new track, subscribe other participants to it -func (r *Room) onTrackPublished(participant types.LocalParticipant, track types.MediaTrack) { +func (r *Room) onTrackPublished(participant types.Participant, track types.MediaTrack) { + r.trackManager.AddTrack(track, participant.Identity(), participant.ID()) + // publish participant update, since track state is changed r.broadcastParticipantState(participant, broadcastOptions{skipSource: true}) @@ -1135,8 +1183,6 @@ func (r *Room) onTrackPublished(participant types.LocalParticipant, track types. onParticipantChanged(participant) } - r.trackManager.AddTrack(track, participant.Identity(), participant.ID()) - // launch jobs r.lock.Lock() hasPublished := r.hasPublished[participant.Identity()] @@ -1180,7 +1226,7 @@ func (r *Room) onTrackPublished(participant types.LocalParticipant, track types. } } -func (r *Room) onTrackUpdated(p types.LocalParticipant, _ types.MediaTrack) { +func (r *Room) onTrackUpdated(p types.Participant, _ types.MediaTrack) { // send track updates to everyone, especially if track was updated by admin r.broadcastParticipantState(p, broadcastOptions{}) if r.onParticipantChanged != nil { @@ -1188,7 +1234,7 @@ func (r *Room) onTrackUpdated(p types.LocalParticipant, _ types.MediaTrack) { } } -func (r *Room) onTrackUnpublished(p types.LocalParticipant, track types.MediaTrack) { +func (r *Room) onTrackUnpublished(p types.Participant, track types.MediaTrack) { r.trackManager.RemoveTrack(track) if !p.IsClosed() { r.broadcastParticipantState(p, broadcastOptions{skipSource: true}) @@ -1198,6 +1244,53 @@ func (r *Room) onTrackUnpublished(p types.LocalParticipant, track types.MediaTra } } +func (r *Room) onDataTrackPublished(participant types.Participant, dt types.DataTrack) { + r.trackManager.AddDataTrack(dt, participant.Identity(), participant.ID()) + + // publish participant update, since a new data track was published + r.broadcastParticipantState(participant, broadcastOptions{skipSource: true}) + + r.lock.RLock() + // subscribe all existing participants to this DataTrack + for _, existingParticipant := range r.participants { + if existingParticipant == participant { + // skip publishing participant + continue + } + if existingParticipant.State() != livekit.ParticipantInfo_ACTIVE { + // not fully joined. don't subscribe yet + continue + } + if !r.autoSubscribe(existingParticipant) { + continue + } + + existingParticipant.GetLogger().Debugw( + "subscribing to new data track", + "publisher", participant.Identity(), + "publisherID", participant.ID(), + "trackID", dt.ID(), + ) + existingParticipant.SubscribeToDataTrack(dt.ID()) + } + onParticipantChanged := r.onParticipantChanged + r.lock.RUnlock() + + if onParticipantChanged != nil { + onParticipantChanged(participant) + } +} + +func (r *Room) onDataTrackUnpublished(p types.Participant, dt types.DataTrack) { + r.trackManager.RemoveDataTrack(dt) + if !p.IsClosed() { + r.broadcastParticipantState(p, broadcastOptions{skipSource: true}) + } + if r.onParticipantChanged != nil { + r.onParticipantChanged(p) + } +} + func (r *Room) onParticipantUpdate(p types.LocalParticipant) { r.protoProxy.MarkDirty(false) // immediately notify when permissions or metadata changed @@ -1247,7 +1340,6 @@ func (r *Room) UpdateSubscriptions( participantTracks []*livekit.ParticipantTracks, subscribe bool, ) { - // handle subscription changes for _, trackID := range trackIDs { if subscribe { participant.SubscribeToTrack(trackID, false) @@ -1267,6 +1359,19 @@ func (r *Room) UpdateSubscriptions( } } +func (r *Room) onUpdateDataSubscriptions(participant types.LocalParticipant, req *livekit.UpdateDataSubscription) { + for _, update := range req.Updates { + trackID := livekit.TrackID(update.TrackSid) + if update.Subscribe { + participant.SubscribeToDataTrack(trackID) + participant.UpdateDataTrackSubscriptionOptions(trackID, update.Options) + } else { + participant.UnsubscribeFromDataTrack(trackID) + participant.UpdateDataTrackSubscriptionOptions(trackID, nil) + } + } +} + func (r *Room) onLeave(p types.LocalParticipant, reason types.ParticipantCloseReason) { r.RemoveParticipant(p.Identity(), p.ID(), reason) } @@ -1332,8 +1437,14 @@ func (r *Room) RemoveParticipant( // remove all published tracks for _, t := range p.GetPublishedTracks() { - p.RemovePublishedTrack(t, false) r.trackManager.RemoveTrack(t) + p.RemovePublishedTrack(t, false) + } + + // remove all published data tracks + for _, t := range p.GetPublishedDataTracks() { + r.trackManager.RemoveDataTrack(t) + p.RemovePublishedDataTrack(t) } if agentJob != nil { @@ -1350,6 +1461,8 @@ func (r *Room) RemoveParticipant( p.OnTrackUpdated(nil) p.OnTrackPublished(nil) p.OnTrackUnpublished(nil) + p.OnDataTrackPublished(nil) + p.OnDataTrackUnpublished(nil) p.OnStateChange(nil) p.OnSubscriberReady(nil) p.OnParticipantUpdate(nil) @@ -1359,6 +1472,7 @@ func (r *Room) RemoveParticipant( p.OnSubscribeStatusChanged(nil) p.OnUpdateSubscriptions(nil) p.OnUpdateSubscriptionPermission(nil) + p.OnUpdateDataSubscriptions(nil) p.OnSyncState(nil) p.OnSimulateScenario(nil) p.OnLeave(nil) @@ -1396,6 +1510,11 @@ func (r *Room) subscribeToExistingTracks(p types.LocalParticipant, isSync bool) trackIDs = append(trackIDs, track.ID()) p.SubscribeToTrack(track.ID(), isSync) } + + for _, track := range op.GetPublishedDataTracks() { + trackIDs = append(trackIDs, track.ID()) + p.SubscribeToDataTrack(track.ID()) + } } if len(trackIDs) > 0 { p.GetLogger().Debugw("subscribed participant to existing tracks", "trackID", trackIDs) @@ -1403,7 +1522,7 @@ func (r *Room) subscribeToExistingTracks(p types.LocalParticipant, isSync bool) } // broadcast an update about participant p -func (r *Room) broadcastParticipantState(p types.LocalParticipant, opts broadcastOptions) { +func (r *Room) broadcastParticipantState(p types.Participant, opts broadcastOptions) { pi := p.ToProto() // send it to the same participant immediately @@ -1414,9 +1533,11 @@ func (r *Room) broadcastParticipantState(p types.LocalParticipant, opts broadcas return } - err := p.SendParticipantUpdate([]*livekit.ParticipantInfo{pi}) - if err != nil { - p.GetLogger().Errorw("could not send update to participant", err) + if lp, ok := p.(types.LocalParticipant); ok { + err := lp.SendParticipantUpdate([]*livekit.ParticipantInfo{pi}) + if err != nil { + lp.GetLogger().Errorw("could not send update to participant", err) + } } }() } diff --git a/pkg/rtc/room_test.go b/pkg/rtc/room_test.go index 03b4835c0..0cce9950b 100644 --- a/pkg/rtc/room_test.go +++ b/pkg/rtc/room_test.go @@ -130,7 +130,7 @@ func TestRoomJoin(t *testing.T) { t.Run("participant state change is broadcasted to others", func(t *testing.T) { rm := newRoomWithParticipants(t, testRoomOpts{num: numParticipants}) var changedParticipant types.Participant - rm.OnParticipantChanged(func(participant types.LocalParticipant) { + rm.OnParticipantChanged(func(participant types.Participant) { changedParticipant = participant }) participants := rm.GetParticipants() diff --git a/pkg/rtc/roomtrackmanager.go b/pkg/rtc/roomtrackmanager.go index cd66f6d79..6d4395def 100644 --- a/pkg/rtc/roomtrackmanager.go +++ b/pkg/rtc/roomtrackmanager.go @@ -31,6 +31,7 @@ type RoomTrackManager struct { changedNotifier *utils.ChangeNotifierManager removedNotifier *utils.ChangeNotifierManager tracks map[livekit.TrackID][]*TrackInfo + dataTracks map[livekit.TrackID][]*DataTrackInfo } type TrackInfo struct { @@ -39,9 +40,16 @@ type TrackInfo struct { PublisherID livekit.ParticipantID } +type DataTrackInfo struct { + DataTrack types.DataTrack + PublisherIdentity livekit.ParticipantIdentity + PublisherID livekit.ParticipantID +} + func NewRoomTrackManager() *RoomTrackManager { return &RoomTrackManager{ tracks: make(map[livekit.TrackID][]*TrackInfo), + dataTracks: make(map[livekit.TrackID][]*DataTrackInfo), changedNotifier: utils.NewChangeNotifierManager(), removedNotifier: utils.NewChangeNotifierManager(), } @@ -145,3 +153,65 @@ func (r *RoomTrackManager) GetOrCreateTrackChangeNotifier(trackID livekit.TrackI func (r *RoomTrackManager) GetOrCreateTrackRemoveNotifier(trackID livekit.TrackID) *utils.ChangeNotifier { return r.removedNotifier.GetOrCreateNotifier(string(trackID)) } + +func (r *RoomTrackManager) AddDataTrack(dataTrack types.DataTrack, publisherIdentity livekit.ParticipantIdentity, publisherID livekit.ParticipantID) { + trackID := dataTrack.ID() + r.lock.Lock() + r.dataTracks[trackID] = append(r.dataTracks[trackID], &DataTrackInfo{ + DataTrack: dataTrack, + PublisherIdentity: publisherIdentity, + PublisherID: publisherID, + }) + r.lock.Unlock() + + r.NotifyTrackChanged(trackID) +} + +func (r *RoomTrackManager) RemoveDataTrack(dataTrack types.DataTrack) { + trackID := dataTrack.ID() + r.lock.Lock() + // ensure we are removing the same track as added + infos, ok := r.dataTracks[trackID] + if !ok { + r.lock.Unlock() + return + } + + found := false + for idx, info := range infos { + if info.DataTrack == dataTrack { + r.dataTracks[trackID] = slices.Delete(r.dataTracks[trackID], idx, idx+1) + if len(r.dataTracks[trackID]) == 0 { + delete(r.dataTracks, trackID) + } + found = true + break + } + } + r.lock.Unlock() + + if !found { + return + } + + n := r.removedNotifier.GetNotifier(string(trackID)) + if n != nil { + n.NotifyChanged() + } + + r.changedNotifier.RemoveNotifier(string(trackID), true) + r.removedNotifier.RemoveNotifier(string(trackID), true) +} + +func (r *RoomTrackManager) GetDataTrackInfo(trackID livekit.TrackID) *DataTrackInfo { + r.lock.RLock() + defer r.lock.RUnlock() + + infos := r.dataTracks[trackID] + if len(infos) == 0 { + return nil + } + + // earliest added data track is used till it is removed + return infos[0] +} diff --git a/pkg/rtc/signalling/interfaces.go b/pkg/rtc/signalling/interfaces.go index e49832452..d193aa4fd 100644 --- a/pkg/rtc/signalling/interfaces.go +++ b/pkg/rtc/signalling/interfaces.go @@ -59,4 +59,7 @@ type ParticipantSignalling interface { SignalSubscriptionPermissionUpdate(subscriptionPermissionUpdate *livekit.SubscriptionPermissionUpdate) proto.Message SignalMediaSectionsRequirement(mediaSectionsRequirement *livekit.MediaSectionsRequirement) proto.Message SignalSubscribedAudioCodecUpdate(subscribedAudioCodecUpdate *livekit.SubscribedAudioCodecUpdate) proto.Message + SignalPublishDataTrackResponse(publishDataTrackResponse *livekit.PublishDataTrackResponse) proto.Message + SignalUnpublishDataTrackResponse(unpublishDataTrackResponse *livekit.UnpublishDataTrackResponse) proto.Message + SignalDataTrackSubscriberHandles(dataTrackSubscriberHandles *livekit.DataTrackSubscriberHandles) proto.Message } diff --git a/pkg/rtc/signalling/signalhandler.go b/pkg/rtc/signalling/signalhandler.go index 9c9ef8881..490e1c48d 100644 --- a/pkg/rtc/signalling/signalhandler.go +++ b/pkg/rtc/signalling/signalhandler.go @@ -142,6 +142,15 @@ func (s *signalhandler) HandleMessage(msg proto.Message) error { if err := s.params.Participant.UpdateVideoTrack(msg.UpdateVideoTrack); err != nil { s.params.Logger.Warnw("could not update video track", err, "update", msg.UpdateVideoTrack) } + + case *livekit.SignalRequest_PublishDataTrackRequest: + s.params.Participant.HandlePublishDataTrackRequest(msg.PublishDataTrackRequest) + + case *livekit.SignalRequest_UnpublishDataTrackRequest: + s.params.Participant.HandleUnpublishDataTrackRequest(msg.UnpublishDataTrackRequest) + + case *livekit.SignalRequest_UpdateDataSubscription: + s.params.Participant.HandleUpdateDataSubscription(msg.UpdateDataSubscription) } return nil diff --git a/pkg/rtc/signalling/signalling.go b/pkg/rtc/signalling/signalling.go index 8b08a6d02..700d125ce 100644 --- a/pkg/rtc/signalling/signalling.go +++ b/pkg/rtc/signalling/signalling.go @@ -236,3 +236,27 @@ func (s *signalling) SignalSubscribedAudioCodecUpdate(subscribedAudioCodecUpdate }, } } + +func (u *signalling) SignalPublishDataTrackResponse(publishDataTrackResponse *livekit.PublishDataTrackResponse) proto.Message { + return &livekit.SignalResponse{ + Message: &livekit.SignalResponse_PublishDataTrackResponse{ + PublishDataTrackResponse: publishDataTrackResponse, + }, + } +} + +func (u *signalling) SignalUnpublishDataTrackResponse(unpublishDataTrackResponse *livekit.UnpublishDataTrackResponse) proto.Message { + return &livekit.SignalResponse{ + Message: &livekit.SignalResponse_UnpublishDataTrackResponse{ + UnpublishDataTrackResponse: unpublishDataTrackResponse, + }, + } +} + +func (u *signalling) SignalDataTrackSubscriberHandles(dataTrackSubscriberHandles *livekit.DataTrackSubscriberHandles) proto.Message { + return &livekit.SignalResponse{ + Message: &livekit.SignalResponse_DataTrackSubscriberHandles{ + DataTrackSubscriberHandles: dataTrackSubscriberHandles, + }, + } +} diff --git a/pkg/rtc/signalling/signallingunimplemented.go b/pkg/rtc/signalling/signallingunimplemented.go index 595877b02..dca48779f 100644 --- a/pkg/rtc/signalling/signallingunimplemented.go +++ b/pkg/rtc/signalling/signallingunimplemented.go @@ -115,3 +115,15 @@ func (u *signallingUnimplemented) SignalMediaSectionsRequirement(mediaSectionsRe func (u *signallingUnimplemented) SignalSubscribedAudioCodecUpdate(subscribedAudioCodecUpdate *livekit.SubscribedAudioCodecUpdate) proto.Message { return nil } + +func (u *signallingUnimplemented) SignalPublishDataTrackResponse(publishDataTrackResponse *livekit.PublishDataTrackResponse) proto.Message { + return nil +} + +func (u *signallingUnimplemented) SignalUnpublishDataTrackResponse(unpublishDataTrackResponse *livekit.UnpublishDataTrackResponse) proto.Message { + return nil +} + +func (u *signallingUnimplemented) SignalDataTrackSubscriberHandles(dataTrackSubscriberHandles *livekit.DataTrackSubscriberHandles) proto.Message { + return nil +} diff --git a/pkg/rtc/subscriptionmanager.go b/pkg/rtc/subscriptionmanager.go index eb7fcb45c..e7a1786be 100644 --- a/pkg/rtc/subscriptionmanager.go +++ b/pkg/rtc/subscriptionmanager.go @@ -61,6 +61,8 @@ type SubscriptionManagerParams struct { SubscriptionLimitVideo, SubscriptionLimitAudio int32 + DataTrackResolver types.DataTrackResolver + UseOneShotSignallingMode bool } @@ -68,27 +70,32 @@ type SubscriptionManagerParams struct { type SubscriptionManager struct { params SubscriptionManagerParams lock sync.RWMutex - subscriptions map[livekit.TrackID]*trackSubscription + subscriptions map[livekit.TrackID]*mediaTrackSubscription pendingUnsubscribes atomic.Int32 subscribedVideoCount, subscribedAudioCount atomic.Int32 - subscribedTo map[livekit.ParticipantID]map[livekit.TrackID]struct{} - reconcileCh chan livekit.TrackID - closeCh chan struct{} - doneCh chan struct{} + subscribedTo map[livekit.ParticipantID]map[livekit.TrackID]struct{} + reconcileCh chan livekit.TrackID + reconcileDataTrackCh chan livekit.TrackID + closeCh chan struct{} + doneCh chan struct{} onSubscribeStatusChanged func(publisherID livekit.ParticipantID, subscribed bool) + + dataTrackSubscriptions map[livekit.TrackID]*dataTrackSubscription } func NewSubscriptionManager(params SubscriptionManagerParams) *SubscriptionManager { m := &SubscriptionManager{ - params: params, - subscriptions: make(map[livekit.TrackID]*trackSubscription), - subscribedTo: make(map[livekit.ParticipantID]map[livekit.TrackID]struct{}), - reconcileCh: make(chan livekit.TrackID, 50), - closeCh: make(chan struct{}), - doneCh: make(chan struct{}), + params: params, + subscriptions: make(map[livekit.TrackID]*mediaTrackSubscription), + subscribedTo: make(map[livekit.ParticipantID]map[livekit.TrackID]struct{}), + reconcileCh: make(chan livekit.TrackID, 50), + reconcileDataTrackCh: make(chan livekit.TrackID, 5), + closeCh: make(chan struct{}), + doneCh: make(chan struct{}), + dataTrackSubscriptions: make(map[livekit.TrackID]*dataTrackSubscription), } go m.reconcileWorker() @@ -129,6 +136,24 @@ func (m *SubscriptionManager) Close(isExpectedToResume bool) { go dt.CloseWithFlush(true) } } + + m.lock.Lock() + for _, sub := range m.dataTrackSubscriptions { + dataDownTrack := sub.getDataDownTrack() + if dataDownTrack == nil { + // already unsubscribed + continue + } + + dataTrack := dataDownTrack.PublishDataTrack() + if dataTrack == nil { + continue + } + + dataTrack.RemoveSubscriber(sub.subscriberID) + } + m.lock.Unlock() + m.notifyDataTrackSubscriberHandles() } func (m *SubscriptionManager) isClosed() bool { @@ -151,7 +176,7 @@ func (m *SubscriptionManager) SubscribeToTrack(trackID livekit.TrackID, isSync b sLogger := m.params.Logger.WithValues( "trackID", trackID, ) - sub = newTrackSubscription(m.params.Participant.ID(), trackID, sLogger) + sub = newMediaTrackSubscription(m.params.Participant.ID(), trackID, sLogger) m.lock.Lock() m.subscriptions[trackID] = sub @@ -186,6 +211,37 @@ func (m *SubscriptionManager) UnsubscribeFromTrack(trackID livekit.TrackID) { m.queueReconcile(trackID) } +func (m *SubscriptionManager) SubscribeToDataTrack(trackID livekit.TrackID) { + sub, desireChanged := m.setDataTrackDesired(trackID, true) + if sub == nil { + sLogger := m.params.Logger.WithValues( + "trackID", trackID, + ) + sub = newDataTrackSubscription(m.params.Participant.ID(), trackID, sLogger) + + m.lock.Lock() + m.dataTrackSubscriptions[trackID] = sub + m.lock.Unlock() + + sub, desireChanged = m.setDataTrackDesired(trackID, true) + } + if desireChanged { + sub.logger.Debugw("subscribing to data track") + } + + m.queueReconcileDataTrack(trackID) +} + +func (m *SubscriptionManager) UnsubscribeFromDataTrack(trackID livekit.TrackID) { + sub, desireChanged := m.setDataTrackDesired(trackID, false) + if sub == nil || !desireChanged { + return + } + + sub.logger.Debugw("unsubscribing from data track") + m.queueReconcileDataTrack(trackID) +} + func (m *SubscriptionManager) ClearAllSubscriptions() { m.params.Logger.Debugw("clearing all subscriptions") @@ -193,6 +249,8 @@ func (m *SubscriptionManager) ClearAllSubscriptions() { for _, track := range m.GetSubscribedTracks() { m.unsubscribeSynchronous(track.ID()) } + + // no synchronous data tracks } numCancellations := 0 @@ -203,6 +261,10 @@ func (m *SubscriptionManager) ClearAllSubscriptions() { } sub.setDesired(false) } + + for _, sub := range m.dataTrackSubscriptions { + sub.setDesired(false) + } m.lock.RUnlock() prometheus.RecordTrackSubscribeCancels(int32(numCancellations)) @@ -289,7 +351,7 @@ func (m *SubscriptionManager) UpdateSubscribedTrackSettings(trackID livekit.Trac sLogger := m.params.Logger.WithValues( "trackID", trackID, ) - sub = newTrackSubscription(m.params.Participant.ID(), trackID, sLogger) + sub = newMediaTrackSubscription(m.params.Participant.ID(), trackID, sLogger) m.subscriptions[trackID] = sub } m.lock.Unlock() @@ -297,6 +359,21 @@ func (m *SubscriptionManager) UpdateSubscribedTrackSettings(trackID livekit.Trac sub.setSettings(settings) } +func (m *SubscriptionManager) UpdateDataTrackSubscriptionOptions(trackID livekit.TrackID, subscriptionOptions *livekit.DataTrackSubscriptionOptions) { + m.lock.Lock() + sub, ok := m.dataTrackSubscriptions[trackID] + if !ok { + sLogger := m.params.Logger.WithValues( + "trackID", trackID, + ) + sub = newDataTrackSubscription(m.params.Participant.ID(), trackID, sLogger) + m.dataTrackSubscriptions[trackID] = sub + } + m.lock.Unlock() + + sub.setSubscriptionOptions(subscriptionOptions) +} + // OnSubscribeStatusChanged callback will be notified when a participant subscribes or unsubscribes to another participant // it will only fire once per publisher. If current participant is subscribed to multiple tracks from another, this // callback will only fire once. @@ -329,9 +406,10 @@ func (m *SubscriptionManager) WaitUntilSubscribed(timeout time.Duration) error { func (m *SubscriptionManager) ReconcileAll() { m.queueReconcile(trackIDForReconcileSubscriptions) + m.queueReconcileDataTrack(trackIDForReconcileSubscriptions) } -func (m *SubscriptionManager) setDesired(trackID livekit.TrackID, desired bool) (*trackSubscription, bool) { +func (m *SubscriptionManager) setDesired(trackID livekit.TrackID, desired bool) (*mediaTrackSubscription, bool) { m.lock.RLock() defer m.lock.RUnlock() @@ -343,6 +421,18 @@ func (m *SubscriptionManager) setDesired(trackID livekit.TrackID, desired bool) return sub, sub.setDesired(desired) } +func (m *SubscriptionManager) setDataTrackDesired(trackID livekit.TrackID, desired bool) (*dataTrackSubscription, bool) { + m.lock.RLock() + defer m.lock.RUnlock() + + sub, ok := m.dataTrackSubscriptions[trackID] + if !ok { + return nil, false + } + + return sub, sub.setDesired(desired) +} + func (m *SubscriptionManager) canReconcile() bool { p := m.params.Participant if m.isClosed() || p.IsClosed() || p.IsDisconnected() { @@ -352,7 +442,7 @@ func (m *SubscriptionManager) canReconcile() bool { } func (m *SubscriptionManager) reconcileSubscriptions() { - var needsToReconcile []*trackSubscription + var needsToReconcile []*mediaTrackSubscription m.lock.RLock() for _, sub := range m.subscriptions { if sub.needsSubscribe() || sub.needsUnsubscribe() || sub.needsBind() || sub.needsCleanup() { @@ -366,7 +456,7 @@ func (m *SubscriptionManager) reconcileSubscriptions() { } } -func (m *SubscriptionManager) reconcileSubscription(s *trackSubscription) { +func (m *SubscriptionManager) reconcileSubscription(s *mediaTrackSubscription) { if !m.canReconcile() { return } @@ -419,15 +509,17 @@ func (m *SubscriptionManager) reconcileSubscription(s *trackSubscription) { default: // all other errors if s.durationSinceStart() > subscriptionTimeout { - s.logger.Warnw("failed to subscribe, triggering error handler", err, - "attempt", numAttempts, + s.logger.Warnw( + "failed to subscribe, triggering error handler", err, + "attempt", s.getNumAttempts(), ) s.maybeRecordError(m.params.Telemetry, s.subscriberID, err, false) m.params.OnSubscriptionError(s.trackID, true, err) } else { - s.logger.Debugw("failed to subscribe, retrying", + s.logger.Debugw( + "failed to subscribe, retrying", "error", err, - "attempt", numAttempts, + "attempt", s.getNumAttempts(), ) } } @@ -471,6 +563,86 @@ func (m *SubscriptionManager) reconcileSubscription(s *trackSubscription) { m.lock.Unlock() } +func (m *SubscriptionManager) reconcileDataTrackSubscriptions() { + var needsToReconcile []*dataTrackSubscription + m.lock.RLock() + for _, sub := range m.dataTrackSubscriptions { + if sub.needsSubscribe() || sub.needsUnsubscribe() { + needsToReconcile = append(needsToReconcile, sub) + } + } + m.lock.RUnlock() + + for _, s := range needsToReconcile { + m.reconcileDataTrackSubscription(s) + } +} + +func (m *SubscriptionManager) reconcileDataTrackSubscription(s *dataTrackSubscription) { + if !m.canReconcile() { + return + } + if s.needsSubscribe() { + if err := m.subscribeDataTrack(s); err != nil { + s.recordAttempt(false) + + switch err { + case ErrNoSubscribePermission: + // these are errors that are outside of our control, so we'll keep trying + // - ErrNoSubscribePermission: participant was not granted canSubscribe, may change any moment + case ErrTrackNotFound: + // source track was never published or closed + // if after timeout we'd unsubscribe from it. + // this is the *only* case we'd change desired state + if s.durationSinceStart() > notFoundTimeout { + s.logger.Infow("unsubscribing from data track after notFoundTimeout", "error", err) + s.setDesired(false) + m.queueReconcile(s.trackID) + } + default: + // all other errors + if s.durationSinceStart() > subscriptionTimeout { + s.logger.Warnw( + "failed to subscribe, triggering error handler", err, + "attempt", s.getNumAttempts(), + ) + } else { + s.logger.Debugw( + "failed to subscribe, retrying", + "error", err, + "attempt", s.getNumAttempts(), + ) + } + } + } else { + s.recordAttempt(true) + m.notifyDataTrackSubscriberHandles() + } + + return + } + + if s.needsUnsubscribe() { + if err := m.unsubscribeDataTrack(s); err != nil { + s.logger.Warnw("failed to unsubscribe", err) + } + + m.lock.Lock() + delete(m.dataTrackSubscriptions, s.trackID) + m.lock.Unlock() + m.notifyDataTrackSubscriberHandles() + return + } + + m.lock.Lock() + if s.needsCleanup() { + s.logger.Debugw("cleanup removing data track subscription") + delete(m.dataTrackSubscriptions, s.trackID) + m.notifyDataTrackSubscriberHandles() + } + m.lock.Unlock() +} + // trigger an immediate reconciliation, when trackID is empty, will reconcile all subscriptions func (m *SubscriptionManager) queueReconcile(trackID livekit.TrackID) { select { @@ -480,17 +652,29 @@ func (m *SubscriptionManager) queueReconcile(trackID livekit.TrackID) { } } +func (m *SubscriptionManager) queueReconcileDataTrack(trackID livekit.TrackID) { + select { + case m.reconcileDataTrackCh <- trackID: + default: + // queue is full, will reconcile based on timer + } +} + func (m *SubscriptionManager) reconcileWorker() { + defer close(m.doneCh) + reconcileTicker := time.NewTicker(reconcileInterval) defer reconcileTicker.Stop() - defer close(m.doneCh) for { select { case <-m.closeCh: return + case <-reconcileTicker.C: m.reconcileSubscriptions() + m.reconcileDataTrackSubscriptions() + case trackID := <-m.reconcileCh: m.lock.Lock() s := m.subscriptions[trackID] @@ -500,6 +684,16 @@ func (m *SubscriptionManager) reconcileWorker() { } else { m.reconcileSubscriptions() } + + case trackID := <-m.reconcileDataTrackCh: + m.lock.Lock() + s := m.dataTrackSubscriptions[trackID] + m.lock.Unlock() + if s != nil { + m.reconcileDataTrackSubscription(s) + } else { + m.reconcileDataTrackSubscriptions() + } } } } @@ -519,31 +713,31 @@ func (m *SubscriptionManager) hasCapacityForSubscription(kind livekit.TrackType) return true } -func (m *SubscriptionManager) subscribe(s *trackSubscription) error { - s.logger.Debugw("executing subscribe") +func (m *SubscriptionManager) subscribe(sub *mediaTrackSubscription) error { + sub.logger.Debugw("executing subscribe") if !m.params.Participant.CanSubscribe() { return ErrNoSubscribePermission } - if kind, ok := s.getKind(); ok && !m.hasCapacityForSubscription(kind) { + if kind, ok := sub.getKind(); ok && !m.hasCapacityForSubscription(kind) { return ErrSubscriptionLimitExceeded } - trackID := s.trackID + trackID := sub.trackID res := m.params.TrackResolver(m.params.Participant, trackID) - s.logger.Debugw("resolved track", "result", res) + sub.logger.Debugw("resolved track", "result", res) - if res.TrackChangedNotifier != nil && s.setChangedNotifier(res.TrackChangedNotifier) { + if res.TrackChangedNotifier != nil && sub.setChangedNotifier(res.TrackChangedNotifier) { // set callback only when we haven't done it before // we set the observer before checking for existence of track, so that we may get notified // when the track becomes available - res.TrackChangedNotifier.AddObserver(string(s.subscriberID), func() { + res.TrackChangedNotifier.AddObserver(string(sub.subscriberID), func() { m.queueReconcile(trackID) }) } - if res.TrackRemovedNotifier != nil && s.setRemovedNotifier(res.TrackRemovedNotifier) { - res.TrackRemovedNotifier.AddObserver(string(s.subscriberID), func() { + if res.TrackRemovedNotifier != nil && sub.setRemovedNotifier(res.TrackRemovedNotifier) { + res.TrackRemovedNotifier.AddObserver(string(sub.subscriberID), func() { // re-resolve the track in case the same track had been re-published res := m.params.TrackResolver(m.params.Participant, trackID) if res.Track != nil { @@ -558,93 +752,26 @@ func (m *SubscriptionManager) subscribe(s *trackSubscription) error { if track == nil { return ErrTrackNotFound } - s.trySetKind(track.Kind()) + sub.trySetKind(track.Kind()) if !m.hasCapacityForSubscription(track.Kind()) { return ErrSubscriptionLimitExceeded } - s.setPublisher(res.PublisherIdentity, res.PublisherID) + sub.setPublisher(res.PublisherIdentity, res.PublisherID) - permChanged := s.setHasPermission(res.HasPermission) + permChanged := sub.setHasPermission(res.HasPermission) if permChanged { - m.params.Participant.SendSubscriptionPermissionUpdate(s.getPublisherID(), trackID, res.HasPermission) + m.params.Participant.SendSubscriptionPermissionUpdate(sub.getPublisherID(), trackID, res.HasPermission) } if !res.HasPermission { return ErrNoTrackPermission } - subTrack, err := track.AddSubscriber(m.params.Participant) - if err != nil && !errors.Is(err, errAlreadySubscribed) { - // ignore error(s): already subscribed - if !utils.ErrorIsOneOf(err, ErrNoReceiver) { - // as track resolution could take some time, not logging errors due to waiting for track resolution - m.params.Logger.Warnw("add subscriber failed", err, "trackID", trackID) - } + if err := m.addSubscriber(sub, track, false); err != nil { return err } - if err == errAlreadySubscribed { - m.params.Logger.Debugw( - "already subscribed to track", - "trackID", trackID, - "subscribedAudioCount", m.subscribedAudioCount.Load(), - "subscribedVideoCount", m.subscribedVideoCount.Load(), - ) - } - if err == nil && subTrack != nil { // subTrack could be nil if already subscribed - subTrack.OnClose(func(isExpectedToResume bool) { - m.handleSubscribedTrackClose(s, isExpectedToResume) - }) - subTrack.AddOnBind(func(err error) { - if err != nil { - s.logger.Infow("failed to bind track", "err", err) - s.maybeRecordError(m.params.Telemetry, s.subscriberID, err, true) - m.UnsubscribeFromTrack(trackID) - m.params.OnSubscriptionError(trackID, false, err) - return - } - s.setBound() - s.maybeRecordSuccess(m.params.Telemetry, s.subscriberID) - }) - s.setSubscribedTrack(subTrack) - switch track.Kind() { - case livekit.TrackType_VIDEO: - m.subscribedVideoCount.Inc() - case livekit.TrackType_AUDIO: - m.subscribedAudioCount.Inc() - } - - if subTrack.NeedsNegotiation() { - m.params.Participant.Negotiate(false) - } - - go m.params.OnTrackSubscribed(subTrack) - - m.params.Logger.Debugw( - "subscribed to track", - "trackID", trackID, - "subscribedAudioCount", m.subscribedAudioCount.Load(), - "subscribedVideoCount", m.subscribedVideoCount.Load(), - ) - } - - // add mark the participant as someone we've subscribed to - firstSubscribe := false - publisherID := s.getPublisherID() - m.lock.Lock() - pTracks := m.subscribedTo[publisherID] - changedCB := m.onSubscribeStatusChanged - if pTracks == nil { - pTracks = make(map[livekit.TrackID]struct{}) - m.subscribedTo[publisherID] = pTracks - firstSubscribe = true - } - pTracks[trackID] = struct{}{} - m.lock.Unlock() - - if changedCB != nil && firstSubscribe { - changedCB(publisherID, true) - } + m.markSubscribedTo(sub.getPublisherID(), trackID) return nil } @@ -669,20 +796,29 @@ func (m *SubscriptionManager) subscribeSynchronous(trackID livekit.TrackID) erro sLogger := m.params.Logger.WithValues( "trackID", trackID, ) - sub = newTrackSubscription(m.params.Participant.ID(), trackID, sLogger) + sub = newMediaTrackSubscription(m.params.Participant.ID(), trackID, sLogger) m.subscriptions[trackID] = sub } m.lock.Unlock() sub.setDesired(true) + return m.addSubscriber(sub, track, true) +} + +func (m *SubscriptionManager) addSubscriber(sub *mediaTrackSubscription, track types.MediaTrack, isSync bool) error { + trackID := track.ID() subTrack, err := track.AddSubscriber(m.params.Participant) if err != nil && !errors.Is(err, errAlreadySubscribed) { + // ignore error(s): already subscribed + if !utils.ErrorIsOneOf(err, ErrNoReceiver) { + // as track resolution could take some time, not logging errors due to waiting for track resolution + m.params.Logger.Warnw("add subscriber failed", err, "trackID", trackID) + } return err } if err == errAlreadySubscribed { - m.params.Logger.Debugw( + sub.logger.Debugw( "already subscribed to track", - "trackID", trackID, "subscribedAudioCount", m.subscribedAudioCount.Load(), "subscribedVideoCount", m.subscribedVideoCount.Load(), ) @@ -691,9 +827,11 @@ func (m *SubscriptionManager) subscribeSynchronous(trackID livekit.TrackID) erro subTrack.OnClose(func(isExpectedToResume bool) { m.handleSubscribedTrackClose(sub, isExpectedToResume) - m.lock.Lock() - delete(m.subscriptions, trackID) - m.lock.Unlock() + if isSync { + m.lock.Lock() + delete(m.subscriptions, trackID) + m.lock.Unlock() + } }) subTrack.AddOnBind(func(err error) { if err != nil { @@ -715,11 +853,14 @@ func (m *SubscriptionManager) subscribeSynchronous(trackID livekit.TrackID) erro m.subscribedAudioCount.Inc() } + if !isSync && subTrack.NeedsNegotiation() { + m.params.Participant.Negotiate(false) + } + go m.params.OnTrackSubscribed(subTrack) - m.params.Logger.Debugw( + sub.logger.Debugw( "subscribed to track", - "trackID", trackID, "subscribedAudioCount", m.subscribedAudioCount.Load(), "subscribedVideoCount", m.subscribedVideoCount.Load(), ) @@ -727,7 +868,7 @@ func (m *SubscriptionManager) subscribeSynchronous(trackID livekit.TrackID) erro return nil } -func (m *SubscriptionManager) unsubscribe(s *trackSubscription) error { +func (m *SubscriptionManager) unsubscribe(s *mediaTrackSubscription) error { s.logger.Debugw("executing unsubscribe") subTrack := s.getSubscribedTrack() @@ -792,7 +933,7 @@ func (m *SubscriptionManager) handleSourceTrackRemoved(trackID livekit.TrackID) // - subscriber-initiated unsubscribe // - UpTrack was closed // - publisher revoked permissions for the participant -func (m *SubscriptionManager) handleSubscribedTrackClose(s *trackSubscription, isExpectedToResume bool) { +func (m *SubscriptionManager) handleSubscribedTrackClose(s *mediaTrackSubscription, isExpectedToResume bool) { s.logger.Debugw( "subscribed track closed", "isExpectedToResume", isExpectedToResume, @@ -814,23 +955,7 @@ func (m *SubscriptionManager) handleSubscribedTrackClose(s *trackSubscription, i relieveFromLimits = m.params.SubscriptionLimitAudio > 0 && audioCount == m.params.SubscriptionLimitAudio-1 } - // remove from subscribedTo - publisherID := s.getPublisherID() - lastSubscription := false - m.lock.Lock() - changedCB := m.onSubscribeStatusChanged - pTracks := m.subscribedTo[publisherID] - if pTracks != nil { - delete(pTracks, s.trackID) - if len(pTracks) == 0 { - delete(m.subscribedTo, publisherID) - lastSubscription = true - } - } - m.lock.Unlock() - if changedCB != nil && lastSubscription { - go changedCB(publisherID, false) - } + m.unmarkSubscribedTo(s.getPublisherID(), s.trackID) go m.params.OnTrackUnsubscribed(subTrack) @@ -895,6 +1020,146 @@ func (m *SubscriptionManager) handleSubscribedTrackClose(s *trackSubscription, i } } +func (m *SubscriptionManager) subscribeDataTrack(sub *dataTrackSubscription) error { + sub.logger.Debugw("executing subscribe") + + if !m.params.Participant.CanSubscribe() { + return ErrNoSubscribePermission + } + + trackID := sub.trackID + res := m.params.DataTrackResolver(m.params.Participant, trackID) + sub.logger.Debugw("resolved data track", "result", res) + + if res.TrackChangedNotifier != nil && sub.setChangedNotifier(res.TrackChangedNotifier) { + // set callback only when we haven't done it before + // we set the observer before checking for existence of track, so that we may get notified + // when the track becomes available + res.TrackChangedNotifier.AddObserver(string(sub.subscriberID), func() { + m.queueReconcileDataTrack(trackID) + }) + } + if res.TrackRemovedNotifier != nil && sub.setRemovedNotifier(res.TrackRemovedNotifier) { + res.TrackRemovedNotifier.AddObserver(string(sub.subscriberID), func() { + // re-resolve the track in case the same track had been re-published + res := m.params.DataTrackResolver(m.params.Participant, trackID) + if res.DataTrack != nil { + // do not unsubscribe, track is still available + return + } + m.handleSourceDataTrackRemoved(trackID) + }) + } + + dataTrack := res.DataTrack + if dataTrack == nil { + return ErrTrackNotFound + } + + sub.setPublisher(res.PublisherIdentity, res.PublisherID) + + dataDownTrack, err := dataTrack.AddSubscriber(m.params.Participant) + if err != nil && !errors.Is(err, errAlreadySubscribed) { + return err + } + if err == errAlreadySubscribed { + sub.logger.Debugw("already subscribed to data track") + } + if err == nil && dataDownTrack != nil { // subTrack could be nil if already subscribed + sub.setDataDownTrack(dataDownTrack) + sub.logger.Debugw("subscribed to data track") + } + + m.markSubscribedTo(sub.getPublisherID(), trackID) + return nil +} + +func (m *SubscriptionManager) unsubscribeDataTrack(s *dataTrackSubscription) error { + s.logger.Debugw("executing unsubscribe") + + dataDownTrack := s.getDataDownTrack() + if dataDownTrack == nil { + // already unsubscribed + return nil + } + + dataTrack := dataDownTrack.PublishDataTrack() + dataTrack.RemoveSubscriber(s.subscriberID) + + m.unmarkSubscribedTo(s.getPublisherID(), s.trackID) + return nil +} + +func (m *SubscriptionManager) notifyDataTrackSubscriberHandles() { + m.lock.Lock() + handles := make(map[uint32]*livekit.DataTrackSubscriberHandles_PublishedDataTrack, len(m.dataTrackSubscriptions)) + for _, sub := range m.dataTrackSubscriptions { + if !sub.isDesired() { + continue + } + dataDownTrack := sub.getDataDownTrack() + if dataDownTrack == nil { + continue + } + handles[uint32(dataDownTrack.Handle())] = &livekit.DataTrackSubscriberHandles_PublishedDataTrack{ + PublisherIdentity: string(sub.publisherIdentity), + PublisherSid: string(sub.publisherID), + TrackSid: string(sub.trackID), + } + } + m.lock.Unlock() + + m.params.Participant.SendDataTrackSubscriberHandles(handles) +} + +func (m *SubscriptionManager) handleSourceDataTrackRemoved(trackID livekit.TrackID) { + m.lock.Lock() + sub := m.dataTrackSubscriptions[trackID] + m.lock.Unlock() + + if sub != nil { + sub.handleSourceTrackRemoved() + } +} + +func (m *SubscriptionManager) markSubscribedTo(publisherID livekit.ParticipantID, trackID livekit.TrackID) { + // add mark the participant as someone we've subscribed to + firstSubscribe := false + m.lock.Lock() + pTracks := m.subscribedTo[publisherID] + changedCB := m.onSubscribeStatusChanged + if pTracks == nil { + pTracks = make(map[livekit.TrackID]struct{}) + m.subscribedTo[publisherID] = pTracks + firstSubscribe = true + } + pTracks[trackID] = struct{}{} + m.lock.Unlock() + + if changedCB != nil && firstSubscribe { + changedCB(publisherID, true) + } +} + +func (m *SubscriptionManager) unmarkSubscribedTo(publisherID livekit.ParticipantID, trackID livekit.TrackID) { + // remove from subscribedTo + lastSubscription := false + m.lock.Lock() + changedCB := m.onSubscribeStatusChanged + pTracks := m.subscribedTo[publisherID] + if pTracks != nil { + delete(pTracks, trackID) + if len(pTracks) == 0 { + delete(m.subscribedTo, publisherID) + lastSubscription = true + } + } + m.lock.Unlock() + if changedCB != nil && lastSubscription { + go changedCB(publisherID, false) + } +} + func (m *SubscriptionManager) getNumCancellations() int { m.lock.RLock() defer m.lock.RUnlock() @@ -915,39 +1180,21 @@ type trackSubscription struct { trackID livekit.TrackID logger logger.Logger - lock sync.RWMutex - desired bool - publisherID livekit.ParticipantID - publisherIdentity livekit.ParticipantIdentity - settings *livekit.UpdateTrackSettings - changedNotifier types.ChangeNotifier - removedNotifier types.ChangeNotifier - hasPermissionInitialized bool - hasPermission bool - subscribedTrack types.SubscribedTrack - eventSent atomic.Bool - numAttempts atomic.Int32 - bound bool - kind atomic.Pointer[livekit.TrackType] + lock sync.RWMutex + desired bool + publisherID livekit.ParticipantID + publisherIdentity livekit.ParticipantIdentity + changedNotifier types.ChangeNotifier + removedNotifier types.ChangeNotifier + + numAttempts atomic.Int32 // the later of when subscription was requested OR when the first failure was encountered OR when permission is granted // this timestamp determines when failures are reported subStartedAt atomic.Pointer[time.Time] // the timestamp when the subscription was started, will be reset when downtrack is closed with expected resume - subscribeAt atomic.Pointer[time.Time] - succRecordCounter atomic.Int32 -} - -func newTrackSubscription(subscriberID livekit.ParticipantID, trackID livekit.TrackID, l logger.Logger) *trackSubscription { - s := &trackSubscription{ - subscriberID: subscriberID, - trackID: trackID, - logger: l, - } - t := time.Now() - s.subscribeAt.Store(&t) - return s + subscribeAt atomic.Pointer[time.Time] } func (s *trackSubscription) setPublisher(publisherIdentity livekit.ParticipantIdentity, publisherID livekit.ParticipantID) { @@ -992,70 +1239,35 @@ func (s *trackSubscription) setDesired(desired bool) bool { return true } -// set permission and return true if it has changed -func (s *trackSubscription) setHasPermission(perm bool) bool { - s.lock.Lock() - defer s.lock.Unlock() - if s.hasPermissionInitialized && s.hasPermission == perm { - return false - } - - s.hasPermissionInitialized = true - s.hasPermission = perm - if s.hasPermission { - // when permission is granted, reset the timer so it has sufficient time to reconcile - t := time.Now() - s.subStartedAt.Store(&t) - s.subscribeAt.Store(&t) - } - return true -} - -func (s *trackSubscription) getHasPermission() bool { - s.lock.RLock() - defer s.lock.RUnlock() - return s.hasPermission -} - func (s *trackSubscription) isDesired() bool { s.lock.RLock() defer s.lock.RUnlock() return s.desired } -func (s *trackSubscription) setSubscribedTrack(track types.SubscribedTrack) { - s.lock.Lock() - oldTrack := s.subscribedTrack - s.subscribedTrack = track - s.bound = false - settings := s.settings - s.lock.Unlock() - - if settings != nil && track != nil { - s.logger.Debugw("restoring subscriber settings", "settings", logger.Proto(settings)) - track.UpdateSubscriberSettings(settings, true) - } - if oldTrack != nil { - oldTrack.OnClose(nil) +func (s *trackSubscription) recordAttempt(success bool) { + if !success { + if s.numAttempts.Load() == 0 { + // on first failure, we'd want to start the timer + t := time.Now() + s.subStartedAt.Store(&t) + } + s.numAttempts.Add(1) + } else { + s.numAttempts.Store(0) } } -func (s *trackSubscription) trySetKind(kind livekit.TrackType) { - s.kind.CompareAndSwap(nil, &kind) +func (s *trackSubscription) getNumAttempts() int32 { + return s.numAttempts.Load() } -func (s *trackSubscription) getKind() (livekit.TrackType, bool) { - kind := s.kind.Load() - if kind == nil { - return livekit.TrackType_AUDIO, false +func (s *trackSubscription) durationSinceStart() time.Duration { + t := s.subStartedAt.Load() + if t == nil { + return 0 } - return *kind, true -} - -func (s *trackSubscription) getSubscribedTrack() types.SubscribedTrack { - s.lock.RLock() - defer s.lock.RUnlock() - return s.subscribedTrack + return time.Since(*t) } func (s *trackSubscription) setChangedNotifier(notifier types.ChangeNotifier) bool { @@ -1098,46 +1310,6 @@ func (s *trackSubscription) setRemovedNotifierLocked(notifier types.ChangeNotifi return true } -func (s *trackSubscription) setSettings(settings *livekit.UpdateTrackSettings) { - s.lock.Lock() - s.settings = settings - subTrack := s.subscribedTrack - s.lock.Unlock() - if subTrack != nil { - subTrack.UpdateSubscriberSettings(settings, false) - } -} - -// mark the subscription as bound - when we've received the client's answer -func (s *trackSubscription) setBound() { - s.lock.Lock() - defer s.lock.Unlock() - s.bound = true -} - -func (s *trackSubscription) isBound() bool { - s.lock.RLock() - defer s.lock.RUnlock() - return s.bound -} - -func (s *trackSubscription) recordAttempt(success bool) { - if !success { - if s.numAttempts.Load() == 0 { - // on first failure, we'd want to start the timer - t := time.Now() - s.subStartedAt.Store(&t) - } - s.numAttempts.Add(1) - } else { - s.numAttempts.Store(0) - } -} - -func (s *trackSubscription) getNumAttempts() int32 { - return s.numAttempts.Load() -} - func (s *trackSubscription) handleSourceTrackRemoved() { s.lock.Lock() defer s.lock.Unlock() @@ -1150,7 +1322,119 @@ func (s *trackSubscription) handleSourceTrackRemoved() { s.setRemovedNotifierLocked(nil) } -func (s *trackSubscription) maybeRecordError(ts telemetry.TelemetryService, pID livekit.ParticipantID, err error, isUserError bool) { +// -------------------------------------------------------------------------------------- + +type mediaTrackSubscription struct { + trackSubscription + + settings *livekit.UpdateTrackSettings + hasPermissionInitialized bool + hasPermission bool + subscribedTrack types.SubscribedTrack + eventSent atomic.Bool + bound bool + kind atomic.Pointer[livekit.TrackType] + + succRecordCounter atomic.Int32 +} + +func newMediaTrackSubscription(subscriberID livekit.ParticipantID, trackID livekit.TrackID, l logger.Logger) *mediaTrackSubscription { + s := &mediaTrackSubscription{ + trackSubscription: trackSubscription{ + subscriberID: subscriberID, + trackID: trackID, + logger: l, + }, + } + t := time.Now() + s.subscribeAt.Store(&t) + return s +} + +// set permission and return true if it has changed +func (s *mediaTrackSubscription) setHasPermission(perm bool) bool { + s.lock.Lock() + defer s.lock.Unlock() + if s.hasPermissionInitialized && s.hasPermission == perm { + return false + } + + s.hasPermissionInitialized = true + s.hasPermission = perm + if s.hasPermission { + // when permission is granted, reset the timer so it has sufficient time to reconcile + t := time.Now() + s.subStartedAt.Store(&t) + s.subscribeAt.Store(&t) + } + return true +} + +func (s *mediaTrackSubscription) getHasPermission() bool { + s.lock.RLock() + defer s.lock.RUnlock() + return s.hasPermission +} + +func (s *mediaTrackSubscription) setSubscribedTrack(track types.SubscribedTrack) { + s.lock.Lock() + oldTrack := s.subscribedTrack + s.subscribedTrack = track + s.bound = false + settings := s.settings + s.lock.Unlock() + + if settings != nil && track != nil { + s.logger.Debugw("restoring subscriber settings", "settings", logger.Proto(settings)) + track.UpdateSubscriberSettings(settings, true) + } + if oldTrack != nil { + oldTrack.OnClose(nil) + } +} + +func (s *mediaTrackSubscription) getSubscribedTrack() types.SubscribedTrack { + s.lock.RLock() + defer s.lock.RUnlock() + return s.subscribedTrack +} + +func (s *mediaTrackSubscription) trySetKind(kind livekit.TrackType) { + s.kind.CompareAndSwap(nil, &kind) +} + +func (s *mediaTrackSubscription) getKind() (livekit.TrackType, bool) { + kind := s.kind.Load() + if kind == nil { + return livekit.TrackType_AUDIO, false + } + return *kind, true +} + +func (s *mediaTrackSubscription) setSettings(settings *livekit.UpdateTrackSettings) { + s.lock.Lock() + s.settings = settings + subTrack := s.subscribedTrack + s.lock.Unlock() + if subTrack != nil { + subTrack.UpdateSubscriberSettings(settings, false) + } +} + +// mark the subscription as bound - when we've received the client's answer +func (s *mediaTrackSubscription) setBound() { + s.lock.Lock() + defer s.lock.Unlock() + s.bound = true +} + +func (s *mediaTrackSubscription) isBound() bool { + s.lock.RLock() + defer s.lock.RUnlock() + return s.bound +} + +func (s *mediaTrackSubscription) maybeRecordError(ts telemetry.TelemetryService, pID livekit.ParticipantID, err error, isUserError bool) { if s.eventSent.Swap(true) { return } @@ -1158,7 +1442,7 @@ func (s *trackSubscription) maybeRecordError(ts telemetry.TelemetryService, pID ts.TrackSubscribeFailed(context.Background(), pID, s.trackID, err, isUserError) } -func (s *trackSubscription) maybeRecordSuccess(ts telemetry.TelemetryService, pID livekit.ParticipantID) { +func (s *mediaTrackSubscription) maybeRecordSuccess(ts telemetry.TelemetryService, pID livekit.ParticipantID) { subTrack := s.getSubscribedTrack() if subTrack == nil { return @@ -1190,38 +1474,101 @@ func (s *trackSubscription) maybeRecordSuccess(ts telemetry.TelemetryService, pI ts.TrackSubscribed(context.Background(), pID, mediaTrack.ToProto(), pi, !eventSent) } -func (s *trackSubscription) isCanceled() bool { +func (s *mediaTrackSubscription) isCanceled() bool { return !s.eventSent.Load() && s.isDesired() } -func (s *trackSubscription) durationSinceStart() time.Duration { - t := s.subStartedAt.Load() - if t == nil { - return 0 - } - return time.Since(*t) -} - -func (s *trackSubscription) needsSubscribe() bool { +func (s *mediaTrackSubscription) needsSubscribe() bool { s.lock.RLock() defer s.lock.RUnlock() return s.desired && s.subscribedTrack == nil } -func (s *trackSubscription) needsUnsubscribe() bool { +func (s *mediaTrackSubscription) needsUnsubscribe() bool { s.lock.RLock() defer s.lock.RUnlock() return !s.desired && s.subscribedTrack != nil } -func (s *trackSubscription) needsBind() bool { +func (s *mediaTrackSubscription) needsBind() bool { s.lock.RLock() defer s.lock.RUnlock() return s.desired && s.subscribedTrack != nil && !s.bound } -func (s *trackSubscription) needsCleanup() bool { +func (s *mediaTrackSubscription) needsCleanup() bool { s.lock.RLock() defer s.lock.RUnlock() return !s.desired && s.subscribedTrack == nil } + +// ----------------------------------------------------------------- + +type dataTrackSubscription struct { + trackSubscription + + subscriptionOptions *livekit.DataTrackSubscriptionOptions + + dataDownTrack types.DataDownTrack +} + +func newDataTrackSubscription(subscriberID livekit.ParticipantID, trackID livekit.TrackID, l logger.Logger) *dataTrackSubscription { + s := &dataTrackSubscription{ + trackSubscription: trackSubscription{ + subscriberID: subscriberID, + trackID: trackID, + logger: l, + }, + } + t := time.Now() + s.subscribeAt.Store(&t) + return s +} + +func (s *dataTrackSubscription) needsSubscribe() bool { + s.lock.RLock() + defer s.lock.RUnlock() + return s.desired && s.dataDownTrack == nil +} + +func (s *dataTrackSubscription) needsUnsubscribe() bool { + s.lock.RLock() + defer s.lock.RUnlock() + return !s.desired && s.dataDownTrack != nil +} + +func (s *dataTrackSubscription) needsCleanup() bool { + s.lock.RLock() + defer s.lock.RUnlock() + return !s.desired && s.dataDownTrack == nil +} + +func (s *dataTrackSubscription) setDataDownTrack(dataDownTrack types.DataDownTrack) { + s.lock.Lock() + s.dataDownTrack = dataDownTrack + subscriptionOptions := s.subscriptionOptions + s.lock.Unlock() + + if dataDownTrack != nil { + s.logger.Debugw("restoring data track subscription options", "subscriptionOptions", logger.Proto(subscriptionOptions)) + dataDownTrack.UpdateSubscriptionOptions(subscriptionOptions) + } + + // DT-TODO - DataTrack close callback on previous if not nil?, see setSubscribedTrack for example +} + +func (s *dataTrackSubscription) getDataDownTrack() types.DataDownTrack { + s.lock.RLock() + defer s.lock.RUnlock() + return s.dataDownTrack +} + +func (s *dataTrackSubscription) setSubscriptionOptions(subscriptionOptions *livekit.DataTrackSubscriptionOptions) { + s.lock.Lock() + s.subscriptionOptions = subscriptionOptions + dataDownTrack := s.dataDownTrack + s.lock.Unlock() + if dataDownTrack != nil { + dataDownTrack.UpdateSubscriptionOptions(subscriptionOptions) + } +} diff --git a/pkg/rtc/subscriptionmanager_test.go b/pkg/rtc/subscriptionmanager_test.go index a4646975a..af7fce880 100644 --- a/pkg/rtc/subscriptionmanager_test.go +++ b/pkg/rtc/subscriptionmanager_test.go @@ -197,15 +197,17 @@ func TestUnsubscribe(t *testing.T) { resolver := newTestResolver(true, true, "pub", "pubID") - s := &trackSubscription{ - trackID: "track", - desired: true, - subscriberID: sm.params.Participant.ID(), - publisherID: "pubID", - publisherIdentity: "pub", - hasPermission: true, - bound: true, - logger: logger.GetLogger(), + s := &mediaTrackSubscription{ + trackSubscription: trackSubscription{ + trackID: "track", + desired: true, + subscriberID: sm.params.Participant.ID(), + publisherID: "pubID", + publisherIdentity: "pub", + logger: logger.GetLogger(), + }, + hasPermission: true, + bound: true, } // a bunch of unfortunate manual wiring res := resolver.Resolve(nil, s.trackID) diff --git a/pkg/rtc/testutils.go b/pkg/rtc/testutils.go index 213175fcb..904956873 100644 --- a/pkg/rtc/testutils.go +++ b/pkg/rtc/testutils.go @@ -59,7 +59,7 @@ func NewMockParticipant(identity livekit.ParticipantIdentity, protocol types.Pro } }) updateTrack := func() { - var f func(participant types.LocalParticipant, track types.MediaTrack) + var f func(participant types.Participant, track types.MediaTrack) if p.OnTrackUpdatedCallCount() > 0 { f = p.OnTrackUpdatedArgsForCall(p.OnTrackUpdatedCallCount() - 1) } diff --git a/pkg/rtc/transport.go b/pkg/rtc/transport.go index 8a4368016..dbcc96267 100644 --- a/pkg/rtc/transport.go +++ b/pkg/rtc/transport.go @@ -19,6 +19,7 @@ import ( "io" "math/rand" "net" + "slices" "strconv" "strings" "sync" @@ -63,8 +64,9 @@ import ( ) const ( - LossyDataChannel = "_lossy" - ReliableDataChannel = "_reliable" + LossyDataChannel = "_lossy" + ReliableDataChannel = "_reliable" + DataTrackDataChannel = "_data_track" fastNegotiationFrequency = 10 * time.Millisecond negotiationFrequency = 150 * time.Millisecond @@ -205,6 +207,7 @@ type PCTransport struct { reliableDCOpened bool lossyDC *datachannel.DataChannelWriter[*webrtc.DataChannel] lossyDCOpened bool + dataTrackDC *datachannel.DataChannelWriter[*webrtc.DataChannel] unlabeledDataChannels []*datachannel.DataChannelWriter[*webrtc.DataChannel] iceStartedAt time.Time @@ -305,6 +308,8 @@ type TransportParams struct { // for development test DatachannelMaxReceiverBufferSize int + + EnableDataTracks bool } func newPeerConnection(params TransportParams, onBandwidthEstimator func(estimator cc.BandwidthEstimator)) (*webrtc.PeerConnection, *webrtc.MediaEngine, error) { @@ -399,12 +404,7 @@ func newPeerConnection(params TransportParams, onBandwidthEstimator func(estimat return true } ipstr := ip.String() - for _, inc := range includeIps { - if inc == ipstr { - return true - } - } - return false + return slices.Contains(includeIps, ipstr) }) } } @@ -657,13 +657,7 @@ func (t *PCTransport) setICEConnectedAt(at time.Time) { // set failure timer for dtls handshake iceDuration := at.Sub(t.iceStartedAt) - connTimeoutAfterICE := minConnectTimeoutAfterICE - if connTimeoutAfterICE < 3*iceDuration { - connTimeoutAfterICE = 3 * iceDuration - } - if connTimeoutAfterICE > maxConnectTimeoutAfterICE { - connTimeoutAfterICE = maxConnectTimeoutAfterICE - } + connTimeoutAfterICE := min(max(minConnectTimeoutAfterICE, 3*iceDuration), maxConnectTimeoutAfterICE) t.params.Logger.Debugw("setting connection timer after ICE connected", "timeout", connTimeoutAfterICE, "iceDuration", iceDuration) t.connectAfterICETimer = time.AfterFunc(connTimeoutAfterICE, func() { state := t.pc.ConnectionState() @@ -830,6 +824,7 @@ func (t *PCTransport) onDataChannel(dc *webrtc.DataChannel) { dc.OnOpen(func() { t.params.Logger.Debugw(dc.Label() + " data channel open") var kind livekit.DataPacket_Kind + var isDataTrack bool var isUnlabeled bool switch dc.Label() { case ReliableDataChannel: @@ -838,6 +833,9 @@ func (t *PCTransport) onDataChannel(dc *webrtc.DataChannel) { case LossyDataChannel: kind = livekit.DataPacket_LOSSY + case DataTrackDataChannel: + isDataTrack = true + default: t.params.Logger.Infow("unlabeled datachannel added", "label", dc.Label()) isUnlabeled = true @@ -849,32 +847,45 @@ func (t *PCTransport) onDataChannel(dc *webrtc.DataChannel) { return } + isHandled := true + t.lock.Lock() switch { case isUnlabeled: - t.lock.Lock() t.unlabeledDataChannels = append( t.unlabeledDataChannels, datachannel.NewDataChannelWriterReliable(dc, rawDC, t.params.DatachannelSlowThreshold), ) - t.lock.Unlock() + + case isDataTrack: + if !t.params.EnableDataTracks { + t.params.Logger.Infow("data tracks not enabled") + isHandled = false + } else { + if t.dataTrackDC != nil { + t.dataTrackDC.Close() + } + t.dataTrackDC = datachannel.NewDataChannelWriterUnreliable(dc, rawDC, 0, 0) + } case kind == livekit.DataPacket_RELIABLE: - t.lock.Lock() if t.reliableDC != nil { t.reliableDC.Close() } t.reliableDC = datachannel.NewDataChannelWriterReliable(dc, rawDC, t.params.DatachannelSlowThreshold) t.reliableDCOpened = true - t.lock.Unlock() case kind == livekit.DataPacket_LOSSY: - t.lock.Lock() if t.lossyDC != nil { t.lossyDC.Close() } t.lossyDC = datachannel.NewDataChannelWriterUnreliable(dc, rawDC, t.params.DatachannelLossyTargetLatency, uint64(lossyDataChannelMinBufferedAmount)) t.lossyDCOpened = true - t.lock.Unlock() + } + t.lock.Unlock() + + if !isHandled { + rawDC.Close() + return } go func() { @@ -893,6 +904,9 @@ func (t *PCTransport) onDataChannel(dc *webrtc.DataChannel) { case isUnlabeled: t.params.Handler.OnDataMessageUnlabeled(buffer[:n]) + case isDataTrack: + t.params.Handler.OnDataTrackMessage(buffer[:n]) + default: t.params.Handler.OnDataMessage(kind, buffer[:n]) } @@ -1159,6 +1173,11 @@ func (t *PCTransport) getNumUnmatchedTransceivers() (uint32, uint32) { } func (t *PCTransport) CreateDataChannel(label string, dci *webrtc.DataChannelInit) error { + if label == DataTrackDataChannel && !t.params.EnableDataTracks { + t.params.Logger.Infow("data tracks not enabled") + return nil + } + dc, err := t.pc.CreateDataChannel(label, dci) if err != nil { return err @@ -1166,6 +1185,7 @@ func (t *PCTransport) CreateDataChannel(label string, dci *webrtc.DataChannelIni var ( dcPtr **datachannel.DataChannelWriter[*webrtc.DataChannel] dcReady *bool + isDataTrack bool isUnlabeled bool kind livekit.DataPacket_Kind ) @@ -1173,14 +1193,20 @@ func (t *PCTransport) CreateDataChannel(label string, dci *webrtc.DataChannelIni default: isUnlabeled = true t.params.Logger.Infow("unlabeled datachannel added", "label", dc.Label()) + case ReliableDataChannel: dcPtr = &t.reliableDC dcReady = &t.reliableDCOpened kind = livekit.DataPacket_RELIABLE + case LossyDataChannel: dcPtr = &t.lossyDC dcReady = &t.lossyDCOpened kind = livekit.DataPacket_LOSSY + + case DataTrackDataChannel: + dcPtr = &t.dataTrackDC + isDataTrack = true } dc.OnOpen(func() { @@ -1205,8 +1231,17 @@ func (t *PCTransport) CreateDataChannel(label string, dci *webrtc.DataChannelIni if *dcPtr != nil { (*dcPtr).Close() } - *dcPtr = datachannel.NewDataChannelWriterReliable(dc, rawDC, slowThreshold) - *dcReady = true + switch { + case dcPtr == &t.reliableDC: + *dcPtr = datachannel.NewDataChannelWriterReliable(dc, rawDC, slowThreshold) + case dcPtr == &t.lossyDC: + *dcPtr = datachannel.NewDataChannelWriterUnreliable(dc, rawDC, t.params.DatachannelLossyTargetLatency, uint64(lossyDataChannelMinBufferedAmount)) + case dcPtr == &t.dataTrackDC: + *dcPtr = datachannel.NewDataChannelWriterUnreliable(dc, rawDC, 0, 0) + } + if dcReady != nil { + *dcReady = true + } } t.lock.Unlock() t.params.Logger.Debugw(dc.Label() + " data channel open") @@ -1227,6 +1262,9 @@ func (t *PCTransport) CreateDataChannel(label string, dci *webrtc.DataChannelIni case isUnlabeled: t.params.Handler.OnDataMessageUnlabeled(buffer[:n]) + case isDataTrack: + t.params.Handler.OnDataTrackMessage(buffer[:n]) + default: t.params.Handler.OnDataMessage(kind, buffer[:n]) } @@ -1281,6 +1319,12 @@ func (t *PCTransport) CreateReadableDataChannel(label string, dci *webrtc.DataCh } func (t *PCTransport) CreateDataChannelIfEmpty(dcLabel string, dci *webrtc.DataChannelInit) (label string, id uint16, existing bool, err error) { + if dcLabel == DataTrackDataChannel && !t.params.EnableDataTracks { + t.params.Logger.Infow("data tracks not enabled") + err = errors.New("data tracks not enabled") + return + } + t.lock.RLock() var dcw *datachannel.DataChannelWriter[*webrtc.DataChannel] switch dcLabel { @@ -1288,6 +1332,8 @@ func (t *PCTransport) CreateDataChannelIfEmpty(dcLabel string, dci *webrtc.DataC dcw = t.reliableDC case LossyDataChannel: dcw = t.lossyDC + case DataTrackDataChannel: + dcw = t.dataTrackDC default: t.params.Logger.Warnw("unknown data channel label", nil, "label", label) err = errors.New("unknown data channel label") @@ -1415,6 +1461,14 @@ func (t *PCTransport) SendDataMessageUnlabeled(data []byte, useRaw bool, sender return t.sendDataMessage(dc, data) } +func (t *PCTransport) SendDataTrackMessage(data []byte) error { + t.lock.RLock() + dc := t.dataTrackDC + t.lock.RUnlock() + + return t.sendDataMessage(dc, data) +} + func (t *PCTransport) sendDataMessage(dc *datachannel.DataChannelWriter[*webrtc.DataChannel], data []byte) error { if dc == nil { return ErrDataChannelUnavailable @@ -1428,7 +1482,6 @@ func (t *PCTransport) sendDataMessage(dc *datachannel.DataChannelWriter[*webrtc. return ErrDataChannelBufferFull } _, err := dc.Write(data) - return err } @@ -1466,6 +1519,11 @@ func (t *PCTransport) Close() { t.lossyDC = nil } + if t.dataTrackDC != nil { + t.dataTrackDC.Close() + t.dataTrackDC = nil + } + for _, dc := range t.unlabeledDataChannels { dc.Close() } diff --git a/pkg/rtc/transport/handler.go b/pkg/rtc/transport/handler.go index 980f28a77..8953416ac 100644 --- a/pkg/rtc/transport/handler.go +++ b/pkg/rtc/transport/handler.go @@ -41,6 +41,7 @@ type Handler interface { OnTrack(track *webrtc.TrackRemote, rtpReceiver *webrtc.RTPReceiver) OnDataMessage(kind livekit.DataPacket_Kind, data []byte) OnDataMessageUnlabeled(data []byte) + OnDataTrackMessage(data []byte) OnDataSendError(err error) OnOffer(sd webrtc.SessionDescription, offerId uint32, midToTrackID map[string]string) error OnSetRemoteDescriptionOffer() @@ -62,6 +63,7 @@ func (h UnimplementedHandler) OnFailed(isShortLived bool) func (h UnimplementedHandler) OnTrack(track *webrtc.TrackRemote, rtpReceiver *webrtc.RTPReceiver) {} func (h UnimplementedHandler) OnDataMessage(kind livekit.DataPacket_Kind, data []byte) {} func (h UnimplementedHandler) OnDataMessageUnlabeled(data []byte) {} +func (h UnimplementedHandler) OnDataTrackMessage(data []byte) {} func (h UnimplementedHandler) OnDataSendError(err error) {} func (h UnimplementedHandler) OnOffer(sd webrtc.SessionDescription, offerId uint32, midToTrackID map[string]string) error { return ErrNoOfferHandler diff --git a/pkg/rtc/transport/transportfakes/fake_handler.go b/pkg/rtc/transport/transportfakes/fake_handler.go index ce332e145..67a2cd57a 100644 --- a/pkg/rtc/transport/transportfakes/fake_handler.go +++ b/pkg/rtc/transport/transportfakes/fake_handler.go @@ -41,6 +41,11 @@ type FakeHandler struct { onDataSendErrorArgsForCall []struct { arg1 error } + OnDataTrackMessageStub func([]byte) + onDataTrackMessageMutex sync.RWMutex + onDataTrackMessageArgsForCall []struct { + arg1 []byte + } OnFailedStub func(bool, *types.ICEConnectionInfo) onFailedMutex sync.RWMutex onFailedArgsForCall []struct { @@ -296,6 +301,43 @@ func (fake *FakeHandler) OnDataSendErrorArgsForCall(i int) error { return argsForCall.arg1 } +func (fake *FakeHandler) OnDataTrackMessage(arg1 []byte) { + var arg1Copy []byte + if arg1 != nil { + arg1Copy = make([]byte, len(arg1)) + copy(arg1Copy, arg1) + } + fake.onDataTrackMessageMutex.Lock() + fake.onDataTrackMessageArgsForCall = append(fake.onDataTrackMessageArgsForCall, struct { + arg1 []byte + }{arg1Copy}) + stub := fake.OnDataTrackMessageStub + fake.recordInvocation("OnDataTrackMessage", []interface{}{arg1Copy}) + fake.onDataTrackMessageMutex.Unlock() + if stub != nil { + fake.OnDataTrackMessageStub(arg1) + } +} + +func (fake *FakeHandler) OnDataTrackMessageCallCount() int { + fake.onDataTrackMessageMutex.RLock() + defer fake.onDataTrackMessageMutex.RUnlock() + return len(fake.onDataTrackMessageArgsForCall) +} + +func (fake *FakeHandler) OnDataTrackMessageCalls(stub func([]byte)) { + fake.onDataTrackMessageMutex.Lock() + defer fake.onDataTrackMessageMutex.Unlock() + fake.OnDataTrackMessageStub = stub +} + +func (fake *FakeHandler) OnDataTrackMessageArgsForCall(i int) []byte { + fake.onDataTrackMessageMutex.RLock() + defer fake.onDataTrackMessageMutex.RUnlock() + argsForCall := fake.onDataTrackMessageArgsForCall[i] + return argsForCall.arg1 +} + func (fake *FakeHandler) OnFailed(arg1 bool, arg2 *types.ICEConnectionInfo) { fake.onFailedMutex.Lock() fake.onFailedArgsForCall = append(fake.onFailedArgsForCall, struct { diff --git a/pkg/rtc/transportmanager.go b/pkg/rtc/transportmanager.go index 02a421c42..04ad8ec95 100644 --- a/pkg/rtc/transportmanager.go +++ b/pkg/rtc/transportmanager.go @@ -97,6 +97,7 @@ type TransportManagerParams struct { DataChannelStats *telemetry.BytesTrackStats UseOneShotSignallingMode bool FireOnTrackBySdp bool + EnableDataTracks bool } type TransportManager struct { @@ -157,6 +158,7 @@ func NewTransportManager(params TransportManagerParams) (*TransportManager, erro DatachannelSlowThreshold: params.DatachannelSlowThreshold, DatachannelLossyTargetLatency: params.DatachannelLossyTargetLatency, FireOnTrackBySdp: params.FireOnTrackBySdp, + EnableDataTracks: params.EnableDataTracks, }) if err != nil { return nil, err @@ -182,6 +184,7 @@ func NewTransportManager(params TransportManagerParams) (*TransportManager, erro Transport: livekit.SignalTarget_SUBSCRIBER, Handler: TransportManagerTransportHandler{params.SubscriberHandler, t, lgr}, FireOnTrackBySdp: params.FireOnTrackBySdp, + EnableDataTracks: params.EnableDataTracks, }) if err != nil { return nil, err @@ -386,8 +389,8 @@ func (t *TransportManager) handleSendDataResult(err error, kind string, size int func (t *TransportManager) createDataChannelsForSubscriber(pendingDataChannels []*livekit.DataChannelInfo) error { var ( - reliableID, lossyID uint16 - reliableIDPtr, lossyIDPtr *uint16 + reliableID, lossyID, dataTrackID uint16 + reliableIDPtr, lossyIDPtr, dataTrackIDPtr *uint16 ) // @@ -398,13 +401,17 @@ func (t *TransportManager) createDataChannelsForSubscriber(pendingDataChannels [ // For new version migration clients, we create data channels with new ID and negotiate with client // for _, dc := range pendingDataChannels { - if dc.Label == ReliableDataChannel { - // pion use step 2 for auto generated ID, so we need to add 4 to avoid conflict - reliableID = uint16(dc.Id) + 4 + switch dc.Label { + case ReliableDataChannel: + // pion use step 2 for auto generated ID, so we need to add 6 to avoid conflict + reliableID = uint16(dc.Id) + 6 reliableIDPtr = &reliableID - } else if dc.Label == LossyDataChannel { - lossyID = uint16(dc.Id) + 4 + case LossyDataChannel: + lossyID = uint16(dc.Id) + 6 lossyIDPtr = &lossyID + case DataTrackDataChannel: + dataTrackID = uint16(dc.Id) + 6 + dataTrackIDPtr = &dataTrackID } } @@ -429,6 +436,17 @@ func (t *TransportManager) createDataChannelsForSubscriber(pendingDataChannels [ }); err != nil { return err } + + negotiated = t.params.Migration && dataTrackIDPtr == nil + if err := t.subscriber.CreateDataChannel(DataTrackDataChannel, &webrtc.DataChannelInit{ + Ordered: &ordered, + MaxRetransmits: &retransmits, + ID: dataTrackIDPtr, + Negotiated: &negotiated, + }); err != nil { + return err + } + return nil } @@ -669,6 +687,10 @@ func (t *TransportManager) GetICEConnectionInfo() []*types.ICEConnectionInfo { return infos } +func (t *TransportManager) GetDataTrackTransport() types.DataTrackTransport { + return t.getTransport(true) +} + func (t *TransportManager) getTransport(isPrimary bool) *PCTransport { switch { case t.publisher == nil: @@ -860,7 +882,15 @@ func (t *TransportManager) ProcessPendingPublisherDataChannels() { dcExisting bool err error ) - if ci.Label == LossyDataChannel { + switch ci.Label { + case ReliableDataChannel: + id := uint16(ci.GetId()) + dcLabel, dcID, dcExisting, err = t.publisher.CreateDataChannelIfEmpty(ReliableDataChannel, &webrtc.DataChannelInit{ + Ordered: &ordered, + Negotiated: &negotiated, + ID: &id, + }) + case LossyDataChannel: ordered = false retransmits := uint16(0) id := uint16(ci.GetId()) @@ -870,12 +900,15 @@ func (t *TransportManager) ProcessPendingPublisherDataChannels() { Negotiated: &negotiated, ID: &id, }) - } else if ci.Label == ReliableDataChannel { + case DataTrackDataChannel: + ordered = false + retransmits := uint16(0) id := uint16(ci.GetId()) - dcLabel, dcID, dcExisting, err = t.publisher.CreateDataChannelIfEmpty(ReliableDataChannel, &webrtc.DataChannelInit{ - Ordered: &ordered, - Negotiated: &negotiated, - ID: &id, + dcLabel, dcID, dcExisting, err = t.publisher.CreateDataChannelIfEmpty(DataTrackDataChannel, &webrtc.DataChannelInit{ + Ordered: &ordered, + MaxRetransmits: &retransmits, + Negotiated: &negotiated, + ID: &id, }) } if err != nil { diff --git a/pkg/rtc/types/interfaces.go b/pkg/rtc/types/interfaces.go index 2e4b5b82b..76bcbb5aa 100644 --- a/pkg/rtc/types/interfaces.go +++ b/pkg/rtc/types/interfaces.go @@ -28,6 +28,7 @@ import ( "github.com/livekit/protocol/utils" "github.com/livekit/livekit-server/pkg/routing" + "github.com/livekit/livekit-server/pkg/rtc/datatrack" "github.com/livekit/livekit-server/pkg/sfu" "github.com/livekit/livekit-server/pkg/sfu/buffer" "github.com/livekit/livekit-server/pkg/sfu/mime" @@ -286,15 +287,22 @@ type Participant interface { IsDependent() bool IsAgent() bool + GetLogger() logger.Logger + CanSkipBroadcast() bool Version() utils.TimedVersion ToProto() *livekit.ParticipantInfo + ToProtoWithVersion() (*livekit.ParticipantInfo, utils.TimedVersion) IsPublisher() bool GetPublishedTrack(trackID livekit.TrackID) MediaTrack GetPublishedTracks() []MediaTrack RemovePublishedTrack(track MediaTrack, isExpectedToResume bool) + GetPublishedDataTracks() []DataTrack + GetPublishedDataTrack(handle uint16) DataTrack + RemovePublishedDataTrack(track DataTrack) + GetAudioLevel() (smoothedLevel float64, active bool) // HasPermission checks permission of the subscriber by identity. Returns true if subscriber is allowed to subscribe @@ -304,7 +312,11 @@ type Participant interface { // permissions Hidden() bool + MigrateState() MigrateState + Close(sendLeave bool, reason ParticipantCloseReason, isExpectedToResume bool) error + IsClosed() bool + IsDisconnected() bool SubscriptionPermission() (*livekit.SubscriptionPermission, utils.TimedVersion) @@ -315,9 +327,19 @@ type Participant interface { resolverBySid func(participantID livekit.ParticipantID) LocalParticipant, ) error - DebugInfo() map[string]interface{} + DebugInfo() map[string]any + // OnTrackPublished - remote added a track + OnTrackPublished(func(Participant, MediaTrack)) + // OnTrackUpdated - one of its publishedTracks changed in status + OnTrackUpdated(callback func(Participant, MediaTrack)) + // OnTrackUnpublished - a track was unpublished + OnTrackUnpublished(callback func(Participant, MediaTrack)) + OnDataTrackPublished(func(Participant, DataTrack)) + OnDataTrackUnpublished(func(Participant, DataTrack)) OnMetrics(callback func(Participant, *livekit.DataPacket)) + + HandleReceivedDataTrackMessage([]byte, *datatrack.Packet) } // ------------------------------------------------------- @@ -343,6 +365,7 @@ type DataMessageCache struct { //counterfeiter:generate . LocalParticipantHelper type LocalParticipantHelper interface { ResolveMediaTrack(LocalParticipant, livekit.TrackID) MediaResolverResult + ResolveDataTrack(LocalParticipant, livekit.TrackID) DataResolverResult GetParticipantInfo(pID livekit.ParticipantID) *livekit.ParticipantInfo GetRegionSettings(ip string) *livekit.RegionSettings GetSubscriberForwarderState(p LocalParticipant) (map[livekit.TrackID]*livekit.RTPForwarderState, error) @@ -355,12 +378,10 @@ type LocalParticipant interface { Participant TelemetryGuard() *telemetry.ReferenceGuard - ToProtoWithVersion() (*livekit.ParticipantInfo, utils.TimedVersion) // getters GetCountry() string GetTrailer() []byte - GetLogger() logger.Logger GetLoggerResolver() logger.DeferredFieldResolver GetReporter() roomobs.ParticipantSessionReporter GetReporterResolver() roomobs.ParticipantReporterResolver @@ -369,9 +390,7 @@ type LocalParticipant interface { SupportsSyncStreamID() bool SupportsTransceiverReuse() bool IsUsingSinglePeerConnection() bool - IsClosed() bool IsReady() bool - IsDisconnected() bool Disconnected() <-chan struct{} IsIdle() bool SubscriberAsPrimary() bool @@ -434,6 +453,9 @@ type LocalParticipant interface { UpdateSubscribedTrackSettings(trackID livekit.TrackID, settings *livekit.UpdateTrackSettings) GetSubscribedTracks() []SubscribedTrack IsTrackNameSubscribed(publisherIdentity livekit.ParticipantIdentity, trackName string) bool + SubscribeToDataTrack(trackID livekit.TrackID) + UnsubscribeFromDataTrack(trackID livekit.TrackID) + UpdateDataTrackSubscriptionOptions(trackID livekit.TrackID, subscriptionOptions *livekit.DataTrackSubscriptionOptions) Verify() bool VerifySubscribeParticipantInfo(pID livekit.ParticipantID, version uint32) // WaitUntilSubscribed waits until all subscriptions have been settled, or if the timeout @@ -458,25 +480,19 @@ type LocalParticipant interface { SendConnectionQualityUpdate(update *livekit.ConnectionQualityUpdate) error SendSubscriptionPermissionUpdate(publisherID livekit.ParticipantID, trackID livekit.TrackID, allowed bool) error SendRefreshToken(token string) error - // RAJA-REMOVE SendRequestResponse(requestResponse *livekit.RequestResponse) error HandleReconnectAndSendResponse(reconnectReason livekit.ReconnectReason, reconnectResponse *livekit.ReconnectResponse) error IssueFullReconnect(reason ParticipantCloseReason) SendRoomMovedResponse(moved *livekit.RoomMovedResponse) error + SendDataTrackSubscriberHandles(handles map[uint32]*livekit.DataTrackSubscriberHandles_PublishedDataTrack) error // callbacks OnStateChange(func(p LocalParticipant)) OnSubscriberReady(callback func(LocalParticipant)) OnMigrateStateChange(func(p LocalParticipant, migrateState MigrateState)) - // OnTrackPublished - remote added a track - OnTrackPublished(func(LocalParticipant, MediaTrack)) - // OnTrackUpdated - one of its publishedTracks changed in status - OnTrackUpdated(callback func(LocalParticipant, MediaTrack)) - // OnTrackUnpublished - a track was unpublished - OnTrackUnpublished(callback func(LocalParticipant, MediaTrack)) - // OnParticipantUpdate - metadata or permission is updated OnParticipantUpdate(callback func(LocalParticipant)) OnDataPacket(callback func(LocalParticipant, livekit.DataPacket_Kind, *livekit.DataPacket)) OnDataMessage(callback func(LocalParticipant, []byte)) + OnDataTrackMessage(callback func(LocalParticipant, []byte, *datatrack.Packet)) OnSubscribeStatusChanged(fn func(publisherID livekit.ParticipantID, subscribed bool)) AddOnClose(key string, callback func(LocalParticipant)) OnClaimsChanged(callback func(LocalParticipant)) @@ -487,6 +503,7 @@ type LocalParticipant interface { bool, )) OnUpdateSubscriptionPermission(func(LocalParticipant, *livekit.SubscriptionPermission) error) + OnUpdateDataSubscriptions(func(LocalParticipant, *livekit.UpdateDataSubscription)) OnSyncState(func(LocalParticipant, *livekit.SyncState) error) OnSimulateScenario(func(LocalParticipant, *livekit.SimulateScenario) error) OnLeave(func(LocalParticipant, ParticipantCloseReason)) @@ -497,13 +514,13 @@ type LocalParticipant interface { MaybeStartMigration(force bool, onStart func()) bool NotifyMigration() SetMigrateState(s MigrateState) - MigrateState() MigrateState SetMigrateInfo( previousOffer *webrtc.SessionDescription, previousAnswer *webrtc.SessionDescription, mediaTracks []*livekit.TrackPublishedResponse, dataChannels []*livekit.DataChannelInfo, dataChannelReceiveState []*livekit.DataChannelReceiveState, + dataTracks []*livekit.PublishDataTrackResponse, ) IsReconnect() bool MoveToRoom(params MoveToRoomParams) @@ -542,9 +559,15 @@ type LocalParticipant interface { HandleSimulateScenario(*livekit.SimulateScenario) error HandleLeaveRequest(reason ParticipantCloseReason) + HandlePublishDataTrackRequest(*livekit.PublishDataTrackRequest) + HandleUnpublishDataTrackRequest(*livekit.UnpublishDataTrackRequest) + HandleUpdateDataSubscription(*livekit.UpdateDataSubscription) + HandleSignalMessage(msg proto.Message) error PerformRpc(req *livekit.PerformRpcRequest, resultCh chan string, errorCh chan error) + + GetDataTrackTransport() DataTrackTransport } // Room is a container of participants, and can provide room-level actions @@ -561,6 +584,7 @@ type Room interface { subscribe bool, ) ResolveMediaTrackForSubscriber(sub LocalParticipant, trackID livekit.TrackID) MediaResolverResult + ResolveDataTrackForSubscriber(sub LocalParticipant, trackID livekit.TrackID) DataResolverResult GetLocalParticipants() []LocalParticipant IsDataMessageUserPacketDuplicate(ip *livekit.UserPacket) bool } @@ -637,6 +661,47 @@ type LocalMediaTrack interface { NotifySubscriberNodeMediaLoss(nodeID livekit.NodeID, fractionalLoss uint8) } +// DataTrack represents a data track +// +//counterfeiter:generate . DataTrack +type DataTrack interface { + ID() livekit.TrackID + PubHandle() uint16 + Name() string + ToProto() *livekit.DataTrackInfo + + AddSubscriber(sub LocalParticipant) (DataDownTrack, error) + RemoveSubscriber(participantID livekit.ParticipantID) + IsSubscriber(subID livekit.ParticipantID) bool + + AddDataDownTrack(sender DataTrackSender) error + DeleteDataDownTrack(subscriberID livekit.ParticipantID) + + HandlePacket(data []byte, packet *datatrack.Packet) + + Close() +} + +//counterfeiter:generate . DataDownTrack +type DataDownTrack interface { + Handle() uint16 + PublishDataTrack() DataTrack + + UpdateSubscriptionOptions(subscriptionOptions *livekit.DataTrackSubscriptionOptions) +} + +//counterfeiter:generate . DataTrackSender +type DataTrackSender interface { + SubscriberID() livekit.ParticipantID + + WritePacket(data []byte, packet *datatrack.Packet) +} + +//counterfeiter:generate . DataTrackTransport +type DataTrackTransport interface { + SendDataTrackMessage(data []byte) error +} + //counterfeiter:generate . SubscribedTrack type SubscribedTrack interface { AddOnBind(f func(error)) @@ -678,9 +743,20 @@ type MediaResolverResult struct { PublisherIdentity livekit.ParticipantIdentity } +type DataResolverResult struct { + TrackChangedNotifier ChangeNotifier + TrackRemovedNotifier ChangeNotifier + DataTrack DataTrack + PublisherID livekit.ParticipantID + PublisherIdentity livekit.ParticipantIdentity +} + // MediaTrackResolver locates a specific media track for a subscriber type MediaTrackResolver func(LocalParticipant, livekit.TrackID) MediaResolverResult +// DataTrackResolver locates a specific data track for a subscriber +type DataTrackResolver func(LocalParticipant, livekit.TrackID) DataResolverResult + // Supervisor/operation monitor related definitions type OperationMonitorEvent int @@ -709,7 +785,7 @@ func (o OperationMonitorEvent) String() string { } } -type OperationMonitorData interface{} +type OperationMonitorData any type OperationMonitor interface { PostEvent(ome OperationMonitorEvent, omd OperationMonitorData) diff --git a/pkg/rtc/types/typesfakes/fake_data_down_track.go b/pkg/rtc/types/typesfakes/fake_data_down_track.go new file mode 100644 index 000000000..f84714919 --- /dev/null +++ b/pkg/rtc/types/typesfakes/fake_data_down_track.go @@ -0,0 +1,201 @@ +// Code generated by counterfeiter. DO NOT EDIT. +package typesfakes + +import ( + "sync" + + "github.com/livekit/livekit-server/pkg/rtc/types" + "github.com/livekit/protocol/livekit" +) + +type FakeDataDownTrack struct { + HandleStub func() uint16 + handleMutex sync.RWMutex + handleArgsForCall []struct { + } + handleReturns struct { + result1 uint16 + } + handleReturnsOnCall map[int]struct { + result1 uint16 + } + PublishDataTrackStub func() types.DataTrack + publishDataTrackMutex sync.RWMutex + publishDataTrackArgsForCall []struct { + } + publishDataTrackReturns struct { + result1 types.DataTrack + } + publishDataTrackReturnsOnCall map[int]struct { + result1 types.DataTrack + } + UpdateSubscriptionOptionsStub func(*livekit.DataTrackSubscriptionOptions) + updateSubscriptionOptionsMutex sync.RWMutex + updateSubscriptionOptionsArgsForCall []struct { + arg1 *livekit.DataTrackSubscriptionOptions + } + invocations map[string][][]interface{} + invocationsMutex sync.RWMutex +} + +func (fake *FakeDataDownTrack) Handle() uint16 { + fake.handleMutex.Lock() + ret, specificReturn := fake.handleReturnsOnCall[len(fake.handleArgsForCall)] + fake.handleArgsForCall = append(fake.handleArgsForCall, struct { + }{}) + stub := fake.HandleStub + fakeReturns := fake.handleReturns + fake.recordInvocation("Handle", []interface{}{}) + fake.handleMutex.Unlock() + if stub != nil { + return stub() + } + if specificReturn { + return ret.result1 + } + return fakeReturns.result1 +} + +func (fake *FakeDataDownTrack) HandleCallCount() int { + fake.handleMutex.RLock() + defer fake.handleMutex.RUnlock() + return len(fake.handleArgsForCall) +} + +func (fake *FakeDataDownTrack) HandleCalls(stub func() uint16) { + fake.handleMutex.Lock() + defer fake.handleMutex.Unlock() + fake.HandleStub = stub +} + +func (fake *FakeDataDownTrack) HandleReturns(result1 uint16) { + fake.handleMutex.Lock() + defer fake.handleMutex.Unlock() + fake.HandleStub = nil + fake.handleReturns = struct { + result1 uint16 + }{result1} +} + +func (fake *FakeDataDownTrack) HandleReturnsOnCall(i int, result1 uint16) { + fake.handleMutex.Lock() + defer fake.handleMutex.Unlock() + fake.HandleStub = nil + if fake.handleReturnsOnCall == nil { + fake.handleReturnsOnCall = make(map[int]struct { + result1 uint16 + }) + } + fake.handleReturnsOnCall[i] = struct { + result1 uint16 + }{result1} +} + +func (fake *FakeDataDownTrack) PublishDataTrack() types.DataTrack { + fake.publishDataTrackMutex.Lock() + ret, specificReturn := fake.publishDataTrackReturnsOnCall[len(fake.publishDataTrackArgsForCall)] + fake.publishDataTrackArgsForCall = append(fake.publishDataTrackArgsForCall, struct { + }{}) + stub := fake.PublishDataTrackStub + fakeReturns := fake.publishDataTrackReturns + fake.recordInvocation("PublishDataTrack", []interface{}{}) + fake.publishDataTrackMutex.Unlock() + if stub != nil { + return stub() + } + if specificReturn { + return ret.result1 + } + return fakeReturns.result1 +} + +func (fake *FakeDataDownTrack) PublishDataTrackCallCount() int { + fake.publishDataTrackMutex.RLock() + defer fake.publishDataTrackMutex.RUnlock() + return len(fake.publishDataTrackArgsForCall) +} + +func (fake *FakeDataDownTrack) PublishDataTrackCalls(stub func() types.DataTrack) { + fake.publishDataTrackMutex.Lock() + defer fake.publishDataTrackMutex.Unlock() + fake.PublishDataTrackStub = stub +} + +func (fake *FakeDataDownTrack) PublishDataTrackReturns(result1 types.DataTrack) { + fake.publishDataTrackMutex.Lock() + defer fake.publishDataTrackMutex.Unlock() + fake.PublishDataTrackStub = nil + fake.publishDataTrackReturns = struct { + result1 types.DataTrack + }{result1} +} + +func (fake *FakeDataDownTrack) PublishDataTrackReturnsOnCall(i int, result1 types.DataTrack) { + fake.publishDataTrackMutex.Lock() + defer fake.publishDataTrackMutex.Unlock() + fake.PublishDataTrackStub = nil + if fake.publishDataTrackReturnsOnCall == nil { + fake.publishDataTrackReturnsOnCall = make(map[int]struct { + result1 types.DataTrack + }) + } + fake.publishDataTrackReturnsOnCall[i] = struct { + result1 types.DataTrack + }{result1} +} + +func (fake *FakeDataDownTrack) UpdateSubscriptionOptions(arg1 *livekit.DataTrackSubscriptionOptions) { + fake.updateSubscriptionOptionsMutex.Lock() + fake.updateSubscriptionOptionsArgsForCall = append(fake.updateSubscriptionOptionsArgsForCall, struct { + arg1 *livekit.DataTrackSubscriptionOptions + }{arg1}) + stub := fake.UpdateSubscriptionOptionsStub + fake.recordInvocation("UpdateSubscriptionOptions", []interface{}{arg1}) + fake.updateSubscriptionOptionsMutex.Unlock() + if stub != nil { + fake.UpdateSubscriptionOptionsStub(arg1) + } +} + +func (fake *FakeDataDownTrack) UpdateSubscriptionOptionsCallCount() int { + fake.updateSubscriptionOptionsMutex.RLock() + defer fake.updateSubscriptionOptionsMutex.RUnlock() + return len(fake.updateSubscriptionOptionsArgsForCall) +} + +func (fake *FakeDataDownTrack) UpdateSubscriptionOptionsCalls(stub func(*livekit.DataTrackSubscriptionOptions)) { + fake.updateSubscriptionOptionsMutex.Lock() + defer fake.updateSubscriptionOptionsMutex.Unlock() + fake.UpdateSubscriptionOptionsStub = stub +} + +func (fake *FakeDataDownTrack) UpdateSubscriptionOptionsArgsForCall(i int) *livekit.DataTrackSubscriptionOptions { + fake.updateSubscriptionOptionsMutex.RLock() + defer fake.updateSubscriptionOptionsMutex.RUnlock() + argsForCall := fake.updateSubscriptionOptionsArgsForCall[i] + return argsForCall.arg1 +} + +func (fake *FakeDataDownTrack) Invocations() map[string][][]interface{} { + fake.invocationsMutex.RLock() + defer fake.invocationsMutex.RUnlock() + copiedInvocations := map[string][][]interface{}{} + for key, value := range fake.invocations { + copiedInvocations[key] = value + } + return copiedInvocations +} + +func (fake *FakeDataDownTrack) 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.DataDownTrack = new(FakeDataDownTrack) diff --git a/pkg/rtc/types/typesfakes/fake_data_track.go b/pkg/rtc/types/typesfakes/fake_data_track.go new file mode 100644 index 000000000..6d3dc9240 --- /dev/null +++ b/pkg/rtc/types/typesfakes/fake_data_track.go @@ -0,0 +1,658 @@ +// Code generated by counterfeiter. DO NOT EDIT. +package typesfakes + +import ( + "sync" + + "github.com/livekit/livekit-server/pkg/rtc/datatrack" + "github.com/livekit/livekit-server/pkg/rtc/types" + "github.com/livekit/protocol/livekit" +) + +type FakeDataTrack struct { + AddDataDownTrackStub func(types.DataTrackSender) error + addDataDownTrackMutex sync.RWMutex + addDataDownTrackArgsForCall []struct { + arg1 types.DataTrackSender + } + addDataDownTrackReturns struct { + result1 error + } + addDataDownTrackReturnsOnCall map[int]struct { + result1 error + } + AddSubscriberStub func(types.LocalParticipant) (types.DataDownTrack, error) + addSubscriberMutex sync.RWMutex + addSubscriberArgsForCall []struct { + arg1 types.LocalParticipant + } + addSubscriberReturns struct { + result1 types.DataDownTrack + result2 error + } + addSubscriberReturnsOnCall map[int]struct { + result1 types.DataDownTrack + result2 error + } + CloseStub func() + closeMutex sync.RWMutex + closeArgsForCall []struct { + } + DeleteDataDownTrackStub func(livekit.ParticipantID) + deleteDataDownTrackMutex sync.RWMutex + deleteDataDownTrackArgsForCall []struct { + arg1 livekit.ParticipantID + } + HandlePacketStub func([]byte, *datatrack.Packet) + handlePacketMutex sync.RWMutex + handlePacketArgsForCall []struct { + arg1 []byte + arg2 *datatrack.Packet + } + IDStub func() livekit.TrackID + iDMutex sync.RWMutex + iDArgsForCall []struct { + } + iDReturns struct { + result1 livekit.TrackID + } + iDReturnsOnCall map[int]struct { + result1 livekit.TrackID + } + IsSubscriberStub func(livekit.ParticipantID) bool + isSubscriberMutex sync.RWMutex + isSubscriberArgsForCall []struct { + arg1 livekit.ParticipantID + } + isSubscriberReturns struct { + result1 bool + } + isSubscriberReturnsOnCall map[int]struct { + result1 bool + } + NameStub func() string + nameMutex sync.RWMutex + nameArgsForCall []struct { + } + nameReturns struct { + result1 string + } + nameReturnsOnCall map[int]struct { + result1 string + } + PubHandleStub func() uint16 + pubHandleMutex sync.RWMutex + pubHandleArgsForCall []struct { + } + pubHandleReturns struct { + result1 uint16 + } + pubHandleReturnsOnCall map[int]struct { + result1 uint16 + } + RemoveSubscriberStub func(livekit.ParticipantID) + removeSubscriberMutex sync.RWMutex + removeSubscriberArgsForCall []struct { + arg1 livekit.ParticipantID + } + ToProtoStub func() *livekit.DataTrackInfo + toProtoMutex sync.RWMutex + toProtoArgsForCall []struct { + } + toProtoReturns struct { + result1 *livekit.DataTrackInfo + } + toProtoReturnsOnCall map[int]struct { + result1 *livekit.DataTrackInfo + } + invocations map[string][][]interface{} + invocationsMutex sync.RWMutex +} + +func (fake *FakeDataTrack) AddDataDownTrack(arg1 types.DataTrackSender) error { + fake.addDataDownTrackMutex.Lock() + ret, specificReturn := fake.addDataDownTrackReturnsOnCall[len(fake.addDataDownTrackArgsForCall)] + fake.addDataDownTrackArgsForCall = append(fake.addDataDownTrackArgsForCall, struct { + arg1 types.DataTrackSender + }{arg1}) + stub := fake.AddDataDownTrackStub + fakeReturns := fake.addDataDownTrackReturns + fake.recordInvocation("AddDataDownTrack", []interface{}{arg1}) + fake.addDataDownTrackMutex.Unlock() + if stub != nil { + return stub(arg1) + } + if specificReturn { + return ret.result1 + } + return fakeReturns.result1 +} + +func (fake *FakeDataTrack) AddDataDownTrackCallCount() int { + fake.addDataDownTrackMutex.RLock() + defer fake.addDataDownTrackMutex.RUnlock() + return len(fake.addDataDownTrackArgsForCall) +} + +func (fake *FakeDataTrack) AddDataDownTrackCalls(stub func(types.DataTrackSender) error) { + fake.addDataDownTrackMutex.Lock() + defer fake.addDataDownTrackMutex.Unlock() + fake.AddDataDownTrackStub = stub +} + +func (fake *FakeDataTrack) AddDataDownTrackArgsForCall(i int) types.DataTrackSender { + fake.addDataDownTrackMutex.RLock() + defer fake.addDataDownTrackMutex.RUnlock() + argsForCall := fake.addDataDownTrackArgsForCall[i] + return argsForCall.arg1 +} + +func (fake *FakeDataTrack) AddDataDownTrackReturns(result1 error) { + fake.addDataDownTrackMutex.Lock() + defer fake.addDataDownTrackMutex.Unlock() + fake.AddDataDownTrackStub = nil + fake.addDataDownTrackReturns = struct { + result1 error + }{result1} +} + +func (fake *FakeDataTrack) AddDataDownTrackReturnsOnCall(i int, result1 error) { + fake.addDataDownTrackMutex.Lock() + defer fake.addDataDownTrackMutex.Unlock() + fake.AddDataDownTrackStub = nil + if fake.addDataDownTrackReturnsOnCall == nil { + fake.addDataDownTrackReturnsOnCall = make(map[int]struct { + result1 error + }) + } + fake.addDataDownTrackReturnsOnCall[i] = struct { + result1 error + }{result1} +} + +func (fake *FakeDataTrack) AddSubscriber(arg1 types.LocalParticipant) (types.DataDownTrack, error) { + fake.addSubscriberMutex.Lock() + ret, specificReturn := fake.addSubscriberReturnsOnCall[len(fake.addSubscriberArgsForCall)] + fake.addSubscriberArgsForCall = append(fake.addSubscriberArgsForCall, struct { + arg1 types.LocalParticipant + }{arg1}) + stub := fake.AddSubscriberStub + fakeReturns := fake.addSubscriberReturns + fake.recordInvocation("AddSubscriber", []interface{}{arg1}) + fake.addSubscriberMutex.Unlock() + if stub != nil { + return stub(arg1) + } + if specificReturn { + return ret.result1, ret.result2 + } + return fakeReturns.result1, fakeReturns.result2 +} + +func (fake *FakeDataTrack) AddSubscriberCallCount() int { + fake.addSubscriberMutex.RLock() + defer fake.addSubscriberMutex.RUnlock() + return len(fake.addSubscriberArgsForCall) +} + +func (fake *FakeDataTrack) AddSubscriberCalls(stub func(types.LocalParticipant) (types.DataDownTrack, error)) { + fake.addSubscriberMutex.Lock() + defer fake.addSubscriberMutex.Unlock() + fake.AddSubscriberStub = stub +} + +func (fake *FakeDataTrack) AddSubscriberArgsForCall(i int) types.LocalParticipant { + fake.addSubscriberMutex.RLock() + defer fake.addSubscriberMutex.RUnlock() + argsForCall := fake.addSubscriberArgsForCall[i] + return argsForCall.arg1 +} + +func (fake *FakeDataTrack) AddSubscriberReturns(result1 types.DataDownTrack, result2 error) { + fake.addSubscriberMutex.Lock() + defer fake.addSubscriberMutex.Unlock() + fake.AddSubscriberStub = nil + fake.addSubscriberReturns = struct { + result1 types.DataDownTrack + result2 error + }{result1, result2} +} + +func (fake *FakeDataTrack) AddSubscriberReturnsOnCall(i int, result1 types.DataDownTrack, result2 error) { + fake.addSubscriberMutex.Lock() + defer fake.addSubscriberMutex.Unlock() + fake.AddSubscriberStub = nil + if fake.addSubscriberReturnsOnCall == nil { + fake.addSubscriberReturnsOnCall = make(map[int]struct { + result1 types.DataDownTrack + result2 error + }) + } + fake.addSubscriberReturnsOnCall[i] = struct { + result1 types.DataDownTrack + result2 error + }{result1, result2} +} + +func (fake *FakeDataTrack) 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 *FakeDataTrack) CloseCallCount() int { + fake.closeMutex.RLock() + defer fake.closeMutex.RUnlock() + return len(fake.closeArgsForCall) +} + +func (fake *FakeDataTrack) CloseCalls(stub func()) { + fake.closeMutex.Lock() + defer fake.closeMutex.Unlock() + fake.CloseStub = stub +} + +func (fake *FakeDataTrack) DeleteDataDownTrack(arg1 livekit.ParticipantID) { + fake.deleteDataDownTrackMutex.Lock() + fake.deleteDataDownTrackArgsForCall = append(fake.deleteDataDownTrackArgsForCall, struct { + arg1 livekit.ParticipantID + }{arg1}) + stub := fake.DeleteDataDownTrackStub + fake.recordInvocation("DeleteDataDownTrack", []interface{}{arg1}) + fake.deleteDataDownTrackMutex.Unlock() + if stub != nil { + fake.DeleteDataDownTrackStub(arg1) + } +} + +func (fake *FakeDataTrack) DeleteDataDownTrackCallCount() int { + fake.deleteDataDownTrackMutex.RLock() + defer fake.deleteDataDownTrackMutex.RUnlock() + return len(fake.deleteDataDownTrackArgsForCall) +} + +func (fake *FakeDataTrack) DeleteDataDownTrackCalls(stub func(livekit.ParticipantID)) { + fake.deleteDataDownTrackMutex.Lock() + defer fake.deleteDataDownTrackMutex.Unlock() + fake.DeleteDataDownTrackStub = stub +} + +func (fake *FakeDataTrack) DeleteDataDownTrackArgsForCall(i int) livekit.ParticipantID { + fake.deleteDataDownTrackMutex.RLock() + defer fake.deleteDataDownTrackMutex.RUnlock() + argsForCall := fake.deleteDataDownTrackArgsForCall[i] + return argsForCall.arg1 +} + +func (fake *FakeDataTrack) HandlePacket(arg1 []byte, arg2 *datatrack.Packet) { + var arg1Copy []byte + if arg1 != nil { + arg1Copy = make([]byte, len(arg1)) + copy(arg1Copy, arg1) + } + fake.handlePacketMutex.Lock() + fake.handlePacketArgsForCall = append(fake.handlePacketArgsForCall, struct { + arg1 []byte + arg2 *datatrack.Packet + }{arg1Copy, arg2}) + stub := fake.HandlePacketStub + fake.recordInvocation("HandlePacket", []interface{}{arg1Copy, arg2}) + fake.handlePacketMutex.Unlock() + if stub != nil { + fake.HandlePacketStub(arg1, arg2) + } +} + +func (fake *FakeDataTrack) HandlePacketCallCount() int { + fake.handlePacketMutex.RLock() + defer fake.handlePacketMutex.RUnlock() + return len(fake.handlePacketArgsForCall) +} + +func (fake *FakeDataTrack) HandlePacketCalls(stub func([]byte, *datatrack.Packet)) { + fake.handlePacketMutex.Lock() + defer fake.handlePacketMutex.Unlock() + fake.HandlePacketStub = stub +} + +func (fake *FakeDataTrack) HandlePacketArgsForCall(i int) ([]byte, *datatrack.Packet) { + fake.handlePacketMutex.RLock() + defer fake.handlePacketMutex.RUnlock() + argsForCall := fake.handlePacketArgsForCall[i] + return argsForCall.arg1, argsForCall.arg2 +} + +func (fake *FakeDataTrack) ID() livekit.TrackID { + 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 *FakeDataTrack) IDCallCount() int { + fake.iDMutex.RLock() + defer fake.iDMutex.RUnlock() + return len(fake.iDArgsForCall) +} + +func (fake *FakeDataTrack) IDCalls(stub func() livekit.TrackID) { + fake.iDMutex.Lock() + defer fake.iDMutex.Unlock() + fake.IDStub = stub +} + +func (fake *FakeDataTrack) IDReturns(result1 livekit.TrackID) { + fake.iDMutex.Lock() + defer fake.iDMutex.Unlock() + fake.IDStub = nil + fake.iDReturns = struct { + result1 livekit.TrackID + }{result1} +} + +func (fake *FakeDataTrack) IDReturnsOnCall(i int, result1 livekit.TrackID) { + fake.iDMutex.Lock() + defer fake.iDMutex.Unlock() + fake.IDStub = nil + if fake.iDReturnsOnCall == nil { + fake.iDReturnsOnCall = make(map[int]struct { + result1 livekit.TrackID + }) + } + fake.iDReturnsOnCall[i] = struct { + result1 livekit.TrackID + }{result1} +} + +func (fake *FakeDataTrack) IsSubscriber(arg1 livekit.ParticipantID) bool { + fake.isSubscriberMutex.Lock() + ret, specificReturn := fake.isSubscriberReturnsOnCall[len(fake.isSubscriberArgsForCall)] + fake.isSubscriberArgsForCall = append(fake.isSubscriberArgsForCall, struct { + arg1 livekit.ParticipantID + }{arg1}) + stub := fake.IsSubscriberStub + fakeReturns := fake.isSubscriberReturns + fake.recordInvocation("IsSubscriber", []interface{}{arg1}) + fake.isSubscriberMutex.Unlock() + if stub != nil { + return stub(arg1) + } + if specificReturn { + return ret.result1 + } + return fakeReturns.result1 +} + +func (fake *FakeDataTrack) IsSubscriberCallCount() int { + fake.isSubscriberMutex.RLock() + defer fake.isSubscriberMutex.RUnlock() + return len(fake.isSubscriberArgsForCall) +} + +func (fake *FakeDataTrack) IsSubscriberCalls(stub func(livekit.ParticipantID) bool) { + fake.isSubscriberMutex.Lock() + defer fake.isSubscriberMutex.Unlock() + fake.IsSubscriberStub = stub +} + +func (fake *FakeDataTrack) IsSubscriberArgsForCall(i int) livekit.ParticipantID { + fake.isSubscriberMutex.RLock() + defer fake.isSubscriberMutex.RUnlock() + argsForCall := fake.isSubscriberArgsForCall[i] + return argsForCall.arg1 +} + +func (fake *FakeDataTrack) IsSubscriberReturns(result1 bool) { + fake.isSubscriberMutex.Lock() + defer fake.isSubscriberMutex.Unlock() + fake.IsSubscriberStub = nil + fake.isSubscriberReturns = struct { + result1 bool + }{result1} +} + +func (fake *FakeDataTrack) IsSubscriberReturnsOnCall(i int, result1 bool) { + fake.isSubscriberMutex.Lock() + defer fake.isSubscriberMutex.Unlock() + fake.IsSubscriberStub = nil + if fake.isSubscriberReturnsOnCall == nil { + fake.isSubscriberReturnsOnCall = make(map[int]struct { + result1 bool + }) + } + fake.isSubscriberReturnsOnCall[i] = struct { + result1 bool + }{result1} +} + +func (fake *FakeDataTrack) Name() string { + fake.nameMutex.Lock() + ret, specificReturn := fake.nameReturnsOnCall[len(fake.nameArgsForCall)] + fake.nameArgsForCall = append(fake.nameArgsForCall, struct { + }{}) + stub := fake.NameStub + fakeReturns := fake.nameReturns + fake.recordInvocation("Name", []interface{}{}) + fake.nameMutex.Unlock() + if stub != nil { + return stub() + } + if specificReturn { + return ret.result1 + } + return fakeReturns.result1 +} + +func (fake *FakeDataTrack) NameCallCount() int { + fake.nameMutex.RLock() + defer fake.nameMutex.RUnlock() + return len(fake.nameArgsForCall) +} + +func (fake *FakeDataTrack) NameCalls(stub func() string) { + fake.nameMutex.Lock() + defer fake.nameMutex.Unlock() + fake.NameStub = stub +} + +func (fake *FakeDataTrack) NameReturns(result1 string) { + fake.nameMutex.Lock() + defer fake.nameMutex.Unlock() + fake.NameStub = nil + fake.nameReturns = struct { + result1 string + }{result1} +} + +func (fake *FakeDataTrack) NameReturnsOnCall(i int, result1 string) { + fake.nameMutex.Lock() + defer fake.nameMutex.Unlock() + fake.NameStub = nil + if fake.nameReturnsOnCall == nil { + fake.nameReturnsOnCall = make(map[int]struct { + result1 string + }) + } + fake.nameReturnsOnCall[i] = struct { + result1 string + }{result1} +} + +func (fake *FakeDataTrack) PubHandle() uint16 { + fake.pubHandleMutex.Lock() + ret, specificReturn := fake.pubHandleReturnsOnCall[len(fake.pubHandleArgsForCall)] + fake.pubHandleArgsForCall = append(fake.pubHandleArgsForCall, struct { + }{}) + stub := fake.PubHandleStub + fakeReturns := fake.pubHandleReturns + fake.recordInvocation("PubHandle", []interface{}{}) + fake.pubHandleMutex.Unlock() + if stub != nil { + return stub() + } + if specificReturn { + return ret.result1 + } + return fakeReturns.result1 +} + +func (fake *FakeDataTrack) PubHandleCallCount() int { + fake.pubHandleMutex.RLock() + defer fake.pubHandleMutex.RUnlock() + return len(fake.pubHandleArgsForCall) +} + +func (fake *FakeDataTrack) PubHandleCalls(stub func() uint16) { + fake.pubHandleMutex.Lock() + defer fake.pubHandleMutex.Unlock() + fake.PubHandleStub = stub +} + +func (fake *FakeDataTrack) PubHandleReturns(result1 uint16) { + fake.pubHandleMutex.Lock() + defer fake.pubHandleMutex.Unlock() + fake.PubHandleStub = nil + fake.pubHandleReturns = struct { + result1 uint16 + }{result1} +} + +func (fake *FakeDataTrack) PubHandleReturnsOnCall(i int, result1 uint16) { + fake.pubHandleMutex.Lock() + defer fake.pubHandleMutex.Unlock() + fake.PubHandleStub = nil + if fake.pubHandleReturnsOnCall == nil { + fake.pubHandleReturnsOnCall = make(map[int]struct { + result1 uint16 + }) + } + fake.pubHandleReturnsOnCall[i] = struct { + result1 uint16 + }{result1} +} + +func (fake *FakeDataTrack) RemoveSubscriber(arg1 livekit.ParticipantID) { + fake.removeSubscriberMutex.Lock() + fake.removeSubscriberArgsForCall = append(fake.removeSubscriberArgsForCall, struct { + arg1 livekit.ParticipantID + }{arg1}) + stub := fake.RemoveSubscriberStub + fake.recordInvocation("RemoveSubscriber", []interface{}{arg1}) + fake.removeSubscriberMutex.Unlock() + if stub != nil { + fake.RemoveSubscriberStub(arg1) + } +} + +func (fake *FakeDataTrack) RemoveSubscriberCallCount() int { + fake.removeSubscriberMutex.RLock() + defer fake.removeSubscriberMutex.RUnlock() + return len(fake.removeSubscriberArgsForCall) +} + +func (fake *FakeDataTrack) RemoveSubscriberCalls(stub func(livekit.ParticipantID)) { + fake.removeSubscriberMutex.Lock() + defer fake.removeSubscriberMutex.Unlock() + fake.RemoveSubscriberStub = stub +} + +func (fake *FakeDataTrack) RemoveSubscriberArgsForCall(i int) livekit.ParticipantID { + fake.removeSubscriberMutex.RLock() + defer fake.removeSubscriberMutex.RUnlock() + argsForCall := fake.removeSubscriberArgsForCall[i] + return argsForCall.arg1 +} + +func (fake *FakeDataTrack) ToProto() *livekit.DataTrackInfo { + fake.toProtoMutex.Lock() + ret, specificReturn := fake.toProtoReturnsOnCall[len(fake.toProtoArgsForCall)] + fake.toProtoArgsForCall = append(fake.toProtoArgsForCall, struct { + }{}) + stub := fake.ToProtoStub + fakeReturns := fake.toProtoReturns + fake.recordInvocation("ToProto", []interface{}{}) + fake.toProtoMutex.Unlock() + if stub != nil { + return stub() + } + if specificReturn { + return ret.result1 + } + return fakeReturns.result1 +} + +func (fake *FakeDataTrack) ToProtoCallCount() int { + fake.toProtoMutex.RLock() + defer fake.toProtoMutex.RUnlock() + return len(fake.toProtoArgsForCall) +} + +func (fake *FakeDataTrack) ToProtoCalls(stub func() *livekit.DataTrackInfo) { + fake.toProtoMutex.Lock() + defer fake.toProtoMutex.Unlock() + fake.ToProtoStub = stub +} + +func (fake *FakeDataTrack) ToProtoReturns(result1 *livekit.DataTrackInfo) { + fake.toProtoMutex.Lock() + defer fake.toProtoMutex.Unlock() + fake.ToProtoStub = nil + fake.toProtoReturns = struct { + result1 *livekit.DataTrackInfo + }{result1} +} + +func (fake *FakeDataTrack) ToProtoReturnsOnCall(i int, result1 *livekit.DataTrackInfo) { + fake.toProtoMutex.Lock() + defer fake.toProtoMutex.Unlock() + fake.ToProtoStub = nil + if fake.toProtoReturnsOnCall == nil { + fake.toProtoReturnsOnCall = make(map[int]struct { + result1 *livekit.DataTrackInfo + }) + } + fake.toProtoReturnsOnCall[i] = struct { + result1 *livekit.DataTrackInfo + }{result1} +} + +func (fake *FakeDataTrack) Invocations() map[string][][]interface{} { + fake.invocationsMutex.RLock() + defer fake.invocationsMutex.RUnlock() + copiedInvocations := map[string][][]interface{}{} + for key, value := range fake.invocations { + copiedInvocations[key] = value + } + return copiedInvocations +} + +func (fake *FakeDataTrack) 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.DataTrack = new(FakeDataTrack) diff --git a/pkg/rtc/types/typesfakes/fake_data_track_sender.go b/pkg/rtc/types/typesfakes/fake_data_track_sender.go new file mode 100644 index 000000000..2f9364ea9 --- /dev/null +++ b/pkg/rtc/types/typesfakes/fake_data_track_sender.go @@ -0,0 +1,146 @@ +// Code generated by counterfeiter. DO NOT EDIT. +package typesfakes + +import ( + "sync" + + "github.com/livekit/livekit-server/pkg/rtc/datatrack" + "github.com/livekit/livekit-server/pkg/rtc/types" + "github.com/livekit/protocol/livekit" +) + +type FakeDataTrackSender struct { + SubscriberIDStub func() livekit.ParticipantID + subscriberIDMutex sync.RWMutex + subscriberIDArgsForCall []struct { + } + subscriberIDReturns struct { + result1 livekit.ParticipantID + } + subscriberIDReturnsOnCall map[int]struct { + result1 livekit.ParticipantID + } + WritePacketStub func([]byte, *datatrack.Packet) + writePacketMutex sync.RWMutex + writePacketArgsForCall []struct { + arg1 []byte + arg2 *datatrack.Packet + } + invocations map[string][][]interface{} + invocationsMutex sync.RWMutex +} + +func (fake *FakeDataTrackSender) SubscriberID() livekit.ParticipantID { + fake.subscriberIDMutex.Lock() + ret, specificReturn := fake.subscriberIDReturnsOnCall[len(fake.subscriberIDArgsForCall)] + fake.subscriberIDArgsForCall = append(fake.subscriberIDArgsForCall, struct { + }{}) + stub := fake.SubscriberIDStub + fakeReturns := fake.subscriberIDReturns + fake.recordInvocation("SubscriberID", []interface{}{}) + fake.subscriberIDMutex.Unlock() + if stub != nil { + return stub() + } + if specificReturn { + return ret.result1 + } + return fakeReturns.result1 +} + +func (fake *FakeDataTrackSender) SubscriberIDCallCount() int { + fake.subscriberIDMutex.RLock() + defer fake.subscriberIDMutex.RUnlock() + return len(fake.subscriberIDArgsForCall) +} + +func (fake *FakeDataTrackSender) SubscriberIDCalls(stub func() livekit.ParticipantID) { + fake.subscriberIDMutex.Lock() + defer fake.subscriberIDMutex.Unlock() + fake.SubscriberIDStub = stub +} + +func (fake *FakeDataTrackSender) SubscriberIDReturns(result1 livekit.ParticipantID) { + fake.subscriberIDMutex.Lock() + defer fake.subscriberIDMutex.Unlock() + fake.SubscriberIDStub = nil + fake.subscriberIDReturns = struct { + result1 livekit.ParticipantID + }{result1} +} + +func (fake *FakeDataTrackSender) SubscriberIDReturnsOnCall(i int, result1 livekit.ParticipantID) { + fake.subscriberIDMutex.Lock() + defer fake.subscriberIDMutex.Unlock() + fake.SubscriberIDStub = nil + if fake.subscriberIDReturnsOnCall == nil { + fake.subscriberIDReturnsOnCall = make(map[int]struct { + result1 livekit.ParticipantID + }) + } + fake.subscriberIDReturnsOnCall[i] = struct { + result1 livekit.ParticipantID + }{result1} +} + +func (fake *FakeDataTrackSender) WritePacket(arg1 []byte, arg2 *datatrack.Packet) { + var arg1Copy []byte + if arg1 != nil { + arg1Copy = make([]byte, len(arg1)) + copy(arg1Copy, arg1) + } + fake.writePacketMutex.Lock() + fake.writePacketArgsForCall = append(fake.writePacketArgsForCall, struct { + arg1 []byte + arg2 *datatrack.Packet + }{arg1Copy, arg2}) + stub := fake.WritePacketStub + fake.recordInvocation("WritePacket", []interface{}{arg1Copy, arg2}) + fake.writePacketMutex.Unlock() + if stub != nil { + fake.WritePacketStub(arg1, arg2) + } +} + +func (fake *FakeDataTrackSender) WritePacketCallCount() int { + fake.writePacketMutex.RLock() + defer fake.writePacketMutex.RUnlock() + return len(fake.writePacketArgsForCall) +} + +func (fake *FakeDataTrackSender) WritePacketCalls(stub func([]byte, *datatrack.Packet)) { + fake.writePacketMutex.Lock() + defer fake.writePacketMutex.Unlock() + fake.WritePacketStub = stub +} + +func (fake *FakeDataTrackSender) WritePacketArgsForCall(i int) ([]byte, *datatrack.Packet) { + fake.writePacketMutex.RLock() + defer fake.writePacketMutex.RUnlock() + argsForCall := fake.writePacketArgsForCall[i] + return argsForCall.arg1, argsForCall.arg2 +} + +func (fake *FakeDataTrackSender) Invocations() map[string][][]interface{} { + fake.invocationsMutex.RLock() + defer fake.invocationsMutex.RUnlock() + copiedInvocations := map[string][][]interface{}{} + for key, value := range fake.invocations { + copiedInvocations[key] = value + } + return copiedInvocations +} + +func (fake *FakeDataTrackSender) 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.DataTrackSender = new(FakeDataTrackSender) diff --git a/pkg/rtc/types/typesfakes/fake_data_track_transport.go b/pkg/rtc/types/typesfakes/fake_data_track_transport.go new file mode 100644 index 000000000..d93c84f75 --- /dev/null +++ b/pkg/rtc/types/typesfakes/fake_data_track_transport.go @@ -0,0 +1,114 @@ +// Code generated by counterfeiter. DO NOT EDIT. +package typesfakes + +import ( + "sync" + + "github.com/livekit/livekit-server/pkg/rtc/types" +) + +type FakeDataTrackTransport struct { + SendDataTrackMessageStub func([]byte) error + sendDataTrackMessageMutex sync.RWMutex + sendDataTrackMessageArgsForCall []struct { + arg1 []byte + } + sendDataTrackMessageReturns struct { + result1 error + } + sendDataTrackMessageReturnsOnCall map[int]struct { + result1 error + } + invocations map[string][][]interface{} + invocationsMutex sync.RWMutex +} + +func (fake *FakeDataTrackTransport) SendDataTrackMessage(arg1 []byte) error { + var arg1Copy []byte + if arg1 != nil { + arg1Copy = make([]byte, len(arg1)) + copy(arg1Copy, arg1) + } + fake.sendDataTrackMessageMutex.Lock() + ret, specificReturn := fake.sendDataTrackMessageReturnsOnCall[len(fake.sendDataTrackMessageArgsForCall)] + fake.sendDataTrackMessageArgsForCall = append(fake.sendDataTrackMessageArgsForCall, struct { + arg1 []byte + }{arg1Copy}) + stub := fake.SendDataTrackMessageStub + fakeReturns := fake.sendDataTrackMessageReturns + fake.recordInvocation("SendDataTrackMessage", []interface{}{arg1Copy}) + fake.sendDataTrackMessageMutex.Unlock() + if stub != nil { + return stub(arg1) + } + if specificReturn { + return ret.result1 + } + return fakeReturns.result1 +} + +func (fake *FakeDataTrackTransport) SendDataTrackMessageCallCount() int { + fake.sendDataTrackMessageMutex.RLock() + defer fake.sendDataTrackMessageMutex.RUnlock() + return len(fake.sendDataTrackMessageArgsForCall) +} + +func (fake *FakeDataTrackTransport) SendDataTrackMessageCalls(stub func([]byte) error) { + fake.sendDataTrackMessageMutex.Lock() + defer fake.sendDataTrackMessageMutex.Unlock() + fake.SendDataTrackMessageStub = stub +} + +func (fake *FakeDataTrackTransport) SendDataTrackMessageArgsForCall(i int) []byte { + fake.sendDataTrackMessageMutex.RLock() + defer fake.sendDataTrackMessageMutex.RUnlock() + argsForCall := fake.sendDataTrackMessageArgsForCall[i] + return argsForCall.arg1 +} + +func (fake *FakeDataTrackTransport) SendDataTrackMessageReturns(result1 error) { + fake.sendDataTrackMessageMutex.Lock() + defer fake.sendDataTrackMessageMutex.Unlock() + fake.SendDataTrackMessageStub = nil + fake.sendDataTrackMessageReturns = struct { + result1 error + }{result1} +} + +func (fake *FakeDataTrackTransport) SendDataTrackMessageReturnsOnCall(i int, result1 error) { + fake.sendDataTrackMessageMutex.Lock() + defer fake.sendDataTrackMessageMutex.Unlock() + fake.SendDataTrackMessageStub = nil + if fake.sendDataTrackMessageReturnsOnCall == nil { + fake.sendDataTrackMessageReturnsOnCall = make(map[int]struct { + result1 error + }) + } + fake.sendDataTrackMessageReturnsOnCall[i] = struct { + result1 error + }{result1} +} + +func (fake *FakeDataTrackTransport) Invocations() map[string][][]interface{} { + fake.invocationsMutex.RLock() + defer fake.invocationsMutex.RUnlock() + copiedInvocations := map[string][][]interface{}{} + for key, value := range fake.invocations { + copiedInvocations[key] = value + } + return copiedInvocations +} + +func (fake *FakeDataTrackTransport) 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.DataTrackTransport = new(FakeDataTrackTransport) diff --git a/pkg/rtc/types/typesfakes/fake_local_participant.go b/pkg/rtc/types/typesfakes/fake_local_participant.go index 7b433d11c..ea3595c50 100644 --- a/pkg/rtc/types/typesfakes/fake_local_participant.go +++ b/pkg/rtc/types/typesfakes/fake_local_participant.go @@ -6,6 +6,7 @@ import ( "time" "github.com/livekit/livekit-server/pkg/routing" + "github.com/livekit/livekit-server/pkg/rtc/datatrack" "github.com/livekit/livekit-server/pkg/rtc/types" "github.com/livekit/livekit-server/pkg/sfu" "github.com/livekit/livekit-server/pkg/sfu/buffer" @@ -171,15 +172,15 @@ type FakeLocalParticipant struct { connectedAtReturnsOnCall map[int]struct { result1 time.Time } - DebugInfoStub func() map[string]interface{} + DebugInfoStub func() map[string]any debugInfoMutex sync.RWMutex debugInfoArgsForCall []struct { } debugInfoReturns struct { - result1 map[string]interface{} + result1 map[string]any } debugInfoReturnsOnCall map[int]struct { - result1 map[string]interface{} + result1 map[string]any } DisconnectedStub func() <-chan struct{} disconnectedMutex sync.RWMutex @@ -290,6 +291,16 @@ type FakeLocalParticipant struct { getCountryReturnsOnCall map[int]struct { result1 string } + GetDataTrackTransportStub func() types.DataTrackTransport + getDataTrackTransportMutex sync.RWMutex + getDataTrackTransportArgsForCall []struct { + } + getDataTrackTransportReturns struct { + result1 types.DataTrackTransport + } + getDataTrackTransportReturnsOnCall map[int]struct { + result1 types.DataTrackTransport + } GetDisableSenderReportPassThroughStub func() bool getDisableSenderReportPassThroughMutex sync.RWMutex getDisableSenderReportPassThroughArgsForCall []struct { @@ -392,6 +403,27 @@ type FakeLocalParticipant struct { getPlayoutDelayConfigReturnsOnCall map[int]struct { result1 *livekit.PlayoutDelay } + GetPublishedDataTrackStub func(uint16) types.DataTrack + getPublishedDataTrackMutex sync.RWMutex + getPublishedDataTrackArgsForCall []struct { + arg1 uint16 + } + getPublishedDataTrackReturns struct { + result1 types.DataTrack + } + getPublishedDataTrackReturnsOnCall map[int]struct { + result1 types.DataTrack + } + GetPublishedDataTracksStub func() []types.DataTrack + getPublishedDataTracksMutex sync.RWMutex + getPublishedDataTracksArgsForCall []struct { + } + getPublishedDataTracksReturns struct { + result1 []types.DataTrack + } + getPublishedDataTracksReturnsOnCall map[int]struct { + result1 []types.DataTrack + } GetPublishedTrackStub func(livekit.TrackID) types.MediaTrack getPublishedTrackMutex sync.RWMutex getPublishedTrackArgsForCall []struct { @@ -547,6 +579,17 @@ type FakeLocalParticipant struct { handleOfferReturnsOnCall map[int]struct { result1 error } + HandlePublishDataTrackRequestStub func(*livekit.PublishDataTrackRequest) + handlePublishDataTrackRequestMutex sync.RWMutex + handlePublishDataTrackRequestArgsForCall []struct { + arg1 *livekit.PublishDataTrackRequest + } + HandleReceivedDataTrackMessageStub func([]byte, *datatrack.Packet) + handleReceivedDataTrackMessageMutex sync.RWMutex + handleReceivedDataTrackMessageArgsForCall []struct { + arg1 []byte + arg2 *datatrack.Packet + } HandleReceiverReportStub func(*sfu.DownTrack, *rtcp.ReceiverReport) handleReceiverReportMutex sync.RWMutex handleReceiverReportArgsForCall []struct { @@ -602,6 +645,16 @@ type FakeLocalParticipant struct { handleSyncStateReturnsOnCall map[int]struct { result1 error } + HandleUnpublishDataTrackRequestStub func(*livekit.UnpublishDataTrackRequest) + handleUnpublishDataTrackRequestMutex sync.RWMutex + handleUnpublishDataTrackRequestArgsForCall []struct { + arg1 *livekit.UnpublishDataTrackRequest + } + HandleUpdateDataSubscriptionStub func(*livekit.UpdateDataSubscription) + handleUpdateDataSubscriptionMutex sync.RWMutex + handleUpdateDataSubscriptionArgsForCall []struct { + arg1 *livekit.UpdateDataSubscription + } HandleUpdateSubscriptionPermissionStub func(*livekit.SubscriptionPermission) error handleUpdateSubscriptionPermissionMutex sync.RWMutex handleUpdateSubscriptionPermissionArgsForCall []struct { @@ -866,6 +919,21 @@ type FakeLocalParticipant struct { onDataPacketArgsForCall []struct { arg1 func(types.LocalParticipant, livekit.DataPacket_Kind, *livekit.DataPacket) } + OnDataTrackMessageStub func(func(types.LocalParticipant, []byte, *datatrack.Packet)) + onDataTrackMessageMutex sync.RWMutex + onDataTrackMessageArgsForCall []struct { + arg1 func(types.LocalParticipant, []byte, *datatrack.Packet) + } + OnDataTrackPublishedStub func(func(types.Participant, types.DataTrack)) + onDataTrackPublishedMutex sync.RWMutex + onDataTrackPublishedArgsForCall []struct { + arg1 func(types.Participant, types.DataTrack) + } + OnDataTrackUnpublishedStub func(func(types.Participant, types.DataTrack)) + onDataTrackUnpublishedMutex sync.RWMutex + onDataTrackUnpublishedArgsForCall []struct { + arg1 func(types.Participant, types.DataTrack) + } OnICEConfigChangedStub func(func(participant types.LocalParticipant, iceConfig *livekit.ICEConfig)) onICEConfigChangedMutex sync.RWMutex onICEConfigChangedArgsForCall []struct { @@ -916,20 +984,25 @@ type FakeLocalParticipant struct { onSyncStateArgsForCall []struct { arg1 func(types.LocalParticipant, *livekit.SyncState) error } - OnTrackPublishedStub func(func(types.LocalParticipant, types.MediaTrack)) + OnTrackPublishedStub func(func(types.Participant, types.MediaTrack)) onTrackPublishedMutex sync.RWMutex onTrackPublishedArgsForCall []struct { - arg1 func(types.LocalParticipant, types.MediaTrack) + arg1 func(types.Participant, types.MediaTrack) } - OnTrackUnpublishedStub func(func(types.LocalParticipant, types.MediaTrack)) + OnTrackUnpublishedStub func(func(types.Participant, types.MediaTrack)) onTrackUnpublishedMutex sync.RWMutex onTrackUnpublishedArgsForCall []struct { - arg1 func(types.LocalParticipant, types.MediaTrack) + arg1 func(types.Participant, types.MediaTrack) } - OnTrackUpdatedStub func(func(types.LocalParticipant, types.MediaTrack)) + OnTrackUpdatedStub func(func(types.Participant, types.MediaTrack)) onTrackUpdatedMutex sync.RWMutex onTrackUpdatedArgsForCall []struct { - arg1 func(types.LocalParticipant, types.MediaTrack) + arg1 func(types.Participant, types.MediaTrack) + } + OnUpdateDataSubscriptionsStub func(func(types.LocalParticipant, *livekit.UpdateDataSubscription)) + onUpdateDataSubscriptionsMutex sync.RWMutex + onUpdateDataSubscriptionsArgsForCall []struct { + arg1 func(types.LocalParticipant, *livekit.UpdateDataSubscription) } OnUpdateSubscriptionPermissionStub func(func(types.LocalParticipant, *livekit.SubscriptionPermission) error) onUpdateSubscriptionPermissionMutex sync.RWMutex @@ -958,6 +1031,11 @@ type FakeLocalParticipant struct { protocolVersionReturnsOnCall map[int]struct { result1 types.ProtocolVersion } + RemovePublishedDataTrackStub func(types.DataTrack) + removePublishedDataTrackMutex sync.RWMutex + removePublishedDataTrackArgsForCall []struct { + arg1 types.DataTrack + } RemovePublishedTrackStub func(types.MediaTrack, bool) removePublishedTrackMutex sync.RWMutex removePublishedTrackArgsForCall []struct { @@ -1013,6 +1091,17 @@ type FakeLocalParticipant struct { sendDataMessageUnlabeledReturnsOnCall map[int]struct { result1 error } + SendDataTrackSubscriberHandlesStub func(map[uint32]*livekit.DataTrackSubscriberHandles_PublishedDataTrack) error + sendDataTrackSubscriberHandlesMutex sync.RWMutex + sendDataTrackSubscriberHandlesArgsForCall []struct { + arg1 map[uint32]*livekit.DataTrackSubscriberHandles_PublishedDataTrack + } + sendDataTrackSubscriberHandlesReturns struct { + result1 error + } + sendDataTrackSubscriberHandlesReturnsOnCall map[int]struct { + result1 error + } SendJoinResponseStub func(*livekit.JoinResponse) error sendJoinResponseMutex sync.RWMutex sendJoinResponseArgsForCall []struct { @@ -1108,7 +1197,7 @@ type FakeLocalParticipant struct { setMetadataArgsForCall []struct { arg1 string } - SetMigrateInfoStub func(*webrtc.SessionDescription, *webrtc.SessionDescription, []*livekit.TrackPublishedResponse, []*livekit.DataChannelInfo, []*livekit.DataChannelReceiveState) + SetMigrateInfoStub func(*webrtc.SessionDescription, *webrtc.SessionDescription, []*livekit.TrackPublishedResponse, []*livekit.DataChannelInfo, []*livekit.DataChannelReceiveState, []*livekit.PublishDataTrackResponse) setMigrateInfoMutex sync.RWMutex setMigrateInfoArgsForCall []struct { arg1 *webrtc.SessionDescription @@ -1116,6 +1205,7 @@ type FakeLocalParticipant struct { arg3 []*livekit.TrackPublishedResponse arg4 []*livekit.DataChannelInfo arg5 []*livekit.DataChannelReceiveState + arg6 []*livekit.PublishDataTrackResponse } SetMigrateStateStub func(types.MigrateState) setMigrateStateMutex sync.RWMutex @@ -1190,6 +1280,11 @@ type FakeLocalParticipant struct { stopAndGetSubscribedTracksForwarderStateReturnsOnCall map[int]struct { result1 map[livekit.TrackID]*livekit.RTPForwarderState } + SubscribeToDataTrackStub func(livekit.TrackID) + subscribeToDataTrackMutex sync.RWMutex + subscribeToDataTrackArgsForCall []struct { + arg1 livekit.TrackID + } SubscribeToTrackStub func(livekit.TrackID, bool) subscribeToTrackMutex sync.RWMutex subscribeToTrackArgsForCall []struct { @@ -1295,6 +1390,11 @@ type FakeLocalParticipant struct { uncacheDownTrackArgsForCall []struct { arg1 *webrtc.RTPTransceiver } + UnsubscribeFromDataTrackStub func(livekit.TrackID) + unsubscribeFromDataTrackMutex sync.RWMutex + unsubscribeFromDataTrackArgsForCall []struct { + arg1 livekit.TrackID + } UnsubscribeFromTrackStub func(livekit.TrackID) unsubscribeFromTrackMutex sync.RWMutex unsubscribeFromTrackArgsForCall []struct { @@ -1311,6 +1411,12 @@ type FakeLocalParticipant struct { updateAudioTrackReturnsOnCall map[int]struct { result1 error } + UpdateDataTrackSubscriptionOptionsStub func(livekit.TrackID, *livekit.DataTrackSubscriptionOptions) + updateDataTrackSubscriptionOptionsMutex sync.RWMutex + updateDataTrackSubscriptionOptionsArgsForCall []struct { + arg1 livekit.TrackID + arg2 *livekit.DataTrackSubscriptionOptions + } UpdateLastSeenSignalStub func() updateLastSeenSignalMutex sync.RWMutex updateLastSeenSignalArgsForCall []struct { @@ -2220,7 +2326,7 @@ func (fake *FakeLocalParticipant) ConnectedAtReturnsOnCall(i int, result1 time.T }{result1} } -func (fake *FakeLocalParticipant) DebugInfo() map[string]interface{} { +func (fake *FakeLocalParticipant) DebugInfo() map[string]any { fake.debugInfoMutex.Lock() ret, specificReturn := fake.debugInfoReturnsOnCall[len(fake.debugInfoArgsForCall)] fake.debugInfoArgsForCall = append(fake.debugInfoArgsForCall, struct { @@ -2244,32 +2350,32 @@ func (fake *FakeLocalParticipant) DebugInfoCallCount() int { return len(fake.debugInfoArgsForCall) } -func (fake *FakeLocalParticipant) DebugInfoCalls(stub func() map[string]interface{}) { +func (fake *FakeLocalParticipant) DebugInfoCalls(stub func() map[string]any) { fake.debugInfoMutex.Lock() defer fake.debugInfoMutex.Unlock() fake.DebugInfoStub = stub } -func (fake *FakeLocalParticipant) DebugInfoReturns(result1 map[string]interface{}) { +func (fake *FakeLocalParticipant) DebugInfoReturns(result1 map[string]any) { fake.debugInfoMutex.Lock() defer fake.debugInfoMutex.Unlock() fake.DebugInfoStub = nil fake.debugInfoReturns = struct { - result1 map[string]interface{} + result1 map[string]any }{result1} } -func (fake *FakeLocalParticipant) DebugInfoReturnsOnCall(i int, result1 map[string]interface{}) { +func (fake *FakeLocalParticipant) DebugInfoReturnsOnCall(i int, result1 map[string]any) { fake.debugInfoMutex.Lock() defer fake.debugInfoMutex.Unlock() fake.DebugInfoStub = nil if fake.debugInfoReturnsOnCall == nil { fake.debugInfoReturnsOnCall = make(map[int]struct { - result1 map[string]interface{} + result1 map[string]any }) } fake.debugInfoReturnsOnCall[i] = struct { - result1 map[string]interface{} + result1 map[string]any }{result1} } @@ -2823,6 +2929,59 @@ func (fake *FakeLocalParticipant) GetCountryReturnsOnCall(i int, result1 string) }{result1} } +func (fake *FakeLocalParticipant) GetDataTrackTransport() types.DataTrackTransport { + fake.getDataTrackTransportMutex.Lock() + ret, specificReturn := fake.getDataTrackTransportReturnsOnCall[len(fake.getDataTrackTransportArgsForCall)] + fake.getDataTrackTransportArgsForCall = append(fake.getDataTrackTransportArgsForCall, struct { + }{}) + stub := fake.GetDataTrackTransportStub + fakeReturns := fake.getDataTrackTransportReturns + fake.recordInvocation("GetDataTrackTransport", []interface{}{}) + fake.getDataTrackTransportMutex.Unlock() + if stub != nil { + return stub() + } + if specificReturn { + return ret.result1 + } + return fakeReturns.result1 +} + +func (fake *FakeLocalParticipant) GetDataTrackTransportCallCount() int { + fake.getDataTrackTransportMutex.RLock() + defer fake.getDataTrackTransportMutex.RUnlock() + return len(fake.getDataTrackTransportArgsForCall) +} + +func (fake *FakeLocalParticipant) GetDataTrackTransportCalls(stub func() types.DataTrackTransport) { + fake.getDataTrackTransportMutex.Lock() + defer fake.getDataTrackTransportMutex.Unlock() + fake.GetDataTrackTransportStub = stub +} + +func (fake *FakeLocalParticipant) GetDataTrackTransportReturns(result1 types.DataTrackTransport) { + fake.getDataTrackTransportMutex.Lock() + defer fake.getDataTrackTransportMutex.Unlock() + fake.GetDataTrackTransportStub = nil + fake.getDataTrackTransportReturns = struct { + result1 types.DataTrackTransport + }{result1} +} + +func (fake *FakeLocalParticipant) GetDataTrackTransportReturnsOnCall(i int, result1 types.DataTrackTransport) { + fake.getDataTrackTransportMutex.Lock() + defer fake.getDataTrackTransportMutex.Unlock() + fake.GetDataTrackTransportStub = nil + if fake.getDataTrackTransportReturnsOnCall == nil { + fake.getDataTrackTransportReturnsOnCall = make(map[int]struct { + result1 types.DataTrackTransport + }) + } + fake.getDataTrackTransportReturnsOnCall[i] = struct { + result1 types.DataTrackTransport + }{result1} +} + func (fake *FakeLocalParticipant) GetDisableSenderReportPassThrough() bool { fake.getDisableSenderReportPassThroughMutex.Lock() ret, specificReturn := fake.getDisableSenderReportPassThroughReturnsOnCall[len(fake.getDisableSenderReportPassThroughArgsForCall)] @@ -3369,6 +3528,120 @@ func (fake *FakeLocalParticipant) GetPlayoutDelayConfigReturnsOnCall(i int, resu }{result1} } +func (fake *FakeLocalParticipant) GetPublishedDataTrack(arg1 uint16) types.DataTrack { + fake.getPublishedDataTrackMutex.Lock() + ret, specificReturn := fake.getPublishedDataTrackReturnsOnCall[len(fake.getPublishedDataTrackArgsForCall)] + fake.getPublishedDataTrackArgsForCall = append(fake.getPublishedDataTrackArgsForCall, struct { + arg1 uint16 + }{arg1}) + stub := fake.GetPublishedDataTrackStub + fakeReturns := fake.getPublishedDataTrackReturns + fake.recordInvocation("GetPublishedDataTrack", []interface{}{arg1}) + fake.getPublishedDataTrackMutex.Unlock() + if stub != nil { + return stub(arg1) + } + if specificReturn { + return ret.result1 + } + return fakeReturns.result1 +} + +func (fake *FakeLocalParticipant) GetPublishedDataTrackCallCount() int { + fake.getPublishedDataTrackMutex.RLock() + defer fake.getPublishedDataTrackMutex.RUnlock() + return len(fake.getPublishedDataTrackArgsForCall) +} + +func (fake *FakeLocalParticipant) GetPublishedDataTrackCalls(stub func(uint16) types.DataTrack) { + fake.getPublishedDataTrackMutex.Lock() + defer fake.getPublishedDataTrackMutex.Unlock() + fake.GetPublishedDataTrackStub = stub +} + +func (fake *FakeLocalParticipant) GetPublishedDataTrackArgsForCall(i int) uint16 { + fake.getPublishedDataTrackMutex.RLock() + defer fake.getPublishedDataTrackMutex.RUnlock() + argsForCall := fake.getPublishedDataTrackArgsForCall[i] + return argsForCall.arg1 +} + +func (fake *FakeLocalParticipant) GetPublishedDataTrackReturns(result1 types.DataTrack) { + fake.getPublishedDataTrackMutex.Lock() + defer fake.getPublishedDataTrackMutex.Unlock() + fake.GetPublishedDataTrackStub = nil + fake.getPublishedDataTrackReturns = struct { + result1 types.DataTrack + }{result1} +} + +func (fake *FakeLocalParticipant) GetPublishedDataTrackReturnsOnCall(i int, result1 types.DataTrack) { + fake.getPublishedDataTrackMutex.Lock() + defer fake.getPublishedDataTrackMutex.Unlock() + fake.GetPublishedDataTrackStub = nil + if fake.getPublishedDataTrackReturnsOnCall == nil { + fake.getPublishedDataTrackReturnsOnCall = make(map[int]struct { + result1 types.DataTrack + }) + } + fake.getPublishedDataTrackReturnsOnCall[i] = struct { + result1 types.DataTrack + }{result1} +} + +func (fake *FakeLocalParticipant) GetPublishedDataTracks() []types.DataTrack { + fake.getPublishedDataTracksMutex.Lock() + ret, specificReturn := fake.getPublishedDataTracksReturnsOnCall[len(fake.getPublishedDataTracksArgsForCall)] + fake.getPublishedDataTracksArgsForCall = append(fake.getPublishedDataTracksArgsForCall, struct { + }{}) + stub := fake.GetPublishedDataTracksStub + fakeReturns := fake.getPublishedDataTracksReturns + fake.recordInvocation("GetPublishedDataTracks", []interface{}{}) + fake.getPublishedDataTracksMutex.Unlock() + if stub != nil { + return stub() + } + if specificReturn { + return ret.result1 + } + return fakeReturns.result1 +} + +func (fake *FakeLocalParticipant) GetPublishedDataTracksCallCount() int { + fake.getPublishedDataTracksMutex.RLock() + defer fake.getPublishedDataTracksMutex.RUnlock() + return len(fake.getPublishedDataTracksArgsForCall) +} + +func (fake *FakeLocalParticipant) GetPublishedDataTracksCalls(stub func() []types.DataTrack) { + fake.getPublishedDataTracksMutex.Lock() + defer fake.getPublishedDataTracksMutex.Unlock() + fake.GetPublishedDataTracksStub = stub +} + +func (fake *FakeLocalParticipant) GetPublishedDataTracksReturns(result1 []types.DataTrack) { + fake.getPublishedDataTracksMutex.Lock() + defer fake.getPublishedDataTracksMutex.Unlock() + fake.GetPublishedDataTracksStub = nil + fake.getPublishedDataTracksReturns = struct { + result1 []types.DataTrack + }{result1} +} + +func (fake *FakeLocalParticipant) GetPublishedDataTracksReturnsOnCall(i int, result1 []types.DataTrack) { + fake.getPublishedDataTracksMutex.Lock() + defer fake.getPublishedDataTracksMutex.Unlock() + fake.GetPublishedDataTracksStub = nil + if fake.getPublishedDataTracksReturnsOnCall == nil { + fake.getPublishedDataTracksReturnsOnCall = make(map[int]struct { + result1 []types.DataTrack + }) + } + fake.getPublishedDataTracksReturnsOnCall[i] = struct { + result1 []types.DataTrack + }{result1} +} + func (fake *FakeLocalParticipant) GetPublishedTrack(arg1 livekit.TrackID) types.MediaTrack { fake.getPublishedTrackMutex.Lock() ret, specificReturn := fake.getPublishedTrackReturnsOnCall[len(fake.getPublishedTrackArgsForCall)] @@ -4201,6 +4474,76 @@ func (fake *FakeLocalParticipant) HandleOfferReturnsOnCall(i int, result1 error) }{result1} } +func (fake *FakeLocalParticipant) HandlePublishDataTrackRequest(arg1 *livekit.PublishDataTrackRequest) { + fake.handlePublishDataTrackRequestMutex.Lock() + fake.handlePublishDataTrackRequestArgsForCall = append(fake.handlePublishDataTrackRequestArgsForCall, struct { + arg1 *livekit.PublishDataTrackRequest + }{arg1}) + stub := fake.HandlePublishDataTrackRequestStub + fake.recordInvocation("HandlePublishDataTrackRequest", []interface{}{arg1}) + fake.handlePublishDataTrackRequestMutex.Unlock() + if stub != nil { + fake.HandlePublishDataTrackRequestStub(arg1) + } +} + +func (fake *FakeLocalParticipant) HandlePublishDataTrackRequestCallCount() int { + fake.handlePublishDataTrackRequestMutex.RLock() + defer fake.handlePublishDataTrackRequestMutex.RUnlock() + return len(fake.handlePublishDataTrackRequestArgsForCall) +} + +func (fake *FakeLocalParticipant) HandlePublishDataTrackRequestCalls(stub func(*livekit.PublishDataTrackRequest)) { + fake.handlePublishDataTrackRequestMutex.Lock() + defer fake.handlePublishDataTrackRequestMutex.Unlock() + fake.HandlePublishDataTrackRequestStub = stub +} + +func (fake *FakeLocalParticipant) HandlePublishDataTrackRequestArgsForCall(i int) *livekit.PublishDataTrackRequest { + fake.handlePublishDataTrackRequestMutex.RLock() + defer fake.handlePublishDataTrackRequestMutex.RUnlock() + argsForCall := fake.handlePublishDataTrackRequestArgsForCall[i] + return argsForCall.arg1 +} + +func (fake *FakeLocalParticipant) HandleReceivedDataTrackMessage(arg1 []byte, arg2 *datatrack.Packet) { + var arg1Copy []byte + if arg1 != nil { + arg1Copy = make([]byte, len(arg1)) + copy(arg1Copy, arg1) + } + fake.handleReceivedDataTrackMessageMutex.Lock() + fake.handleReceivedDataTrackMessageArgsForCall = append(fake.handleReceivedDataTrackMessageArgsForCall, struct { + arg1 []byte + arg2 *datatrack.Packet + }{arg1Copy, arg2}) + stub := fake.HandleReceivedDataTrackMessageStub + fake.recordInvocation("HandleReceivedDataTrackMessage", []interface{}{arg1Copy, arg2}) + fake.handleReceivedDataTrackMessageMutex.Unlock() + if stub != nil { + fake.HandleReceivedDataTrackMessageStub(arg1, arg2) + } +} + +func (fake *FakeLocalParticipant) HandleReceivedDataTrackMessageCallCount() int { + fake.handleReceivedDataTrackMessageMutex.RLock() + defer fake.handleReceivedDataTrackMessageMutex.RUnlock() + return len(fake.handleReceivedDataTrackMessageArgsForCall) +} + +func (fake *FakeLocalParticipant) HandleReceivedDataTrackMessageCalls(stub func([]byte, *datatrack.Packet)) { + fake.handleReceivedDataTrackMessageMutex.Lock() + defer fake.handleReceivedDataTrackMessageMutex.Unlock() + fake.HandleReceivedDataTrackMessageStub = stub +} + +func (fake *FakeLocalParticipant) HandleReceivedDataTrackMessageArgsForCall(i int) ([]byte, *datatrack.Packet) { + fake.handleReceivedDataTrackMessageMutex.RLock() + defer fake.handleReceivedDataTrackMessageMutex.RUnlock() + argsForCall := fake.handleReceivedDataTrackMessageArgsForCall[i] + return argsForCall.arg1, argsForCall.arg2 +} + func (fake *FakeLocalParticipant) HandleReceiverReport(arg1 *sfu.DownTrack, arg2 *rtcp.ReceiverReport) { fake.handleReceiverReportMutex.Lock() fake.handleReceiverReportArgsForCall = append(fake.handleReceiverReportArgsForCall, struct { @@ -4503,6 +4846,70 @@ func (fake *FakeLocalParticipant) HandleSyncStateReturnsOnCall(i int, result1 er }{result1} } +func (fake *FakeLocalParticipant) HandleUnpublishDataTrackRequest(arg1 *livekit.UnpublishDataTrackRequest) { + fake.handleUnpublishDataTrackRequestMutex.Lock() + fake.handleUnpublishDataTrackRequestArgsForCall = append(fake.handleUnpublishDataTrackRequestArgsForCall, struct { + arg1 *livekit.UnpublishDataTrackRequest + }{arg1}) + stub := fake.HandleUnpublishDataTrackRequestStub + fake.recordInvocation("HandleUnpublishDataTrackRequest", []interface{}{arg1}) + fake.handleUnpublishDataTrackRequestMutex.Unlock() + if stub != nil { + fake.HandleUnpublishDataTrackRequestStub(arg1) + } +} + +func (fake *FakeLocalParticipant) HandleUnpublishDataTrackRequestCallCount() int { + fake.handleUnpublishDataTrackRequestMutex.RLock() + defer fake.handleUnpublishDataTrackRequestMutex.RUnlock() + return len(fake.handleUnpublishDataTrackRequestArgsForCall) +} + +func (fake *FakeLocalParticipant) HandleUnpublishDataTrackRequestCalls(stub func(*livekit.UnpublishDataTrackRequest)) { + fake.handleUnpublishDataTrackRequestMutex.Lock() + defer fake.handleUnpublishDataTrackRequestMutex.Unlock() + fake.HandleUnpublishDataTrackRequestStub = stub +} + +func (fake *FakeLocalParticipant) HandleUnpublishDataTrackRequestArgsForCall(i int) *livekit.UnpublishDataTrackRequest { + fake.handleUnpublishDataTrackRequestMutex.RLock() + defer fake.handleUnpublishDataTrackRequestMutex.RUnlock() + argsForCall := fake.handleUnpublishDataTrackRequestArgsForCall[i] + return argsForCall.arg1 +} + +func (fake *FakeLocalParticipant) HandleUpdateDataSubscription(arg1 *livekit.UpdateDataSubscription) { + fake.handleUpdateDataSubscriptionMutex.Lock() + fake.handleUpdateDataSubscriptionArgsForCall = append(fake.handleUpdateDataSubscriptionArgsForCall, struct { + arg1 *livekit.UpdateDataSubscription + }{arg1}) + stub := fake.HandleUpdateDataSubscriptionStub + fake.recordInvocation("HandleUpdateDataSubscription", []interface{}{arg1}) + fake.handleUpdateDataSubscriptionMutex.Unlock() + if stub != nil { + fake.HandleUpdateDataSubscriptionStub(arg1) + } +} + +func (fake *FakeLocalParticipant) HandleUpdateDataSubscriptionCallCount() int { + fake.handleUpdateDataSubscriptionMutex.RLock() + defer fake.handleUpdateDataSubscriptionMutex.RUnlock() + return len(fake.handleUpdateDataSubscriptionArgsForCall) +} + +func (fake *FakeLocalParticipant) HandleUpdateDataSubscriptionCalls(stub func(*livekit.UpdateDataSubscription)) { + fake.handleUpdateDataSubscriptionMutex.Lock() + defer fake.handleUpdateDataSubscriptionMutex.Unlock() + fake.HandleUpdateDataSubscriptionStub = stub +} + +func (fake *FakeLocalParticipant) HandleUpdateDataSubscriptionArgsForCall(i int) *livekit.UpdateDataSubscription { + fake.handleUpdateDataSubscriptionMutex.RLock() + defer fake.handleUpdateDataSubscriptionMutex.RUnlock() + argsForCall := fake.handleUpdateDataSubscriptionArgsForCall[i] + return argsForCall.arg1 +} + func (fake *FakeLocalParticipant) HandleUpdateSubscriptionPermission(arg1 *livekit.SubscriptionPermission) error { fake.handleUpdateSubscriptionPermissionMutex.Lock() ret, specificReturn := fake.handleUpdateSubscriptionPermissionReturnsOnCall[len(fake.handleUpdateSubscriptionPermissionArgsForCall)] @@ -5951,6 +6358,102 @@ func (fake *FakeLocalParticipant) OnDataPacketArgsForCall(i int) func(types.Loca return argsForCall.arg1 } +func (fake *FakeLocalParticipant) OnDataTrackMessage(arg1 func(types.LocalParticipant, []byte, *datatrack.Packet)) { + fake.onDataTrackMessageMutex.Lock() + fake.onDataTrackMessageArgsForCall = append(fake.onDataTrackMessageArgsForCall, struct { + arg1 func(types.LocalParticipant, []byte, *datatrack.Packet) + }{arg1}) + stub := fake.OnDataTrackMessageStub + fake.recordInvocation("OnDataTrackMessage", []interface{}{arg1}) + fake.onDataTrackMessageMutex.Unlock() + if stub != nil { + fake.OnDataTrackMessageStub(arg1) + } +} + +func (fake *FakeLocalParticipant) OnDataTrackMessageCallCount() int { + fake.onDataTrackMessageMutex.RLock() + defer fake.onDataTrackMessageMutex.RUnlock() + return len(fake.onDataTrackMessageArgsForCall) +} + +func (fake *FakeLocalParticipant) OnDataTrackMessageCalls(stub func(func(types.LocalParticipant, []byte, *datatrack.Packet))) { + fake.onDataTrackMessageMutex.Lock() + defer fake.onDataTrackMessageMutex.Unlock() + fake.OnDataTrackMessageStub = stub +} + +func (fake *FakeLocalParticipant) OnDataTrackMessageArgsForCall(i int) func(types.LocalParticipant, []byte, *datatrack.Packet) { + fake.onDataTrackMessageMutex.RLock() + defer fake.onDataTrackMessageMutex.RUnlock() + argsForCall := fake.onDataTrackMessageArgsForCall[i] + return argsForCall.arg1 +} + +func (fake *FakeLocalParticipant) OnDataTrackPublished(arg1 func(types.Participant, types.DataTrack)) { + fake.onDataTrackPublishedMutex.Lock() + fake.onDataTrackPublishedArgsForCall = append(fake.onDataTrackPublishedArgsForCall, struct { + arg1 func(types.Participant, types.DataTrack) + }{arg1}) + stub := fake.OnDataTrackPublishedStub + fake.recordInvocation("OnDataTrackPublished", []interface{}{arg1}) + fake.onDataTrackPublishedMutex.Unlock() + if stub != nil { + fake.OnDataTrackPublishedStub(arg1) + } +} + +func (fake *FakeLocalParticipant) OnDataTrackPublishedCallCount() int { + fake.onDataTrackPublishedMutex.RLock() + defer fake.onDataTrackPublishedMutex.RUnlock() + return len(fake.onDataTrackPublishedArgsForCall) +} + +func (fake *FakeLocalParticipant) OnDataTrackPublishedCalls(stub func(func(types.Participant, types.DataTrack))) { + fake.onDataTrackPublishedMutex.Lock() + defer fake.onDataTrackPublishedMutex.Unlock() + fake.OnDataTrackPublishedStub = stub +} + +func (fake *FakeLocalParticipant) OnDataTrackPublishedArgsForCall(i int) func(types.Participant, types.DataTrack) { + fake.onDataTrackPublishedMutex.RLock() + defer fake.onDataTrackPublishedMutex.RUnlock() + argsForCall := fake.onDataTrackPublishedArgsForCall[i] + return argsForCall.arg1 +} + +func (fake *FakeLocalParticipant) OnDataTrackUnpublished(arg1 func(types.Participant, types.DataTrack)) { + fake.onDataTrackUnpublishedMutex.Lock() + fake.onDataTrackUnpublishedArgsForCall = append(fake.onDataTrackUnpublishedArgsForCall, struct { + arg1 func(types.Participant, types.DataTrack) + }{arg1}) + stub := fake.OnDataTrackUnpublishedStub + fake.recordInvocation("OnDataTrackUnpublished", []interface{}{arg1}) + fake.onDataTrackUnpublishedMutex.Unlock() + if stub != nil { + fake.OnDataTrackUnpublishedStub(arg1) + } +} + +func (fake *FakeLocalParticipant) OnDataTrackUnpublishedCallCount() int { + fake.onDataTrackUnpublishedMutex.RLock() + defer fake.onDataTrackUnpublishedMutex.RUnlock() + return len(fake.onDataTrackUnpublishedArgsForCall) +} + +func (fake *FakeLocalParticipant) OnDataTrackUnpublishedCalls(stub func(func(types.Participant, types.DataTrack))) { + fake.onDataTrackUnpublishedMutex.Lock() + defer fake.onDataTrackUnpublishedMutex.Unlock() + fake.OnDataTrackUnpublishedStub = stub +} + +func (fake *FakeLocalParticipant) OnDataTrackUnpublishedArgsForCall(i int) func(types.Participant, types.DataTrack) { + fake.onDataTrackUnpublishedMutex.RLock() + defer fake.onDataTrackUnpublishedMutex.RUnlock() + argsForCall := fake.onDataTrackUnpublishedArgsForCall[i] + return argsForCall.arg1 +} + func (fake *FakeLocalParticipant) OnICEConfigChanged(arg1 func(participant types.LocalParticipant, iceConfig *livekit.ICEConfig)) { fake.onICEConfigChangedMutex.Lock() fake.onICEConfigChangedArgsForCall = append(fake.onICEConfigChangedArgsForCall, struct { @@ -6271,10 +6774,10 @@ func (fake *FakeLocalParticipant) OnSyncStateArgsForCall(i int) func(types.Local return argsForCall.arg1 } -func (fake *FakeLocalParticipant) OnTrackPublished(arg1 func(types.LocalParticipant, types.MediaTrack)) { +func (fake *FakeLocalParticipant) OnTrackPublished(arg1 func(types.Participant, types.MediaTrack)) { fake.onTrackPublishedMutex.Lock() fake.onTrackPublishedArgsForCall = append(fake.onTrackPublishedArgsForCall, struct { - arg1 func(types.LocalParticipant, types.MediaTrack) + arg1 func(types.Participant, types.MediaTrack) }{arg1}) stub := fake.OnTrackPublishedStub fake.recordInvocation("OnTrackPublished", []interface{}{arg1}) @@ -6290,23 +6793,23 @@ func (fake *FakeLocalParticipant) OnTrackPublishedCallCount() int { return len(fake.onTrackPublishedArgsForCall) } -func (fake *FakeLocalParticipant) OnTrackPublishedCalls(stub func(func(types.LocalParticipant, types.MediaTrack))) { +func (fake *FakeLocalParticipant) OnTrackPublishedCalls(stub func(func(types.Participant, types.MediaTrack))) { fake.onTrackPublishedMutex.Lock() defer fake.onTrackPublishedMutex.Unlock() fake.OnTrackPublishedStub = stub } -func (fake *FakeLocalParticipant) OnTrackPublishedArgsForCall(i int) func(types.LocalParticipant, types.MediaTrack) { +func (fake *FakeLocalParticipant) OnTrackPublishedArgsForCall(i int) func(types.Participant, types.MediaTrack) { fake.onTrackPublishedMutex.RLock() defer fake.onTrackPublishedMutex.RUnlock() argsForCall := fake.onTrackPublishedArgsForCall[i] return argsForCall.arg1 } -func (fake *FakeLocalParticipant) OnTrackUnpublished(arg1 func(types.LocalParticipant, types.MediaTrack)) { +func (fake *FakeLocalParticipant) OnTrackUnpublished(arg1 func(types.Participant, types.MediaTrack)) { fake.onTrackUnpublishedMutex.Lock() fake.onTrackUnpublishedArgsForCall = append(fake.onTrackUnpublishedArgsForCall, struct { - arg1 func(types.LocalParticipant, types.MediaTrack) + arg1 func(types.Participant, types.MediaTrack) }{arg1}) stub := fake.OnTrackUnpublishedStub fake.recordInvocation("OnTrackUnpublished", []interface{}{arg1}) @@ -6322,23 +6825,23 @@ func (fake *FakeLocalParticipant) OnTrackUnpublishedCallCount() int { return len(fake.onTrackUnpublishedArgsForCall) } -func (fake *FakeLocalParticipant) OnTrackUnpublishedCalls(stub func(func(types.LocalParticipant, types.MediaTrack))) { +func (fake *FakeLocalParticipant) OnTrackUnpublishedCalls(stub func(func(types.Participant, types.MediaTrack))) { fake.onTrackUnpublishedMutex.Lock() defer fake.onTrackUnpublishedMutex.Unlock() fake.OnTrackUnpublishedStub = stub } -func (fake *FakeLocalParticipant) OnTrackUnpublishedArgsForCall(i int) func(types.LocalParticipant, types.MediaTrack) { +func (fake *FakeLocalParticipant) OnTrackUnpublishedArgsForCall(i int) func(types.Participant, types.MediaTrack) { fake.onTrackUnpublishedMutex.RLock() defer fake.onTrackUnpublishedMutex.RUnlock() argsForCall := fake.onTrackUnpublishedArgsForCall[i] return argsForCall.arg1 } -func (fake *FakeLocalParticipant) OnTrackUpdated(arg1 func(types.LocalParticipant, types.MediaTrack)) { +func (fake *FakeLocalParticipant) OnTrackUpdated(arg1 func(types.Participant, types.MediaTrack)) { fake.onTrackUpdatedMutex.Lock() fake.onTrackUpdatedArgsForCall = append(fake.onTrackUpdatedArgsForCall, struct { - arg1 func(types.LocalParticipant, types.MediaTrack) + arg1 func(types.Participant, types.MediaTrack) }{arg1}) stub := fake.OnTrackUpdatedStub fake.recordInvocation("OnTrackUpdated", []interface{}{arg1}) @@ -6354,19 +6857,51 @@ func (fake *FakeLocalParticipant) OnTrackUpdatedCallCount() int { return len(fake.onTrackUpdatedArgsForCall) } -func (fake *FakeLocalParticipant) OnTrackUpdatedCalls(stub func(func(types.LocalParticipant, types.MediaTrack))) { +func (fake *FakeLocalParticipant) OnTrackUpdatedCalls(stub func(func(types.Participant, types.MediaTrack))) { fake.onTrackUpdatedMutex.Lock() defer fake.onTrackUpdatedMutex.Unlock() fake.OnTrackUpdatedStub = stub } -func (fake *FakeLocalParticipant) OnTrackUpdatedArgsForCall(i int) func(types.LocalParticipant, types.MediaTrack) { +func (fake *FakeLocalParticipant) OnTrackUpdatedArgsForCall(i int) func(types.Participant, types.MediaTrack) { fake.onTrackUpdatedMutex.RLock() defer fake.onTrackUpdatedMutex.RUnlock() argsForCall := fake.onTrackUpdatedArgsForCall[i] return argsForCall.arg1 } +func (fake *FakeLocalParticipant) OnUpdateDataSubscriptions(arg1 func(types.LocalParticipant, *livekit.UpdateDataSubscription)) { + fake.onUpdateDataSubscriptionsMutex.Lock() + fake.onUpdateDataSubscriptionsArgsForCall = append(fake.onUpdateDataSubscriptionsArgsForCall, struct { + arg1 func(types.LocalParticipant, *livekit.UpdateDataSubscription) + }{arg1}) + stub := fake.OnUpdateDataSubscriptionsStub + fake.recordInvocation("OnUpdateDataSubscriptions", []interface{}{arg1}) + fake.onUpdateDataSubscriptionsMutex.Unlock() + if stub != nil { + fake.OnUpdateDataSubscriptionsStub(arg1) + } +} + +func (fake *FakeLocalParticipant) OnUpdateDataSubscriptionsCallCount() int { + fake.onUpdateDataSubscriptionsMutex.RLock() + defer fake.onUpdateDataSubscriptionsMutex.RUnlock() + return len(fake.onUpdateDataSubscriptionsArgsForCall) +} + +func (fake *FakeLocalParticipant) OnUpdateDataSubscriptionsCalls(stub func(func(types.LocalParticipant, *livekit.UpdateDataSubscription))) { + fake.onUpdateDataSubscriptionsMutex.Lock() + defer fake.onUpdateDataSubscriptionsMutex.Unlock() + fake.OnUpdateDataSubscriptionsStub = stub +} + +func (fake *FakeLocalParticipant) OnUpdateDataSubscriptionsArgsForCall(i int) func(types.LocalParticipant, *livekit.UpdateDataSubscription) { + fake.onUpdateDataSubscriptionsMutex.RLock() + defer fake.onUpdateDataSubscriptionsMutex.RUnlock() + argsForCall := fake.onUpdateDataSubscriptionsArgsForCall[i] + return argsForCall.arg1 +} + func (fake *FakeLocalParticipant) OnUpdateSubscriptionPermission(arg1 func(types.LocalParticipant, *livekit.SubscriptionPermission) error) { fake.onUpdateSubscriptionPermissionMutex.Lock() fake.onUpdateSubscriptionPermissionArgsForCall = append(fake.onUpdateSubscriptionPermissionArgsForCall, struct { @@ -6518,6 +7053,38 @@ func (fake *FakeLocalParticipant) ProtocolVersionReturnsOnCall(i int, result1 ty }{result1} } +func (fake *FakeLocalParticipant) RemovePublishedDataTrack(arg1 types.DataTrack) { + fake.removePublishedDataTrackMutex.Lock() + fake.removePublishedDataTrackArgsForCall = append(fake.removePublishedDataTrackArgsForCall, struct { + arg1 types.DataTrack + }{arg1}) + stub := fake.RemovePublishedDataTrackStub + fake.recordInvocation("RemovePublishedDataTrack", []interface{}{arg1}) + fake.removePublishedDataTrackMutex.Unlock() + if stub != nil { + fake.RemovePublishedDataTrackStub(arg1) + } +} + +func (fake *FakeLocalParticipant) RemovePublishedDataTrackCallCount() int { + fake.removePublishedDataTrackMutex.RLock() + defer fake.removePublishedDataTrackMutex.RUnlock() + return len(fake.removePublishedDataTrackArgsForCall) +} + +func (fake *FakeLocalParticipant) RemovePublishedDataTrackCalls(stub func(types.DataTrack)) { + fake.removePublishedDataTrackMutex.Lock() + defer fake.removePublishedDataTrackMutex.Unlock() + fake.RemovePublishedDataTrackStub = stub +} + +func (fake *FakeLocalParticipant) RemovePublishedDataTrackArgsForCall(i int) types.DataTrack { + fake.removePublishedDataTrackMutex.RLock() + defer fake.removePublishedDataTrackMutex.RUnlock() + argsForCall := fake.removePublishedDataTrackArgsForCall[i] + return argsForCall.arg1 +} + func (fake *FakeLocalParticipant) RemovePublishedTrack(arg1 types.MediaTrack, arg2 bool) { fake.removePublishedTrackMutex.Lock() fake.removePublishedTrackArgsForCall = append(fake.removePublishedTrackArgsForCall, struct { @@ -6810,6 +7377,67 @@ func (fake *FakeLocalParticipant) SendDataMessageUnlabeledReturnsOnCall(i int, r }{result1} } +func (fake *FakeLocalParticipant) SendDataTrackSubscriberHandles(arg1 map[uint32]*livekit.DataTrackSubscriberHandles_PublishedDataTrack) error { + fake.sendDataTrackSubscriberHandlesMutex.Lock() + ret, specificReturn := fake.sendDataTrackSubscriberHandlesReturnsOnCall[len(fake.sendDataTrackSubscriberHandlesArgsForCall)] + fake.sendDataTrackSubscriberHandlesArgsForCall = append(fake.sendDataTrackSubscriberHandlesArgsForCall, struct { + arg1 map[uint32]*livekit.DataTrackSubscriberHandles_PublishedDataTrack + }{arg1}) + stub := fake.SendDataTrackSubscriberHandlesStub + fakeReturns := fake.sendDataTrackSubscriberHandlesReturns + fake.recordInvocation("SendDataTrackSubscriberHandles", []interface{}{arg1}) + fake.sendDataTrackSubscriberHandlesMutex.Unlock() + if stub != nil { + return stub(arg1) + } + if specificReturn { + return ret.result1 + } + return fakeReturns.result1 +} + +func (fake *FakeLocalParticipant) SendDataTrackSubscriberHandlesCallCount() int { + fake.sendDataTrackSubscriberHandlesMutex.RLock() + defer fake.sendDataTrackSubscriberHandlesMutex.RUnlock() + return len(fake.sendDataTrackSubscriberHandlesArgsForCall) +} + +func (fake *FakeLocalParticipant) SendDataTrackSubscriberHandlesCalls(stub func(map[uint32]*livekit.DataTrackSubscriberHandles_PublishedDataTrack) error) { + fake.sendDataTrackSubscriberHandlesMutex.Lock() + defer fake.sendDataTrackSubscriberHandlesMutex.Unlock() + fake.SendDataTrackSubscriberHandlesStub = stub +} + +func (fake *FakeLocalParticipant) SendDataTrackSubscriberHandlesArgsForCall(i int) map[uint32]*livekit.DataTrackSubscriberHandles_PublishedDataTrack { + fake.sendDataTrackSubscriberHandlesMutex.RLock() + defer fake.sendDataTrackSubscriberHandlesMutex.RUnlock() + argsForCall := fake.sendDataTrackSubscriberHandlesArgsForCall[i] + return argsForCall.arg1 +} + +func (fake *FakeLocalParticipant) SendDataTrackSubscriberHandlesReturns(result1 error) { + fake.sendDataTrackSubscriberHandlesMutex.Lock() + defer fake.sendDataTrackSubscriberHandlesMutex.Unlock() + fake.SendDataTrackSubscriberHandlesStub = nil + fake.sendDataTrackSubscriberHandlesReturns = struct { + result1 error + }{result1} +} + +func (fake *FakeLocalParticipant) SendDataTrackSubscriberHandlesReturnsOnCall(i int, result1 error) { + fake.sendDataTrackSubscriberHandlesMutex.Lock() + defer fake.sendDataTrackSubscriberHandlesMutex.Unlock() + fake.SendDataTrackSubscriberHandlesStub = nil + if fake.sendDataTrackSubscriberHandlesReturnsOnCall == nil { + fake.sendDataTrackSubscriberHandlesReturnsOnCall = make(map[int]struct { + result1 error + }) + } + fake.sendDataTrackSubscriberHandlesReturnsOnCall[i] = struct { + result1 error + }{result1} +} + func (fake *FakeLocalParticipant) SendJoinResponse(arg1 *livekit.JoinResponse) error { fake.sendJoinResponseMutex.Lock() ret, specificReturn := fake.sendJoinResponseReturnsOnCall[len(fake.sendJoinResponseArgsForCall)] @@ -7346,7 +7974,7 @@ func (fake *FakeLocalParticipant) SetMetadataArgsForCall(i int) string { return argsForCall.arg1 } -func (fake *FakeLocalParticipant) SetMigrateInfo(arg1 *webrtc.SessionDescription, arg2 *webrtc.SessionDescription, arg3 []*livekit.TrackPublishedResponse, arg4 []*livekit.DataChannelInfo, arg5 []*livekit.DataChannelReceiveState) { +func (fake *FakeLocalParticipant) SetMigrateInfo(arg1 *webrtc.SessionDescription, arg2 *webrtc.SessionDescription, arg3 []*livekit.TrackPublishedResponse, arg4 []*livekit.DataChannelInfo, arg5 []*livekit.DataChannelReceiveState, arg6 []*livekit.PublishDataTrackResponse) { var arg3Copy []*livekit.TrackPublishedResponse if arg3 != nil { arg3Copy = make([]*livekit.TrackPublishedResponse, len(arg3)) @@ -7362,6 +7990,11 @@ func (fake *FakeLocalParticipant) SetMigrateInfo(arg1 *webrtc.SessionDescription arg5Copy = make([]*livekit.DataChannelReceiveState, len(arg5)) copy(arg5Copy, arg5) } + var arg6Copy []*livekit.PublishDataTrackResponse + if arg6 != nil { + arg6Copy = make([]*livekit.PublishDataTrackResponse, len(arg6)) + copy(arg6Copy, arg6) + } fake.setMigrateInfoMutex.Lock() fake.setMigrateInfoArgsForCall = append(fake.setMigrateInfoArgsForCall, struct { arg1 *webrtc.SessionDescription @@ -7369,12 +8002,13 @@ func (fake *FakeLocalParticipant) SetMigrateInfo(arg1 *webrtc.SessionDescription arg3 []*livekit.TrackPublishedResponse arg4 []*livekit.DataChannelInfo arg5 []*livekit.DataChannelReceiveState - }{arg1, arg2, arg3Copy, arg4Copy, arg5Copy}) + arg6 []*livekit.PublishDataTrackResponse + }{arg1, arg2, arg3Copy, arg4Copy, arg5Copy, arg6Copy}) stub := fake.SetMigrateInfoStub - fake.recordInvocation("SetMigrateInfo", []interface{}{arg1, arg2, arg3Copy, arg4Copy, arg5Copy}) + fake.recordInvocation("SetMigrateInfo", []interface{}{arg1, arg2, arg3Copy, arg4Copy, arg5Copy, arg6Copy}) fake.setMigrateInfoMutex.Unlock() if stub != nil { - fake.SetMigrateInfoStub(arg1, arg2, arg3, arg4, arg5) + fake.SetMigrateInfoStub(arg1, arg2, arg3, arg4, arg5, arg6) } } @@ -7384,17 +8018,17 @@ func (fake *FakeLocalParticipant) SetMigrateInfoCallCount() int { return len(fake.setMigrateInfoArgsForCall) } -func (fake *FakeLocalParticipant) SetMigrateInfoCalls(stub func(*webrtc.SessionDescription, *webrtc.SessionDescription, []*livekit.TrackPublishedResponse, []*livekit.DataChannelInfo, []*livekit.DataChannelReceiveState)) { +func (fake *FakeLocalParticipant) SetMigrateInfoCalls(stub func(*webrtc.SessionDescription, *webrtc.SessionDescription, []*livekit.TrackPublishedResponse, []*livekit.DataChannelInfo, []*livekit.DataChannelReceiveState, []*livekit.PublishDataTrackResponse)) { fake.setMigrateInfoMutex.Lock() defer fake.setMigrateInfoMutex.Unlock() fake.SetMigrateInfoStub = stub } -func (fake *FakeLocalParticipant) SetMigrateInfoArgsForCall(i int) (*webrtc.SessionDescription, *webrtc.SessionDescription, []*livekit.TrackPublishedResponse, []*livekit.DataChannelInfo, []*livekit.DataChannelReceiveState) { +func (fake *FakeLocalParticipant) SetMigrateInfoArgsForCall(i int) (*webrtc.SessionDescription, *webrtc.SessionDescription, []*livekit.TrackPublishedResponse, []*livekit.DataChannelInfo, []*livekit.DataChannelReceiveState, []*livekit.PublishDataTrackResponse) { fake.setMigrateInfoMutex.RLock() defer fake.setMigrateInfoMutex.RUnlock() argsForCall := fake.setMigrateInfoArgsForCall[i] - return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3, argsForCall.arg4, argsForCall.arg5 + return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3, argsForCall.arg4, argsForCall.arg5, argsForCall.arg6 } func (fake *FakeLocalParticipant) SetMigrateState(arg1 types.MigrateState) { @@ -7818,6 +8452,38 @@ func (fake *FakeLocalParticipant) StopAndGetSubscribedTracksForwarderStateReturn }{result1} } +func (fake *FakeLocalParticipant) SubscribeToDataTrack(arg1 livekit.TrackID) { + fake.subscribeToDataTrackMutex.Lock() + fake.subscribeToDataTrackArgsForCall = append(fake.subscribeToDataTrackArgsForCall, struct { + arg1 livekit.TrackID + }{arg1}) + stub := fake.SubscribeToDataTrackStub + fake.recordInvocation("SubscribeToDataTrack", []interface{}{arg1}) + fake.subscribeToDataTrackMutex.Unlock() + if stub != nil { + fake.SubscribeToDataTrackStub(arg1) + } +} + +func (fake *FakeLocalParticipant) SubscribeToDataTrackCallCount() int { + fake.subscribeToDataTrackMutex.RLock() + defer fake.subscribeToDataTrackMutex.RUnlock() + return len(fake.subscribeToDataTrackArgsForCall) +} + +func (fake *FakeLocalParticipant) SubscribeToDataTrackCalls(stub func(livekit.TrackID)) { + fake.subscribeToDataTrackMutex.Lock() + defer fake.subscribeToDataTrackMutex.Unlock() + fake.SubscribeToDataTrackStub = stub +} + +func (fake *FakeLocalParticipant) SubscribeToDataTrackArgsForCall(i int) livekit.TrackID { + fake.subscribeToDataTrackMutex.RLock() + defer fake.subscribeToDataTrackMutex.RUnlock() + argsForCall := fake.subscribeToDataTrackArgsForCall[i] + return argsForCall.arg1 +} + func (fake *FakeLocalParticipant) SubscribeToTrack(arg1 livekit.TrackID, arg2 bool) { fake.subscribeToTrackMutex.Lock() fake.subscribeToTrackArgsForCall = append(fake.subscribeToTrackArgsForCall, struct { @@ -8366,6 +9032,38 @@ func (fake *FakeLocalParticipant) UncacheDownTrackArgsForCall(i int) *webrtc.RTP return argsForCall.arg1 } +func (fake *FakeLocalParticipant) UnsubscribeFromDataTrack(arg1 livekit.TrackID) { + fake.unsubscribeFromDataTrackMutex.Lock() + fake.unsubscribeFromDataTrackArgsForCall = append(fake.unsubscribeFromDataTrackArgsForCall, struct { + arg1 livekit.TrackID + }{arg1}) + stub := fake.UnsubscribeFromDataTrackStub + fake.recordInvocation("UnsubscribeFromDataTrack", []interface{}{arg1}) + fake.unsubscribeFromDataTrackMutex.Unlock() + if stub != nil { + fake.UnsubscribeFromDataTrackStub(arg1) + } +} + +func (fake *FakeLocalParticipant) UnsubscribeFromDataTrackCallCount() int { + fake.unsubscribeFromDataTrackMutex.RLock() + defer fake.unsubscribeFromDataTrackMutex.RUnlock() + return len(fake.unsubscribeFromDataTrackArgsForCall) +} + +func (fake *FakeLocalParticipant) UnsubscribeFromDataTrackCalls(stub func(livekit.TrackID)) { + fake.unsubscribeFromDataTrackMutex.Lock() + defer fake.unsubscribeFromDataTrackMutex.Unlock() + fake.UnsubscribeFromDataTrackStub = stub +} + +func (fake *FakeLocalParticipant) UnsubscribeFromDataTrackArgsForCall(i int) livekit.TrackID { + fake.unsubscribeFromDataTrackMutex.RLock() + defer fake.unsubscribeFromDataTrackMutex.RUnlock() + argsForCall := fake.unsubscribeFromDataTrackArgsForCall[i] + return argsForCall.arg1 +} + func (fake *FakeLocalParticipant) UnsubscribeFromTrack(arg1 livekit.TrackID) { fake.unsubscribeFromTrackMutex.Lock() fake.unsubscribeFromTrackArgsForCall = append(fake.unsubscribeFromTrackArgsForCall, struct { @@ -8459,6 +9157,39 @@ func (fake *FakeLocalParticipant) UpdateAudioTrackReturnsOnCall(i int, result1 e }{result1} } +func (fake *FakeLocalParticipant) UpdateDataTrackSubscriptionOptions(arg1 livekit.TrackID, arg2 *livekit.DataTrackSubscriptionOptions) { + fake.updateDataTrackSubscriptionOptionsMutex.Lock() + fake.updateDataTrackSubscriptionOptionsArgsForCall = append(fake.updateDataTrackSubscriptionOptionsArgsForCall, struct { + arg1 livekit.TrackID + arg2 *livekit.DataTrackSubscriptionOptions + }{arg1, arg2}) + stub := fake.UpdateDataTrackSubscriptionOptionsStub + fake.recordInvocation("UpdateDataTrackSubscriptionOptions", []interface{}{arg1, arg2}) + fake.updateDataTrackSubscriptionOptionsMutex.Unlock() + if stub != nil { + fake.UpdateDataTrackSubscriptionOptionsStub(arg1, arg2) + } +} + +func (fake *FakeLocalParticipant) UpdateDataTrackSubscriptionOptionsCallCount() int { + fake.updateDataTrackSubscriptionOptionsMutex.RLock() + defer fake.updateDataTrackSubscriptionOptionsMutex.RUnlock() + return len(fake.updateDataTrackSubscriptionOptionsArgsForCall) +} + +func (fake *FakeLocalParticipant) UpdateDataTrackSubscriptionOptionsCalls(stub func(livekit.TrackID, *livekit.DataTrackSubscriptionOptions)) { + fake.updateDataTrackSubscriptionOptionsMutex.Lock() + defer fake.updateDataTrackSubscriptionOptionsMutex.Unlock() + fake.UpdateDataTrackSubscriptionOptionsStub = stub +} + +func (fake *FakeLocalParticipant) UpdateDataTrackSubscriptionOptionsArgsForCall(i int) (livekit.TrackID, *livekit.DataTrackSubscriptionOptions) { + fake.updateDataTrackSubscriptionOptionsMutex.RLock() + defer fake.updateDataTrackSubscriptionOptionsMutex.RUnlock() + argsForCall := fake.updateDataTrackSubscriptionOptionsArgsForCall[i] + return argsForCall.arg1, argsForCall.arg2 +} + func (fake *FakeLocalParticipant) UpdateLastSeenSignal() { fake.updateLastSeenSignalMutex.Lock() fake.updateLastSeenSignalArgsForCall = append(fake.updateLastSeenSignalArgsForCall, struct { diff --git a/pkg/rtc/types/typesfakes/fake_local_participant_helper.go b/pkg/rtc/types/typesfakes/fake_local_participant_helper.go index 3deea6b0c..4676dc6d4 100644 --- a/pkg/rtc/types/typesfakes/fake_local_participant_helper.go +++ b/pkg/rtc/types/typesfakes/fake_local_participant_helper.go @@ -55,6 +55,18 @@ type FakeLocalParticipantHelper struct { result1 map[livekit.TrackID]*livekit.RTPForwarderState result2 error } + ResolveDataTrackStub func(types.LocalParticipant, livekit.TrackID) types.DataResolverResult + resolveDataTrackMutex sync.RWMutex + resolveDataTrackArgsForCall []struct { + arg1 types.LocalParticipant + arg2 livekit.TrackID + } + resolveDataTrackReturns struct { + result1 types.DataResolverResult + } + resolveDataTrackReturnsOnCall map[int]struct { + result1 types.DataResolverResult + } ResolveMediaTrackStub func(types.LocalParticipant, livekit.TrackID) types.MediaResolverResult resolveMediaTrackMutex sync.RWMutex resolveMediaTrackArgsForCall []struct { @@ -328,6 +340,68 @@ func (fake *FakeLocalParticipantHelper) GetSubscriberForwarderStateReturnsOnCall }{result1, result2} } +func (fake *FakeLocalParticipantHelper) ResolveDataTrack(arg1 types.LocalParticipant, arg2 livekit.TrackID) types.DataResolverResult { + fake.resolveDataTrackMutex.Lock() + ret, specificReturn := fake.resolveDataTrackReturnsOnCall[len(fake.resolveDataTrackArgsForCall)] + fake.resolveDataTrackArgsForCall = append(fake.resolveDataTrackArgsForCall, struct { + arg1 types.LocalParticipant + arg2 livekit.TrackID + }{arg1, arg2}) + stub := fake.ResolveDataTrackStub + fakeReturns := fake.resolveDataTrackReturns + fake.recordInvocation("ResolveDataTrack", []interface{}{arg1, arg2}) + fake.resolveDataTrackMutex.Unlock() + if stub != nil { + return stub(arg1, arg2) + } + if specificReturn { + return ret.result1 + } + return fakeReturns.result1 +} + +func (fake *FakeLocalParticipantHelper) ResolveDataTrackCallCount() int { + fake.resolveDataTrackMutex.RLock() + defer fake.resolveDataTrackMutex.RUnlock() + return len(fake.resolveDataTrackArgsForCall) +} + +func (fake *FakeLocalParticipantHelper) ResolveDataTrackCalls(stub func(types.LocalParticipant, livekit.TrackID) types.DataResolverResult) { + fake.resolveDataTrackMutex.Lock() + defer fake.resolveDataTrackMutex.Unlock() + fake.ResolveDataTrackStub = stub +} + +func (fake *FakeLocalParticipantHelper) ResolveDataTrackArgsForCall(i int) (types.LocalParticipant, livekit.TrackID) { + fake.resolveDataTrackMutex.RLock() + defer fake.resolveDataTrackMutex.RUnlock() + argsForCall := fake.resolveDataTrackArgsForCall[i] + return argsForCall.arg1, argsForCall.arg2 +} + +func (fake *FakeLocalParticipantHelper) ResolveDataTrackReturns(result1 types.DataResolverResult) { + fake.resolveDataTrackMutex.Lock() + defer fake.resolveDataTrackMutex.Unlock() + fake.ResolveDataTrackStub = nil + fake.resolveDataTrackReturns = struct { + result1 types.DataResolverResult + }{result1} +} + +func (fake *FakeLocalParticipantHelper) ResolveDataTrackReturnsOnCall(i int, result1 types.DataResolverResult) { + fake.resolveDataTrackMutex.Lock() + defer fake.resolveDataTrackMutex.Unlock() + fake.ResolveDataTrackStub = nil + if fake.resolveDataTrackReturnsOnCall == nil { + fake.resolveDataTrackReturnsOnCall = make(map[int]struct { + result1 types.DataResolverResult + }) + } + fake.resolveDataTrackReturnsOnCall[i] = struct { + result1 types.DataResolverResult + }{result1} +} + func (fake *FakeLocalParticipantHelper) ResolveMediaTrack(arg1 types.LocalParticipant, arg2 livekit.TrackID) types.MediaResolverResult { fake.resolveMediaTrackMutex.Lock() ret, specificReturn := fake.resolveMediaTrackReturnsOnCall[len(fake.resolveMediaTrackArgsForCall)] diff --git a/pkg/rtc/types/typesfakes/fake_participant.go b/pkg/rtc/types/typesfakes/fake_participant.go index 8c55fd97d..32863c28b 100644 --- a/pkg/rtc/types/typesfakes/fake_participant.go +++ b/pkg/rtc/types/typesfakes/fake_participant.go @@ -5,8 +5,10 @@ import ( "sync" "time" + "github.com/livekit/livekit-server/pkg/rtc/datatrack" "github.com/livekit/livekit-server/pkg/rtc/types" "github.com/livekit/protocol/livekit" + "github.com/livekit/protocol/logger" "github.com/livekit/protocol/utils" ) @@ -54,15 +56,15 @@ type FakeParticipant struct { connectedAtReturnsOnCall map[int]struct { result1 time.Time } - DebugInfoStub func() map[string]interface{} + DebugInfoStub func() map[string]any debugInfoMutex sync.RWMutex debugInfoArgsForCall []struct { } debugInfoReturns struct { - result1 map[string]interface{} + result1 map[string]any } debugInfoReturnsOnCall map[int]struct { - result1 map[string]interface{} + result1 map[string]any } GetAudioLevelStub func() (float64, bool) getAudioLevelMutex sync.RWMutex @@ -76,6 +78,37 @@ type FakeParticipant struct { result1 float64 result2 bool } + GetLoggerStub func() logger.Logger + getLoggerMutex sync.RWMutex + getLoggerArgsForCall []struct { + } + getLoggerReturns struct { + result1 logger.Logger + } + getLoggerReturnsOnCall map[int]struct { + result1 logger.Logger + } + GetPublishedDataTrackStub func(uint16) types.DataTrack + getPublishedDataTrackMutex sync.RWMutex + getPublishedDataTrackArgsForCall []struct { + arg1 uint16 + } + getPublishedDataTrackReturns struct { + result1 types.DataTrack + } + getPublishedDataTrackReturnsOnCall map[int]struct { + result1 types.DataTrack + } + GetPublishedDataTracksStub func() []types.DataTrack + getPublishedDataTracksMutex sync.RWMutex + getPublishedDataTracksArgsForCall []struct { + } + getPublishedDataTracksReturns struct { + result1 []types.DataTrack + } + getPublishedDataTracksReturnsOnCall map[int]struct { + result1 []types.DataTrack + } GetPublishedTrackStub func(livekit.TrackID) types.MediaTrack getPublishedTrackMutex sync.RWMutex getPublishedTrackArgsForCall []struct { @@ -97,6 +130,12 @@ type FakeParticipant struct { getPublishedTracksReturnsOnCall map[int]struct { result1 []types.MediaTrack } + HandleReceivedDataTrackMessageStub func([]byte, *datatrack.Packet) + handleReceivedDataTrackMessageMutex sync.RWMutex + handleReceivedDataTrackMessageArgsForCall []struct { + arg1 []byte + arg2 *datatrack.Packet + } HasPermissionStub func(livekit.TrackID, livekit.ParticipantIdentity) bool hasPermissionMutex sync.RWMutex hasPermissionArgsForCall []struct { @@ -149,6 +188,16 @@ type FakeParticipant struct { isAgentReturnsOnCall map[int]struct { result1 bool } + IsClosedStub func() bool + isClosedMutex sync.RWMutex + isClosedArgsForCall []struct { + } + isClosedReturns struct { + result1 bool + } + isClosedReturnsOnCall map[int]struct { + result1 bool + } IsDependentStub func() bool isDependentMutex sync.RWMutex isDependentArgsForCall []struct { @@ -159,6 +208,16 @@ type FakeParticipant struct { isDependentReturnsOnCall map[int]struct { result1 bool } + IsDisconnectedStub func() bool + isDisconnectedMutex sync.RWMutex + isDisconnectedArgsForCall []struct { + } + isDisconnectedReturns struct { + result1 bool + } + isDisconnectedReturnsOnCall map[int]struct { + result1 bool + } IsPublisherStub func() bool isPublisherMutex sync.RWMutex isPublisherArgsForCall []struct { @@ -189,11 +248,51 @@ type FakeParticipant struct { kindReturnsOnCall map[int]struct { result1 livekit.ParticipantInfo_Kind } + MigrateStateStub func() types.MigrateState + migrateStateMutex sync.RWMutex + migrateStateArgsForCall []struct { + } + migrateStateReturns struct { + result1 types.MigrateState + } + migrateStateReturnsOnCall map[int]struct { + result1 types.MigrateState + } + OnDataTrackPublishedStub func(func(types.Participant, types.DataTrack)) + onDataTrackPublishedMutex sync.RWMutex + onDataTrackPublishedArgsForCall []struct { + arg1 func(types.Participant, types.DataTrack) + } + OnDataTrackUnpublishedStub func(func(types.Participant, types.DataTrack)) + onDataTrackUnpublishedMutex sync.RWMutex + onDataTrackUnpublishedArgsForCall []struct { + arg1 func(types.Participant, types.DataTrack) + } OnMetricsStub func(func(types.Participant, *livekit.DataPacket)) onMetricsMutex sync.RWMutex onMetricsArgsForCall []struct { arg1 func(types.Participant, *livekit.DataPacket) } + OnTrackPublishedStub func(func(types.Participant, types.MediaTrack)) + onTrackPublishedMutex sync.RWMutex + onTrackPublishedArgsForCall []struct { + arg1 func(types.Participant, types.MediaTrack) + } + OnTrackUnpublishedStub func(func(types.Participant, types.MediaTrack)) + onTrackUnpublishedMutex sync.RWMutex + onTrackUnpublishedArgsForCall []struct { + arg1 func(types.Participant, types.MediaTrack) + } + OnTrackUpdatedStub func(func(types.Participant, types.MediaTrack)) + onTrackUpdatedMutex sync.RWMutex + onTrackUpdatedArgsForCall []struct { + arg1 func(types.Participant, types.MediaTrack) + } + RemovePublishedDataTrackStub func(types.DataTrack) + removePublishedDataTrackMutex sync.RWMutex + removePublishedDataTrackArgsForCall []struct { + arg1 types.DataTrack + } RemovePublishedTrackStub func(types.MediaTrack, bool) removePublishedTrackMutex sync.RWMutex removePublishedTrackArgsForCall []struct { @@ -232,6 +331,18 @@ type FakeParticipant struct { toProtoReturnsOnCall map[int]struct { result1 *livekit.ParticipantInfo } + ToProtoWithVersionStub func() (*livekit.ParticipantInfo, utils.TimedVersion) + toProtoWithVersionMutex sync.RWMutex + toProtoWithVersionArgsForCall []struct { + } + toProtoWithVersionReturns struct { + result1 *livekit.ParticipantInfo + result2 utils.TimedVersion + } + toProtoWithVersionReturnsOnCall map[int]struct { + result1 *livekit.ParticipantInfo + result2 utils.TimedVersion + } UpdateSubscriptionPermissionStub func(*livekit.SubscriptionPermission, utils.TimedVersion, func(participantID livekit.ParticipantID) types.LocalParticipant) error updateSubscriptionPermissionMutex sync.RWMutex updateSubscriptionPermissionArgsForCall []struct { @@ -481,7 +592,7 @@ func (fake *FakeParticipant) ConnectedAtReturnsOnCall(i int, result1 time.Time) }{result1} } -func (fake *FakeParticipant) DebugInfo() map[string]interface{} { +func (fake *FakeParticipant) DebugInfo() map[string]any { fake.debugInfoMutex.Lock() ret, specificReturn := fake.debugInfoReturnsOnCall[len(fake.debugInfoArgsForCall)] fake.debugInfoArgsForCall = append(fake.debugInfoArgsForCall, struct { @@ -505,32 +616,32 @@ func (fake *FakeParticipant) DebugInfoCallCount() int { return len(fake.debugInfoArgsForCall) } -func (fake *FakeParticipant) DebugInfoCalls(stub func() map[string]interface{}) { +func (fake *FakeParticipant) DebugInfoCalls(stub func() map[string]any) { fake.debugInfoMutex.Lock() defer fake.debugInfoMutex.Unlock() fake.DebugInfoStub = stub } -func (fake *FakeParticipant) DebugInfoReturns(result1 map[string]interface{}) { +func (fake *FakeParticipant) DebugInfoReturns(result1 map[string]any) { fake.debugInfoMutex.Lock() defer fake.debugInfoMutex.Unlock() fake.DebugInfoStub = nil fake.debugInfoReturns = struct { - result1 map[string]interface{} + result1 map[string]any }{result1} } -func (fake *FakeParticipant) DebugInfoReturnsOnCall(i int, result1 map[string]interface{}) { +func (fake *FakeParticipant) DebugInfoReturnsOnCall(i int, result1 map[string]any) { fake.debugInfoMutex.Lock() defer fake.debugInfoMutex.Unlock() fake.DebugInfoStub = nil if fake.debugInfoReturnsOnCall == nil { fake.debugInfoReturnsOnCall = make(map[int]struct { - result1 map[string]interface{} + result1 map[string]any }) } fake.debugInfoReturnsOnCall[i] = struct { - result1 map[string]interface{} + result1 map[string]any }{result1} } @@ -590,6 +701,173 @@ func (fake *FakeParticipant) GetAudioLevelReturnsOnCall(i int, result1 float64, }{result1, result2} } +func (fake *FakeParticipant) GetLogger() logger.Logger { + fake.getLoggerMutex.Lock() + ret, specificReturn := fake.getLoggerReturnsOnCall[len(fake.getLoggerArgsForCall)] + fake.getLoggerArgsForCall = append(fake.getLoggerArgsForCall, struct { + }{}) + stub := fake.GetLoggerStub + fakeReturns := fake.getLoggerReturns + fake.recordInvocation("GetLogger", []interface{}{}) + fake.getLoggerMutex.Unlock() + if stub != nil { + return stub() + } + if specificReturn { + return ret.result1 + } + return fakeReturns.result1 +} + +func (fake *FakeParticipant) GetLoggerCallCount() int { + fake.getLoggerMutex.RLock() + defer fake.getLoggerMutex.RUnlock() + return len(fake.getLoggerArgsForCall) +} + +func (fake *FakeParticipant) GetLoggerCalls(stub func() logger.Logger) { + fake.getLoggerMutex.Lock() + defer fake.getLoggerMutex.Unlock() + fake.GetLoggerStub = stub +} + +func (fake *FakeParticipant) GetLoggerReturns(result1 logger.Logger) { + fake.getLoggerMutex.Lock() + defer fake.getLoggerMutex.Unlock() + fake.GetLoggerStub = nil + fake.getLoggerReturns = struct { + result1 logger.Logger + }{result1} +} + +func (fake *FakeParticipant) GetLoggerReturnsOnCall(i int, result1 logger.Logger) { + fake.getLoggerMutex.Lock() + defer fake.getLoggerMutex.Unlock() + fake.GetLoggerStub = nil + if fake.getLoggerReturnsOnCall == nil { + fake.getLoggerReturnsOnCall = make(map[int]struct { + result1 logger.Logger + }) + } + fake.getLoggerReturnsOnCall[i] = struct { + result1 logger.Logger + }{result1} +} + +func (fake *FakeParticipant) GetPublishedDataTrack(arg1 uint16) types.DataTrack { + fake.getPublishedDataTrackMutex.Lock() + ret, specificReturn := fake.getPublishedDataTrackReturnsOnCall[len(fake.getPublishedDataTrackArgsForCall)] + fake.getPublishedDataTrackArgsForCall = append(fake.getPublishedDataTrackArgsForCall, struct { + arg1 uint16 + }{arg1}) + stub := fake.GetPublishedDataTrackStub + fakeReturns := fake.getPublishedDataTrackReturns + fake.recordInvocation("GetPublishedDataTrack", []interface{}{arg1}) + fake.getPublishedDataTrackMutex.Unlock() + if stub != nil { + return stub(arg1) + } + if specificReturn { + return ret.result1 + } + return fakeReturns.result1 +} + +func (fake *FakeParticipant) GetPublishedDataTrackCallCount() int { + fake.getPublishedDataTrackMutex.RLock() + defer fake.getPublishedDataTrackMutex.RUnlock() + return len(fake.getPublishedDataTrackArgsForCall) +} + +func (fake *FakeParticipant) GetPublishedDataTrackCalls(stub func(uint16) types.DataTrack) { + fake.getPublishedDataTrackMutex.Lock() + defer fake.getPublishedDataTrackMutex.Unlock() + fake.GetPublishedDataTrackStub = stub +} + +func (fake *FakeParticipant) GetPublishedDataTrackArgsForCall(i int) uint16 { + fake.getPublishedDataTrackMutex.RLock() + defer fake.getPublishedDataTrackMutex.RUnlock() + argsForCall := fake.getPublishedDataTrackArgsForCall[i] + return argsForCall.arg1 +} + +func (fake *FakeParticipant) GetPublishedDataTrackReturns(result1 types.DataTrack) { + fake.getPublishedDataTrackMutex.Lock() + defer fake.getPublishedDataTrackMutex.Unlock() + fake.GetPublishedDataTrackStub = nil + fake.getPublishedDataTrackReturns = struct { + result1 types.DataTrack + }{result1} +} + +func (fake *FakeParticipant) GetPublishedDataTrackReturnsOnCall(i int, result1 types.DataTrack) { + fake.getPublishedDataTrackMutex.Lock() + defer fake.getPublishedDataTrackMutex.Unlock() + fake.GetPublishedDataTrackStub = nil + if fake.getPublishedDataTrackReturnsOnCall == nil { + fake.getPublishedDataTrackReturnsOnCall = make(map[int]struct { + result1 types.DataTrack + }) + } + fake.getPublishedDataTrackReturnsOnCall[i] = struct { + result1 types.DataTrack + }{result1} +} + +func (fake *FakeParticipant) GetPublishedDataTracks() []types.DataTrack { + fake.getPublishedDataTracksMutex.Lock() + ret, specificReturn := fake.getPublishedDataTracksReturnsOnCall[len(fake.getPublishedDataTracksArgsForCall)] + fake.getPublishedDataTracksArgsForCall = append(fake.getPublishedDataTracksArgsForCall, struct { + }{}) + stub := fake.GetPublishedDataTracksStub + fakeReturns := fake.getPublishedDataTracksReturns + fake.recordInvocation("GetPublishedDataTracks", []interface{}{}) + fake.getPublishedDataTracksMutex.Unlock() + if stub != nil { + return stub() + } + if specificReturn { + return ret.result1 + } + return fakeReturns.result1 +} + +func (fake *FakeParticipant) GetPublishedDataTracksCallCount() int { + fake.getPublishedDataTracksMutex.RLock() + defer fake.getPublishedDataTracksMutex.RUnlock() + return len(fake.getPublishedDataTracksArgsForCall) +} + +func (fake *FakeParticipant) GetPublishedDataTracksCalls(stub func() []types.DataTrack) { + fake.getPublishedDataTracksMutex.Lock() + defer fake.getPublishedDataTracksMutex.Unlock() + fake.GetPublishedDataTracksStub = stub +} + +func (fake *FakeParticipant) GetPublishedDataTracksReturns(result1 []types.DataTrack) { + fake.getPublishedDataTracksMutex.Lock() + defer fake.getPublishedDataTracksMutex.Unlock() + fake.GetPublishedDataTracksStub = nil + fake.getPublishedDataTracksReturns = struct { + result1 []types.DataTrack + }{result1} +} + +func (fake *FakeParticipant) GetPublishedDataTracksReturnsOnCall(i int, result1 []types.DataTrack) { + fake.getPublishedDataTracksMutex.Lock() + defer fake.getPublishedDataTracksMutex.Unlock() + fake.GetPublishedDataTracksStub = nil + if fake.getPublishedDataTracksReturnsOnCall == nil { + fake.getPublishedDataTracksReturnsOnCall = make(map[int]struct { + result1 []types.DataTrack + }) + } + fake.getPublishedDataTracksReturnsOnCall[i] = struct { + result1 []types.DataTrack + }{result1} +} + func (fake *FakeParticipant) GetPublishedTrack(arg1 livekit.TrackID) types.MediaTrack { fake.getPublishedTrackMutex.Lock() ret, specificReturn := fake.getPublishedTrackReturnsOnCall[len(fake.getPublishedTrackArgsForCall)] @@ -704,6 +982,44 @@ func (fake *FakeParticipant) GetPublishedTracksReturnsOnCall(i int, result1 []ty }{result1} } +func (fake *FakeParticipant) HandleReceivedDataTrackMessage(arg1 []byte, arg2 *datatrack.Packet) { + var arg1Copy []byte + if arg1 != nil { + arg1Copy = make([]byte, len(arg1)) + copy(arg1Copy, arg1) + } + fake.handleReceivedDataTrackMessageMutex.Lock() + fake.handleReceivedDataTrackMessageArgsForCall = append(fake.handleReceivedDataTrackMessageArgsForCall, struct { + arg1 []byte + arg2 *datatrack.Packet + }{arg1Copy, arg2}) + stub := fake.HandleReceivedDataTrackMessageStub + fake.recordInvocation("HandleReceivedDataTrackMessage", []interface{}{arg1Copy, arg2}) + fake.handleReceivedDataTrackMessageMutex.Unlock() + if stub != nil { + fake.HandleReceivedDataTrackMessageStub(arg1, arg2) + } +} + +func (fake *FakeParticipant) HandleReceivedDataTrackMessageCallCount() int { + fake.handleReceivedDataTrackMessageMutex.RLock() + defer fake.handleReceivedDataTrackMessageMutex.RUnlock() + return len(fake.handleReceivedDataTrackMessageArgsForCall) +} + +func (fake *FakeParticipant) HandleReceivedDataTrackMessageCalls(stub func([]byte, *datatrack.Packet)) { + fake.handleReceivedDataTrackMessageMutex.Lock() + defer fake.handleReceivedDataTrackMessageMutex.Unlock() + fake.HandleReceivedDataTrackMessageStub = stub +} + +func (fake *FakeParticipant) HandleReceivedDataTrackMessageArgsForCall(i int) ([]byte, *datatrack.Packet) { + fake.handleReceivedDataTrackMessageMutex.RLock() + defer fake.handleReceivedDataTrackMessageMutex.RUnlock() + argsForCall := fake.handleReceivedDataTrackMessageArgsForCall[i] + return argsForCall.arg1, argsForCall.arg2 +} + func (fake *FakeParticipant) HasPermission(arg1 livekit.TrackID, arg2 livekit.ParticipantIdentity) bool { fake.hasPermissionMutex.Lock() ret, specificReturn := fake.hasPermissionReturnsOnCall[len(fake.hasPermissionArgsForCall)] @@ -978,6 +1294,59 @@ func (fake *FakeParticipant) IsAgentReturnsOnCall(i int, result1 bool) { }{result1} } +func (fake *FakeParticipant) IsClosed() bool { + fake.isClosedMutex.Lock() + ret, specificReturn := fake.isClosedReturnsOnCall[len(fake.isClosedArgsForCall)] + fake.isClosedArgsForCall = append(fake.isClosedArgsForCall, struct { + }{}) + stub := fake.IsClosedStub + fakeReturns := fake.isClosedReturns + fake.recordInvocation("IsClosed", []interface{}{}) + fake.isClosedMutex.Unlock() + if stub != nil { + return stub() + } + if specificReturn { + return ret.result1 + } + return fakeReturns.result1 +} + +func (fake *FakeParticipant) IsClosedCallCount() int { + fake.isClosedMutex.RLock() + defer fake.isClosedMutex.RUnlock() + return len(fake.isClosedArgsForCall) +} + +func (fake *FakeParticipant) IsClosedCalls(stub func() bool) { + fake.isClosedMutex.Lock() + defer fake.isClosedMutex.Unlock() + fake.IsClosedStub = stub +} + +func (fake *FakeParticipant) IsClosedReturns(result1 bool) { + fake.isClosedMutex.Lock() + defer fake.isClosedMutex.Unlock() + fake.IsClosedStub = nil + fake.isClosedReturns = struct { + result1 bool + }{result1} +} + +func (fake *FakeParticipant) IsClosedReturnsOnCall(i int, result1 bool) { + fake.isClosedMutex.Lock() + defer fake.isClosedMutex.Unlock() + fake.IsClosedStub = nil + if fake.isClosedReturnsOnCall == nil { + fake.isClosedReturnsOnCall = make(map[int]struct { + result1 bool + }) + } + fake.isClosedReturnsOnCall[i] = struct { + result1 bool + }{result1} +} + func (fake *FakeParticipant) IsDependent() bool { fake.isDependentMutex.Lock() ret, specificReturn := fake.isDependentReturnsOnCall[len(fake.isDependentArgsForCall)] @@ -1031,6 +1400,59 @@ func (fake *FakeParticipant) IsDependentReturnsOnCall(i int, result1 bool) { }{result1} } +func (fake *FakeParticipant) IsDisconnected() bool { + fake.isDisconnectedMutex.Lock() + ret, specificReturn := fake.isDisconnectedReturnsOnCall[len(fake.isDisconnectedArgsForCall)] + fake.isDisconnectedArgsForCall = append(fake.isDisconnectedArgsForCall, struct { + }{}) + stub := fake.IsDisconnectedStub + fakeReturns := fake.isDisconnectedReturns + fake.recordInvocation("IsDisconnected", []interface{}{}) + fake.isDisconnectedMutex.Unlock() + if stub != nil { + return stub() + } + if specificReturn { + return ret.result1 + } + return fakeReturns.result1 +} + +func (fake *FakeParticipant) IsDisconnectedCallCount() int { + fake.isDisconnectedMutex.RLock() + defer fake.isDisconnectedMutex.RUnlock() + return len(fake.isDisconnectedArgsForCall) +} + +func (fake *FakeParticipant) IsDisconnectedCalls(stub func() bool) { + fake.isDisconnectedMutex.Lock() + defer fake.isDisconnectedMutex.Unlock() + fake.IsDisconnectedStub = stub +} + +func (fake *FakeParticipant) IsDisconnectedReturns(result1 bool) { + fake.isDisconnectedMutex.Lock() + defer fake.isDisconnectedMutex.Unlock() + fake.IsDisconnectedStub = nil + fake.isDisconnectedReturns = struct { + result1 bool + }{result1} +} + +func (fake *FakeParticipant) IsDisconnectedReturnsOnCall(i int, result1 bool) { + fake.isDisconnectedMutex.Lock() + defer fake.isDisconnectedMutex.Unlock() + fake.IsDisconnectedStub = nil + if fake.isDisconnectedReturnsOnCall == nil { + fake.isDisconnectedReturnsOnCall = make(map[int]struct { + result1 bool + }) + } + fake.isDisconnectedReturnsOnCall[i] = struct { + result1 bool + }{result1} +} + func (fake *FakeParticipant) IsPublisher() bool { fake.isPublisherMutex.Lock() ret, specificReturn := fake.isPublisherReturnsOnCall[len(fake.isPublisherArgsForCall)] @@ -1190,6 +1612,123 @@ func (fake *FakeParticipant) KindReturnsOnCall(i int, result1 livekit.Participan }{result1} } +func (fake *FakeParticipant) MigrateState() types.MigrateState { + fake.migrateStateMutex.Lock() + ret, specificReturn := fake.migrateStateReturnsOnCall[len(fake.migrateStateArgsForCall)] + fake.migrateStateArgsForCall = append(fake.migrateStateArgsForCall, struct { + }{}) + stub := fake.MigrateStateStub + fakeReturns := fake.migrateStateReturns + fake.recordInvocation("MigrateState", []interface{}{}) + fake.migrateStateMutex.Unlock() + if stub != nil { + return stub() + } + if specificReturn { + return ret.result1 + } + return fakeReturns.result1 +} + +func (fake *FakeParticipant) MigrateStateCallCount() int { + fake.migrateStateMutex.RLock() + defer fake.migrateStateMutex.RUnlock() + return len(fake.migrateStateArgsForCall) +} + +func (fake *FakeParticipant) MigrateStateCalls(stub func() types.MigrateState) { + fake.migrateStateMutex.Lock() + defer fake.migrateStateMutex.Unlock() + fake.MigrateStateStub = stub +} + +func (fake *FakeParticipant) MigrateStateReturns(result1 types.MigrateState) { + fake.migrateStateMutex.Lock() + defer fake.migrateStateMutex.Unlock() + fake.MigrateStateStub = nil + fake.migrateStateReturns = struct { + result1 types.MigrateState + }{result1} +} + +func (fake *FakeParticipant) MigrateStateReturnsOnCall(i int, result1 types.MigrateState) { + fake.migrateStateMutex.Lock() + defer fake.migrateStateMutex.Unlock() + fake.MigrateStateStub = nil + if fake.migrateStateReturnsOnCall == nil { + fake.migrateStateReturnsOnCall = make(map[int]struct { + result1 types.MigrateState + }) + } + fake.migrateStateReturnsOnCall[i] = struct { + result1 types.MigrateState + }{result1} +} + +func (fake *FakeParticipant) OnDataTrackPublished(arg1 func(types.Participant, types.DataTrack)) { + fake.onDataTrackPublishedMutex.Lock() + fake.onDataTrackPublishedArgsForCall = append(fake.onDataTrackPublishedArgsForCall, struct { + arg1 func(types.Participant, types.DataTrack) + }{arg1}) + stub := fake.OnDataTrackPublishedStub + fake.recordInvocation("OnDataTrackPublished", []interface{}{arg1}) + fake.onDataTrackPublishedMutex.Unlock() + if stub != nil { + fake.OnDataTrackPublishedStub(arg1) + } +} + +func (fake *FakeParticipant) OnDataTrackPublishedCallCount() int { + fake.onDataTrackPublishedMutex.RLock() + defer fake.onDataTrackPublishedMutex.RUnlock() + return len(fake.onDataTrackPublishedArgsForCall) +} + +func (fake *FakeParticipant) OnDataTrackPublishedCalls(stub func(func(types.Participant, types.DataTrack))) { + fake.onDataTrackPublishedMutex.Lock() + defer fake.onDataTrackPublishedMutex.Unlock() + fake.OnDataTrackPublishedStub = stub +} + +func (fake *FakeParticipant) OnDataTrackPublishedArgsForCall(i int) func(types.Participant, types.DataTrack) { + fake.onDataTrackPublishedMutex.RLock() + defer fake.onDataTrackPublishedMutex.RUnlock() + argsForCall := fake.onDataTrackPublishedArgsForCall[i] + return argsForCall.arg1 +} + +func (fake *FakeParticipant) OnDataTrackUnpublished(arg1 func(types.Participant, types.DataTrack)) { + fake.onDataTrackUnpublishedMutex.Lock() + fake.onDataTrackUnpublishedArgsForCall = append(fake.onDataTrackUnpublishedArgsForCall, struct { + arg1 func(types.Participant, types.DataTrack) + }{arg1}) + stub := fake.OnDataTrackUnpublishedStub + fake.recordInvocation("OnDataTrackUnpublished", []interface{}{arg1}) + fake.onDataTrackUnpublishedMutex.Unlock() + if stub != nil { + fake.OnDataTrackUnpublishedStub(arg1) + } +} + +func (fake *FakeParticipant) OnDataTrackUnpublishedCallCount() int { + fake.onDataTrackUnpublishedMutex.RLock() + defer fake.onDataTrackUnpublishedMutex.RUnlock() + return len(fake.onDataTrackUnpublishedArgsForCall) +} + +func (fake *FakeParticipant) OnDataTrackUnpublishedCalls(stub func(func(types.Participant, types.DataTrack))) { + fake.onDataTrackUnpublishedMutex.Lock() + defer fake.onDataTrackUnpublishedMutex.Unlock() + fake.OnDataTrackUnpublishedStub = stub +} + +func (fake *FakeParticipant) OnDataTrackUnpublishedArgsForCall(i int) func(types.Participant, types.DataTrack) { + fake.onDataTrackUnpublishedMutex.RLock() + defer fake.onDataTrackUnpublishedMutex.RUnlock() + argsForCall := fake.onDataTrackUnpublishedArgsForCall[i] + return argsForCall.arg1 +} + func (fake *FakeParticipant) OnMetrics(arg1 func(types.Participant, *livekit.DataPacket)) { fake.onMetricsMutex.Lock() fake.onMetricsArgsForCall = append(fake.onMetricsArgsForCall, struct { @@ -1222,6 +1761,134 @@ func (fake *FakeParticipant) OnMetricsArgsForCall(i int) func(types.Participant, return argsForCall.arg1 } +func (fake *FakeParticipant) OnTrackPublished(arg1 func(types.Participant, types.MediaTrack)) { + fake.onTrackPublishedMutex.Lock() + fake.onTrackPublishedArgsForCall = append(fake.onTrackPublishedArgsForCall, struct { + arg1 func(types.Participant, types.MediaTrack) + }{arg1}) + stub := fake.OnTrackPublishedStub + fake.recordInvocation("OnTrackPublished", []interface{}{arg1}) + fake.onTrackPublishedMutex.Unlock() + if stub != nil { + fake.OnTrackPublishedStub(arg1) + } +} + +func (fake *FakeParticipant) OnTrackPublishedCallCount() int { + fake.onTrackPublishedMutex.RLock() + defer fake.onTrackPublishedMutex.RUnlock() + return len(fake.onTrackPublishedArgsForCall) +} + +func (fake *FakeParticipant) OnTrackPublishedCalls(stub func(func(types.Participant, types.MediaTrack))) { + fake.onTrackPublishedMutex.Lock() + defer fake.onTrackPublishedMutex.Unlock() + fake.OnTrackPublishedStub = stub +} + +func (fake *FakeParticipant) OnTrackPublishedArgsForCall(i int) func(types.Participant, types.MediaTrack) { + fake.onTrackPublishedMutex.RLock() + defer fake.onTrackPublishedMutex.RUnlock() + argsForCall := fake.onTrackPublishedArgsForCall[i] + return argsForCall.arg1 +} + +func (fake *FakeParticipant) OnTrackUnpublished(arg1 func(types.Participant, types.MediaTrack)) { + fake.onTrackUnpublishedMutex.Lock() + fake.onTrackUnpublishedArgsForCall = append(fake.onTrackUnpublishedArgsForCall, struct { + arg1 func(types.Participant, types.MediaTrack) + }{arg1}) + stub := fake.OnTrackUnpublishedStub + fake.recordInvocation("OnTrackUnpublished", []interface{}{arg1}) + fake.onTrackUnpublishedMutex.Unlock() + if stub != nil { + fake.OnTrackUnpublishedStub(arg1) + } +} + +func (fake *FakeParticipant) OnTrackUnpublishedCallCount() int { + fake.onTrackUnpublishedMutex.RLock() + defer fake.onTrackUnpublishedMutex.RUnlock() + return len(fake.onTrackUnpublishedArgsForCall) +} + +func (fake *FakeParticipant) OnTrackUnpublishedCalls(stub func(func(types.Participant, types.MediaTrack))) { + fake.onTrackUnpublishedMutex.Lock() + defer fake.onTrackUnpublishedMutex.Unlock() + fake.OnTrackUnpublishedStub = stub +} + +func (fake *FakeParticipant) OnTrackUnpublishedArgsForCall(i int) func(types.Participant, types.MediaTrack) { + fake.onTrackUnpublishedMutex.RLock() + defer fake.onTrackUnpublishedMutex.RUnlock() + argsForCall := fake.onTrackUnpublishedArgsForCall[i] + return argsForCall.arg1 +} + +func (fake *FakeParticipant) OnTrackUpdated(arg1 func(types.Participant, types.MediaTrack)) { + fake.onTrackUpdatedMutex.Lock() + fake.onTrackUpdatedArgsForCall = append(fake.onTrackUpdatedArgsForCall, struct { + arg1 func(types.Participant, types.MediaTrack) + }{arg1}) + stub := fake.OnTrackUpdatedStub + fake.recordInvocation("OnTrackUpdated", []interface{}{arg1}) + fake.onTrackUpdatedMutex.Unlock() + if stub != nil { + fake.OnTrackUpdatedStub(arg1) + } +} + +func (fake *FakeParticipant) OnTrackUpdatedCallCount() int { + fake.onTrackUpdatedMutex.RLock() + defer fake.onTrackUpdatedMutex.RUnlock() + return len(fake.onTrackUpdatedArgsForCall) +} + +func (fake *FakeParticipant) OnTrackUpdatedCalls(stub func(func(types.Participant, types.MediaTrack))) { + fake.onTrackUpdatedMutex.Lock() + defer fake.onTrackUpdatedMutex.Unlock() + fake.OnTrackUpdatedStub = stub +} + +func (fake *FakeParticipant) OnTrackUpdatedArgsForCall(i int) func(types.Participant, types.MediaTrack) { + fake.onTrackUpdatedMutex.RLock() + defer fake.onTrackUpdatedMutex.RUnlock() + argsForCall := fake.onTrackUpdatedArgsForCall[i] + return argsForCall.arg1 +} + +func (fake *FakeParticipant) RemovePublishedDataTrack(arg1 types.DataTrack) { + fake.removePublishedDataTrackMutex.Lock() + fake.removePublishedDataTrackArgsForCall = append(fake.removePublishedDataTrackArgsForCall, struct { + arg1 types.DataTrack + }{arg1}) + stub := fake.RemovePublishedDataTrackStub + fake.recordInvocation("RemovePublishedDataTrack", []interface{}{arg1}) + fake.removePublishedDataTrackMutex.Unlock() + if stub != nil { + fake.RemovePublishedDataTrackStub(arg1) + } +} + +func (fake *FakeParticipant) RemovePublishedDataTrackCallCount() int { + fake.removePublishedDataTrackMutex.RLock() + defer fake.removePublishedDataTrackMutex.RUnlock() + return len(fake.removePublishedDataTrackArgsForCall) +} + +func (fake *FakeParticipant) RemovePublishedDataTrackCalls(stub func(types.DataTrack)) { + fake.removePublishedDataTrackMutex.Lock() + defer fake.removePublishedDataTrackMutex.Unlock() + fake.RemovePublishedDataTrackStub = stub +} + +func (fake *FakeParticipant) RemovePublishedDataTrackArgsForCall(i int) types.DataTrack { + fake.removePublishedDataTrackMutex.RLock() + defer fake.removePublishedDataTrackMutex.RUnlock() + argsForCall := fake.removePublishedDataTrackArgsForCall[i] + return argsForCall.arg1 +} + func (fake *FakeParticipant) RemovePublishedTrack(arg1 types.MediaTrack, arg2 bool) { fake.removePublishedTrackMutex.Lock() fake.removePublishedTrackArgsForCall = append(fake.removePublishedTrackArgsForCall, struct { @@ -1417,6 +2084,62 @@ func (fake *FakeParticipant) ToProtoReturnsOnCall(i int, result1 *livekit.Partic }{result1} } +func (fake *FakeParticipant) ToProtoWithVersion() (*livekit.ParticipantInfo, utils.TimedVersion) { + fake.toProtoWithVersionMutex.Lock() + ret, specificReturn := fake.toProtoWithVersionReturnsOnCall[len(fake.toProtoWithVersionArgsForCall)] + fake.toProtoWithVersionArgsForCall = append(fake.toProtoWithVersionArgsForCall, struct { + }{}) + stub := fake.ToProtoWithVersionStub + fakeReturns := fake.toProtoWithVersionReturns + fake.recordInvocation("ToProtoWithVersion", []interface{}{}) + fake.toProtoWithVersionMutex.Unlock() + if stub != nil { + return stub() + } + if specificReturn { + return ret.result1, ret.result2 + } + return fakeReturns.result1, fakeReturns.result2 +} + +func (fake *FakeParticipant) ToProtoWithVersionCallCount() int { + fake.toProtoWithVersionMutex.RLock() + defer fake.toProtoWithVersionMutex.RUnlock() + return len(fake.toProtoWithVersionArgsForCall) +} + +func (fake *FakeParticipant) ToProtoWithVersionCalls(stub func() (*livekit.ParticipantInfo, utils.TimedVersion)) { + fake.toProtoWithVersionMutex.Lock() + defer fake.toProtoWithVersionMutex.Unlock() + fake.ToProtoWithVersionStub = stub +} + +func (fake *FakeParticipant) ToProtoWithVersionReturns(result1 *livekit.ParticipantInfo, result2 utils.TimedVersion) { + fake.toProtoWithVersionMutex.Lock() + defer fake.toProtoWithVersionMutex.Unlock() + fake.ToProtoWithVersionStub = nil + fake.toProtoWithVersionReturns = struct { + result1 *livekit.ParticipantInfo + result2 utils.TimedVersion + }{result1, result2} +} + +func (fake *FakeParticipant) ToProtoWithVersionReturnsOnCall(i int, result1 *livekit.ParticipantInfo, result2 utils.TimedVersion) { + fake.toProtoWithVersionMutex.Lock() + defer fake.toProtoWithVersionMutex.Unlock() + fake.ToProtoWithVersionStub = nil + if fake.toProtoWithVersionReturnsOnCall == nil { + fake.toProtoWithVersionReturnsOnCall = make(map[int]struct { + result1 *livekit.ParticipantInfo + result2 utils.TimedVersion + }) + } + fake.toProtoWithVersionReturnsOnCall[i] = struct { + result1 *livekit.ParticipantInfo + result2 utils.TimedVersion + }{result1, result2} +} + func (fake *FakeParticipant) UpdateSubscriptionPermission(arg1 *livekit.SubscriptionPermission, arg2 utils.TimedVersion, arg3 func(participantID livekit.ParticipantID) types.LocalParticipant) error { fake.updateSubscriptionPermissionMutex.Lock() ret, specificReturn := fake.updateSubscriptionPermissionReturnsOnCall[len(fake.updateSubscriptionPermissionArgsForCall)] diff --git a/pkg/rtc/types/typesfakes/fake_room.go b/pkg/rtc/types/typesfakes/fake_room.go index 38519ca41..292fad07c 100644 --- a/pkg/rtc/types/typesfakes/fake_room.go +++ b/pkg/rtc/types/typesfakes/fake_room.go @@ -57,6 +57,18 @@ type FakeRoom struct { arg2 livekit.ParticipantID arg3 types.ParticipantCloseReason } + ResolveDataTrackForSubscriberStub func(types.LocalParticipant, livekit.TrackID) types.DataResolverResult + resolveDataTrackForSubscriberMutex sync.RWMutex + resolveDataTrackForSubscriberArgsForCall []struct { + arg1 types.LocalParticipant + arg2 livekit.TrackID + } + resolveDataTrackForSubscriberReturns struct { + result1 types.DataResolverResult + } + resolveDataTrackForSubscriberReturnsOnCall map[int]struct { + result1 types.DataResolverResult + } ResolveMediaTrackForSubscriberStub func(types.LocalParticipant, livekit.TrackID) types.MediaResolverResult resolveMediaTrackForSubscriberMutex sync.RWMutex resolveMediaTrackForSubscriberArgsForCall []struct { @@ -335,6 +347,68 @@ func (fake *FakeRoom) RemoveParticipantArgsForCall(i int) (livekit.ParticipantId return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3 } +func (fake *FakeRoom) ResolveDataTrackForSubscriber(arg1 types.LocalParticipant, arg2 livekit.TrackID) types.DataResolverResult { + fake.resolveDataTrackForSubscriberMutex.Lock() + ret, specificReturn := fake.resolveDataTrackForSubscriberReturnsOnCall[len(fake.resolveDataTrackForSubscriberArgsForCall)] + fake.resolveDataTrackForSubscriberArgsForCall = append(fake.resolveDataTrackForSubscriberArgsForCall, struct { + arg1 types.LocalParticipant + arg2 livekit.TrackID + }{arg1, arg2}) + stub := fake.ResolveDataTrackForSubscriberStub + fakeReturns := fake.resolveDataTrackForSubscriberReturns + fake.recordInvocation("ResolveDataTrackForSubscriber", []interface{}{arg1, arg2}) + fake.resolveDataTrackForSubscriberMutex.Unlock() + if stub != nil { + return stub(arg1, arg2) + } + if specificReturn { + return ret.result1 + } + return fakeReturns.result1 +} + +func (fake *FakeRoom) ResolveDataTrackForSubscriberCallCount() int { + fake.resolveDataTrackForSubscriberMutex.RLock() + defer fake.resolveDataTrackForSubscriberMutex.RUnlock() + return len(fake.resolveDataTrackForSubscriberArgsForCall) +} + +func (fake *FakeRoom) ResolveDataTrackForSubscriberCalls(stub func(types.LocalParticipant, livekit.TrackID) types.DataResolverResult) { + fake.resolveDataTrackForSubscriberMutex.Lock() + defer fake.resolveDataTrackForSubscriberMutex.Unlock() + fake.ResolveDataTrackForSubscriberStub = stub +} + +func (fake *FakeRoom) ResolveDataTrackForSubscriberArgsForCall(i int) (types.LocalParticipant, livekit.TrackID) { + fake.resolveDataTrackForSubscriberMutex.RLock() + defer fake.resolveDataTrackForSubscriberMutex.RUnlock() + argsForCall := fake.resolveDataTrackForSubscriberArgsForCall[i] + return argsForCall.arg1, argsForCall.arg2 +} + +func (fake *FakeRoom) ResolveDataTrackForSubscriberReturns(result1 types.DataResolverResult) { + fake.resolveDataTrackForSubscriberMutex.Lock() + defer fake.resolveDataTrackForSubscriberMutex.Unlock() + fake.ResolveDataTrackForSubscriberStub = nil + fake.resolveDataTrackForSubscriberReturns = struct { + result1 types.DataResolverResult + }{result1} +} + +func (fake *FakeRoom) ResolveDataTrackForSubscriberReturnsOnCall(i int, result1 types.DataResolverResult) { + fake.resolveDataTrackForSubscriberMutex.Lock() + defer fake.resolveDataTrackForSubscriberMutex.Unlock() + fake.ResolveDataTrackForSubscriberStub = nil + if fake.resolveDataTrackForSubscriberReturnsOnCall == nil { + fake.resolveDataTrackForSubscriberReturnsOnCall = make(map[int]struct { + result1 types.DataResolverResult + }) + } + fake.resolveDataTrackForSubscriberReturnsOnCall[i] = struct { + result1 types.DataResolverResult + }{result1} +} + func (fake *FakeRoom) ResolveMediaTrackForSubscriber(arg1 types.LocalParticipant, arg2 livekit.TrackID) types.MediaResolverResult { fake.resolveMediaTrackForSubscriberMutex.Lock() ret, specificReturn := fake.resolveMediaTrackForSubscriberReturnsOnCall[len(fake.resolveMediaTrackForSubscriberArgsForCall)] diff --git a/pkg/rtc/updatatrackmanager.go b/pkg/rtc/updatatrackmanager.go new file mode 100644 index 000000000..354126282 --- /dev/null +++ b/pkg/rtc/updatatrackmanager.go @@ -0,0 +1,137 @@ +// Copyright 2023 LiveKit, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package rtc + +import ( + "sync" + + "github.com/livekit/livekit-server/pkg/rtc/datatrack" + "github.com/livekit/livekit-server/pkg/rtc/types" + "github.com/livekit/protocol/livekit" + "github.com/livekit/protocol/logger" + "golang.org/x/exp/maps" +) + +type UpDataTrackManagerParams struct { + Logger logger.Logger + Participant types.Participant +} + +type UpDataTrackManager struct { + params UpDataTrackManagerParams + + lock sync.RWMutex + dataTracks map[uint16]types.DataTrack + + onDataTrackPublished func(types.Participant, types.DataTrack) + onDataTrackUnpublished func(types.Participant, types.DataTrack) +} + +func NewUpDataTrackManager(params UpDataTrackManagerParams) *UpDataTrackManager { + return &UpDataTrackManager{ + params: params, + dataTracks: make(map[uint16]types.DataTrack), + } +} + +func (u *UpDataTrackManager) OnDataTrackPublished(callback func(types.Participant, types.DataTrack)) { + u.lock.Lock() + u.onDataTrackPublished = callback + u.lock.Unlock() +} + +func (u *UpDataTrackManager) GetOnDataTrackPublished() func(types.Participant, types.DataTrack) { + u.lock.RLock() + defer u.lock.RUnlock() + return u.onDataTrackPublished +} + +func (u *UpDataTrackManager) OnDataTrackUnpublished(callback func(types.Participant, types.DataTrack)) { + u.lock.Lock() + u.onDataTrackUnpublished = callback + u.lock.Unlock() +} + +func (u *UpDataTrackManager) GetOnDataTrackUnpublished() func(types.Participant, types.DataTrack) { + u.lock.RLock() + defer u.lock.RUnlock() + return u.onDataTrackUnpublished +} + +func (u *UpDataTrackManager) AddPublishedDataTrack(dt types.DataTrack) { + u.lock.Lock() + u.dataTracks[dt.PubHandle()] = dt + u.lock.Unlock() + + if onDataTrackPublished := u.GetOnDataTrackPublished(); onDataTrackPublished != nil { + onDataTrackPublished(u.params.Participant, dt) + } +} + +func (u *UpDataTrackManager) RemovePublishedDataTrack(dt types.DataTrack) { + var found bool + pubHandle := dt.PubHandle() + u.lock.Lock() + if u.dataTracks[pubHandle] == dt { + delete(u.dataTracks, pubHandle) + found = true + } + u.lock.Unlock() + + if found { + dt.Close() + + if onDataTrackUnpublished := u.GetOnDataTrackUnpublished(); onDataTrackUnpublished != nil { + onDataTrackUnpublished(u.params.Participant, dt) + } + } +} + +func (u *UpDataTrackManager) GetPublishedDataTracks() []types.DataTrack { + u.lock.RLock() + defer u.lock.RUnlock() + + return maps.Values(u.dataTracks) +} + +func (u *UpDataTrackManager) GetPublishedDataTrack(handle uint16) types.DataTrack { + u.lock.RLock() + defer u.lock.RUnlock() + + return u.dataTracks[handle] +} + +func (u *UpDataTrackManager) HandleReceivedDataTrackMessage(data []byte, packet *datatrack.Packet) { + u.lock.RLock() + dt := u.dataTracks[packet.Handle] + u.lock.RUnlock() + if dt == nil { + return + } + + dt.HandlePacket(data, packet) +} + +func (u *UpDataTrackManager) ToProto() []*livekit.DataTrackInfo { + u.lock.RLock() + defer u.lock.RUnlock() + + var dataTrackInfos []*livekit.DataTrackInfo + for _, dt := range u.dataTracks { + dataTrackInfos = append(dataTrackInfos, dt.ToProto()) + } + + return dataTrackInfos +} diff --git a/pkg/service/roommanager.go b/pkg/service/roommanager.go index 97e660af8..bdf34e250 100644 --- a/pkg/service/roommanager.go +++ b/pkg/service/roommanager.go @@ -502,6 +502,7 @@ func (r *RoomManager) StartSession( DatachannelLossyTargetLatency: r.config.RTC.DatachannelLossyTargetLatency, FireOnTrackBySdp: true, UseSinglePeerConnection: pi.UseSinglePeerConnection, + EnableDataTracks: r.config.EnableDataTracks, }) if err != nil { return err @@ -664,7 +665,7 @@ func (r *RoomManager) getOrCreateRoom(ctx context.Context, createRoom *livekit.C } }) - newRoom.OnParticipantChanged(func(p types.LocalParticipant) { + newRoom.OnParticipantChanged(func(p types.Participant) { if !p.IsDisconnected() { if err := r.roomStore.StoreParticipant(ctx, roomName, p.ToProto()); err != nil { newRoom.Logger().Errorw("could not handle participant change", err) @@ -1118,6 +1119,10 @@ func (h *roomManagerParticipantHelper) ResolveMediaTrack(lp types.LocalParticipa return h.room.ResolveMediaTrackForSubscriber(lp, trackID) } +func (h *roomManagerParticipantHelper) ResolveDataTrack(lp types.LocalParticipant, trackID livekit.TrackID) types.DataResolverResult { + return h.room.ResolveDataTrackForSubscriber(lp, trackID) +} + func (h *roomManagerParticipantHelper) ShouldRegressCodec() bool { return h.codecRegressionThreshold == 0 || h.room.GetParticipantCount() < h.codecRegressionThreshold } diff --git a/pkg/sfu/buffer/buffer_test.go b/pkg/sfu/buffer/buffer_test.go index 15036aa63..c49ee3931 100644 --- a/pkg/sfu/buffer/buffer_test.go +++ b/pkg/sfu/buffer/buffer_test.go @@ -265,7 +265,7 @@ func TestFractionLostReport(t *testing.T) { HeaderExtensions: nil, Codecs: []webrtc.RTPCodecParameters{opusCodec}, }, opusCodec.RTPCodecCapability, 0) - for i := 0; i < 15; i++ { + for i := range 15 { pkt := rtp.Packet{ Header: rtp.Header{ Version: 2, @@ -303,7 +303,7 @@ func TestFractionLostReport(t *testing.T) { HeaderExtensions: nil, Codecs: []webrtc.RTPCodecParameters{opusCodec}, }, opusCodec.RTPCodecCapability, 0) - for i := 0; i < 15; i++ { + for i := range 15 { pkt := rtp.Packet{ Header: rtp.Header{ Version: 2, diff --git a/pkg/sfu/receiver.go b/pkg/sfu/receiver.go index afea73327..621c97f03 100644 --- a/pkg/sfu/receiver.go +++ b/pkg/sfu/receiver.go @@ -37,6 +37,7 @@ import ( "github.com/livekit/livekit-server/pkg/sfu/mime" "github.com/livekit/livekit-server/pkg/sfu/rtpstats" "github.com/livekit/livekit-server/pkg/sfu/streamtracker" + sfuutils "github.com/livekit/livekit-server/pkg/sfu/utils" ) var ( @@ -197,7 +198,7 @@ type WebRTCReceiver struct { streamTrackerManager *StreamTrackerManager - downTrackSpreader *DownTrackSpreader + downTrackSpreader *sfuutils.DownTrackSpreader[TrackSender] connectionStats *connectionquality.ConnectionStats @@ -274,7 +275,7 @@ func NewWebRTCReceiver( } w.trackInfo.Store(utils.CloneProto(trackInfo)) - w.downTrackSpreader = NewDownTrackSpreader(DownTrackSpreaderParams{ + w.downTrackSpreader = sfuutils.NewDownTrackSpreader[TrackSender](sfuutils.DownTrackSpreaderParams{ Threshold: w.lbThreshold, Logger: logger, }) @@ -921,7 +922,7 @@ func (w *WebRTCReceiver) GetPrimaryReceiverForRed() TrackReceiver { rt := w.redTransformer.Load() if rt == nil { - pr := NewRedPrimaryReceiver(w, DownTrackSpreaderParams{ + pr := NewRedPrimaryReceiver(w, sfuutils.DownTrackSpreaderParams{ Threshold: w.lbThreshold, Logger: w.logger, }) @@ -945,7 +946,7 @@ func (w *WebRTCReceiver) GetRedReceiver() TrackReceiver { rt := w.redTransformer.Load() if rt == nil { - pr := NewRedReceiver(w, DownTrackSpreaderParams{ + pr := NewRedReceiver(w, sfuutils.DownTrackSpreaderParams{ Threshold: w.lbThreshold, Logger: w.logger, }) diff --git a/pkg/sfu/redprimaryreceiver.go b/pkg/sfu/redprimaryreceiver.go index 7e884a433..18674f945 100644 --- a/pkg/sfu/redprimaryreceiver.go +++ b/pkg/sfu/redprimaryreceiver.go @@ -24,6 +24,7 @@ import ( "github.com/pion/webrtc/v4" "github.com/livekit/livekit-server/pkg/sfu/buffer" + "github.com/livekit/livekit-server/pkg/sfu/utils" "github.com/livekit/protocol/livekit" "github.com/livekit/protocol/logger" ) @@ -35,7 +36,7 @@ var ( type RedPrimaryReceiver struct { TrackReceiver - downTrackSpreader *DownTrackSpreader + downTrackSpreader *utils.DownTrackSpreader[TrackSender] logger logger.Logger closed atomic.Bool redPT uint8 @@ -47,10 +48,10 @@ type RedPrimaryReceiver struct { pktHistory byte } -func NewRedPrimaryReceiver(receiver TrackReceiver, dsp DownTrackSpreaderParams) *RedPrimaryReceiver { +func NewRedPrimaryReceiver(receiver TrackReceiver, dsp utils.DownTrackSpreaderParams) *RedPrimaryReceiver { return &RedPrimaryReceiver{ TrackReceiver: receiver, - downTrackSpreader: NewDownTrackSpreader(dsp), + downTrackSpreader: utils.NewDownTrackSpreader[TrackSender](dsp), logger: dsp.Logger, redPT: uint8(receiver.Codec().PayloadType), } diff --git a/pkg/sfu/redreceiver.go b/pkg/sfu/redreceiver.go index 8e4d68af3..4d67c7109 100644 --- a/pkg/sfu/redreceiver.go +++ b/pkg/sfu/redreceiver.go @@ -24,6 +24,7 @@ import ( "github.com/pion/webrtc/v4" "github.com/livekit/livekit-server/pkg/sfu/buffer" + "github.com/livekit/livekit-server/pkg/sfu/utils" "github.com/livekit/mediatransportutil/pkg/bucket" "github.com/livekit/protocol/livekit" "github.com/livekit/protocol/logger" @@ -43,17 +44,17 @@ const ( type RedReceiver struct { TrackReceiver - downTrackSpreader *DownTrackSpreader + downTrackSpreader *utils.DownTrackSpreader[TrackSender] logger logger.Logger closed atomic.Bool pktBuff [maxRedCount]*rtp.Packet redPayloadBuf [mtuSize]byte } -func NewRedReceiver(receiver TrackReceiver, dsp DownTrackSpreaderParams) *RedReceiver { +func NewRedReceiver(receiver TrackReceiver, dsp utils.DownTrackSpreaderParams) *RedReceiver { return &RedReceiver{ TrackReceiver: receiver, - downTrackSpreader: NewDownTrackSpreader(dsp), + downTrackSpreader: utils.NewDownTrackSpreader[TrackSender](dsp), logger: dsp.Logger, } } diff --git a/pkg/sfu/downtrackspreader.go b/pkg/sfu/utils/downtrackspreader.go similarity index 66% rename from pkg/sfu/downtrackspreader.go rename to pkg/sfu/utils/downtrackspreader.go index 456a2ab3b..996889c56 100644 --- a/pkg/sfu/downtrackspreader.go +++ b/pkg/sfu/utils/downtrackspreader.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package sfu +package utils import ( "sync" @@ -22,55 +22,59 @@ import ( "github.com/livekit/protocol/utils" ) +type sender interface { + SubscriberID() livekit.ParticipantID +} + type DownTrackSpreaderParams struct { Threshold int Logger logger.Logger } -type DownTrackSpreader struct { +type DownTrackSpreader[T sender] struct { params DownTrackSpreaderParams downTrackMu sync.RWMutex - downTracks map[livekit.ParticipantID]TrackSender - downTracksShadow []TrackSender + downTracks map[livekit.ParticipantID]T + downTracksShadow []T } -func NewDownTrackSpreader(params DownTrackSpreaderParams) *DownTrackSpreader { - d := &DownTrackSpreader{ +func NewDownTrackSpreader[T sender](params DownTrackSpreaderParams) *DownTrackSpreader[T] { + d := &DownTrackSpreader[T]{ params: params, - downTracks: make(map[livekit.ParticipantID]TrackSender), + downTracks: make(map[livekit.ParticipantID]T), } return d } -func (d *DownTrackSpreader) GetDownTracks() []TrackSender { +func (d *DownTrackSpreader[T]) GetDownTracks() []T { d.downTrackMu.RLock() defer d.downTrackMu.RUnlock() return d.downTracksShadow } -func (d *DownTrackSpreader) ResetAndGetDownTracks() []TrackSender { +func (d *DownTrackSpreader[T]) ResetAndGetDownTracks() []T { d.downTrackMu.Lock() defer d.downTrackMu.Unlock() downTracks := d.downTracksShadow - d.downTracks = make(map[livekit.ParticipantID]TrackSender) + d.downTracks = make(map[livekit.ParticipantID]T) d.downTracksShadow = nil return downTracks } -func (d *DownTrackSpreader) Store(ts TrackSender) { +func (d *DownTrackSpreader[T]) Store(sender T) { d.downTrackMu.Lock() defer d.downTrackMu.Unlock() - d.downTracks[ts.SubscriberID()] = ts + d.downTracks[sender.SubscriberID()] = sender d.shadowDownTracks() } -func (d *DownTrackSpreader) Free(subscriberID livekit.ParticipantID) { +func (d *DownTrackSpreader[T]) Free(subscriberID livekit.ParticipantID) { d.downTrackMu.Lock() defer d.downTrackMu.Unlock() @@ -78,7 +82,7 @@ func (d *DownTrackSpreader) Free(subscriberID livekit.ParticipantID) { d.shadowDownTracks() } -func (d *DownTrackSpreader) HasDownTrack(subscriberID livekit.ParticipantID) bool { +func (d *DownTrackSpreader[T]) HasDownTrack(subscriberID livekit.ParticipantID) bool { d.downTrackMu.RLock() defer d.downTrackMu.RUnlock() @@ -86,7 +90,7 @@ func (d *DownTrackSpreader) HasDownTrack(subscriberID livekit.ParticipantID) boo return ok } -func (d *DownTrackSpreader) Broadcast(writer func(TrackSender)) { +func (d *DownTrackSpreader[T]) Broadcast(writer func(T)) { downTracks := d.GetDownTracks() if len(downTracks) == 0 { return @@ -103,14 +107,14 @@ func (d *DownTrackSpreader) Broadcast(writer func(TrackSender)) { utils.ParallelExec(downTracks, threshold, step, writer) } -func (d *DownTrackSpreader) DownTrackCount() int { +func (d *DownTrackSpreader[T]) DownTrackCount() int { d.downTrackMu.RLock() defer d.downTrackMu.RUnlock() return len(d.downTracksShadow) } -func (d *DownTrackSpreader) shadowDownTracks() { - d.downTracksShadow = make([]TrackSender, 0, len(d.downTracks)) +func (d *DownTrackSpreader[T]) shadowDownTracks() { + d.downTracksShadow = make([]T, 0, len(d.downTracks)) for _, dt := range d.downTracks { d.downTracksShadow = append(d.downTracksShadow, dt) } diff --git a/test/client/client.go b/test/client/client.go index 55b01d43f..641b79cbd 100644 --- a/test/client/client.go +++ b/test/client/client.go @@ -21,6 +21,8 @@ import ( "errors" "fmt" "io" + "maps" + "math/rand/v2" "net/http" "net/url" "path/filepath" @@ -43,6 +45,7 @@ import ( "github.com/livekit/protocol/signalling" "github.com/livekit/livekit-server/pkg/rtc" + "github.com/livekit/livekit-server/pkg/rtc/datatrack" "github.com/livekit/livekit-server/pkg/rtc/transport/transportfakes" "github.com/livekit/livekit-server/pkg/rtc/types" "github.com/livekit/livekit-server/pkg/sfu/buffer" @@ -88,7 +91,7 @@ type RTCClient struct { // remote tracks waiting to be processed pendingRemoteTracks []*webrtc.TrackRemote - pendingTrackWriters []*TrackWriter + pendingTrackWriters []TrackWriter OnConnected func() OnDataReceived func(data []byte, sid string) OnDataUnlabeledReceived func(data []byte) @@ -99,6 +102,11 @@ type RTCClient struct { bytesReceived map[livekit.ParticipantID]uint64 subscriptionResponse atomic.Pointer[livekit.SubscriptionResponse] + + nextDataTrackHandle atomic.Uint32 + pendingPublishedDataTracks map[uint16]*livekit.DataTrackInfo + pendingDataTrackWriters []TrackWriter + subscribedDataTracks map[livekit.ParticipantID]map[uint16]*DataTrackRemote } var ( @@ -209,17 +217,20 @@ func NewRTCClient(conn *websocket.Conn, useSinglePeerConnection bool, opts *Opti var err error c := &RTCClient{ - useSinglePeerConnection: useSinglePeerConnection, - conn: conn, - localTracks: make(map[string]webrtc.TrackLocal), - trackSenders: make(map[string]*webrtc.RTPSender), - pendingPublishedTracks: make(map[string]*livekit.TrackInfo), - subscribedTracks: make(map[livekit.ParticipantID][]*webrtc.TrackRemote), - remoteParticipants: make(map[livekit.ParticipantID]*livekit.ParticipantInfo), - me: &webrtc.MediaEngine{}, - lastPackets: make(map[livekit.ParticipantID]*rtp.Packet), - bytesReceived: make(map[livekit.ParticipantID]uint64), + useSinglePeerConnection: useSinglePeerConnection, + conn: conn, + localTracks: make(map[string]webrtc.TrackLocal), + trackSenders: make(map[string]*webrtc.RTPSender), + pendingPublishedTracks: make(map[string]*livekit.TrackInfo), + subscribedTracks: make(map[livekit.ParticipantID][]*webrtc.TrackRemote), + remoteParticipants: make(map[livekit.ParticipantID]*livekit.ParticipantInfo), + me: &webrtc.MediaEngine{}, + lastPackets: make(map[livekit.ParticipantID]*rtp.Packet), + bytesReceived: make(map[livekit.ParticipantID]uint64), + pendingPublishedDataTracks: make(map[uint16]*livekit.DataTrackInfo), + subscribedDataTracks: make(map[livekit.ParticipantID]map[uint16]*DataTrackRemote), } + c.nextDataTrackHandle.Store(uint32(rand.IntN(8192))) c.ctx, c.cancel = context.WithCancel(context.Background()) conf := rtc.WebRTCConfig{ @@ -274,6 +285,7 @@ func NewRTCClient(conn *websocket.Conn, useSinglePeerConnection bool, opts *Opti DatachannelMaxReceiverBufferSize: 1500, DatachannelSlowThreshold: 1024 * 1024 * 1024, FireOnTrackBySdp: true, + EnableDataTracks: true, }) if err != nil { return nil, err @@ -287,6 +299,7 @@ func NewRTCClient(conn *websocket.Conn, useSinglePeerConnection bool, opts *Opti }) publisherHandler.OnDataMessageCalls(c.handleDataMessage) publisherHandler.OnDataMessageUnlabeledCalls(c.handleDataMessageUnlabeled) + publisherHandler.OnDataTrackMessageCalls(c.handleDataTrackMessage) publisherHandler.OnInitialConnectedCalls(func() { logger.Debugw("publisher initial connected", "participant", c.localParticipant.Identity) @@ -297,9 +310,15 @@ func NewRTCClient(conn *websocket.Conn, useSinglePeerConnection bool, opts *Opti logger.Errorw("track writer error", err) } } - c.pendingTrackWriters = nil + for _, dtw := range c.pendingDataTrackWriters { + if err := dtw.Start(); err != nil { + logger.Errorw("data track writer error", err) + } + } + c.pendingDataTrackWriters = nil + if c.OnConnected != nil { go c.OnConnected() } @@ -317,6 +336,12 @@ func NewRTCClient(conn *websocket.Conn, useSinglePeerConnection bool, opts *Opti return nil, err } + if err := c.publisher.CreateDataChannel("pubraw", &webrtc.DataChannelInit{ + Ordered: &ordered, + }); err != nil { + return nil, err + } + ordered = false maxRetransmits := uint16(0) if err := c.publisher.CreateDataChannel(rtc.LossyDataChannel, &webrtc.DataChannelInit{ @@ -326,8 +351,9 @@ func NewRTCClient(conn *websocket.Conn, useSinglePeerConnection bool, opts *Opti return nil, err } - if err := c.publisher.CreateDataChannel("pubraw", &webrtc.DataChannelInit{ - Ordered: &ordered, + if err := c.publisher.CreateDataChannel(rtc.DataTrackDataChannel, &webrtc.DataChannelInit{ + Ordered: &ordered, + MaxRetransmits: &maxRetransmits, }); err != nil { return nil, err } @@ -342,12 +368,13 @@ func NewRTCClient(conn *websocket.Conn, useSinglePeerConnection bool, opts *Opti DatachannelMaxReceiverBufferSize: 1500, DatachannelSlowThreshold: 1024 * 1024 * 1024, FireOnTrackBySdp: true, + EnableDataTracks: true, }) if err != nil { return nil, err } - ordered := false + ordered := true if err := c.subscriber.CreateReadableDataChannel("subraw", &webrtc.DataChannelInit{ Ordered: &ordered, }); err != nil { @@ -365,6 +392,7 @@ func NewRTCClient(conn *websocket.Conn, useSinglePeerConnection bool, opts *Opti }) subscriberHandler.OnDataMessageCalls(c.handleDataMessage) subscriberHandler.OnDataMessageUnlabeledCalls(c.handleDataMessageUnlabeled) + subscriberHandler.OnDataTrackMessageCalls(c.handleDataTrackMessage) subscriberHandler.OnInitialConnectedCalls(func() { logger.Debugw("subscriber initial connected", "participant", c.localParticipant.Identity) @@ -375,9 +403,15 @@ func NewRTCClient(conn *websocket.Conn, useSinglePeerConnection bool, opts *Opti logger.Errorw("track writer error", err) } } - c.pendingTrackWriters = nil + for _, dtw := range c.pendingDataTrackWriters { + if err := dtw.Start(); err != nil { + logger.Errorw("data track writer error", err) + } + } + c.pendingDataTrackWriters = nil + if c.OnConnected != nil { go c.OnConnected() } @@ -399,6 +433,8 @@ func NewRTCClient(conn *websocket.Conn, useSinglePeerConnection bool, opts *Opti }, }) }) + } else { + go c.ensurePublisherConnected() } if opts != nil { @@ -460,7 +496,12 @@ func (c *RTCClient) handleSignalResponse(res *livekit.SignalResponse) error { c.subscriberAsPrimary.Store(true) } - logger.Infow("join accepted, awaiting offer", "participant", msg.Join.Participant.Identity) + if c.subscriber != nil { + logger.Infow("join accepted, awaiting offer", "participant", msg.Join.Participant.Identity) + } else { + logger.Infow("join accepted", "participant", msg.Join.Participant.Identity) + } + case *livekit.SignalResponse_Answer: logger.Infow( "received server answer", @@ -468,6 +509,7 @@ func (c *RTCClient) handleSignalResponse(res *livekit.SignalResponse) error { "answer", msg.Answer.Sdp, ) c.handleAnswer(signalling.FromProtoSessionDescription(msg.Answer)) + case *livekit.SignalResponse_Offer: desc, offerId, midToTrackID := signalling.FromProtoSessionDescription(msg.Offer) logger.Infow( @@ -478,6 +520,7 @@ func (c *RTCClient) handleSignalResponse(res *livekit.SignalResponse) error { "midToTrackID", midToTrackID, ) c.handleOffer(desc, offerId, midToTrackID) + case *livekit.SignalResponse_Trickle: candidateInit, err := signalling.FromProtoTrickle(msg.Trickle) if err != nil { @@ -488,6 +531,7 @@ func (c *RTCClient) handleSignalResponse(res *livekit.SignalResponse) error { } else { c.subscriber.AddICECandidate(candidateInit) } + case *livekit.SignalResponse_Update: c.lock.Lock() for _, p := range msg.Update.Participants { @@ -502,15 +546,22 @@ func (c *RTCClient) handleSignalResponse(res *livekit.SignalResponse) error { c.lock.Unlock() case *livekit.SignalResponse_TrackPublished: - logger.Debugw("track published", "trackID", msg.TrackPublished.Track.Name, "participant", c.localParticipant.Sid, - "cid", msg.TrackPublished.Cid, "trackSid", msg.TrackPublished.Track.Sid) + logger.Debugw( + "track published", + "participant", c.localParticipant.Identity, + "cid", msg.TrackPublished.Cid, + "trackID", msg.TrackPublished.Track.Sid, + "trackName", msg.TrackPublished.Track.Name, + ) c.lock.Lock() c.pendingPublishedTracks[msg.TrackPublished.Cid] = msg.TrackPublished.Track c.lock.Unlock() + case *livekit.SignalResponse_RefreshToken: c.lock.Lock() c.refreshToken = msg.RefreshToken c.lock.Unlock() + case *livekit.SignalResponse_TrackUnpublished: sid := msg.TrackUnpublished.TrackSid c.lock.Lock() @@ -523,10 +574,13 @@ func (c *RTCClient) handleSignalResponse(res *livekit.SignalResponse) error { delete(c.trackSenders, sid) delete(c.localTracks, sid) c.lock.Unlock() + case *livekit.SignalResponse_Pong: c.pongReceivedAt.Store(msg.Pong) + case *livekit.SignalResponse_SubscriptionResponse: c.subscriptionResponse.Store(msg.SubscriptionResponse) + case *livekit.SignalResponse_MediaSectionsRequirement: logger.Infow( "received media sections requirement", @@ -535,6 +589,58 @@ func (c *RTCClient) handleSignalResponse(res *livekit.SignalResponse) error { "numVideos", msg.MediaSectionsRequirement.NumVideos, ) c.handleMediaSectionsRequirement(msg.MediaSectionsRequirement) + + case *livekit.SignalResponse_PublishDataTrackResponse: + logger.Debugw( + "data track published", + "participant", c.localParticipant.Identity, + "trackID", msg.PublishDataTrackResponse.Info.Sid, + "trackHandle", msg.PublishDataTrackResponse.Info.PubHandle, + "trackName", msg.PublishDataTrackResponse.Info.Name, + ) + c.lock.Lock() + c.pendingPublishedDataTracks[uint16(msg.PublishDataTrackResponse.Info.PubHandle)] = msg.PublishDataTrackResponse.Info + c.lock.Unlock() + + case *livekit.SignalResponse_DataTrackSubscriberHandles: + logger.Infow( + "received data track subscriber handles", + "participant", c.localParticipant.Identity, + "handles", msg.DataTrackSubscriberHandles.SubHandles, + ) + c.lock.Lock() + // create new remote data tracks if one does not exist for a handle + for handle, publishedDataTrack := range msg.DataTrackSubscriberHandles.SubHandles { + publisherID := livekit.ParticipantID(publishedDataTrack.PublisherSid) + tracks := c.subscribedDataTracks[publisherID] + if tracks == nil { + c.subscribedDataTracks[publisherID] = make(map[uint16]*DataTrackRemote) + tracks = c.subscribedDataTracks[publisherID] + } + if tracks[uint16(handle)] == nil { + tracks[uint16(handle)] = NewDataTrackRemote( + livekit.ParticipantIdentity(publishedDataTrack.PublisherIdentity), + livekit.ParticipantID(publishedDataTrack.PublisherSid), + uint16(handle), + livekit.TrackID(publishedDataTrack.TrackSid), + logger.GetLogger().WithValues("participant", c.localParticipant.Identity, "pID", c.localParticipant.Sid), + ) + } + } + + // delete remote data tracks that have gone away + for publisherID, tracks := range c.subscribedDataTracks { + for handle, dataTrackRemote := range tracks { + if msg.DataTrackSubscriberHandles.SubHandles[uint32(handle)] == nil { + dataTrackRemote.Close() + delete(tracks, handle) + if len(tracks) == 0 { + delete(c.subscribedDataTracks, publisherID) + } + } + } + } + c.lock.Unlock() } return nil } @@ -596,9 +702,16 @@ func (c *RTCClient) SubscribedTracks() map[livekit.ParticipantID][]*webrtc.Track c.lock.Lock() defer c.lock.Unlock() tracks := make(map[livekit.ParticipantID][]*webrtc.TrackRemote, len(c.subscribedTracks)) - for key, val := range c.subscribedTracks { - tracks[key] = val - } + maps.Copy(tracks, c.subscribedTracks) + return tracks +} + +func (c *RTCClient) SubscribedDataTracks() map[livekit.ParticipantID]map[uint16]*DataTrackRemote { + // create a copy of this + c.lock.Lock() + defer c.lock.Unlock() + tracks := make(map[livekit.ParticipantID]map[uint16]*DataTrackRemote, len(c.subscribedDataTracks)) + maps.Copy(tracks, c.subscribedDataTracks) return tracks } @@ -720,7 +833,7 @@ func AddTrackNoWriter() AddTrackOption { } } -func (c *RTCClient) AddTrack(track *webrtc.TrackLocalStaticSample, path string, opts ...AddTrackOption) (writer *TrackWriter, err error) { +func (c *RTCClient) AddTrack(track *webrtc.TrackLocalStaticSample, path string, opts ...AddTrackOption) (writer TrackWriter, err error) { var params AddTrackParams for _, opt := range opts { opt(¶ms) @@ -789,11 +902,11 @@ func (c *RTCClient) AddTrack(track *webrtc.TrackLocalStaticSample, path string, return } -func (c *RTCClient) AddStaticTrack(mime string, id string, label string, opts ...AddTrackOption) (writer *TrackWriter, err error) { +func (c *RTCClient) AddStaticTrack(mime string, id string, label string, opts ...AddTrackOption) (writer TrackWriter, err error) { return c.AddStaticTrackWithCodec(webrtc.RTPCodecCapability{MimeType: mime}, id, label, opts...) } -func (c *RTCClient) AddStaticTrackWithCodec(codec webrtc.RTPCodecCapability, id string, label string, opts ...AddTrackOption) (writer *TrackWriter, err error) { +func (c *RTCClient) AddStaticTrackWithCodec(codec webrtc.RTPCodecCapability, id string, label string, opts ...AddTrackOption) (writer TrackWriter, err error) { track, err := webrtc.NewTrackLocalStaticSample(codec, id, label) if err != nil { return @@ -802,7 +915,7 @@ func (c *RTCClient) AddStaticTrackWithCodec(codec webrtc.RTPCodecCapability, id return c.AddTrack(track, "", opts...) } -func (c *RTCClient) AddFileTrack(path string, id string, label string) (writer *TrackWriter, err error) { +func (c *RTCClient) AddFileTrack(path string, id string, label string) (writer TrackWriter, err error) { // determine file mime mime, ok := extMimeMapping[filepath.Ext(path)] if !ok { @@ -867,6 +980,60 @@ func (c *RTCClient) PublishDataUnlabeled(data []byte) error { return c.publisher.SendDataMessageUnlabeled(data, true, "test") } +func (c *RTCClient) PublishDataTrack() (writer TrackWriter, err error) { + if err = c.ensurePublisherConnected(); err != nil { + return + } + + dataTrackHandle := uint16(c.nextDataTrackHandle.Inc()) + if err = c.SendRequest(&livekit.SignalRequest{ + Message: &livekit.SignalRequest_PublishDataTrackRequest{ + PublishDataTrackRequest: &livekit.PublishDataTrackRequest{ + PubHandle: uint32(dataTrackHandle), + Name: fmt.Sprintf("data_track_%d", dataTrackHandle), + }, + }, + }); err != nil { + return + } + + // wait till data track published message is received + timeout := time.After(5 * time.Second) + var dti *livekit.DataTrackInfo + for { + select { + case <-timeout: + return nil, errors.New("could not publish data track after timeout") + default: + c.lock.Lock() + dti = c.pendingPublishedDataTracks[dataTrackHandle] + if dti != nil { + delete(c.pendingPublishedDataTracks, dataTrackHandle) + c.lock.Unlock() + break + } + c.lock.Unlock() + time.Sleep(50 * time.Millisecond) + } + if dti != nil { + break + } + } + + c.lock.Lock() + defer c.lock.Unlock() + + writer = NewDataTrackWriter(c.ctx, dataTrackHandle, c.publisher) + + // write data tracks only after connection established + if c.hasPrimaryEverConnected() { + err = writer.Start() + } else { + c.pendingDataTrackWriters = append(c.pendingDataTrackWriters, writer) + } + return +} + func (c *RTCClient) GetPublishedTrackIDs() []string { c.lock.Lock() defer c.lock.Unlock() @@ -925,6 +1092,27 @@ func (c *RTCClient) handleDataMessageUnlabeled(data []byte) { } } +func (c *RTCClient) handleDataTrackMessage(data []byte) { + var packet datatrack.Packet + if err := packet.Unmarshal(data); err != nil { + return + } + + var dataTrackRemote *DataTrackRemote + c.lock.Lock() + for _, tracks := range c.subscribedDataTracks { + if tracks[packet.Handle] != nil { + dataTrackRemote = tracks[packet.Handle] + break + } + } + c.lock.Unlock() + + if dataTrackRemote != nil { + dataTrackRemote.PacketReceived(&packet) + } +} + // handles a server initiated offer, handle on subscriber PC func (c *RTCClient) handleOffer(desc webrtc.SessionDescription, offerId uint32, _midToTrackID map[string]string) { logger.Infow("handling server offer", "participant", c.localParticipant.Identity) @@ -944,7 +1132,7 @@ func (c *RTCClient) handleAnswer(desc webrtc.SessionDescription, answerId uint32 // the client handles media sections requirement on the publisher PC func (c *RTCClient) handleMediaSectionsRequirement(mediaSectionsRequirement *livekit.MediaSectionsRequirement) { addTransceivers := func(kind webrtc.RTPCodecType, count uint32) { - for i := uint32(0); i < count; i++ { + for range count { if _, err := c.publisher.AddTransceiverFromKind( kind, webrtc.RTPTransceiverInit{ diff --git a/test/client/datatrack_remote.go b/test/client/datatrack_remote.go new file mode 100644 index 000000000..d13d33b71 --- /dev/null +++ b/test/client/datatrack_remote.go @@ -0,0 +1,86 @@ +package client + +import ( + "github.com/frostbyte73/core" + "github.com/livekit/livekit-server/pkg/rtc/datatrack" + "github.com/livekit/protocol/livekit" + "github.com/livekit/protocol/logger" + "go.uber.org/atomic" +) + +type DataTrackRemote struct { + publisherIdentity livekit.ParticipantIdentity + publisherID livekit.ParticipantID + handle uint16 + trackID livekit.TrackID + logger logger.Logger + numReceivedPackets atomic.Uint32 + + closed core.Fuse +} + +func NewDataTrackRemote( + publisherIdentity livekit.ParticipantIdentity, + publisherID livekit.ParticipantID, + handle uint16, + trackID livekit.TrackID, + logger logger.Logger, +) *DataTrackRemote { + logger.Infow( + "creating data track remote", + "publisherIdentity", publisherIdentity, + "publisherID", publisherID, + "handle", handle, + "trackID", trackID, + ) + return &DataTrackRemote{ + publisherIdentity: publisherIdentity, + publisherID: publisherID, + handle: handle, + trackID: trackID, + logger: logger, + } +} + +func (d *DataTrackRemote) Close() { + d.logger.Infow( + "closing data track remote", + "publisherIdentity", d.publisherIdentity, + "publisherID", d.publisherID, + "handle", d.handle, + "trackID", d.trackID, + ) + d.closed.Break() +} + +func (d *DataTrackRemote) Handle() uint16 { + return d.handle +} + +func (d *DataTrackRemote) ID() livekit.TrackID { + return d.trackID +} + +func (d *DataTrackRemote) PacketReceived(packet *datatrack.Packet) { + if d.closed.IsBroken() { + return + } + + valid := true + if len(packet.Payload) == 0 { + valid = false + } + for i := range len(packet.Payload) { + if packet.Payload[i] != byte(255-i) { + valid = false + break + } + } + if valid { + d.numReceivedPackets.Inc() + } +} + +func (d *DataTrackRemote) NumReceivedPackets() uint32 { + return d.numReceivedPackets.Load() +} diff --git a/test/client/datatrack_writer.go b/test/client/datatrack_writer.go new file mode 100644 index 000000000..82c6a188b --- /dev/null +++ b/test/client/datatrack_writer.go @@ -0,0 +1,79 @@ +// Copyright 2023 LiveKit, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package client + +import ( + "context" + "math/rand" + "time" + + "github.com/livekit/livekit-server/pkg/rtc/datatrack" + "github.com/livekit/livekit-server/pkg/rtc/types" + "github.com/livekit/protocol/logger" +) + +type dataTrackWriter struct { + ctx context.Context + cancel context.CancelFunc + handle uint16 + transport types.DataTrackTransport +} + +func NewDataTrackWriter(ctx context.Context, handle uint16, transport types.DataTrackTransport) TrackWriter { + ctx, cancel := context.WithCancel(ctx) + return &dataTrackWriter{ + ctx: ctx, + cancel: cancel, + handle: handle, + transport: transport, + } +} + +func (d *dataTrackWriter) Start() error { + go d.writeFrames() + return nil +} + +func (d *dataTrackWriter) Stop() { + d.cancel() +} + +func (d *dataTrackWriter) writeFrames() { + seqNum := uint16(0) + frameNum := uint16(0) + for { + select { + case <-d.ctx.Done(): + return + + default: + packets := datatrack.GenerateRawDataPackets(d.handle, seqNum, frameNum, 1, rand.Intn(2048)+1, 100*time.Millisecond) + for _, packet := range packets { + if err := d.transport.SendDataTrackMessage(packet); err != nil { + logger.Errorw("could not send data track packet", err) + } + } + + if len(packets) != 0 { + var lastPacket datatrack.Packet + if err := lastPacket.Unmarshal(packets[len(packets)-1]); err == nil { + seqNum = lastPacket.SequenceNumber + 1 + frameNum = lastPacket.FrameNumber + 1 + } + } + time.Sleep(100 * time.Millisecond) + } + } +} diff --git a/test/client/trackwriter.go b/test/client/trackwriter.go index 803bf3348..eae2da44f 100644 --- a/test/client/trackwriter.go +++ b/test/client/trackwriter.go @@ -30,9 +30,14 @@ import ( "github.com/livekit/protocol/logger" ) +type TrackWriter interface { + Start() error + Stop() +} + // Writes a file to an RTP track. // makes it easier to debug and create RTP streams -type TrackWriter struct { +type trackWriter struct { ctx context.Context cancel context.CancelFunc track *webrtc.TrackLocalStaticSample @@ -45,9 +50,9 @@ type TrackWriter struct { h264 *h264reader.H264Reader } -func NewTrackWriter(ctx context.Context, track *webrtc.TrackLocalStaticSample, filePath string) *TrackWriter { +func NewTrackWriter(ctx context.Context, track *webrtc.TrackLocalStaticSample, filePath string) TrackWriter { ctx, cancel := context.WithCancel(ctx) - return &TrackWriter{ + return &trackWriter{ ctx: ctx, cancel: cancel, track: track, @@ -56,7 +61,7 @@ func NewTrackWriter(ctx context.Context, track *webrtc.TrackLocalStaticSample, f } } -func (w *TrackWriter) Start() error { +func (w *trackWriter) Start() error { if w.filePath == "" { go w.writeNull() return nil @@ -95,11 +100,11 @@ func (w *TrackWriter) Start() error { return nil } -func (w *TrackWriter) Stop() { +func (w *trackWriter) Stop() { w.cancel() } -func (w *TrackWriter) writeNull() { +func (w *trackWriter) writeNull() { defer w.onWriteComplete() sample := media.Sample{Data: []byte{0x0, 0xff, 0xff, 0xff, 0xff}, Duration: 30 * time.Millisecond} h264Sample := media.Sample{Data: []byte{0x00, 0x00, 0x00, 0x01, 0x7, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x8, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x5, 0xff, 0xff, 0xff, 0xff}, Duration: 30 * time.Millisecond} @@ -117,7 +122,7 @@ func (w *TrackWriter) writeNull() { } } -func (w *TrackWriter) writeOgg() { +func (w *trackWriter) writeOgg() { // Keep track of last granule, the difference is the amount of samples in the buffer var lastGranule uint64 for { @@ -150,7 +155,7 @@ func (w *TrackWriter) writeOgg() { } } -func (w *TrackWriter) writeVP8() { +func (w *trackWriter) writeVP8() { // Send our video file frame at a time. Pace our sending such that we send it at the same speed it should be played back as. // This isn't required since the video is timestamped, but we will such much higher loss if we send all at once. sleepTime := time.Millisecond * time.Duration((float32(w.ivfheader.TimebaseNumerator)/float32(w.ivfheader.TimebaseDenominator))*1000) @@ -178,10 +183,9 @@ func (w *TrackWriter) writeVP8() { } } -func (w *TrackWriter) writeH264() { +func (w *trackWriter) writeH264() { // TODO: this is harder } -func (w *TrackWriter) onWriteComplete() { - +func (w *trackWriter) onWriteComplete() { } diff --git a/test/integration_helpers.go b/test/integration_helpers.go index 6fed71acd..272f6d68d 100644 --- a/test/integration_helpers.go +++ b/test/integration_helpers.go @@ -154,6 +154,7 @@ func createSingleNodeServer(configUpdater func(*config.Config)) *service.Livekit panic(fmt.Sprintf("could not create config: %v", err)) } conf.Keys = map[string]string{testApiKey: testApiSecret} + conf.EnableDataTracks = true if configUpdater != nil { configUpdater(conf) } @@ -184,6 +185,7 @@ func createMultiNodeServer(nodeID string, port uint32) *service.LivekitServer { conf.RTC.TCPPort = port + 2 conf.Redis.Address = "localhost:6379" conf.Keys = map[string]string{testApiKey: testApiSecret} + conf.EnableDataTracks = true currentNode, err := routing.NewLocalNode(conf) if err != nil { @@ -302,7 +304,7 @@ func listRoomToken() string { return t } -func stopWriters(writers ...*testclient.TrackWriter) { +func stopWriters(writers ...testclient.TrackWriter) { for _, w := range writers { w.Stop() } diff --git a/test/multinode_test.go b/test/multinode_test.go index 25372e3af..35647def0 100644 --- a/test/multinode_test.go +++ b/test/multinode_test.go @@ -98,7 +98,6 @@ func TestConnectWithoutCreation(t *testing.T) { } } -// testing multiple scenarios rooms func TestMultinodePublishingUponJoining(t *testing.T) { if testing.Short() { t.SkipNow() @@ -169,6 +168,7 @@ func TestMultinodeDataPublishing(t *testing.T) { scenarioDataPublish(t) scenarioDataUnlabeledPublish(t) + scenarioDataTracksPublishingUponJoining(t) } func TestMultiNodeJoinAfterClose(t *testing.T) { diff --git a/test/scenarios.go b/test/scenarios.go index ef73aa412..3499bb0b8 100644 --- a/test/scenarios.go +++ b/test/scenarios.go @@ -148,7 +148,7 @@ func scenarioReceiveBeforePublish(t *testing.T) { func scenarioDataPublish(t *testing.T) { for _, useSinglePeerConnection := range []bool{false, true} { - t.Run(fmt.Sprintf("singlePeerConnection=%+v", useSinglePeerConnection), func(t *testing.T) { + t.Run(fmt.Sprintf("scenarioDataPublish/singlePeerConnection=%+v", useSinglePeerConnection), func(t *testing.T) { c1 := createRTCClient("dp1", defaultServerPort, useSinglePeerConnection, nil) c2 := createRTCClient("dp2", secondServerPort, useSinglePeerConnection, nil) waitUntilConnected(t, c1, c2) @@ -178,7 +178,7 @@ func scenarioDataPublish(t *testing.T) { func scenarioDataUnlabeledPublish(t *testing.T) { for _, useSinglePeerConnection := range []bool{false, true} { - t.Run(fmt.Sprintf("singlePeerConnection=%+v", useSinglePeerConnection), func(t *testing.T) { + t.Run(fmt.Sprintf("scenarioDataUnlabeledPublish/singlePeerConnection=%+v", useSinglePeerConnection), func(t *testing.T) { c1 := createRTCClient("dp1", defaultServerPort, useSinglePeerConnection, nil) c2 := createRTCClient("dp2", secondServerPort, useSinglePeerConnection, nil) waitUntilConnected(t, c1, c2) @@ -206,6 +206,93 @@ func scenarioDataUnlabeledPublish(t *testing.T) { } } +func scenarioDataTracksPublishingUponJoining(t *testing.T) { + for _, useSinglePeerConnection := range []bool{false, true} { + t.Run(fmt.Sprintf("scenarioDataTracksPublishingUponJoining/singlePeerConnection=%+v", useSinglePeerConnection), func(t *testing.T) { + c1 := createRTCClient("dtpuj_1", defaultServerPort, useSinglePeerConnection, nil) + c2 := createRTCClient("dtpuj_2", secondServerPort, useSinglePeerConnection, &testclient.Options{AutoSubscribe: true}) + c3 := createRTCClient("dtpuj_3", defaultServerPort, useSinglePeerConnection, &testclient.Options{AutoSubscribe: true}) + defer stopClients(c1, c2, c3) + + waitUntilConnected(t, c1, c2, c3) + + // c1 and c2 publishing, c3 just receiving + writers := publishDataTracksForClients(t, c1, c2) + defer stopWriters(writers...) + + logger.Infow("waiting to receive data tracks from c1 and c2") + testutils.WithTimeout(t, func() string { + tracks := c3.SubscribedDataTracks() + if len(tracks[c1.ID()]) != 2 { + return "did not receive data tracks from c1" + } + if len(tracks[c2.ID()]) != 2 { + return "did not receive data tracks from c2" + } + for _, dts := range tracks { + for _, dt := range dts { + if dt.NumReceivedPackets() == 0 { + return fmt.Sprintf("no packets received from %s", dt.ID()) + } + } + } + return "" + }) + + // after a delay, c2 reconnects, then publishing + time.Sleep(syncDelay) + c2.Stop() + + logger.Infow("waiting for c2 data tracks to be gone") + testutils.WithTimeout(t, func() string { + tracks := c3.SubscribedDataTracks() + + if len(tracks[c1.ID()]) != 2 { + return fmt.Sprintf("c3 should be subscribed to 2 data tracks from c1, actual: %d", len(tracks[c1.ID()])) + } + if len(tracks[c2.ID()]) != 0 { + return fmt.Sprintf("c3 should be subscribed to 0 data tracks from c2, actual: %d", len(tracks[c2.ID()])) + } + if len(c1.SubscribedDataTracks()[c2.ID()]) != 0 { + return fmt.Sprintf("c3 should be subscribed to 0 data tracks from c2, actual: %d", len(c1.SubscribedTracks()[c2.ID()])) + } + return "" + }) + + logger.Infow("c2 reconnecting") + // connect to a diff port + c2 = createRTCClient("dtpuj_2", defaultServerPort, useSinglePeerConnection, nil) + defer c2.Stop() + waitUntilConnected(t, c2) + writers = publishDataTracksForClients(t, c2) + defer stopWriters(writers...) + + testutils.WithTimeout(t, func() string { + tracks := c3.SubscribedDataTracks() + // new c2 data tracks should be published again + if len(tracks[c2.ID()]) != 2 { + return fmt.Sprintf("c3 should be subscribed to 2 data tracks from c2, actual: %d", len(tracks[c2.ID()])) + } + for _, dt := range tracks[c2.ID()] { + if dt.NumReceivedPackets() == 0 { + return fmt.Sprintf("c3 did not receive packets from c2 data track after reconnecting %s", dt.ID()) + } + } + + if len(c1.SubscribedDataTracks()[c2.ID()]) != 2 { + return fmt.Sprintf("c1 should be subscribed to 2 data tracks from c2, actual: %d", len(c1.SubscribedTracks()[c2.ID()])) + } + for _, dt := range c1.SubscribedDataTracks()[c2.ID()] { + if dt.NumReceivedPackets() == 0 { + return fmt.Sprintf("c1 did not receive packets from c2 data track after reconnecting %s", dt.ID()) + } + } + return "" + }) + }) + } +} + func scenarioJoinClosedRoom(t *testing.T) { for _, useSinglePeerConnection := range []bool{false, true} { t.Run(fmt.Sprintf("singlePeerConnection=%+v", useSinglePeerConnection), func(t *testing.T) { @@ -240,15 +327,15 @@ func closeNonRTCRoom(t *testing.T) { require.NoError(t, err) } -func publishTracksForClients(t *testing.T, clients ...*testclient.RTCClient) []*testclient.TrackWriter { +func publishTracksForClients(t *testing.T, clients ...*testclient.RTCClient) []testclient.TrackWriter { logger.Infow("publishing tracks for clients") - var writers []*testclient.TrackWriter + var writers []testclient.TrackWriter for i := range clients { c := clients[i] tw, err := c.AddStaticTrack("audio/opus", "audio", "webcam") require.NoError(t, err) - writers = append(writers, tw) + tw, err = c.AddStaticTrack("video/vp8", "video", "webcam") require.NoError(t, err) writers = append(writers, tw) @@ -256,6 +343,22 @@ func publishTracksForClients(t *testing.T, clients ...*testclient.RTCClient) []* return writers } +func publishDataTracksForClients(t *testing.T, clients ...*testclient.RTCClient) []testclient.TrackWriter { + logger.Infow("publishing data tracks for clients") + var writers []testclient.TrackWriter + for i := range clients { + c := clients[i] + dtw, err := c.PublishDataTrack() + require.NoError(t, err) + writers = append(writers, dtw) + + dtw, err = c.PublishDataTrack() + require.NoError(t, err) + writers = append(writers, dtw) + } + return writers +} + // Room service tests func roomServiceListRoom(t *testing.T) { diff --git a/test/singlenode_test.go b/test/singlenode_test.go index 510fcf08b..cd6147514 100644 --- a/test/singlenode_test.go +++ b/test/singlenode_test.go @@ -166,7 +166,7 @@ func TestSinglePublisher(t *testing.T) { c2 := createRTCClient("c2", defaultServerPort, useSinglePeerConnection, nil) waitUntilConnected(t, c1, c2) - // publish a track and ensure clients receive it ok + // publish an audio and video track and ensure clients receive it ok t1, err := c1.AddStaticTrack("audio/OPUS", "audio", "webcamaudio") require.NoError(t, err) defer t1.Stop() @@ -688,11 +688,7 @@ func TestDeviceCodecOverride(t *testing.T) { break } } - if desc == nil { - return false - } - - return true + return desc != nil }, waitTimeout, waitTick, "did not receive answer") hasSeenVP8 := false @@ -1018,3 +1014,79 @@ func TestFireTrackBySdp(t *testing.T) { }) } } + +func TestSinglePublisherDataTrack(t *testing.T) { + if testing.Short() { + t.SkipNow() + return + } + + s, finish := setupSingleNodeTest("TestSinglePublisherDataTrack") + defer finish() + + for _, useSinglePeerConnection := range []bool{false, true} { + t.Run(fmt.Sprintf("singlePeerConnection=%+v", useSinglePeerConnection), func(t *testing.T) { + c1 := createRTCClient("c1", defaultServerPort, useSinglePeerConnection, nil) + c2 := createRTCClient("c2", defaultServerPort, useSinglePeerConnection, nil) + waitUntilConnected(t, c1, c2) + + // publish a couple of data tracks and ensure clients receive it ok + dt1, err := c1.PublishDataTrack() + require.NoError(t, err) + defer dt1.Stop() + + dt2, err := c1.PublishDataTrack() + require.NoError(t, err) + defer dt2.Stop() + + testutils.WithTimeout(t, func() string { + if len(c2.SubscribedDataTracks()) == 0 { + return "c2 was not subscribed to any data tracks" + } + // should have received two data tracks + if len(c2.SubscribedDataTracks()[c1.ID()]) != 2 { + return "c2 didn't subscribe to both data tracks from c1" + } + return "" + }) + + // a new client joins and should get the initial stream + c3 := createRTCClient("c3", defaultServerPort, useSinglePeerConnection, nil) + + // ensure that new client that has joined also received data tracks + waitUntilConnected(t, c3) + testutils.WithTimeout(t, func() string { + if len(c3.SubscribedDataTracks()) == 0 { + return "c3 didn't subscribe to any data tracks" + } + // should have received two data tracks + if len(c3.SubscribedDataTracks()[c1.ID()]) != 2 { + return "c3 didn't subscribe to tracks from c1" + } + return "" + }) + + // ensure that the data track ids are generated by server + tracks := c3.SubscribedDataTracks()[c1.ID()] + for _, tr := range tracks { + require.True(t, strings.HasPrefix(string(tr.ID()), "DTR_"), "data track should begin with DTR") + } + + // when c3 disconnects, ensure subscriber is cleaned up correctly + c3.Stop() + + testutils.WithTimeout(t, func() string { + room := s.RoomManager().GetRoom(context.Background(), testRoom) + p := room.GetParticipant("c1") + require.NotNil(t, p) + + for _, t := range p.GetPublishedDataTracks() { + if t.IsSubscriber(c3.ID()) { + return "c3 was not a subscriber of c1's data tracks" + } + } + return "" + }) + }) + } +}