mirror of
https://github.com/livekit/livekit.git
synced 2026-05-11 03:57:12 +00:00
Merge remote-tracking branch 'origin/master' into raja_fr
This commit is contained in:
Binary file not shown.
|
After Width: | Height: | Size: 129 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 48 KiB |
@@ -1,3 +1,17 @@
|
||||
# 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.
|
||||
|
||||
name: Test
|
||||
|
||||
on:
|
||||
@@ -19,7 +33,7 @@ jobs:
|
||||
- run: redis-cli ping
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v3
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: '1.18'
|
||||
|
||||
|
||||
@@ -1,3 +1,17 @@
|
||||
# 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.
|
||||
|
||||
name: Release to Docker
|
||||
|
||||
# Controls when the action will run.
|
||||
@@ -25,7 +39,7 @@ jobs:
|
||||
type=semver,pattern=v{{major}}.{{minor}}
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v3
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: '>=1.18'
|
||||
|
||||
|
||||
@@ -1,3 +1,17 @@
|
||||
# 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.
|
||||
|
||||
name: Release
|
||||
|
||||
on:
|
||||
@@ -19,7 +33,7 @@ jobs:
|
||||
run: git fetch --force --tags
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v3
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: '>=1.18'
|
||||
|
||||
|
||||
+1
-1
@@ -24,6 +24,6 @@ proto/
|
||||
.DS_Store
|
||||
|
||||
# IDE
|
||||
.idea
|
||||
.idea/
|
||||
|
||||
dist/
|
||||
|
||||
@@ -1,3 +1,17 @@
|
||||
# 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.
|
||||
|
||||
before:
|
||||
hooks:
|
||||
- go mod tidy
|
||||
|
||||
Generated
-8
@@ -1,8 +0,0 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
-7
@@ -1,7 +0,0 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<profile version="1.0">
|
||||
<option name="myName" value="Project Default" />
|
||||
<inspection_tool class="GoCommentStart" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="GoNameStartsWithPackageName" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
||||
</profile>
|
||||
</component>
|
||||
Generated
-19
@@ -1,19 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="WEB_MODULE" version="4">
|
||||
<component name="Go" enabled="true">
|
||||
<buildTags>
|
||||
<option name="customFlags">
|
||||
<array>
|
||||
<option value="wireinject" />
|
||||
</array>
|
||||
</option>
|
||||
</buildTags>
|
||||
</component>
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<excludeFolder url="file://$MODULE_DIR$/bin" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
Generated
-8
@@ -1,8 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/livekit-server.iml" filepath="$PROJECT_DIR$/.idea/livekit-server.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
Generated
-20
@@ -1,20 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProtobufLanguageSettings">
|
||||
<option name="autoConfigEnabled" value="false" />
|
||||
<option name="importPathEntries">
|
||||
<list>
|
||||
<ImportPathEntry>
|
||||
<option name="location" value="jar://$APPLICATION_PLUGINS_DIR$/protobuf-editor.jar!/include" />
|
||||
</ImportPathEntry>
|
||||
<ImportPathEntry>
|
||||
<option name="location" value="file://$PROJECT_DIR$/proto" />
|
||||
</ImportPathEntry>
|
||||
<ImportPathEntry>
|
||||
<option name="location" value="file://$USER_HOME$/Library/Caches/JetBrains/GoLand2022.3/protoeditor" />
|
||||
</ImportPathEntry>
|
||||
</list>
|
||||
</option>
|
||||
<option name="descriptorPath" value="google/protobuf/descriptor.proto" />
|
||||
</component>
|
||||
</project>
|
||||
Generated
-6
@@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
@@ -2,6 +2,148 @@
|
||||
|
||||
This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [1.4.4] - 2023-07-08
|
||||
|
||||
### Added
|
||||
- Add dependency descriptor stream tracker for svc codecs (#1788)
|
||||
- Full reconnect on publication mismatch on resume. (#1823)
|
||||
- Pacer interface in down stream path. (#1835)
|
||||
- retry egress on timeout/resource exhausted (#1852)
|
||||
|
||||
### Fixed
|
||||
- Send Room metadata updates immediately after update (#1787)
|
||||
- Do not send ParticipantJoined webhook if connection was resumed (#1795)
|
||||
- Reduce memory leaks by avoiding references in closure. (#1809)
|
||||
- Honor bind address passed as `--bind` also for RTC ports (#1815)
|
||||
- Avoid dangling downtracks by always deleting them in receiver close. (#1842)
|
||||
- Better cleanup of subscriptions with needsCleanup. (#1845)
|
||||
- Fix nack issue for svc codecs (#1856)
|
||||
- Fixed hidden participant update were still sent when track is published (#1857)
|
||||
- Fixed Redis lockup when unlocking room with canceled request context (#1859)
|
||||
|
||||
### Changed
|
||||
- Improvements to A/V sync (#1773 #1781 #1784 )
|
||||
- Improved probing to be less disruptive in low bandwidth scenarios (#1782 #1834 #1839)
|
||||
- Do not mute forwarder when paused due to bandwidth congestion. (#1796)
|
||||
- Improvements to congestion controller (#1800 #1802 )
|
||||
- Close participant on full reconnect. (#1818)
|
||||
- Do not process events after participant close. (#1824)
|
||||
- Improvements to dependency descriptor based selection forwarder (#1808)
|
||||
- Discount out-of-order packets in downstream score. (#1831)
|
||||
- Adaptive stream to select highest layer of equal dimensions (#1841)
|
||||
- Return 404 with DeleteRoom/RemoveParticipant when deleting non-existent resources (#1860)
|
||||
|
||||
## [1.4.3] - 2023-06-03
|
||||
|
||||
### Added
|
||||
- Send quality stats to prometheus. (#1708)
|
||||
- Support for disabling publishing codec on specific devices (#1728)
|
||||
- Add support for bypass_transcoding field in ingress (#1741)
|
||||
- Include await_start_signal for Web Egress (#1759)
|
||||
|
||||
### Fixed
|
||||
- Handle time stamp increment across mute for A/V sync (#1705)
|
||||
- Additional A/V sync improvements (#1712 #1724 #1737 #1738 #1764)
|
||||
- Check egress status on UpdateStream failure (#1716)
|
||||
- Start signal relay sessions with the correct node (#1721)
|
||||
- Fix unwrap for out-of-order packet (#1729)
|
||||
- Fix dynacast for svc codec (#1742 #1743)
|
||||
- Ignore receiver reports that have a sequence number before first packet (#1745)
|
||||
- Fix node stats updates on Windows (#1748)
|
||||
- Avoid reconnect loop for unsupported downtrack (#1754)
|
||||
- Perform unsubscribe in parallel to avoid blocking (#1760)
|
||||
|
||||
### Changed
|
||||
- Make signal close async. (#1711 #1722)
|
||||
- Don't add nack if it is already present in track codec (#1714)
|
||||
- Tweaked connection quality algorithm to be less sensitive to jitter (#1719)
|
||||
- Adjust sender report time stamp for slow publishers (#1740)
|
||||
- Split probe controller from StreamAllocator (#1751)
|
||||
|
||||
## [1.4.2] - 2023-04-27
|
||||
### Added
|
||||
- VP9 codec with SVC support (#1586)
|
||||
- Support for source-specific permissions and client-initiated metadata updates (#1590)
|
||||
- Batch support for signal relay (#1593 #1596)
|
||||
- Support for simulating subscriber bandwidth (#1609)
|
||||
- Support for subscription limits (#1629)
|
||||
- Send Room updates when participant counts change (#1647)
|
||||
|
||||
### Fixed
|
||||
- Fixed process return code to 0 (#1589)
|
||||
- Fixed VP9 stutter when not using dependency descriptors (#1595)
|
||||
- Fixed stutter when using dependency descriptors (#1600)
|
||||
- Fixed Redis cluster support when using Egress or Ingress (#1606)
|
||||
- Fixed simulcast parsing error for slower clients (camera and screenshare) (#1621)
|
||||
- Don't close RTCP reader if Downtrack will be resumed (#1632)
|
||||
- Restore VP8 munger state properly. (#1634)
|
||||
- Fixed incorrect node routing when using signal relay (#1645)
|
||||
- Do not send hidden participants to others after resume (#1689)
|
||||
- Fix for potential webhook delivery delays (#1690)
|
||||
|
||||
### Changed
|
||||
- Refactored video layer selector (#1588 #1591 #1592)
|
||||
- Improved transport fallback when client is resuming (#1597)
|
||||
- Improved webhook reliability with delivery retries (#1607 #1615)
|
||||
- Congestion controller improvements (#1614 #1616 #1617 #1623 #1628 #1631 #1652)
|
||||
- Reduced memory usage by releasing ParticipantInfo after JoinResponse is transmitted (#1619)
|
||||
- Ensure safe access in sequencer (#1625)
|
||||
- Run quality scorer when there are no streams. (#1633)
|
||||
- Participant version is only incremented after updates (#1646)
|
||||
- Connection quality attribution improvements (#1653 #1664)
|
||||
- Remove disallowed subscriptions on close. (#1668)
|
||||
- A/V sync improvements (#1681 #1684 #1687 #1693 #1695 #1696 #1698 #1704)
|
||||
- RTCP sender reports every three seconds. (#1692)
|
||||
|
||||
### Removed
|
||||
- Remove deprecated (non-psrpc) egress client (#1701)
|
||||
|
||||
## [1.4.1] - 2023-04-05
|
||||
### Added
|
||||
- Added prometheus metrics for internal signaling API #1571
|
||||
|
||||
### Fixed
|
||||
- Fix regressions in RTC when using redis with psrpc signaling #1584 #1582 #1580 #1567
|
||||
- Fix required bitrate assessment under channel congestion #1577
|
||||
|
||||
### Changed
|
||||
- Improve DTLS reliability in regions with internet filters #1568
|
||||
- Reduce memory usage from logging #1576
|
||||
|
||||
## [1.4.0] - 2023-03-27
|
||||
### Added
|
||||
- Added config to disable active RED encoding. Use NACK instead #1476 #1477
|
||||
- Added option to skip TCP fallback if TCP RTT is high #1484
|
||||
- psrpc based signaling between signal and RTC #1485
|
||||
- Connection quality algorithm revamp #1490 #1491 #1493 #1496 #1497 #1500 #1505 #1507 #1509 #1516 #1520 #1521 #1527 #1528 #1536
|
||||
- Support for topics in data channel messages #1489
|
||||
- Added active filter to ListEgress #1517
|
||||
- Handling for React Native and Rust SDK ClientInfo #1544
|
||||
|
||||
### Fixed
|
||||
- Fixed unsubscribed speakers stuck as speaking to clients #1475
|
||||
- Do not include packet in RED if timestamp is too far back #1478
|
||||
- Prevent PLI layer lock getting stuck #1481
|
||||
- Fix a case of changing video quality not succeeding #1483
|
||||
- Resync on pub muted for audio to avoid jump in sequence numbers on unmute #1487
|
||||
- Fixed a case of data race #1492
|
||||
- Inform reconnecting participant about recently disconnected users #1495
|
||||
- Send room update that may be missed by reconnected participant #1499
|
||||
- Fixed regression for AV1 forwarding #1538
|
||||
- Ensure sequence number continuity #1539
|
||||
- Give proper grace period when recorder is still in the room #1547
|
||||
- Fix sequence number offset on packet drop #1556
|
||||
- Fix signal client message buffer size #1561
|
||||
|
||||
### Changed
|
||||
- Reduce lock scope getting RTCP sender reports #1473
|
||||
- Avoid duplicate queueReconcile in subscription manager #1474
|
||||
- Do not log TURN errors with prefix "error when handling datagram" #1494
|
||||
- Improvements to TCP fallback mode #1498
|
||||
- Unify forwarder between dependency descriptor and no DD case. #1543
|
||||
- Increase sequence number cache to handle high rate tracks #1560
|
||||
|
||||
|
||||
## [1.3.5] - 2023-02-25
|
||||
### Added
|
||||
- Allow for strict ACKs to be disabled or subscriber peer connections #1410
|
||||
|
||||
+14
@@ -1,3 +1,17 @@
|
||||
# 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.
|
||||
|
||||
FROM golang:1.20-alpine as builder
|
||||
|
||||
ARG TARGETPLATFORM
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
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.
|
||||
@@ -1,7 +1,15 @@
|
||||
# LiveKit: High-performance WebRTC
|
||||
<!--BEGIN_BANNER_IMAGE-->
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="/.github/banner_dark.png">
|
||||
<source media="(prefers-color-scheme: light)" srcset="/.github/banner_light.png">
|
||||
<img style="width:100%;" alt="The LiveKit icon, the name of the repository and some sample code in the background." src="/.github/banner_light.png">
|
||||
</picture>
|
||||
<!--END_BANNER_IMAGE-->
|
||||
|
||||
LiveKit is an open source project that provides scalable, multi-user conferencing based on WebRTC. It's designed to
|
||||
provide everything you need to build real-time video/audio/data capabilities in your applications.
|
||||
# LiveKit: Real-time video, audio and data for developers
|
||||
|
||||
[LiveKit](https://livekit.io) is an open source project that provides scalable, multi-user conferencing based on WebRTC.
|
||||
It's designed to provide everything you need to build real-time video audio data capabilities in your applications.
|
||||
|
||||
LiveKit's server is written in Go, using the awesome [Pion WebRTC](https://github.com/pion/webrtc) implementation.
|
||||
|
||||
@@ -9,7 +17,7 @@ LiveKit's server is written in Go, using the awesome [Pion WebRTC](https://githu
|
||||
[](https://livekit.io/join-slack)
|
||||
[](https://twitter.com/livekitted)
|
||||
[](https://github.com/livekit/livekit/releases/latest)
|
||||
[](https://github.com/livekit/livekit/actions/workflows/buildtest.yaml)
|
||||
[](https://github.com/livekit/livekit/actions/workflows/buildtest.yaml)
|
||||
[](https://github.com/livekit/livekit/blob/master/LICENSE)
|
||||
|
||||
## Features
|
||||
@@ -32,10 +40,12 @@ LiveKit's server is written in Go, using the awesome [Pion WebRTC](https://githu
|
||||
|
||||
https://docs.livekit.io
|
||||
|
||||
## Try it live
|
||||
## Live Demos
|
||||
|
||||
Head to [our playground](https://livekit.io/playground) and give it a spin. Build a Zoom-like conferencing app in under
|
||||
100 lines of code!
|
||||
- [LiveKit Meet](https://meet.livekit.io) ([source](https://github.com/livekit-examples/meet))
|
||||
- [Spatial Audio](https://spatial-audio-demo.livekit.io/) ([source](https://github.com/livekit-examples/spatial-audio))
|
||||
- Livestreaming from OBS Studio ([source](https://github.com/livekit-examples/livestream))
|
||||
- [AI voice assistant using ChatGPT](https://livekit.io/kitt) ([source](https://github.com/livekit-examples/kitt))
|
||||
|
||||
## SDKs & Tools
|
||||
|
||||
@@ -107,8 +117,9 @@ Client SDKs enable your frontend to include interactive, multi-user experiences.
|
||||
<a href="https://github.com/livekit/client-sdk-android/tree/main/sample-app-compose/src/main/java/io/livekit/android/composesample" target="_blank" rel="noopener noreferrer">Compose example</a>
|
||||
</td>
|
||||
</tr>
|
||||
<!-- Flutter -->
|
||||
<tr>
|
||||
<td>Flutter</td>
|
||||
<td>Flutter (all platforms)</td>
|
||||
<td>
|
||||
<a href="https://github.com/livekit/client-sdk-flutter" target="_blank" rel="noopener noreferrer">client-sdk-flutter</a>
|
||||
</td>
|
||||
@@ -139,6 +150,15 @@ Client SDKs enable your frontend to include interactive, multi-user experiences.
|
||||
<td>native</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<!-- Rust -->
|
||||
<tr>
|
||||
<td>Rust</td>
|
||||
<td>
|
||||
<a href="https://github.com/livekit/client-sdk-rust" target="_blank" rel="noopener noreferrer">client-sdk-rust</a>
|
||||
</td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
### Server SDKs
|
||||
@@ -159,8 +179,9 @@ enabling you to build automations that behave like end-users.
|
||||
|
||||
### Ecosystem & Tools
|
||||
|
||||
- [Egress](https://github.com/livekit/egress) - export and record your rooms
|
||||
- [CLI](https://github.com/livekit/livekit-cli) - command line interface & load tester
|
||||
- [Egress](https://github.com/livekit/egress) - export and record your rooms
|
||||
- [Ingress](https://github.com/livekit/ingress) - ingest streams from RTMP / OBS Studio
|
||||
- [Docker image](https://hub.docker.com/r/livekit/livekit-server)
|
||||
- [Helm charts](https://github.com/livekit/livekit-helm)
|
||||
|
||||
@@ -235,11 +256,14 @@ simulation.
|
||||
## Deployment
|
||||
|
||||
### Use LiveKit Cloud
|
||||
LiveKit Cloud is the fastest and most reliable way to run LiveKit. Every project gets free monthly bandwidth and transcoding credits.
|
||||
|
||||
LiveKit Cloud is the fastest and most reliable way to run LiveKit. Every project gets free monthly bandwidth and
|
||||
transcoding credits.
|
||||
|
||||
Sign up for [LiveKit Cloud](https://cloud.livekit.io/).
|
||||
|
||||
### Self-host
|
||||
|
||||
Read our [deployment docs](https://docs.livekit.io/deploy/) for more information.
|
||||
|
||||
## Building from source
|
||||
@@ -266,3 +290,15 @@ We welcome your contributions toward improving LiveKit! Please join us
|
||||
## License
|
||||
|
||||
LiveKit server is licensed under Apache License v2.0.
|
||||
|
||||
<!--BEGIN_REPO_NAV-->
|
||||
<br/><table>
|
||||
<thead><tr><th colspan="2">LiveKit Ecosystem</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td>Client SDKs</td><td><a href="https://github.com/livekit/components-js">Components</a> · <a href="https://github.com/livekit/client-sdk-js">JavaScript</a> · <a href="https://github.com/livekit/client-sdk-swift">iOS/macOS</a> · <a href="https://github.com/livekit/client-sdk-android">Android</a> · <a href="https://github.com/livekit/client-sdk-flutter">Flutter</a> · <a href="https://github.com/livekit/client-sdk-react-native">React Native</a> · <a href="https://github.com/livekit/client-sdk-rust">Rust</a> · <a href="https://github.com/livekit/client-sdk-python">Python</a> · <a href="https://github.com/livekit/client-sdk-unity-web">Unity (web)</a> · <a href="https://github.com/livekit/client-sdk-unity">Unity (beta)</a></td></tr><tr></tr>
|
||||
<tr><td>Server SDKs</td><td><a href="https://github.com/livekit/server-sdk-js">Node.js</a> · <a href="https://github.com/livekit/server-sdk-go">Golang</a> · <a href="https://github.com/livekit/server-sdk-ruby">Ruby</a> · <a href="https://github.com/livekit/server-sdk-kotlin">Java/Kotlin</a> · <a href="https://github.com/agence104/livekit-server-sdk-php">PHP (community)</a> · <a href="https://github.com/tradablebits/livekit-server-sdk-python">Python (community)</a></td></tr><tr></tr>
|
||||
<tr><td>Services</td><td><b>Livekit server</b> · <a href="https://github.com/livekit/egress">Egress</a> · <a href="https://github.com/livekit/ingress">Ingress</a></td></tr><tr></tr>
|
||||
<tr><td>Resources</td><td><a href="https://docs.livekit.io">Docs</a> · <a href="https://github.com/livekit-examples">Example apps</a> · <a href="https://livekit.io/cloud">Cloud</a> · <a href="https://docs.livekit.io/oss/deployment">Self-hosting</a> · <a href="https://github.com/livekit/livekit-cli">CLI</a></td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<!--END_REPO_NAV-->
|
||||
|
||||
@@ -1,4 +1,18 @@
|
||||
#!/bin/bash
|
||||
# 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.
|
||||
|
||||
|
||||
if ! command -v mage &> /dev/null
|
||||
then
|
||||
|
||||
@@ -1,3 +1,17 @@
|
||||
// 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 main
|
||||
|
||||
import (
|
||||
|
||||
+35
-5
@@ -1,8 +1,23 @@
|
||||
// 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 main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"net"
|
||||
"os"
|
||||
"os/signal"
|
||||
"runtime"
|
||||
@@ -12,7 +27,6 @@ import (
|
||||
|
||||
"github.com/urfave/cli/v2"
|
||||
|
||||
serverlogger "github.com/livekit/livekit-server/pkg/logger"
|
||||
"github.com/livekit/livekit-server/pkg/rtc"
|
||||
"github.com/livekit/livekit-server/pkg/telemetry/prometheus"
|
||||
"github.com/livekit/protocol/logger"
|
||||
@@ -103,8 +117,9 @@ func init() {
|
||||
|
||||
func main() {
|
||||
defer func() {
|
||||
rtc.Recover(logger.GetLogger())
|
||||
os.Exit(1)
|
||||
if rtc.Recover(logger.GetLogger()) != nil {
|
||||
os.Exit(1)
|
||||
}
|
||||
}()
|
||||
|
||||
generatedFlags, err := config.GenerateCLIFlags(baseFlags, true)
|
||||
@@ -187,7 +202,7 @@ func getConfig(c *cli.Context) (*config.Config, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
serverlogger.InitFromConfig(conf.Logging)
|
||||
config.InitLoggerFromConfig(conf.Logging)
|
||||
|
||||
if c.String("config") == "" && c.String("config-body") == "" && conf.Development {
|
||||
// use single port UDP when no config is provided
|
||||
@@ -204,11 +219,26 @@ func getConfig(c *cli.Context) (*config.Config, error) {
|
||||
conf.Keys = map[string]string{
|
||||
"devkey": "secret",
|
||||
}
|
||||
shouldMatchRTCIP := false
|
||||
// when dev mode and using shared keys, we'll bind to localhost by default
|
||||
if conf.BindAddresses == nil {
|
||||
conf.BindAddresses = []string{
|
||||
"127.0.0.1",
|
||||
"[::1]",
|
||||
"::1",
|
||||
}
|
||||
} else {
|
||||
// if non-loopback addresses are provided, then we'll match RTC IP to bind address
|
||||
// our IP discovery ignores loopback addresses
|
||||
for _, addr := range conf.BindAddresses {
|
||||
ip := net.ParseIP(addr)
|
||||
if ip != nil && !ip.IsLoopback() && !ip.IsUnspecified() {
|
||||
shouldMatchRTCIP = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if shouldMatchRTCIP {
|
||||
for _, bindAddr := range conf.BindAddresses {
|
||||
conf.RTC.IPs.Includes = append(conf.RTC.IPs.Includes, bindAddr+"/24")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,17 @@
|
||||
// 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 main
|
||||
|
||||
import (
|
||||
|
||||
+26
-9
@@ -163,6 +163,23 @@ keys:
|
||||
# urls:
|
||||
# - https://your-host.com/handler
|
||||
|
||||
# Signal Relay
|
||||
# since v1.4.0, a more reliable, psrpc based signal relay is available
|
||||
# this gives us the ability to reliably proxy messages between a signal server and RTC node
|
||||
# signal_relay:
|
||||
# # disabled by default. will be enabled by default in future versions
|
||||
# enabled: true
|
||||
# # amount of time a message delivery is tried before giving up
|
||||
# retry_timeout: 30s
|
||||
# # minimum amount of time to wait for RTC node to ack,
|
||||
# # retries use exponentially increasing wait on every subsequent try
|
||||
# # with an upper bound of max_retry_interval
|
||||
# min_retry_interval: 500ms
|
||||
# # maximum amount of time to wait for RTC node to ack
|
||||
# max_retry_interval: 5s
|
||||
# # number of messages to buffer before dropping
|
||||
# stream_buffer_size: 1000
|
||||
|
||||
# customize audio level sensitivity
|
||||
# audio:
|
||||
# # minimum level to be considered active, 0-127, where 0 is loudest
|
||||
@@ -195,7 +212,7 @@ keys:
|
||||
# # set UDP port range for TURN relay to connect to LiveKit SFU, by default it uses a any available port
|
||||
# relay_range_start: 1024
|
||||
# relay_range_end: 30000
|
||||
# # set external_tl to true if using a L4 load balancer to terminate TLS. when enabled,
|
||||
# # set external_tls to true if using a L4 load balancer to terminate TLS. when enabled,
|
||||
# # LiveKit expects unencrypted traffic on tls_port, and still advertise tls_port as a TURN/TLS candidate.
|
||||
# external_tls: true
|
||||
# # needs to match tls cert domain
|
||||
@@ -207,15 +224,9 @@ keys:
|
||||
# ingress server
|
||||
# ingress:
|
||||
# # Prefix used to generate RTMP URLs for RTMP ingress.
|
||||
# # The stream_key will be appended to this base and returned as part of the
|
||||
# # ingress info
|
||||
# rtmp_base_url: "rtmp://my.domain.com/live"
|
||||
|
||||
# egress server
|
||||
# egress:
|
||||
# # Whether to use the PSRPC enabled RPC implementation. This requires livekit egress version >=1.5.4
|
||||
# # The legacy, non PSRPC RPC implementation will be removed eventually
|
||||
# use_psrpc: false
|
||||
# # Prefix used to generate WHIP URLs for WHIP ingress.
|
||||
# whip_base_url: "http://my.domain.com/whip"
|
||||
|
||||
# Region of the current node. Required if using regionaware node selector
|
||||
# region: us-west-2
|
||||
@@ -244,3 +255,9 @@ keys:
|
||||
# num_tracks: -1
|
||||
# # defaults to 1 GB/s, or just under 10 Gbps
|
||||
# bytes_per_sec: 1_000_000_000
|
||||
# # how many tracks (audio / video) that a single participant can subscribe at same time.
|
||||
# # if the limit is exceeded, subscriptions will be pending until any subscribed track has been unsubscribed.
|
||||
# # value less or equal than 0 means no limit.
|
||||
# subscription_limit_video: 0
|
||||
# subscription_limit_audio: 0
|
||||
|
||||
|
||||
@@ -4,52 +4,50 @@ go 1.18
|
||||
|
||||
require (
|
||||
github.com/bep/debounce v1.2.1
|
||||
github.com/d5/tengo/v2 v2.13.0
|
||||
github.com/d5/tengo/v2 v2.16.1
|
||||
github.com/dustin/go-humanize v1.0.1
|
||||
github.com/elliotchance/orderedmap/v2 v2.2.0
|
||||
github.com/florianl/go-tc v0.4.2
|
||||
github.com/frostbyte73/core v0.0.4
|
||||
github.com/gammazero/deque v0.1.0
|
||||
github.com/gammazero/workerpool v1.1.2
|
||||
github.com/frostbyte73/core v0.0.9
|
||||
github.com/gammazero/deque v0.2.1
|
||||
github.com/gammazero/workerpool v1.1.3
|
||||
github.com/google/wire v0.5.0
|
||||
github.com/gorilla/websocket v1.5.0
|
||||
github.com/hashicorp/go-version v1.6.0
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.1
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.4
|
||||
github.com/jxskiss/base62 v1.1.0
|
||||
github.com/livekit/mageutil v0.0.0-20230125210925-54e8a70427c1
|
||||
github.com/livekit/mediatransportutil v0.0.0-20230130133657-96cfb115473a
|
||||
github.com/livekit/protocol v1.5.0
|
||||
github.com/livekit/psrpc v0.2.10-0.20230310095745-5cd63568998d
|
||||
github.com/mackerelio/go-osstat v0.2.3
|
||||
github.com/magefile/mage v1.14.0
|
||||
github.com/maxbrunsfeld/counterfeiter/v6 v6.6.1
|
||||
github.com/livekit/mediatransportutil v0.0.0-20230716190407-fc4944cbc33a
|
||||
github.com/livekit/protocol v1.5.11-0.20230729124740-d45d830f69e2
|
||||
github.com/livekit/psrpc v0.3.2
|
||||
github.com/mackerelio/go-osstat v0.2.4
|
||||
github.com/magefile/mage v1.15.0
|
||||
github.com/maxbrunsfeld/counterfeiter/v6 v6.6.2
|
||||
github.com/mitchellh/go-homedir v1.1.0
|
||||
github.com/olekukonko/tablewriter v0.0.5
|
||||
github.com/pion/ice/v2 v2.3.1
|
||||
github.com/pion/interceptor v0.1.12
|
||||
github.com/pion/logging v0.2.2
|
||||
github.com/pion/dtls/v2 v2.2.7
|
||||
github.com/pion/ice/v2 v2.3.9
|
||||
github.com/pion/interceptor v0.1.17
|
||||
github.com/pion/rtcp v1.2.10
|
||||
github.com/pion/rtp v1.7.13
|
||||
github.com/pion/rtp v1.8.0
|
||||
github.com/pion/sdp/v3 v3.0.6
|
||||
github.com/pion/stun v0.4.0
|
||||
github.com/pion/transport/v2 v2.0.2
|
||||
github.com/pion/turn/v2 v2.1.0
|
||||
github.com/pion/webrtc/v3 v3.1.56
|
||||
github.com/pion/transport/v2 v2.2.1
|
||||
github.com/pion/turn/v2 v2.1.2
|
||||
github.com/pion/webrtc/v3 v3.2.13
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/prometheus/client_golang v1.14.0
|
||||
github.com/redis/go-redis/v9 v9.0.2
|
||||
github.com/rs/cors v1.8.3
|
||||
github.com/sebest/xff v0.0.0-20210106013422-671bd2870b3a
|
||||
github.com/stretchr/testify v1.8.2
|
||||
github.com/prometheus/client_golang v1.16.0
|
||||
github.com/redis/go-redis/v9 v9.0.5
|
||||
github.com/rs/cors v1.9.0
|
||||
github.com/stretchr/testify v1.8.4
|
||||
github.com/thoas/go-funk v0.9.3
|
||||
github.com/twitchtv/twirp v8.1.3+incompatible
|
||||
github.com/ua-parser/uap-go v0.0.0-20211112212520-00c877edfe0f
|
||||
github.com/urfave/cli/v2 v2.24.2
|
||||
github.com/urfave/cli/v2 v2.25.7
|
||||
github.com/urfave/negroni/v3 v3.0.0
|
||||
go.uber.org/atomic v1.10.0
|
||||
go.uber.org/zap v1.24.0
|
||||
golang.org/x/sync v0.1.0
|
||||
google.golang.org/protobuf v1.29.0
|
||||
go.uber.org/atomic v1.11.0
|
||||
golang.org/x/exp v0.0.0-20230728194245-b0cb94b80691
|
||||
golang.org/x/sync v0.3.0
|
||||
google.golang.org/protobuf v1.31.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
)
|
||||
|
||||
@@ -61,44 +59,48 @@ require (
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
github.com/eapache/channels v1.1.0 // indirect
|
||||
github.com/eapache/queue v1.1.0 // indirect
|
||||
github.com/go-logr/logr v1.2.3 // indirect
|
||||
github.com/go-jose/go-jose/v3 v3.0.0 // indirect
|
||||
github.com/go-logr/logr v1.2.4 // indirect
|
||||
github.com/golang/protobuf v1.5.3 // indirect
|
||||
github.com/google/go-cmp v0.5.9 // indirect
|
||||
github.com/google/subcommands v1.2.0 // indirect
|
||||
github.com/google/uuid v1.3.0 // indirect
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
||||
github.com/hashicorp/go-retryablehttp v0.7.4 // indirect
|
||||
github.com/josharian/native v1.1.0 // indirect
|
||||
github.com/lithammer/shortuuid/v3 v3.0.7 // indirect
|
||||
github.com/klauspost/compress v1.16.5 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.0.9 // indirect
|
||||
github.com/lithammer/shortuuid/v4 v4.0.0 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.9 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
|
||||
github.com/mdlayher/netlink v1.7.1 // indirect
|
||||
github.com/mdlayher/socket v0.4.0 // indirect
|
||||
github.com/nats-io/nats.go v1.24.0 // indirect
|
||||
github.com/nats-io/nkeys v0.3.0 // indirect
|
||||
github.com/nats-io/nats.go v1.26.0 // indirect
|
||||
github.com/nats-io/nkeys v0.4.4 // indirect
|
||||
github.com/nats-io/nuid v1.0.1 // indirect
|
||||
github.com/pion/datachannel v1.5.5 // indirect
|
||||
github.com/pion/dtls/v2 v2.2.6 // indirect
|
||||
github.com/pion/logging v0.2.2 // indirect
|
||||
github.com/pion/mdns v0.0.7 // indirect
|
||||
github.com/pion/randutil v0.1.0 // indirect
|
||||
github.com/pion/sctp v1.8.6 // indirect
|
||||
github.com/pion/srtp/v2 v2.0.12 // indirect
|
||||
github.com/pion/udp/v2 v2.0.1 // indirect
|
||||
github.com/pion/sctp v1.8.7 // indirect
|
||||
github.com/pion/srtp/v2 v2.0.15 // indirect
|
||||
github.com/pion/stun v0.6.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/prometheus/client_model v0.3.0 // indirect
|
||||
github.com/prometheus/common v0.37.0 // indirect
|
||||
github.com/prometheus/procfs v0.8.0 // indirect
|
||||
github.com/prometheus/common v0.42.0 // indirect
|
||||
github.com/prometheus/procfs v0.10.1 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
|
||||
github.com/zeebo/xxh3 v1.0.2 // indirect
|
||||
go.uber.org/multierr v1.6.0 // indirect
|
||||
golang.org/x/crypto v0.7.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20230310171629-522b1b587ee0 // indirect
|
||||
golang.org/x/mod v0.8.0 // indirect
|
||||
golang.org/x/net v0.8.0 // indirect
|
||||
golang.org/x/sys v0.6.0 // indirect
|
||||
golang.org/x/text v0.8.0 // indirect
|
||||
golang.org/x/tools v0.6.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4 // indirect
|
||||
google.golang.org/grpc v1.53.0 // indirect
|
||||
gopkg.in/square/go-jose.v2 v2.6.0 // indirect
|
||||
go.uber.org/zap v1.24.0 // indirect
|
||||
golang.org/x/crypto v0.10.0 // indirect
|
||||
golang.org/x/mod v0.11.0 // indirect
|
||||
golang.org/x/net v0.11.0 // indirect
|
||||
golang.org/x/sys v0.9.0 // indirect
|
||||
golang.org/x/text v0.10.0 // indirect
|
||||
golang.org/x/tools v0.9.3 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc // indirect
|
||||
google.golang.org/grpc v1.57.0 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
)
|
||||
|
||||
@@ -1,71 +1,21 @@
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
|
||||
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
|
||||
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
|
||||
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
|
||||
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
|
||||
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
|
||||
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
|
||||
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
|
||||
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
|
||||
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
|
||||
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
|
||||
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
|
||||
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
|
||||
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
||||
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
|
||||
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
|
||||
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
|
||||
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
|
||||
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
|
||||
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
||||
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
|
||||
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
|
||||
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
|
||||
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
|
||||
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
|
||||
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
|
||||
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
|
||||
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
|
||||
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
|
||||
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
|
||||
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/bep/debounce v1.2.1 h1:v67fRdBA9UQu2NhLFXrSg0Brw7CexQekrBwDMM8bzeY=
|
||||
github.com/bep/debounce v1.2.1/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0=
|
||||
github.com/bsm/ginkgo/v2 v2.5.0 h1:aOAnND1T40wEdAtkGSkvSICWeQ8L3UASX7YVCqQx+eQ=
|
||||
github.com/bsm/gomega v1.20.0 h1:JhAwLmtRzXFTx2AkALSLa8ijZafntmhSoU63Ok18Uq8=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/bsm/ginkgo/v2 v2.7.0 h1:ItPMPH90RbmZJt5GtkcNvIRuGEdwlBItdNVoyzaNQao=
|
||||
github.com/bsm/gomega v1.26.0 h1:LhQm+AFcgV2M0WyKroMASzAzCAJVpAxQXv4SaI9a69Y=
|
||||
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/cilium/ebpf v0.5.0/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs=
|
||||
github.com/cilium/ebpf v0.7.0/go.mod h1:/oI2+1shJiTGAMgl6/RgJr36Eo1jzrRcAWbcXO2usCA=
|
||||
github.com/cilium/ebpf v0.8.1 h1:bLSSEbBLqGPXxls55pGr5qWZaTqcmfDJHhou7t254ao=
|
||||
github.com/cilium/ebpf v0.8.1/go.mod h1:f5zLIM0FSNuAkSyLAN7X+Hy6yznlF1mNiWUMfxMtrgk=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/d5/tengo/v2 v2.13.0 h1:4pZ5mR4vjOejpp+PMeIMpjZdObK7iwWoLTpVyhT+0Jk=
|
||||
github.com/d5/tengo/v2 v2.13.0/go.mod h1:XRGjEs5I9jYIKTxly6HCF8oiiilk5E/RYXOZ5b0DZC8=
|
||||
github.com/d5/tengo/v2 v2.16.1 h1:/N6dqiGu9toqANInZEOQMM8I06icdZnmb+81DG/lZdw=
|
||||
github.com/d5/tengo/v2 v2.16.1/go.mod h1:XRGjEs5I9jYIKTxly6HCF8oiiilk5E/RYXOZ5b0DZC8=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
@@ -79,76 +29,40 @@ github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc=
|
||||
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
|
||||
github.com/elliotchance/orderedmap/v2 v2.2.0 h1:7/2iwO98kYT4XkOjA9mBEIwvi4KpGB4cyHeOFOnj4Vk=
|
||||
github.com/elliotchance/orderedmap/v2 v2.2.0/go.mod h1:85lZyVbpGaGvHvnKa7Qhx7zncAdBIBq6u56Hb1PRU5Q=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/florianl/go-tc v0.4.2 h1:jan5zcOWCLhA9SRBHZhQ0SSAq7cmDUagiRPngAi5AOQ=
|
||||
github.com/florianl/go-tc v0.4.2/go.mod h1:2W1jSMFryiYlpQigr4ZpSSpE9XNze+bW7cTsCXWbMwo=
|
||||
github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k=
|
||||
github.com/frankban/quicktest v1.14.0/go.mod h1:NeW+ay9A/U67EYXNFA1nPE8e/tnQv/09mUdL/ijj8og=
|
||||
github.com/frostbyte73/core v0.0.4 h1:CwwoYfKPdNSO/QbOOMWMRSYoNW14ov4XHnt094AuMX8=
|
||||
github.com/frostbyte73/core v0.0.4/go.mod h1:mqHHSVFS5DE6kSdhU1/s9Mm0YCnLB8Ou2DD/eX1Zbr4=
|
||||
github.com/frostbyte73/core v0.0.9 h1:AmE9GjgGpPsWk9ZkmY3HsYUs2hf2tZt+/W6r49URBQI=
|
||||
github.com/frostbyte73/core v0.0.9/go.mod h1:XsOGqrqe/VEV7+8vJ+3a8qnCIXNbKsoEiu/czs7nrcU=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/gammazero/deque v0.1.0 h1:f9LnNmq66VDeuAlSAapemq/U7hJ2jpIWa4c09q8Dlik=
|
||||
github.com/gammazero/deque v0.1.0/go.mod h1:KQw7vFau1hHuM8xmI9RbgKFbAsQFWmBpqQ2KenFLk6M=
|
||||
github.com/gammazero/workerpool v1.1.2 h1:vuioDQbgrz4HoaCi2q1HLlOXdpbap5AET7xu5/qj87g=
|
||||
github.com/gammazero/workerpool v1.1.2/go.mod h1:UelbXcO0zCIGFcufcirHhq2/xtLXJdQ29qZNlXG9OjQ=
|
||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
|
||||
github.com/go-kit/log v0.2.0/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0=
|
||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
|
||||
github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
|
||||
github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0=
|
||||
github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/gammazero/deque v0.2.1 h1:qSdsbG6pgp6nL7A0+K/B7s12mcCY/5l5SIUpMOl+dC0=
|
||||
github.com/gammazero/deque v0.2.1/go.mod h1:LFroj8x4cMYCukHJDbxFCkT+r9AndaJnFMuZDV34tuU=
|
||||
github.com/gammazero/workerpool v1.1.3 h1:WixN4xzukFoN0XSeXF6puqEqFTl2mECI9S6W44HWy9Q=
|
||||
github.com/gammazero/workerpool v1.1.3/go.mod h1:wPjyBLDbyKnUn2XwwyD3EEwo9dHutia9/fwNmSHWACc=
|
||||
github.com/go-jose/go-jose/v3 v3.0.0 h1:s6rrhirfEP/CGIoc6p+PZAeogN2SxKav6Wp7+dyMWVo=
|
||||
github.com/go-jose/go-jose/v3 v3.0.0/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8=
|
||||
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
|
||||
github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
|
||||
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
@@ -156,42 +70,30 @@ github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
||||
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/subcommands v1.0.1/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk=
|
||||
github.com/google/subcommands v1.2.0 h1:vWQspBTo2nEqTUFita5/KeEWlUL8kQObDFbub/EN9oE=
|
||||
github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk=
|
||||
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/wire v0.5.0 h1:I7ELFeVBr3yfPIcc8+MWvrjk+3VjbcSzoXm3JVa+jD8=
|
||||
github.com/google/wire v0.5.0/go.mod h1:ngWDr9Qvq3yZA10YrxfyGELY/AFWGVpy9c1LTRi1EoU=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
||||
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
|
||||
github.com/hashicorp/go-hclog v0.9.2 h1:CG6TE5H9/JXsFWJCfoIVpKFIkFe6ysEuHirp4DxCsHI=
|
||||
github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
|
||||
github.com/hashicorp/go-retryablehttp v0.7.4 h1:ZQgVdpTdAL7WpMIwLzCfbalOcSUdkDZnpUv3/+BxzFA=
|
||||
github.com/hashicorp/go-retryablehttp v0.7.4/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8=
|
||||
github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek=
|
||||
github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.1 h1:5pv5N1lT1fjLg2VQ5KWc7kmucp2x/kvFOnxuVTqZ6x4=
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.1/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.4 h1:7GHuZcgid37q8o5i3QI9KMT4nCWQQ3Kx3Ov6bb9MfK0=
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.4/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/josharian/native v0.0.0-20200817173448-b6b71def0850/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
|
||||
github.com/josharian/native v1.0.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
|
||||
github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA=
|
||||
github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
|
||||
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
|
||||
github.com/jsimonetti/rtnetlink v0.0.0-20190606172950-9527aa82566a/go.mod h1:Oz+70psSo5OFh8DBl0Zv2ACw7Esh6pPUphlvZG9x7uw=
|
||||
github.com/jsimonetti/rtnetlink v0.0.0-20200117123717-f846d4f6c1f4/go.mod h1:WGuG/smIU4J/54PblvSbh+xvCZmpJnFgr3ds6Z55XMQ=
|
||||
github.com/jsimonetti/rtnetlink v0.0.0-20201009170750-9c6f07d100c1/go.mod h1:hqoO/u39cqLeBLebZ8fWdE96O7FxrAsRYhnVOdgHxok=
|
||||
@@ -202,51 +104,40 @@ github.com/jsimonetti/rtnetlink v0.0.0-20210212075122-66c871082f2b/go.mod h1:8w9
|
||||
github.com/jsimonetti/rtnetlink v0.0.0-20210525051524-4cc836578190/go.mod h1:NmKSdU4VGSiv1bMsdqNALI4RSvvjtz65tTMCnD05qLo=
|
||||
github.com/jsimonetti/rtnetlink v0.0.0-20211022192332-93da33804786 h1:N527AHMa793TP5z5GNAn/VLPzlc0ewzWdeP/25gDfgQ=
|
||||
github.com/jsimonetti/rtnetlink v0.0.0-20211022192332-93da33804786/go.mod h1:v4hqbTdfQngbVSZJVWUhGE/lbTFf9jb+ygmNUDQMuOs=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
|
||||
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/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/compress v1.15.15 h1:EF27CXIuDsYJ6mmvtBRlEuB2UVOqHG1tAXgZ7yIO+lw=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||
github.com/klauspost/compress v1.16.5 h1:IFV2oUNUzZaz+XyusxpLzpzS8Pt5rh0Z16For/djlyI=
|
||||
github.com/klauspost/compress v1.16.5/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
|
||||
github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4=
|
||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/lithammer/shortuuid/v3 v3.0.7 h1:trX0KTHy4Pbwo/6ia8fscyHoGA+mf1jWbPJVuvyJQQ8=
|
||||
github.com/lithammer/shortuuid/v3 v3.0.7/go.mod h1:vMk8ke37EmiewwolSO1NLW8vP4ZaKlRuDIi8tWWmAts=
|
||||
github.com/lithammer/shortuuid/v4 v4.0.0 h1:QRbbVkfgNippHOS8PXDkti4NaWeyYfcBTHtw7k08o4c=
|
||||
github.com/lithammer/shortuuid/v4 v4.0.0/go.mod h1:Zs8puNcrvf2rV9rTH51ZLLcj7ZXqQI3lv67aw4KiB1Y=
|
||||
github.com/livekit/mageutil v0.0.0-20230125210925-54e8a70427c1 h1:jm09419p0lqTkDaKb5iXdynYrzB84ErPPO4LbRASk58=
|
||||
github.com/livekit/mageutil v0.0.0-20230125210925-54e8a70427c1/go.mod h1:Rs3MhFwutWhGwmY1VQsygw28z5bWcnEYmS1OG9OxjOQ=
|
||||
github.com/livekit/mediatransportutil v0.0.0-20230130133657-96cfb115473a h1:5UkGQpskXp7HcBmyrCwWtO7ygDWbqtjN09Yva4l/nyE=
|
||||
github.com/livekit/mediatransportutil v0.0.0-20230130133657-96cfb115473a/go.mod h1:1Dlx20JPoIKGP45eo+yuj0HjeE25zmyeX/EWHiPCjFw=
|
||||
github.com/livekit/protocol v1.5.0 h1:jFGSkSEv0PTjUlrW/WnmERejwxyHOSE9If4VU33PYgk=
|
||||
github.com/livekit/protocol v1.5.0/go.mod h1:hkK/G0wwFiLUGp9F5kxeQxq2CQuIzkmfBwKhTsc71us=
|
||||
github.com/livekit/psrpc v0.2.10-0.20230310095745-5cd63568998d h1:3wfbd8zi7zGQCR+xfG3r2k9m2RwXUiIzR0SN4BHewwU=
|
||||
github.com/livekit/psrpc v0.2.10-0.20230310095745-5cd63568998d/go.mod h1:K0j8f1PgLShR7Lx80KbmwFkDH2BvOnycXGV0OSRURKc=
|
||||
github.com/mackerelio/go-osstat v0.2.3 h1:jAMXD5erlDE39kdX2CU7YwCGRcxIO33u/p8+Fhe5dJw=
|
||||
github.com/mackerelio/go-osstat v0.2.3/go.mod h1:DQbPOnsss9JHIXgBStc/dnhhir3gbd3YH+Dbdi7ptMA=
|
||||
github.com/magefile/mage v1.14.0 h1:6QDX3g6z1YvJ4olPhT1wksUcSa/V0a1B+pJb73fBjyo=
|
||||
github.com/magefile/mage v1.14.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
|
||||
github.com/livekit/mediatransportutil v0.0.0-20230716190407-fc4944cbc33a h1:JWpPHcMFuw0fP4swE89CfMgeUXiSN5IKvCJL/5HLI3A=
|
||||
github.com/livekit/mediatransportutil v0.0.0-20230716190407-fc4944cbc33a/go.mod h1:xirUXW8xnLGmfCwUeAv/nj1VGo1OO1BmgxrYP7jK/14=
|
||||
github.com/livekit/protocol v1.5.11-0.20230729124740-d45d830f69e2 h1:KxQIooCpXmn+qzxQxNbxBtRXstEFd2/7ihH4Pp1dOc4=
|
||||
github.com/livekit/protocol v1.5.11-0.20230729124740-d45d830f69e2/go.mod h1:3Dt53NrYnuA7pAJjAjXLJ2q5rU3JKoebvMttZPZWDH8=
|
||||
github.com/livekit/psrpc v0.3.2 h1:eAaJhASme33gtoBhCRLH9jsnWcdm1tHWf0WzaDk56ew=
|
||||
github.com/livekit/psrpc v0.3.2/go.mod h1:n6JntEg+zT6Ji8InoyTpV7wusPNwGqqtxmHlkNhDN0U=
|
||||
github.com/mackerelio/go-osstat v0.2.4 h1:qxGbdPkFo65PXOb/F/nhDKpF2nGmGaCFDLXoZjJTtUs=
|
||||
github.com/mackerelio/go-osstat v0.2.4/go.mod h1:Zy+qzGdZs3A9cuIqmgbJvwbmLQH9dJvtio5ZjJTbdlQ=
|
||||
github.com/magefile/mage v1.15.0 h1:BvGheCMAsG3bWUDbZ8AyXXpCNwU9u5CB6sM+HNb9HYg=
|
||||
github.com/magefile/mage v1.15.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
|
||||
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
|
||||
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/maxbrunsfeld/counterfeiter/v6 v6.6.1 h1:9XE5ykDiC8eNSqIPkxx0EsV3kMX1oe4kQWRZjIgytUA=
|
||||
github.com/maxbrunsfeld/counterfeiter/v6 v6.6.1/go.mod h1:qbKwBR+qQODzH2WD/s53mdgp/xVcXMlJb59GRFOp6Z4=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
|
||||
github.com/maxbrunsfeld/counterfeiter/v6 v6.6.2 h1:CEy7VRV/Vbm7YLuZo3pGKa5GlPX4zzric6dEubIJTx0=
|
||||
github.com/maxbrunsfeld/counterfeiter/v6 v6.6.2/go.mod h1:otjOyjeqm3LALYcmX2AQIGH0VlojDoSd8aGOzsHAnBc=
|
||||
github.com/mdlayher/ethtool v0.0.0-20210210192532-2b88debcdd43/go.mod h1:+t7E0lkKfbBsebllff1xdTmyJt8lH37niI6kwFk9OTo=
|
||||
github.com/mdlayher/genetlink v1.0.0/go.mod h1:0rJ0h4itni50A86M2kHcgS85ttZazNt7a8H2a2cw0Gc=
|
||||
github.com/mdlayher/netlink v0.0.0-20190409211403-11939a169225/go.mod h1:eQB3mZE4aiYnlUsyGGCOpPETfdQq4Jhsgf1fk3cwQaA=
|
||||
@@ -269,19 +160,12 @@ github.com/mdlayher/socket v0.4.0/go.mod h1:xxFqz5GRCUN3UEOm9CZqEJsAbe1C8OwSK46N
|
||||
github.com/minio/highwayhash v1.0.2 h1:Aak5U0nElisjDCfPSG79Tgzkn2gl66NxOMspRrKnA/g=
|
||||
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/nats-io/jwt/v2 v2.3.0 h1:z2mA1a7tIf5ShggOFlR1oBPgd6hGqcDYsISxZByUzdI=
|
||||
github.com/nats-io/nats-server/v2 v2.9.8 h1:jgxZsv+A3Reb3MgwxaINcNq/za8xZInKhDg9Q0cGN1o=
|
||||
github.com/nats-io/nats.go v1.24.0 h1:CRiD8L5GOQu/DcfkmgBcTTIQORMwizF+rPk6T0RaHVQ=
|
||||
github.com/nats-io/nats.go v1.24.0/go.mod h1:dVQF+BK3SzUZpwyzHedXsvH3EO38aVKuOPkkHlv5hXA=
|
||||
github.com/nats-io/nkeys v0.3.0 h1:cgM5tL53EvYRU+2YLXIK0G2mJtK12Ft9oeooSZMA2G8=
|
||||
github.com/nats-io/nkeys v0.3.0/go.mod h1:gvUNGjVcM2IPr5rCsRsC6Wb3Hr2CQAm08dsxtV6A5y4=
|
||||
github.com/nats-io/nats.go v1.26.0 h1:fWJTYPnZ8DzxIaqIHOAMfColuznchnd5Ab5dbJpgPIE=
|
||||
github.com/nats-io/nats.go v1.26.0/go.mod h1:XpbWUlOElGwTYbMR7imivs7jJj9GtK7ypv321Wp6pjc=
|
||||
github.com/nats-io/nkeys v0.4.4 h1:xvBJ8d69TznjcQl9t6//Q5xXuVhyYiSos6RPtvQNTwA=
|
||||
github.com/nats-io/nkeys v0.4.4/go.mod h1:XUkxdLPTufzlihbamfzQ7mw/VGx6ObUs+0bN5sNvt64=
|
||||
github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
|
||||
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
|
||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||
@@ -295,15 +179,15 @@ github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042
|
||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||
github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
|
||||
github.com/onsi/gomega v1.26.0 h1:03cDLK28U6hWvCAns6NeydX3zIm4SF3ci69ulidS32Q=
|
||||
github.com/onsi/gomega v1.27.8 h1:gegWiwZjBsf2DgiSbf5hpokZ98JVDMcWkUiigk6/KXc=
|
||||
github.com/pion/datachannel v1.5.5 h1:10ef4kwdjije+M9d7Xm9im2Y3O6A6ccQb0zcqZcJew8=
|
||||
github.com/pion/datachannel v1.5.5/go.mod h1:iMz+lECmfdCMqFRhXhcA/219B0SQlbpoR2V118yimL0=
|
||||
github.com/pion/dtls/v2 v2.2.6 h1:yXMxKr0Skd+Ub6A8UqXTRLSywskx93ooMRHsQUtd+Z4=
|
||||
github.com/pion/dtls/v2 v2.2.6/go.mod h1:t8fWJCIquY5rlQZwA2yWxUS1+OCrAdXrhVKXB5oD/wY=
|
||||
github.com/pion/ice/v2 v2.3.1 h1:FQCmUfZe2Jpe7LYStVBOP6z1DiSzbIateih3TztgTjc=
|
||||
github.com/pion/ice/v2 v2.3.1/go.mod h1:aq2kc6MtYNcn4XmMhobAv6hTNJiHzvD0yXRz80+bnP8=
|
||||
github.com/pion/interceptor v0.1.12 h1:CslaNriCFUItiXS5o+hh5lpL0t0ytQkFnUcbbCs2Zq8=
|
||||
github.com/pion/interceptor v0.1.12/go.mod h1:bDtgAD9dRkBZpWHGKaoKb42FhDHTG2rX8Ii9LRALLVA=
|
||||
github.com/pion/dtls/v2 v2.2.7 h1:cSUBsETxepsCSFSxC3mc/aDo14qQLMSL+O6IjG28yV8=
|
||||
github.com/pion/dtls/v2 v2.2.7/go.mod h1:8WiMkebSHFD0T+dIU+UeBaoV7kDhOW5oDCzZ7WZ/F9s=
|
||||
github.com/pion/ice/v2 v2.3.9 h1:7yZpHf3PhPxJGT4JkMj1Y8Rl5cQ6fB709iz99aeMd/U=
|
||||
github.com/pion/ice/v2 v2.3.9/go.mod h1:lT3kv5uUIlHfXHU/ZRD7uKD/ufM202+eTa3C/umgGf4=
|
||||
github.com/pion/interceptor v0.1.17 h1:prJtgwFh/gB8zMqGZoOgJPHivOwVAp61i2aG61Du/1w=
|
||||
github.com/pion/interceptor v0.1.17/go.mod h1:SY8kpmfVBvrbUzvj2bsXz7OJt5JvmVNZ+4Kjq7FcwrI=
|
||||
github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY=
|
||||
github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms=
|
||||
github.com/pion/mdns v0.0.7 h1:P0UB4Sr6xDWEox0kTVxF0LmQihtCbSAdW0H2nEgkA3U=
|
||||
@@ -312,198 +196,114 @@ github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA=
|
||||
github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8=
|
||||
github.com/pion/rtcp v1.2.10 h1:nkr3uj+8Sp97zyItdN60tE/S6vk4al5CPRR6Gejsdjc=
|
||||
github.com/pion/rtcp v1.2.10/go.mod h1:ztfEwXZNLGyF1oQDttz/ZKIBaeeg/oWbRYqzBM9TL1I=
|
||||
github.com/pion/rtp v1.7.13 h1:qcHwlmtiI50t1XivvoawdCGTP4Uiypzfrsap+bijcoA=
|
||||
github.com/pion/rtp v1.7.13/go.mod h1:bDb5n+BFZxXx0Ea7E5qe+klMuqiBrP+w8XSjiWtCUko=
|
||||
github.com/pion/rtp v1.8.0 h1:SYD7040IR+NqrGBOc2GDU5iDjAR+0m5rnX/EWCUMNhw=
|
||||
github.com/pion/rtp v1.8.0/go.mod h1:pBGHaFt/yW7bf1jjWAoUjpSNoDnw98KTMg+jWWvziqU=
|
||||
github.com/pion/sctp v1.8.5/go.mod h1:SUFFfDpViyKejTAdwD1d/HQsCu+V/40cCs2nZIvC3s0=
|
||||
github.com/pion/sctp v1.8.6 h1:CUex11Vkt9YS++VhLf8b55O3VqKrWL6W3SDwX4jAqsI=
|
||||
github.com/pion/sctp v1.8.6/go.mod h1:SUFFfDpViyKejTAdwD1d/HQsCu+V/40cCs2nZIvC3s0=
|
||||
github.com/pion/sctp v1.8.7 h1:JnABvFakZueGAn4KU/4PSKg+GWbF6QWbKTWZOSGJjXw=
|
||||
github.com/pion/sctp v1.8.7/go.mod h1:g1Ul+ARqZq5JEmoFy87Q/4CePtKnTJ1QCL9dBBdN6AU=
|
||||
github.com/pion/sdp/v3 v3.0.6 h1:WuDLhtuFUUVpTfus9ILC4HRyHsW6TdugjEX/QY9OiUw=
|
||||
github.com/pion/sdp/v3 v3.0.6/go.mod h1:iiFWFpQO8Fy3S5ldclBkpXqmWy02ns78NOKoLLL0YQw=
|
||||
github.com/pion/srtp/v2 v2.0.12 h1:WrmiVCubGMOAObBU1vwWjG0H3VSyQHawKeer2PVA5rY=
|
||||
github.com/pion/srtp/v2 v2.0.12/go.mod h1:C3Ep44hlOo2qEYaq4ddsmK5dL63eLehXFbHaZ9F5V9Y=
|
||||
github.com/pion/stun v0.4.0 h1:vgRrbBE2htWHy7l3Zsxckk7rkjnjOsSM7PHZnBwo8rk=
|
||||
github.com/pion/stun v0.4.0/go.mod h1:QPsh1/SbXASntw3zkkrIk3ZJVKz4saBY2G7S10P3wCw=
|
||||
github.com/pion/srtp/v2 v2.0.15 h1:+tqRtXGsGwHC0G0IUIAzRmdkHvriF79IHVfZGfHrQoA=
|
||||
github.com/pion/srtp/v2 v2.0.15/go.mod h1:b/pQOlDrbB0HEH5EUAQXzSYxikFbNcNuKmF8tM0hCtw=
|
||||
github.com/pion/stun v0.6.1 h1:8lp6YejULeHBF8NmV8e2787BogQhduZugh5PdhDyyN4=
|
||||
github.com/pion/stun v0.6.1/go.mod h1:/hO7APkX4hZKu/D0f2lHzNyvdkTGtIy3NDmLR7kSz/8=
|
||||
github.com/pion/transport v0.14.1 h1:XSM6olwW+o8J4SCmOBb/BpwZypkHeyM0PGFCxNQBr40=
|
||||
github.com/pion/transport v0.14.1/go.mod h1:4tGmbk00NeYA3rUa9+n+dzCCoKkcy3YlYb99Jn2fNnI=
|
||||
github.com/pion/transport/v2 v2.0.0/go.mod h1:HS2MEBJTwD+1ZI2eSXSvHJx/HnzQqRy2/LXxt6eVMHc=
|
||||
github.com/pion/transport/v2 v2.0.2 h1:St+8o+1PEzPT51O9bv+tH/KYYLMNR5Vwm5Z3Qkjsywg=
|
||||
github.com/pion/transport/v2 v2.0.2/go.mod h1:vrz6bUbFr/cjdwbnxq8OdDDzHf7JJfGsIRkxfpZoTA0=
|
||||
github.com/pion/turn/v2 v2.1.0 h1:5wGHSgGhJhP/RpabkUb/T9PdsAjkGLS6toYz5HNzoSI=
|
||||
github.com/pion/turn/v2 v2.1.0/go.mod h1:yrT5XbXSGX1VFSF31A3c1kCNB5bBZgk/uu5LET162qs=
|
||||
github.com/pion/udp/v2 v2.0.1 h1:xP0z6WNux1zWEjhC7onRA3EwwSliXqu1ElUZAQhUP54=
|
||||
github.com/pion/udp/v2 v2.0.1/go.mod h1:B7uvTMP00lzWdyMr/1PVZXtV3wpPIxBRd4Wl6AksXn8=
|
||||
github.com/pion/webrtc/v3 v3.1.56 h1:ScaiqKQN3liQwT+kJwOBaYP6TwSfixzdUnZmzHAo0a0=
|
||||
github.com/pion/webrtc/v3 v3.1.56/go.mod h1:7VhbA6ihqJlz6R/INHjyh1b8HpiV9Ct4UQvE1OB/xoM=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pion/transport/v2 v2.1.0/go.mod h1:AdSw4YBZVDkZm8fpoz+fclXyQwANWmZAlDuQdctTThQ=
|
||||
github.com/pion/transport/v2 v2.2.0/go.mod h1:AdSw4YBZVDkZm8fpoz+fclXyQwANWmZAlDuQdctTThQ=
|
||||
github.com/pion/transport/v2 v2.2.1 h1:7qYnCBlpgSJNYMbLCKuSY9KbQdBFoETvPNETv0y4N7c=
|
||||
github.com/pion/transport/v2 v2.2.1/go.mod h1:cXXWavvCnFF6McHTft3DWS9iic2Mftcz1Aq29pGcU5g=
|
||||
github.com/pion/turn/v2 v2.1.2 h1:wj0cAoGKltaZ790XEGW9HwoUewqjliwmhtxCuB2ApyM=
|
||||
github.com/pion/turn/v2 v2.1.2/go.mod h1:1kjnPkBcex3dhCU2Am+AAmxDcGhLX3WnMfmkNpvSTQU=
|
||||
github.com/pion/webrtc/v3 v3.2.13 h1://ltbnahZewBWHvQYunlyLVWrHrsoyxYDfi3Ux6V4Gk=
|
||||
github.com/pion/webrtc/v3 v3.2.13/go.mod h1:KS57v8u+fNMYAVM6gNsceIHtciyHlnfPNXU/7klJMFU=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
||||
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
|
||||
github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
|
||||
github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY=
|
||||
github.com/prometheus/client_golang v1.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw=
|
||||
github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8=
|
||||
github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc=
|
||||
github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4=
|
||||
github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w=
|
||||
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
|
||||
github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
|
||||
github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
|
||||
github.com/prometheus/common v0.37.0 h1:ccBbHCgIiT9uSoFY0vX8H3zsNR5eLt17/RQLUvn8pXE=
|
||||
github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA=
|
||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
|
||||
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
|
||||
github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
|
||||
github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5mo=
|
||||
github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4=
|
||||
github.com/redis/go-redis/v9 v9.0.2 h1:BA426Zqe/7r56kCcvxYLWe1mkaz71LKF77GwgFzSxfE=
|
||||
github.com/redis/go-redis/v9 v9.0.2/go.mod h1:/xDTe9EF1LM61hek62Poq2nzQSGj0xSrEtEHbBQevps=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k=
|
||||
github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM=
|
||||
github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc=
|
||||
github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+PymziUAg=
|
||||
github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM=
|
||||
github.com/redis/go-redis/v9 v9.0.5 h1:CuQcn5HIEeK7BgElubPP8CGtE0KakrnbBSTLjathl5o=
|
||||
github.com/redis/go-redis/v9 v9.0.5/go.mod h1:WqMKv5vnQbRuZstUwxQI195wHy+t4PuXDOjzMvcuQHk=
|
||||
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||
github.com/rs/cors v1.8.3 h1:O+qNyWn7Z+F9M0ILBHgMVPuB1xTOucVd5gtaYyXBpRo=
|
||||
github.com/rs/cors v1.8.3/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
|
||||
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||
github.com/rs/cors v1.9.0 h1:l9HGsTsHJcvW14Nk7J9KFz8bzeAWXn3CG6bgt7LsrAE=
|
||||
github.com/rs/cors v1.9.0/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
|
||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw=
|
||||
github.com/sclevine/spec v1.4.0 h1:z/Q9idDcay5m5irkZ28M7PtQM4aOISzOpj4bUPkDee8=
|
||||
github.com/sebest/xff v0.0.0-20210106013422-671bd2870b3a h1:iLcLb5Fwwz7g/DLK89F+uQBDeAhHhwdzB5fSlVdhGcM=
|
||||
github.com/sebest/xff v0.0.0-20210106013422-671bd2870b3a/go.mod h1:wozgYq9WEBQBaIJe4YZ0qTSFAMxmcwBhQH0fO0R34Z0=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
|
||||
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/thoas/go-funk v0.9.3 h1:7+nAEx3kn5ZJcnDm2Bh23N2yOtweO14bi//dvRtgLpw=
|
||||
github.com/thoas/go-funk v0.9.3/go.mod h1:+IWnUfUmFO1+WVYQWQtIJHeRRdaIyyYglZN7xzUPe4Q=
|
||||
github.com/twitchtv/twirp v8.1.3+incompatible h1:+F4TdErPgSUbMZMwp13Q/KgDVuI7HJXP61mNV3/7iuU=
|
||||
github.com/twitchtv/twirp v8.1.3+incompatible/go.mod h1:RRJoFSAmTEh2weEqWtpPE3vFK5YBhA6bqp2l1kfCC5A=
|
||||
github.com/ua-parser/uap-go v0.0.0-20211112212520-00c877edfe0f h1:A+MmlgpvrHLeUP8dkBVn4Pnf5Bp5Yk2OALm7SEJLLE8=
|
||||
github.com/ua-parser/uap-go v0.0.0-20211112212520-00c877edfe0f/go.mod h1:OBcG9bn7sHtXgarhUEb3OfCnNsgtGnkVf41ilSZ3K3E=
|
||||
github.com/urfave/cli/v2 v2.24.2 h1:q1VA+ofZ8SWfEKB9xXHUD4QZaeI9e+ItEqSbfH2JBXk=
|
||||
github.com/urfave/cli/v2 v2.24.2/go.mod h1:GHupkWPMM0M/sj1a2b4wUrWBPzazNrIjouW6fmdJLxc=
|
||||
github.com/urfave/cli/v2 v2.25.7 h1:VAzn5oq403l5pHjc4OhD54+XGO9cdKVL/7lDjF+iKUs=
|
||||
github.com/urfave/cli/v2 v2.25.7/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ=
|
||||
github.com/urfave/negroni/v3 v3.0.0 h1:Vo8CeZfu1lFR9gW8GnAb6dOGCJyijfil9j/jKKc/JhU=
|
||||
github.com/urfave/negroni/v3 v3.0.0/go.mod h1:jWvnX03kcSjDBl/ShB0iHvx5uOs7mAzZXW+JvJ5XYAs=
|
||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
|
||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
|
||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ=
|
||||
github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0=
|
||||
github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA=
|
||||
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ=
|
||||
go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||
go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI=
|
||||
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
|
||||
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||
go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA=
|
||||
go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4=
|
||||
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
|
||||
go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60=
|
||||
go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210314154223-e6e6c4f2bb5b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU=
|
||||
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
|
||||
golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A=
|
||||
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
|
||||
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
|
||||
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
||||
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
||||
golang.org/x/exp v0.0.0-20230310171629-522b1b587ee0 h1:LGJsf5LRplCck6jUCH3dBL2dmycNruWNF5xugkSlfXw=
|
||||
golang.org/x/exp v0.0.0-20230310171629-522b1b587ee0/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
|
||||
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
||||
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
|
||||
golang.org/x/crypto v0.10.0 h1:LKqV2xt9+kDzSTfOhx4FrkEBcMrAgHSYgzywV9zcGmM=
|
||||
golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I=
|
||||
golang.org/x/exp v0.0.0-20230728194245-b0cb94b80691 h1:/yRP+0AN7mf5DkD3BAI6TOFnd51gEoDEb8o35jIFtgw=
|
||||
golang.org/x/exp v0.0.0-20230728194245-b0cb94b80691/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
|
||||
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=
|
||||
golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/mod v0.11.0 h1:bUO06HqtnRcc/7l71XBe4WcqTZ+3AH1J59zWDDwLKgU=
|
||||
golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191007182048-72f939374954/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
@@ -515,73 +315,36 @@ golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT
|
||||
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20210928044308-7d9f5e0b762b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
|
||||
golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
|
||||
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
|
||||
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.11.0 h1:Gi2tvZIJyBtO9SDr1q9h5hEQCp/4L2RQ+ar0qjx2oNU=
|
||||
golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
|
||||
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190411185658-b44545bcd369/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201118182958-a01c418693c7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -595,12 +358,10 @@ golang.org/x/sys v0.0.0-20210216163648-f7da38b97c65/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210525143221-35b2ab0089ea/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
@@ -608,186 +369,71 @@ golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s=
|
||||
golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
|
||||
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
|
||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
golang.org/x/term v0.9.0/go.mod h1:M6DEAAIenWoTxdKrOltXcmDY3rSplQUkrvaDU5FcQyo=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68=
|
||||
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.10.0 h1:UpjohKhiEgNc0CSauXmwYftY1+LlaC75SJwh0SgCX58=
|
||||
golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190422233926-fe54fb35175b/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
||||
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
||||
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
|
||||
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.9.3 h1:Gn1I8+64MsuTb/HpH+LmQtNas23LhUVr3rYZ0eKuaMM=
|
||||
golang.org/x/tools v0.9.3/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
||||
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
||||
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
||||
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
|
||||
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
|
||||
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
|
||||
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
|
||||
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
|
||||
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4 h1:DdoeryqhaXp1LtT/emMP1BRJPHHKFi5akj/nbx/zNTA=
|
||||
google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
|
||||
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
|
||||
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.53.0 h1:LAv2ds7cmFV/XTS3XG1NneeENYrXGmorPxsBbptIjNc=
|
||||
google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc h1:XSJ8Vk1SWuNr8S18z1NZSziL0CPIXLCCMDOEFtHBOFc=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA=
|
||||
google.golang.org/grpc v1.57.0 h1:kfzNeI/klCGD2YPMUlaGNT3pxvYfga7smW3Vth8Zsiw=
|
||||
google.golang.org/grpc v1.57.0/go.mod h1:Sd+9RMTACXwmub0zcNY2c4arhtrbBYD1AUHI/dt16Mo=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.29.0 h1:44S3JjaKmLEE4YIkjzexaP+NzZsudE3Zin5Njn/pYX0=
|
||||
google.golang.org/protobuf v1.29.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
|
||||
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/square/go-jose.v2 v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI=
|
||||
gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
||||
|
||||
@@ -1,4 +1,18 @@
|
||||
#!/usr/bin/env bash
|
||||
# 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.
|
||||
|
||||
# LiveKit install script for Linux
|
||||
|
||||
set -u
|
||||
|
||||
+14
@@ -1,3 +1,17 @@
|
||||
// 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.
|
||||
|
||||
//go:build mage
|
||||
// +build mage
|
||||
|
||||
|
||||
@@ -1,3 +1,17 @@
|
||||
// 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.
|
||||
|
||||
//go:build mage && !windows
|
||||
// +build mage,!windows
|
||||
|
||||
|
||||
@@ -1,3 +1,17 @@
|
||||
// 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.
|
||||
|
||||
//go:build mage
|
||||
// +build mage
|
||||
|
||||
|
||||
@@ -1,6 +1,24 @@
|
||||
// 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 clientconfiguration
|
||||
|
||||
// configurations for livekit-client, add more configuration to StaticConfigurations as need
|
||||
import (
|
||||
"github.com/livekit/protocol/livekit"
|
||||
)
|
||||
|
||||
// StaticConfigurations list specific device-side limitations that should be disabled at a global level
|
||||
var StaticConfigurations = []ConfigurationItem{
|
||||
// {
|
||||
// Match: &ScriptMatch{Expr: `c.protocol <= 5 || c.browser == "firefox"`},
|
||||
@@ -14,4 +32,13 @@ var StaticConfigurations = []ConfigurationItem{
|
||||
// }}},
|
||||
// Merge: false,
|
||||
// },
|
||||
{
|
||||
Match: &ScriptMatch{Expr: `c.device_model == "Xiaomi 2201117TI" && c.os == "android"`},
|
||||
Configuration: &livekit.ClientConfiguration{
|
||||
DisabledCodecs: &livekit.DisabledCodecs{
|
||||
Publish: []*livekit.Codec{{Mime: "video/h264"}},
|
||||
},
|
||||
},
|
||||
Merge: false,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,3 +1,17 @@
|
||||
// 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 clientconfiguration
|
||||
|
||||
import (
|
||||
|
||||
@@ -1,3 +1,17 @@
|
||||
// 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 clientconfiguration
|
||||
|
||||
import (
|
||||
|
||||
@@ -1,3 +1,17 @@
|
||||
// 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 clientconfiguration
|
||||
|
||||
import (
|
||||
|
||||
@@ -1,3 +1,17 @@
|
||||
// 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 clientconfiguration
|
||||
|
||||
import (
|
||||
|
||||
+305
-236
@@ -1,3 +1,17 @@
|
||||
// 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 config
|
||||
|
||||
import (
|
||||
@@ -13,15 +27,12 @@ import (
|
||||
"github.com/urfave/cli/v2"
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/livekit/mediatransportutil/pkg/rtcconfig"
|
||||
"github.com/livekit/protocol/logger"
|
||||
"github.com/livekit/protocol/logger/pionlogger"
|
||||
redisLiveKit "github.com/livekit/protocol/redis"
|
||||
)
|
||||
|
||||
var DefaultStunServers = []string{
|
||||
"stun.l.google.com:19302",
|
||||
"stun1.l.google.com:19302",
|
||||
}
|
||||
|
||||
type CongestionControlProbeMode string
|
||||
type StreamTrackerType string
|
||||
|
||||
@@ -39,13 +50,13 @@ const (
|
||||
)
|
||||
|
||||
var (
|
||||
ErrKeyFileIncorrectPermission = errors.New("key file must have 0600 permission")
|
||||
ErrKeyFileIncorrectPermission = errors.New("key file others permissions must be set to 0")
|
||||
ErrKeysNotSet = errors.New("one of key-file or keys must be provided")
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Port uint32 `yaml:"port"`
|
||||
BindAddresses []string `yaml:"bind_addresses"`
|
||||
BindAddresses []string `yaml:"bind_addresses,omitempty"`
|
||||
PrometheusPort uint32 `yaml:"prometheus_port,omitempty"`
|
||||
Environment string `yaml:"environment,omitempty"`
|
||||
RTC RTCConfig `yaml:"rtc,omitempty"`
|
||||
@@ -54,7 +65,6 @@ type Config struct {
|
||||
Video VideoConfig `yaml:"video,omitempty"`
|
||||
Room RoomConfig `yaml:"room,omitempty"`
|
||||
TURN TURNConfig `yaml:"turn,omitempty"`
|
||||
Egress EgressConfig `yaml:"egress,omitempty"`
|
||||
Ingress IngressConfig `yaml:"ingress,omitempty"`
|
||||
WebHook WebHookConfig `yaml:"webhook,omitempty"`
|
||||
NodeSelector NodeSelectorConfig `yaml:"node_selector,omitempty"`
|
||||
@@ -71,21 +81,11 @@ type Config struct {
|
||||
}
|
||||
|
||||
type RTCConfig struct {
|
||||
UDPPort uint32 `yaml:"udp_port,omitempty"`
|
||||
TCPPort uint32 `yaml:"tcp_port,omitempty"`
|
||||
ICEPortRangeStart uint32 `yaml:"port_range_start,omitempty"`
|
||||
ICEPortRangeEnd uint32 `yaml:"port_range_end,omitempty"`
|
||||
NodeIP string `yaml:"node_ip,omitempty"`
|
||||
NodeIPAutoGenerated bool `yaml:"-"`
|
||||
STUNServers []string `yaml:"stun_servers,omitempty"`
|
||||
TURNServers []TURNServer `yaml:"turn_servers,omitempty"`
|
||||
UseExternalIP bool `yaml:"use_external_ip"`
|
||||
UseICELite bool `yaml:"use_ice_lite,omitempty"`
|
||||
Interfaces InterfacesConfig `yaml:"interfaces"`
|
||||
IPs IPsConfig `yaml:"ips"`
|
||||
EnableLoopbackCandidate bool `yaml:"enable_loopback_candidate"`
|
||||
UseMDNS bool `yaml:"use_mdns"`
|
||||
StrictACKs bool `yaml:"strict_acks"`
|
||||
rtcconfig.RTCConfig `yaml:",inline"`
|
||||
|
||||
TURNServers []TURNServer `yaml:"turn_servers,omitempty"`
|
||||
|
||||
StrictACKs bool `yaml:"strict_acks,omitempty"`
|
||||
|
||||
// Number of packets to buffer for NACK
|
||||
PacketBufferSize int `yaml:"packet_buffer_size,omitempty"`
|
||||
@@ -98,9 +98,6 @@ type RTCConfig struct {
|
||||
// allow TCP and TURN/TLS fallback
|
||||
AllowTCPFallback *bool `yaml:"allow_tcp_fallback,omitempty"`
|
||||
|
||||
// for testing, disable UDP
|
||||
ForceTCP bool `yaml:"force_tcp,omitempty"`
|
||||
|
||||
// force a reconnect on a publication error
|
||||
ReconnectOnPublicationError *bool `yaml:"reconnect_on_publication_error,omitempty"`
|
||||
|
||||
@@ -122,43 +119,64 @@ type PLIThrottleConfig struct {
|
||||
HighQuality time.Duration `yaml:"high_quality,omitempty"`
|
||||
}
|
||||
|
||||
type CongestionControlProbeConfig struct {
|
||||
BaseInterval time.Duration `yaml:"base_interval,omitempty"`
|
||||
BackoffFactor float64 `yaml:"backoff_factor,omitempty"`
|
||||
MaxInterval time.Duration `yaml:"max_interval,omitempty"`
|
||||
|
||||
SettleWait time.Duration `yaml:"settle_wait,omitempty"`
|
||||
SettleWaitMax time.Duration `yaml:"settle_wait_max,omitempty"`
|
||||
|
||||
TrendWait time.Duration `yaml:"trend_wait,omitempty"`
|
||||
|
||||
OveragePct int64 `yaml:"overage_pct,omitempty"`
|
||||
MinBps int64 `yaml:"min_bps,omitempty"`
|
||||
MinDuration time.Duration `yaml:"min_duration,omitempty"`
|
||||
MaxDuration time.Duration `yaml:"max_duration,omitempty"`
|
||||
DurationOverflowFactor float64 `yaml:"duration_overflow_factor,omitempty"`
|
||||
DurationIncreaseFactor float64 `yaml:"duration_increase_factor,omitempty"`
|
||||
}
|
||||
|
||||
type CongestionControlChannelObserverConfig struct {
|
||||
EstimateRequiredSamples int `yaml:"estimate_required_samples,omitempty"`
|
||||
EstimateDownwardTrendThreshold float64 `yaml:"estimate_downward_trend_threshold,omitempty"`
|
||||
EstimateCollapseThreshold time.Duration `yaml:"estimate_collapse_threshold,omitempty"`
|
||||
EstimateValidityWindow time.Duration `yaml:"estimate_validity_window,omitempty"`
|
||||
NackWindowMinDuration time.Duration `yaml:"nack_window_min_duration,omitempty"`
|
||||
NackWindowMaxDuration time.Duration `yaml:"nack_window_max_duration,omitempty"`
|
||||
NackRatioThreshold float64 `yaml:"nack_ratio_threshold,omitempty"`
|
||||
}
|
||||
|
||||
type CongestionControlConfig struct {
|
||||
Enabled bool `yaml:"enabled"`
|
||||
AllowPause bool `yaml:"allow_pause"`
|
||||
UseSendSideBWE bool `yaml:"send_side_bandwidth_estimation,omitempty"`
|
||||
ProbeMode CongestionControlProbeMode `yaml:"padding_mode,omitempty"`
|
||||
MinChannelCapacity int64 `yaml:"min_channel_capacity,omitempty"`
|
||||
}
|
||||
|
||||
type InterfacesConfig struct {
|
||||
Includes []string `yaml:"includes"`
|
||||
Excludes []string `yaml:"excludes"`
|
||||
}
|
||||
|
||||
type IPsConfig struct {
|
||||
Includes []string `yaml:"includes"`
|
||||
Excludes []string `yaml:"excludes"`
|
||||
Enabled bool `yaml:"enabled"`
|
||||
AllowPause bool `yaml:"allow_pause"`
|
||||
UseSendSideBWE bool `yaml:"send_side_bandwidth_estimation,omitempty"`
|
||||
ProbeMode CongestionControlProbeMode `yaml:"padding_mode,omitempty"`
|
||||
MinChannelCapacity int64 `yaml:"min_channel_capacity,omitempty"`
|
||||
ProbeConfig CongestionControlProbeConfig `yaml:"probe_config,omitempty"`
|
||||
ChannelObserverProbeConfig CongestionControlChannelObserverConfig `yaml:"channel_observer_probe_config,omitempty"`
|
||||
ChannelObserverNonProbeConfig CongestionControlChannelObserverConfig `yaml:"channel_observer_non_probe_config,omitempty"`
|
||||
}
|
||||
|
||||
type AudioConfig struct {
|
||||
// minimum level to be considered active, 0-127, where 0 is loudest
|
||||
ActiveLevel uint8 `yaml:"active_level"`
|
||||
ActiveLevel uint8 `yaml:"active_level,omitempty"`
|
||||
// percentile to measure, a participant is considered active if it has exceeded the ActiveLevel more than
|
||||
// MinPercentile% of the time
|
||||
MinPercentile uint8 `yaml:"min_percentile"`
|
||||
MinPercentile uint8 `yaml:"min_percentile,omitempty"`
|
||||
// interval to update clients, in ms
|
||||
UpdateInterval uint32 `yaml:"update_interval"`
|
||||
UpdateInterval uint32 `yaml:"update_interval,omitempty"`
|
||||
// smoothing for audioLevel values sent to the client.
|
||||
// audioLevel will be an average of `smooth_intervals`, 0 to disable
|
||||
SmoothIntervals uint32 `yaml:"smooth_intervals"`
|
||||
SmoothIntervals uint32 `yaml:"smooth_intervals,omitempty"`
|
||||
// enable red encoding downtrack for opus only audio up track
|
||||
ActiveREDEncoding bool `yaml:"active_red_encoding"`
|
||||
ActiveREDEncoding bool `yaml:"active_red_encoding,omitempty"`
|
||||
}
|
||||
|
||||
type StreamTrackerPacketConfig struct {
|
||||
SamplesRequired uint32 `yaml:"samples_required"` // number of samples needed per cycle
|
||||
CyclesRequired uint32 `yaml:"cycles_required"` // number of cycles needed to be active
|
||||
CycleDuration time.Duration `yaml:"cycle_duration"`
|
||||
SamplesRequired uint32 `yaml:"samples_required,omitempty"` // number of samples needed per cycle
|
||||
CyclesRequired uint32 `yaml:"cycles_required,omitempty"` // number of cycles needed to be active
|
||||
CycleDuration time.Duration `yaml:"cycle_duration,omitempty"`
|
||||
}
|
||||
|
||||
type StreamTrackerFrameConfig struct {
|
||||
@@ -173,8 +191,8 @@ type StreamTrackerConfig struct {
|
||||
}
|
||||
|
||||
type StreamTrackersConfig struct {
|
||||
Video StreamTrackerConfig `yaml:"video"`
|
||||
Screenshare StreamTrackerConfig `yaml:"screenshare"`
|
||||
Video StreamTrackerConfig `yaml:"video,omitempty"`
|
||||
Screenshare StreamTrackerConfig `yaml:"screenshare,omitempty"`
|
||||
}
|
||||
|
||||
type VideoConfig struct {
|
||||
@@ -184,12 +202,12 @@ type VideoConfig struct {
|
||||
|
||||
type RoomConfig struct {
|
||||
// enable rooms to be automatically created
|
||||
AutoCreate bool `yaml:"auto_create"`
|
||||
EnabledCodecs []CodecSpec `yaml:"enabled_codecs"`
|
||||
MaxParticipants uint32 `yaml:"max_participants"`
|
||||
EmptyTimeout uint32 `yaml:"empty_timeout"`
|
||||
EnableRemoteUnmute bool `yaml:"enable_remote_unmute"`
|
||||
MaxMetadataSize uint32 `yaml:"max_metadata_size"`
|
||||
AutoCreate bool `yaml:"auto_create,omitempty"`
|
||||
EnabledCodecs []CodecSpec `yaml:"enabled_codecs,omitempty"`
|
||||
MaxParticipants uint32 `yaml:"max_participants,omitempty"`
|
||||
EmptyTimeout uint32 `yaml:"empty_timeout,omitempty"`
|
||||
EnableRemoteUnmute bool `yaml:"enable_remote_unmute,omitempty"`
|
||||
MaxMetadataSize uint32 `yaml:"max_metadata_size,omitempty"`
|
||||
}
|
||||
|
||||
type CodecSpec struct {
|
||||
@@ -204,14 +222,14 @@ type LoggingConfig struct {
|
||||
|
||||
type TURNConfig struct {
|
||||
Enabled bool `yaml:"enabled"`
|
||||
Domain string `yaml:"domain"`
|
||||
CertFile string `yaml:"cert_file"`
|
||||
KeyFile string `yaml:"key_file"`
|
||||
TLSPort int `yaml:"tls_port"`
|
||||
UDPPort int `yaml:"udp_port"`
|
||||
Domain string `yaml:"domain,omitempty"`
|
||||
CertFile string `yaml:"cert_file,omitempty"`
|
||||
KeyFile string `yaml:"key_file,omitempty"`
|
||||
TLSPort int `yaml:"tls_port,omitempty"`
|
||||
UDPPort int `yaml:"udp_port,omitempty"`
|
||||
RelayPortRangeStart uint16 `yaml:"relay_range_start,omitempty"`
|
||||
RelayPortRangeEnd uint16 `yaml:"relay_range_end,omitempty"`
|
||||
ExternalTLS bool `yaml:"external_tls"`
|
||||
ExternalTLS bool `yaml:"external_tls,omitempty"`
|
||||
}
|
||||
|
||||
type WebHookConfig struct {
|
||||
@@ -222,18 +240,18 @@ type WebHookConfig struct {
|
||||
|
||||
type NodeSelectorConfig struct {
|
||||
Kind string `yaml:"kind"`
|
||||
SortBy string `yaml:"sort_by"`
|
||||
CPULoadLimit float32 `yaml:"cpu_load_limit"`
|
||||
SysloadLimit float32 `yaml:"sysload_limit"`
|
||||
Regions []RegionConfig `yaml:"regions"`
|
||||
SortBy string `yaml:"sort_by,omitempty"`
|
||||
CPULoadLimit float32 `yaml:"cpu_load_limit,omitempty"`
|
||||
SysloadLimit float32 `yaml:"sysload_limit,omitempty"`
|
||||
Regions []RegionConfig `yaml:"regions,omitempty"`
|
||||
}
|
||||
|
||||
type SignalRelayConfig struct {
|
||||
Enabled bool `yaml:"enabled"`
|
||||
MaxAttempts int `yaml:"max_attempts"`
|
||||
Timeout time.Duration `yaml:"timeout"`
|
||||
Backoff time.Duration `yaml:"backoff"`
|
||||
StreamBufferSize int `yaml:"stream_buffer_size"`
|
||||
RetryTimeout time.Duration `yaml:"retry_timeout,omitempty"`
|
||||
MinRetryInterval time.Duration `yaml:"min_retry_interval,omitempty"`
|
||||
MaxRetryInterval time.Duration `yaml:"max_retry_interval,omitempty"`
|
||||
StreamBufferSize int `yaml:"stream_buffer_size,omitempty"`
|
||||
}
|
||||
|
||||
// RegionConfig lists available regions and their latitude/longitude, so the selector would prefer
|
||||
@@ -245,16 +263,15 @@ type RegionConfig struct {
|
||||
}
|
||||
|
||||
type LimitConfig struct {
|
||||
NumTracks int32 `yaml:"num_tracks"`
|
||||
BytesPerSec float32 `yaml:"bytes_per_sec"`
|
||||
}
|
||||
|
||||
type EgressConfig struct {
|
||||
UsePsRPC bool `yaml:"use_psrpc"`
|
||||
NumTracks int32 `yaml:"num_tracks,omitempty"`
|
||||
BytesPerSec float32 `yaml:"bytes_per_sec,omitempty"`
|
||||
SubscriptionLimitVideo int32 `yaml:"subscription_limit_video,omitempty"`
|
||||
SubscriptionLimitAudio int32 `yaml:"subscription_limit_audio,omitempty"`
|
||||
}
|
||||
|
||||
type IngressConfig struct {
|
||||
RTMPBaseURL string `yaml:"rtmp_base_url"`
|
||||
WHIPBaseURL string `yaml:"whip_base_url"`
|
||||
}
|
||||
|
||||
// not exposed to YAML
|
||||
@@ -273,156 +290,198 @@ func DefaultAPIConfig() APIConfig {
|
||||
}
|
||||
}
|
||||
|
||||
func NewConfig(confString string, strictMode bool, c *cli.Context, baseFlags []cli.Flag) (*Config, error) {
|
||||
// start with defaults
|
||||
conf := &Config{
|
||||
Port: 7880,
|
||||
RTC: RTCConfig{
|
||||
var DefaultConfig = Config{
|
||||
Port: 7880,
|
||||
RTC: RTCConfig{
|
||||
RTCConfig: rtcconfig.RTCConfig{
|
||||
UseExternalIP: false,
|
||||
TCPPort: 7881,
|
||||
UDPPort: 0,
|
||||
ICEPortRangeStart: 0,
|
||||
ICEPortRangeEnd: 0,
|
||||
STUNServers: []string{},
|
||||
PacketBufferSize: 500,
|
||||
StrictACKs: true,
|
||||
PLIThrottle: PLIThrottleConfig{
|
||||
LowQuality: 500 * time.Millisecond,
|
||||
MidQuality: time.Second,
|
||||
HighQuality: time.Second,
|
||||
},
|
||||
CongestionControl: CongestionControlConfig{
|
||||
Enabled: true,
|
||||
AllowPause: false,
|
||||
ProbeMode: CongestionControlProbeModePadding,
|
||||
},
|
||||
},
|
||||
Audio: AudioConfig{
|
||||
ActiveLevel: 35, // -35dBov
|
||||
MinPercentile: 40,
|
||||
UpdateInterval: 400,
|
||||
SmoothIntervals: 2,
|
||||
PacketBufferSize: 500,
|
||||
StrictACKs: true,
|
||||
PLIThrottle: PLIThrottleConfig{
|
||||
LowQuality: 500 * time.Millisecond,
|
||||
MidQuality: time.Second,
|
||||
HighQuality: time.Second,
|
||||
},
|
||||
Video: VideoConfig{
|
||||
DynacastPauseDelay: 5 * time.Second,
|
||||
StreamTracker: StreamTrackersConfig{
|
||||
Video: StreamTrackerConfig{
|
||||
StreamTrackerType: StreamTrackerTypePacket,
|
||||
BitrateReportInterval: map[int32]time.Duration{
|
||||
0: 1 * time.Second,
|
||||
1: 1 * time.Second,
|
||||
2: 1 * time.Second,
|
||||
},
|
||||
PacketTracker: map[int32]StreamTrackerPacketConfig{
|
||||
0: StreamTrackerPacketConfig{
|
||||
SamplesRequired: 1,
|
||||
CyclesRequired: 4,
|
||||
CycleDuration: 500 * time.Millisecond,
|
||||
},
|
||||
1: StreamTrackerPacketConfig{
|
||||
SamplesRequired: 5,
|
||||
CyclesRequired: 20,
|
||||
CycleDuration: 500 * time.Millisecond,
|
||||
},
|
||||
2: StreamTrackerPacketConfig{
|
||||
SamplesRequired: 5,
|
||||
CyclesRequired: 20,
|
||||
CycleDuration: 500 * time.Millisecond,
|
||||
},
|
||||
},
|
||||
FrameTracker: map[int32]StreamTrackerFrameConfig{
|
||||
0: StreamTrackerFrameConfig{
|
||||
MinFPS: 5.0,
|
||||
},
|
||||
1: StreamTrackerFrameConfig{
|
||||
MinFPS: 5.0,
|
||||
},
|
||||
2: StreamTrackerFrameConfig{
|
||||
MinFPS: 5.0,
|
||||
},
|
||||
},
|
||||
},
|
||||
Screenshare: StreamTrackerConfig{
|
||||
StreamTrackerType: StreamTrackerTypePacket,
|
||||
BitrateReportInterval: map[int32]time.Duration{
|
||||
0: 4 * time.Second,
|
||||
1: 4 * time.Second,
|
||||
2: 4 * time.Second,
|
||||
},
|
||||
PacketTracker: map[int32]StreamTrackerPacketConfig{
|
||||
0: StreamTrackerPacketConfig{
|
||||
SamplesRequired: 1,
|
||||
CyclesRequired: 1,
|
||||
CycleDuration: 2 * time.Second,
|
||||
},
|
||||
1: StreamTrackerPacketConfig{
|
||||
SamplesRequired: 1,
|
||||
CyclesRequired: 1,
|
||||
CycleDuration: 2 * time.Second,
|
||||
},
|
||||
2: StreamTrackerPacketConfig{
|
||||
SamplesRequired: 1,
|
||||
CyclesRequired: 1,
|
||||
CycleDuration: 2 * time.Second,
|
||||
},
|
||||
},
|
||||
FrameTracker: map[int32]StreamTrackerFrameConfig{
|
||||
0: StreamTrackerFrameConfig{
|
||||
MinFPS: 0.5,
|
||||
},
|
||||
1: StreamTrackerFrameConfig{
|
||||
MinFPS: 0.5,
|
||||
},
|
||||
2: StreamTrackerFrameConfig{
|
||||
MinFPS: 0.5,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Redis: redisLiveKit.RedisConfig{},
|
||||
Room: RoomConfig{
|
||||
AutoCreate: true,
|
||||
EnabledCodecs: []CodecSpec{
|
||||
{Mime: webrtc.MimeTypeOpus},
|
||||
{Mime: "audio/red"},
|
||||
{Mime: webrtc.MimeTypeVP8},
|
||||
{Mime: webrtc.MimeTypeH264},
|
||||
// {Mime: webrtc.MimeTypeAV1},
|
||||
// {Mime: webrtc.MimeTypeVP9},
|
||||
},
|
||||
EmptyTimeout: 5 * 60,
|
||||
},
|
||||
Logging: LoggingConfig{
|
||||
PionLevel: "error",
|
||||
},
|
||||
TURN: TURNConfig{
|
||||
Enabled: false,
|
||||
},
|
||||
NodeSelector: NodeSelectorConfig{
|
||||
Kind: "any",
|
||||
SortBy: "random",
|
||||
SysloadLimit: 0.9,
|
||||
CPULoadLimit: 0.9,
|
||||
},
|
||||
SignalRelay: SignalRelayConfig{
|
||||
Enabled: false,
|
||||
MaxAttempts: 3,
|
||||
Timeout: 500 * time.Millisecond,
|
||||
Backoff: 500 * time.Millisecond,
|
||||
StreamBufferSize: 1000,
|
||||
},
|
||||
Keys: map[string]string{},
|
||||
}
|
||||
CongestionControl: CongestionControlConfig{
|
||||
Enabled: true,
|
||||
AllowPause: false,
|
||||
ProbeMode: CongestionControlProbeModePadding,
|
||||
ProbeConfig: CongestionControlProbeConfig{
|
||||
BaseInterval: 3 * time.Second,
|
||||
BackoffFactor: 1.5,
|
||||
MaxInterval: 2 * time.Minute,
|
||||
|
||||
SettleWait: 250 * time.Millisecond,
|
||||
SettleWaitMax: 10 * time.Second,
|
||||
|
||||
TrendWait: 2 * time.Second,
|
||||
|
||||
OveragePct: 120,
|
||||
MinBps: 200_000,
|
||||
MinDuration: 200 * time.Millisecond,
|
||||
MaxDuration: 20 * time.Second,
|
||||
DurationOverflowFactor: 1.25,
|
||||
DurationIncreaseFactor: 1.5,
|
||||
},
|
||||
ChannelObserverProbeConfig: CongestionControlChannelObserverConfig{
|
||||
EstimateRequiredSamples: 3,
|
||||
EstimateDownwardTrendThreshold: 0.0,
|
||||
EstimateCollapseThreshold: 0,
|
||||
EstimateValidityWindow: 10 * time.Second,
|
||||
NackWindowMinDuration: 500 * time.Millisecond,
|
||||
NackWindowMaxDuration: 1 * time.Second,
|
||||
NackRatioThreshold: 0.04,
|
||||
},
|
||||
ChannelObserverNonProbeConfig: CongestionControlChannelObserverConfig{
|
||||
EstimateRequiredSamples: 8,
|
||||
EstimateDownwardTrendThreshold: -0.5,
|
||||
EstimateCollapseThreshold: 500 * time.Millisecond,
|
||||
EstimateValidityWindow: 10 * time.Second,
|
||||
NackWindowMinDuration: 1 * time.Second,
|
||||
NackWindowMaxDuration: 2 * time.Second,
|
||||
NackRatioThreshold: 0.08,
|
||||
},
|
||||
},
|
||||
},
|
||||
Audio: AudioConfig{
|
||||
ActiveLevel: 35, // -35dBov
|
||||
MinPercentile: 40,
|
||||
UpdateInterval: 400,
|
||||
SmoothIntervals: 2,
|
||||
},
|
||||
Video: VideoConfig{
|
||||
DynacastPauseDelay: 5 * time.Second,
|
||||
StreamTracker: StreamTrackersConfig{
|
||||
Video: StreamTrackerConfig{
|
||||
StreamTrackerType: StreamTrackerTypePacket,
|
||||
BitrateReportInterval: map[int32]time.Duration{
|
||||
0: 1 * time.Second,
|
||||
1: 1 * time.Second,
|
||||
2: 1 * time.Second,
|
||||
},
|
||||
PacketTracker: map[int32]StreamTrackerPacketConfig{
|
||||
0: {
|
||||
SamplesRequired: 1,
|
||||
CyclesRequired: 4,
|
||||
CycleDuration: 500 * time.Millisecond,
|
||||
},
|
||||
1: {
|
||||
SamplesRequired: 5,
|
||||
CyclesRequired: 20,
|
||||
CycleDuration: 500 * time.Millisecond,
|
||||
},
|
||||
2: {
|
||||
SamplesRequired: 5,
|
||||
CyclesRequired: 20,
|
||||
CycleDuration: 500 * time.Millisecond,
|
||||
},
|
||||
},
|
||||
FrameTracker: map[int32]StreamTrackerFrameConfig{
|
||||
0: {
|
||||
MinFPS: 5.0,
|
||||
},
|
||||
1: {
|
||||
MinFPS: 5.0,
|
||||
},
|
||||
2: {
|
||||
MinFPS: 5.0,
|
||||
},
|
||||
},
|
||||
},
|
||||
Screenshare: StreamTrackerConfig{
|
||||
StreamTrackerType: StreamTrackerTypePacket,
|
||||
BitrateReportInterval: map[int32]time.Duration{
|
||||
0: 4 * time.Second,
|
||||
1: 4 * time.Second,
|
||||
2: 4 * time.Second,
|
||||
},
|
||||
PacketTracker: map[int32]StreamTrackerPacketConfig{
|
||||
0: {
|
||||
SamplesRequired: 1,
|
||||
CyclesRequired: 1,
|
||||
CycleDuration: 2 * time.Second,
|
||||
},
|
||||
1: {
|
||||
SamplesRequired: 1,
|
||||
CyclesRequired: 1,
|
||||
CycleDuration: 2 * time.Second,
|
||||
},
|
||||
2: {
|
||||
SamplesRequired: 1,
|
||||
CyclesRequired: 1,
|
||||
CycleDuration: 2 * time.Second,
|
||||
},
|
||||
},
|
||||
FrameTracker: map[int32]StreamTrackerFrameConfig{
|
||||
0: {
|
||||
MinFPS: 0.5,
|
||||
},
|
||||
1: {
|
||||
MinFPS: 0.5,
|
||||
},
|
||||
2: {
|
||||
MinFPS: 0.5,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Redis: redisLiveKit.RedisConfig{},
|
||||
Room: RoomConfig{
|
||||
AutoCreate: true,
|
||||
EnabledCodecs: []CodecSpec{
|
||||
{Mime: webrtc.MimeTypeOpus},
|
||||
{Mime: "audio/red"},
|
||||
{Mime: webrtc.MimeTypeVP8},
|
||||
{Mime: webrtc.MimeTypeH264},
|
||||
// {Mime: webrtc.MimeTypeAV1},
|
||||
// {Mime: webrtc.MimeTypeVP9},
|
||||
},
|
||||
EmptyTimeout: 5 * 60,
|
||||
},
|
||||
Logging: LoggingConfig{
|
||||
PionLevel: "error",
|
||||
},
|
||||
TURN: TURNConfig{
|
||||
Enabled: false,
|
||||
},
|
||||
NodeSelector: NodeSelectorConfig{
|
||||
Kind: "any",
|
||||
SortBy: "random",
|
||||
SysloadLimit: 0.9,
|
||||
CPULoadLimit: 0.9,
|
||||
},
|
||||
SignalRelay: SignalRelayConfig{
|
||||
Enabled: false,
|
||||
RetryTimeout: 7500 * time.Millisecond,
|
||||
MinRetryInterval: 500 * time.Millisecond,
|
||||
MaxRetryInterval: 4 * time.Second,
|
||||
StreamBufferSize: 1000,
|
||||
},
|
||||
Keys: map[string]string{},
|
||||
}
|
||||
|
||||
func NewConfig(confString string, strictMode bool, c *cli.Context, baseFlags []cli.Flag) (*Config, error) {
|
||||
// start with defaults
|
||||
conf := DefaultConfig
|
||||
if confString != "" {
|
||||
decoder := yaml.NewDecoder(strings.NewReader(confString))
|
||||
decoder.KnownFields(strictMode)
|
||||
if err := decoder.Decode(conf); err != nil {
|
||||
if err := decoder.Decode(&conf); err != nil {
|
||||
return nil, fmt.Errorf("could not parse config: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := conf.RTC.Validate(conf.Development); err != nil {
|
||||
return nil, fmt.Errorf("could not validate RTC config: %v", err)
|
||||
}
|
||||
|
||||
if c != nil {
|
||||
if err := conf.updateFromCLI(c, baseFlags); err != nil {
|
||||
return nil, err
|
||||
@@ -436,17 +495,6 @@ func NewConfig(confString string, strictMode bool, c *cli.Context, baseFlags []c
|
||||
}
|
||||
conf.KeyFile = file
|
||||
|
||||
// set defaults for ports if none are set
|
||||
if conf.RTC.UDPPort == 0 && conf.RTC.ICEPortRangeStart == 0 {
|
||||
// to make it easier to run in dev mode/docker, default to single port
|
||||
if conf.Development {
|
||||
conf.RTC.UDPPort = 7882
|
||||
} else {
|
||||
conf.RTC.ICEPortRangeStart = 50000
|
||||
conf.RTC.ICEPortRangeEnd = 60000
|
||||
}
|
||||
}
|
||||
|
||||
// set defaults for Turn relay if none are set
|
||||
if conf.TURN.RelayPortRangeStart == 0 || conf.TURN.RelayPortRangeEnd == 0 {
|
||||
// to make it easier to run in dev mode/docker, default to two ports
|
||||
@@ -459,14 +507,6 @@ func NewConfig(confString string, strictMode bool, c *cli.Context, baseFlags []c
|
||||
}
|
||||
}
|
||||
|
||||
if conf.RTC.NodeIP == "" {
|
||||
conf.RTC.NodeIP, err = conf.determineIP()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
conf.RTC.NodeIPAutoGenerated = true
|
||||
}
|
||||
|
||||
if conf.LogLevel != "" {
|
||||
conf.Logging.Level = conf.LogLevel
|
||||
}
|
||||
@@ -478,7 +518,7 @@ func NewConfig(confString string, strictMode bool, c *cli.Context, baseFlags []c
|
||||
conf.Environment = "dev"
|
||||
}
|
||||
|
||||
return conf, nil
|
||||
return &conf, nil
|
||||
}
|
||||
|
||||
func (conf *Config) IsTURNSEnabled() bool {
|
||||
@@ -514,13 +554,22 @@ func (conf *Config) ToCLIFlagNames(existingFlags []cli.Flag) map[string]reflect.
|
||||
for i := 0; i < currNode.TypeNode.NumField(); i++ {
|
||||
// inspect yaml tag from struct field to get path
|
||||
field := currNode.TypeNode.Type().Field(i)
|
||||
yamlTag := strings.SplitN(field.Tag.Get("yaml"), ",", 2)[0]
|
||||
if yamlTag == "" || yamlTag == "-" {
|
||||
yamlTagArray := strings.SplitN(field.Tag.Get("yaml"), ",", 2)
|
||||
yamlTag := yamlTagArray[0]
|
||||
isInline := false
|
||||
if len(yamlTagArray) > 1 && yamlTagArray[1] == "inline" {
|
||||
isInline = true
|
||||
}
|
||||
if (yamlTag == "" && (!isInline || currNode.TagPrefix == "")) || yamlTag == "-" {
|
||||
continue
|
||||
}
|
||||
yamlPath := yamlTag
|
||||
if currNode.TagPrefix != "" {
|
||||
yamlPath = fmt.Sprintf("%s.%s", currNode.TagPrefix, yamlTag)
|
||||
if isInline {
|
||||
yamlPath = currNode.TagPrefix
|
||||
} else {
|
||||
yamlPath = fmt.Sprintf("%s.%s", currNode.TagPrefix, yamlTag)
|
||||
}
|
||||
}
|
||||
if existingFlagNames[yamlPath] {
|
||||
continue
|
||||
@@ -542,9 +591,10 @@ func (conf *Config) ToCLIFlagNames(existingFlags []cli.Flag) map[string]reflect.
|
||||
func (conf *Config) ValidateKeys() error {
|
||||
// prefer keyfile if set
|
||||
if conf.KeyFile != "" {
|
||||
var otherFilter os.FileMode = 0007
|
||||
if st, err := os.Stat(conf.KeyFile); err != nil {
|
||||
return err
|
||||
} else if st.Mode().Perm() != 0600 {
|
||||
} else if st.Mode().Perm()&otherFilter != 0000 {
|
||||
return ErrKeyFileIncorrectPermission
|
||||
}
|
||||
f, err := os.Open(conf.KeyFile)
|
||||
@@ -576,7 +626,7 @@ func (conf *Config) ValidateKeys() error {
|
||||
|
||||
func GenerateCLIFlags(existingFlags []cli.Flag, hidden bool) ([]cli.Flag, error) {
|
||||
blankConfig := &Config{}
|
||||
flags := []cli.Flag{}
|
||||
flags := make([]cli.Flag, 0)
|
||||
for name, value := range blankConfig.ToCLIFlagNames(existingFlags) {
|
||||
kind := value.Kind()
|
||||
if kind == reflect.Ptr {
|
||||
@@ -628,6 +678,13 @@ func GenerateCLIFlags(existingFlags []cli.Flag, hidden bool) ([]cli.Flag, error)
|
||||
Usage: generatedCLIFlagUsage,
|
||||
Hidden: hidden,
|
||||
}
|
||||
case reflect.Float64:
|
||||
flag = &cli.Float64Flag{
|
||||
Name: name,
|
||||
EnvVars: []string{envVar},
|
||||
Usage: generatedCLIFlagUsage,
|
||||
Hidden: hidden,
|
||||
}
|
||||
case reflect.Slice:
|
||||
// TODO
|
||||
continue
|
||||
@@ -679,6 +736,8 @@ func (conf *Config) updateFromCLI(c *cli.Context, baseFlags []cli.Flag) error {
|
||||
configValue.SetUint(c.Uint64(flagName))
|
||||
case reflect.Float32:
|
||||
configValue.SetFloat(c.Float64(flagName))
|
||||
case reflect.Float64:
|
||||
configValue.SetFloat(c.Float64(flagName))
|
||||
// case reflect.Slice:
|
||||
// // TODO
|
||||
// case reflect.Map:
|
||||
@@ -741,3 +800,13 @@ func (conf *Config) unmarshalKeys(keys string) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Note: only pass in logr.Logger with default depth
|
||||
func SetLogger(l logger.Logger) {
|
||||
logger.SetLogger(l, "livekit")
|
||||
}
|
||||
|
||||
func InitLoggerFromConfig(config LoggingConfig) {
|
||||
pionlogger.SetLogLevel(config.PionLevel)
|
||||
logger.InitFromConfig(config.Config, "livekit")
|
||||
}
|
||||
|
||||
@@ -1,3 +1,17 @@
|
||||
// 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 config
|
||||
|
||||
import (
|
||||
|
||||
@@ -1,200 +0,0 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/pion/stun"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/livekit/protocol/logger"
|
||||
)
|
||||
|
||||
func (conf *Config) determineIP() (string, error) {
|
||||
if conf.RTC.UseExternalIP {
|
||||
stunServers := conf.RTC.STUNServers
|
||||
if len(stunServers) == 0 {
|
||||
stunServers = DefaultStunServers
|
||||
}
|
||||
var err error
|
||||
for i := 0; i < 3; i++ {
|
||||
var ip string
|
||||
ip, err = GetExternalIP(context.Background(), stunServers, nil)
|
||||
if err == nil {
|
||||
return ip, nil
|
||||
} else {
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
return "", errors.Errorf("could not resolve external IP: %v", err)
|
||||
}
|
||||
|
||||
// use local ip instead
|
||||
addresses, err := GetLocalIPAddresses(false)
|
||||
if len(addresses) > 0 {
|
||||
return addresses[0], err
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
|
||||
func GetLocalIPAddresses(includeLoopback bool) ([]string, error) {
|
||||
ifaces, err := net.Interfaces()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
loopBacks := make([]string, 0)
|
||||
addresses := make([]string, 0)
|
||||
for _, iface := range ifaces {
|
||||
addrs, err := iface.Addrs()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
for _, addr := range addrs {
|
||||
var ip net.IP
|
||||
switch typedAddr := addr.(type) {
|
||||
case *net.IPNet:
|
||||
ip = typedAddr.IP.To4()
|
||||
case *net.IPAddr:
|
||||
ip = typedAddr.IP.To4()
|
||||
default:
|
||||
continue
|
||||
}
|
||||
if ip == nil {
|
||||
continue
|
||||
}
|
||||
if ip.IsLoopback() {
|
||||
loopBacks = append(loopBacks, ip.String())
|
||||
} else {
|
||||
addresses = append(addresses, ip.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if includeLoopback {
|
||||
addresses = append(addresses, loopBacks...)
|
||||
}
|
||||
|
||||
if len(addresses) > 0 {
|
||||
return addresses, nil
|
||||
}
|
||||
if len(loopBacks) > 0 {
|
||||
return loopBacks, nil
|
||||
}
|
||||
return nil, fmt.Errorf("could not find local IP address")
|
||||
}
|
||||
|
||||
// GetExternalIP return external IP for localAddr from stun server. If localAddr is nil, a local address is chosen automatically,
|
||||
// else the address will be used to validate the external IP is accessible from the outside.
|
||||
func GetExternalIP(ctx context.Context, stunServers []string, localAddr net.Addr) (string, error) {
|
||||
if len(stunServers) == 0 {
|
||||
return "", errors.New("STUN servers are required but not defined")
|
||||
}
|
||||
dialer := &net.Dialer{
|
||||
LocalAddr: localAddr,
|
||||
}
|
||||
conn, err := dialer.Dial("udp4", stunServers[0])
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
c, err := stun.NewClient(conn)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer c.Close()
|
||||
|
||||
message, err := stun.Build(stun.TransactionID, stun.BindingRequest)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
var stunErr error
|
||||
// sufficiently large buffer to not block it
|
||||
ipChan := make(chan string, 20)
|
||||
err = c.Start(message, func(res stun.Event) {
|
||||
if res.Error != nil {
|
||||
stunErr = res.Error
|
||||
return
|
||||
}
|
||||
|
||||
var xorAddr stun.XORMappedAddress
|
||||
if err := xorAddr.GetFrom(res.Message); err != nil {
|
||||
stunErr = err
|
||||
return
|
||||
}
|
||||
ip := xorAddr.IP.To4()
|
||||
if ip != nil {
|
||||
ipChan <- ip.String()
|
||||
}
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
ctx1, cancel := context.WithTimeout(ctx, 5*time.Second)
|
||||
defer cancel()
|
||||
select {
|
||||
case nodeIP := <-ipChan:
|
||||
if localAddr == nil {
|
||||
return nodeIP, nil
|
||||
}
|
||||
_ = c.Close()
|
||||
return nodeIP, validateExternalIP(ctx1, nodeIP, localAddr.(*net.UDPAddr))
|
||||
case <-ctx1.Done():
|
||||
msg := "could not determine public IP"
|
||||
if stunErr != nil {
|
||||
return "", errors.Wrap(stunErr, msg)
|
||||
} else {
|
||||
return "", fmt.Errorf(msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// validateExternalIP validates that the external IP is accessible from the outside by listen the local address,
|
||||
// it will send a magic string to the external IP and check the string is received by the local address.
|
||||
func validateExternalIP(ctx context.Context, nodeIP string, addr *net.UDPAddr) error {
|
||||
srv, err := net.ListenUDP("udp", addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer srv.Close()
|
||||
|
||||
magicString := "9#B8D2Nvg2xg5P$ZRwJ+f)*^Nne6*W3WamGY"
|
||||
|
||||
validCh := make(chan struct{})
|
||||
go func() {
|
||||
buf := make([]byte, 1024)
|
||||
for {
|
||||
n, err := srv.Read(buf)
|
||||
if err != nil {
|
||||
logger.Debugw("error reading from UDP socket", "err", err)
|
||||
return
|
||||
}
|
||||
if string(buf[:n]) == magicString {
|
||||
close(validCh)
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
cli, err := net.DialUDP("udp", nil, &net.UDPAddr{IP: net.ParseIP(nodeIP), Port: srv.LocalAddr().(*net.UDPAddr).Port})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer cli.Close()
|
||||
|
||||
if _, err = cli.Write([]byte(magicString)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx1, cancel := context.WithTimeout(ctx, 1*time.Second)
|
||||
defer cancel()
|
||||
select {
|
||||
case <-validCh:
|
||||
return nil
|
||||
case <-ctx1.Done():
|
||||
break
|
||||
}
|
||||
return fmt.Errorf("could not validate external IP")
|
||||
}
|
||||
@@ -1,118 +0,0 @@
|
||||
package serverlogger
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"go.uber.org/zap/zapcore"
|
||||
|
||||
"github.com/livekit/protocol/logger"
|
||||
)
|
||||
|
||||
// implements webrtc.LeveledLogger
|
||||
type logAdapter struct {
|
||||
logger logger.Logger
|
||||
level zapcore.Level
|
||||
ignoredPrefixes []string
|
||||
}
|
||||
|
||||
func (l *logAdapter) Trace(msg string) {
|
||||
// ignore trace
|
||||
}
|
||||
|
||||
func (l *logAdapter) Tracef(format string, args ...interface{}) {
|
||||
// ignore trace
|
||||
}
|
||||
|
||||
func (l *logAdapter) Debug(msg string) {
|
||||
if l.level > zapcore.DebugLevel {
|
||||
return
|
||||
}
|
||||
if l.shouldIgnore(msg) {
|
||||
return
|
||||
}
|
||||
l.logger.Debugw(msg)
|
||||
}
|
||||
|
||||
func (l *logAdapter) Debugf(format string, args ...interface{}) {
|
||||
if l.level > zapcore.DebugLevel {
|
||||
return
|
||||
}
|
||||
msg := fmt.Sprintf(format, args...)
|
||||
if l.shouldIgnore(msg) {
|
||||
return
|
||||
}
|
||||
l.logger.Debugw(msg)
|
||||
}
|
||||
|
||||
func (l *logAdapter) Info(msg string) {
|
||||
if l.level > zapcore.InfoLevel {
|
||||
return
|
||||
}
|
||||
if l.shouldIgnore(msg) {
|
||||
return
|
||||
}
|
||||
l.logger.Infow(msg)
|
||||
}
|
||||
|
||||
func (l *logAdapter) Infof(format string, args ...interface{}) {
|
||||
if l.level > zapcore.InfoLevel {
|
||||
return
|
||||
}
|
||||
msg := fmt.Sprintf(format, args...)
|
||||
if l.shouldIgnore(msg) {
|
||||
return
|
||||
}
|
||||
l.logger.Infow(msg)
|
||||
}
|
||||
|
||||
func (l *logAdapter) Warn(msg string) {
|
||||
if l.level > zapcore.WarnLevel {
|
||||
return
|
||||
}
|
||||
if l.shouldIgnore(msg) {
|
||||
return
|
||||
}
|
||||
l.logger.Warnw(msg, nil)
|
||||
}
|
||||
|
||||
func (l *logAdapter) Warnf(format string, args ...interface{}) {
|
||||
if l.level > zapcore.WarnLevel {
|
||||
return
|
||||
}
|
||||
msg := fmt.Sprintf(format, args...)
|
||||
if l.shouldIgnore(msg) {
|
||||
return
|
||||
}
|
||||
l.logger.Warnw(msg, nil)
|
||||
}
|
||||
|
||||
func (l *logAdapter) Error(msg string) {
|
||||
if l.level > zapcore.ErrorLevel {
|
||||
return
|
||||
}
|
||||
if l.shouldIgnore(msg) {
|
||||
return
|
||||
}
|
||||
l.logger.Errorw(msg, nil)
|
||||
}
|
||||
|
||||
func (l *logAdapter) Errorf(format string, args ...interface{}) {
|
||||
if l.level > zapcore.ErrorLevel {
|
||||
return
|
||||
}
|
||||
msg := fmt.Sprintf(format, args...)
|
||||
if l.shouldIgnore(msg) {
|
||||
return
|
||||
}
|
||||
l.logger.Errorw(msg, nil)
|
||||
}
|
||||
|
||||
func (l *logAdapter) shouldIgnore(msg string) bool {
|
||||
for _, prefix := range l.ignoredPrefixes {
|
||||
if strings.HasPrefix(msg, prefix) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
package serverlogger
|
||||
|
||||
import (
|
||||
"github.com/pion/logging"
|
||||
"go.uber.org/zap/zapcore"
|
||||
|
||||
"github.com/livekit/protocol/logger"
|
||||
|
||||
"github.com/livekit/livekit-server/pkg/config"
|
||||
)
|
||||
|
||||
var (
|
||||
pionLevel zapcore.Level
|
||||
pionIgnoredPrefixes = map[string][]string{
|
||||
"ice": {
|
||||
"pingAllCandidates called with no candidate pairs",
|
||||
"failed to send packet: io: read/write on closed pipe",
|
||||
"Ignoring remote candidate with tcpType active",
|
||||
"discard message from",
|
||||
"Failed to discover mDNS candidate",
|
||||
"Failed to read from candidate tcp",
|
||||
"remote mDNS candidate added, but mDNS is disabled",
|
||||
},
|
||||
"pc": {
|
||||
"Failed to accept RTCP stream is already closed",
|
||||
"Failed to accept RTP stream is already closed",
|
||||
"Incoming unhandled RTCP ssrc",
|
||||
},
|
||||
"tcp_mux": {
|
||||
"Error reading first packet from",
|
||||
"error closing connection",
|
||||
},
|
||||
"turn": {
|
||||
"error when handling datagram",
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
// implements webrtc.LoggerFactory
|
||||
type LoggerFactory struct {
|
||||
logger logger.Logger
|
||||
}
|
||||
|
||||
func NewLoggerFactory(logger logger.Logger) *LoggerFactory {
|
||||
return &LoggerFactory{
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
func (f *LoggerFactory) NewLogger(scope string) logging.LeveledLogger {
|
||||
return &logAdapter{
|
||||
logger: f.logger.WithName(scope),
|
||||
level: pionLevel,
|
||||
ignoredPrefixes: pionIgnoredPrefixes[scope],
|
||||
}
|
||||
}
|
||||
|
||||
// Note: only pass in logr.Logger with default depth
|
||||
func SetLogger(l logger.Logger) {
|
||||
logger.SetLogger(l, "livekit")
|
||||
}
|
||||
|
||||
func InitFromConfig(config config.LoggingConfig) {
|
||||
pionLevel = logger.ParseZapLevel(config.PionLevel)
|
||||
logger.InitFromConfig(config.Config, "livekit")
|
||||
}
|
||||
@@ -1,3 +1,17 @@
|
||||
// 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 routing
|
||||
|
||||
import "errors"
|
||||
|
||||
+43
-14
@@ -1,3 +1,17 @@
|
||||
// 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 routing
|
||||
|
||||
import (
|
||||
@@ -23,6 +37,7 @@ type MessageSink interface {
|
||||
WriteMessage(msg proto.Message) error
|
||||
IsClosed() bool
|
||||
Close()
|
||||
ConnectionID() livekit.ConnectionID
|
||||
}
|
||||
|
||||
//counterfeiter:generate . MessageSource
|
||||
@@ -31,19 +46,21 @@ type MessageSource interface {
|
||||
ReadChan() <-chan proto.Message
|
||||
IsClosed() bool
|
||||
Close()
|
||||
ConnectionID() livekit.ConnectionID
|
||||
}
|
||||
|
||||
type ParticipantInit struct {
|
||||
Identity livekit.ParticipantIdentity
|
||||
Name livekit.ParticipantName
|
||||
Reconnect bool
|
||||
ReconnectReason livekit.ReconnectReason
|
||||
AutoSubscribe bool
|
||||
Client *livekit.ClientInfo
|
||||
Grants *auth.ClaimGrants
|
||||
Region string
|
||||
AdaptiveStream bool
|
||||
ID livekit.ParticipantID
|
||||
Identity livekit.ParticipantIdentity
|
||||
Name livekit.ParticipantName
|
||||
Reconnect bool
|
||||
ReconnectReason livekit.ReconnectReason
|
||||
AutoSubscribe bool
|
||||
Client *livekit.ClientInfo
|
||||
Grants *auth.ClaimGrants
|
||||
Region string
|
||||
AdaptiveStream bool
|
||||
ID livekit.ParticipantID
|
||||
SubscriberAllowPause *bool
|
||||
}
|
||||
|
||||
type NewParticipantCallback func(
|
||||
@@ -117,7 +134,7 @@ func (pi *ParticipantInit) ToStartSession(roomName livekit.RoomName, connectionI
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &livekit.StartSession{
|
||||
ss := &livekit.StartSession{
|
||||
RoomName: string(roomName),
|
||||
Identity: string(pi.Identity),
|
||||
Name: string(pi.Name),
|
||||
@@ -130,7 +147,13 @@ func (pi *ParticipantInit) ToStartSession(roomName livekit.RoomName, connectionI
|
||||
GrantsJson: string(claims),
|
||||
AdaptiveStream: pi.AdaptiveStream,
|
||||
ParticipantId: string(pi.ID),
|
||||
}, nil
|
||||
}
|
||||
if pi.SubscriberAllowPause != nil {
|
||||
subscriberAllowPause := *pi.SubscriberAllowPause
|
||||
ss.SubscriberAllowPause = &subscriberAllowPause
|
||||
}
|
||||
|
||||
return ss, nil
|
||||
}
|
||||
|
||||
func ParticipantInitFromStartSession(ss *livekit.StartSession, region string) (*ParticipantInit, error) {
|
||||
@@ -139,7 +162,7 @@ func ParticipantInitFromStartSession(ss *livekit.StartSession, region string) (*
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &ParticipantInit{
|
||||
pi := &ParticipantInit{
|
||||
Identity: livekit.ParticipantIdentity(ss.Identity),
|
||||
Name: livekit.ParticipantName(ss.Name),
|
||||
Reconnect: ss.Reconnect,
|
||||
@@ -150,5 +173,11 @@ func ParticipantInitFromStartSession(ss *livekit.StartSession, region string) (*
|
||||
Region: region,
|
||||
AdaptiveStream: ss.AdaptiveStream,
|
||||
ID: livekit.ParticipantID(ss.ParticipantId),
|
||||
}, nil
|
||||
}
|
||||
if ss.SubscriberAllowPause != nil {
|
||||
subscriberAllowPause := *ss.SubscriberAllowPause
|
||||
pi.SubscriberAllowPause = &subscriberAllowPause
|
||||
}
|
||||
|
||||
return pi, nil
|
||||
}
|
||||
|
||||
+24
-10
@@ -1,3 +1,17 @@
|
||||
// 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 routing
|
||||
|
||||
import (
|
||||
@@ -38,7 +52,7 @@ func NewLocalRouter(currentNode LocalNode, signalClient SignalClient) *LocalRout
|
||||
signalClient: signalClient,
|
||||
requestChannels: make(map[string]*MessageChannel),
|
||||
responseChannels: make(map[string]*MessageChannel),
|
||||
rtcMessageChan: NewMessageChannel(localRTCChannelSize),
|
||||
rtcMessageChan: NewMessageChannel(livekit.ConnectionID("local"), localRTCChannelSize),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,12 +102,12 @@ func (r *LocalRouter) StartParticipantSignal(ctx context.Context, roomName livek
|
||||
}
|
||||
|
||||
func (r *LocalRouter) StartParticipantSignalWithNodeID(ctx context.Context, roomName livekit.RoomName, pi ParticipantInit, nodeID livekit.NodeID) (connectionID livekit.ConnectionID, reqSink MessageSink, resSource MessageSource, err error) {
|
||||
connectionID, reqSink, resSource, err = r.signalClient.StartParticipantSignal(ctx, roomName, pi, livekit.NodeID(r.currentNode.Id))
|
||||
connectionID, reqSink, resSource, err = r.signalClient.StartParticipantSignal(ctx, roomName, pi, nodeID)
|
||||
if err != nil {
|
||||
logger.Errorw("could not handle new participant", err,
|
||||
"room", roomName,
|
||||
"participant", pi.Identity,
|
||||
"connectionID", connectionID,
|
||||
"connID", connectionID,
|
||||
)
|
||||
}
|
||||
return
|
||||
@@ -103,17 +117,17 @@ func (r *LocalRouter) WriteParticipantRTC(_ context.Context, roomName livekit.Ro
|
||||
r.lock.Lock()
|
||||
if r.rtcMessageChan.IsClosed() {
|
||||
// create a new one
|
||||
r.rtcMessageChan = NewMessageChannel(localRTCChannelSize)
|
||||
r.rtcMessageChan = NewMessageChannel(livekit.ConnectionID("local"), localRTCChannelSize)
|
||||
}
|
||||
r.lock.Unlock()
|
||||
msg.ParticipantKey = string(participantKeyLegacy(roomName, identity))
|
||||
msg.ParticipantKeyB62 = string(participantKey(roomName, identity))
|
||||
msg.ParticipantKey = string(ParticipantKeyLegacy(roomName, identity))
|
||||
msg.ParticipantKeyB62 = string(ParticipantKey(roomName, identity))
|
||||
return r.writeRTCMessage(r.rtcMessageChan, msg)
|
||||
}
|
||||
|
||||
func (r *LocalRouter) WriteRoomRTC(ctx context.Context, roomName livekit.RoomName, msg *livekit.RTCNodeMessage) error {
|
||||
msg.ParticipantKey = string(participantKeyLegacy(roomName, ""))
|
||||
msg.ParticipantKeyB62 = string(participantKey(roomName, ""))
|
||||
msg.ParticipantKey = string(ParticipantKeyLegacy(roomName, ""))
|
||||
msg.ParticipantKeyB62 = string(ParticipantKey(roomName, ""))
|
||||
return r.WriteNodeRTC(ctx, r.currentNode.Id, msg)
|
||||
}
|
||||
|
||||
@@ -121,7 +135,7 @@ func (r *LocalRouter) WriteNodeRTC(_ context.Context, _ string, msg *livekit.RTC
|
||||
r.lock.Lock()
|
||||
if r.rtcMessageChan.IsClosed() {
|
||||
// create a new one
|
||||
r.rtcMessageChan = NewMessageChannel(localRTCChannelSize)
|
||||
r.rtcMessageChan = NewMessageChannel(livekit.ConnectionID("local"), localRTCChannelSize)
|
||||
}
|
||||
r.lock.Unlock()
|
||||
return r.writeRTCMessage(r.rtcMessageChan, msg)
|
||||
@@ -254,7 +268,7 @@ func (r *LocalRouter) getOrCreateMessageChannel(target map[string]*MessageChanne
|
||||
return mc
|
||||
}
|
||||
|
||||
mc = NewMessageChannel(DefaultMessageChannelSize)
|
||||
mc = NewMessageChannel(livekit.ConnectionID(key), DefaultMessageChannelSize)
|
||||
mc.OnClose(func() {
|
||||
r.lock.Lock()
|
||||
delete(target, key)
|
||||
|
||||
@@ -1,26 +1,43 @@
|
||||
// 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 routing
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/livekit/protocol/livekit"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
const DefaultMessageChannelSize = 200
|
||||
|
||||
type MessageChannel struct {
|
||||
msgChan chan proto.Message
|
||||
onClose func()
|
||||
isClosed bool
|
||||
lock sync.RWMutex
|
||||
connectionID livekit.ConnectionID
|
||||
msgChan chan proto.Message
|
||||
onClose func()
|
||||
isClosed bool
|
||||
lock sync.RWMutex
|
||||
}
|
||||
|
||||
func NewDefaultMessageChannel() *MessageChannel {
|
||||
return NewMessageChannel(DefaultMessageChannelSize)
|
||||
func NewDefaultMessageChannel(connectionID livekit.ConnectionID) *MessageChannel {
|
||||
return NewMessageChannel(connectionID, DefaultMessageChannelSize)
|
||||
}
|
||||
|
||||
func NewMessageChannel(size int) *MessageChannel {
|
||||
func NewMessageChannel(connectionID livekit.ConnectionID, size int) *MessageChannel {
|
||||
return &MessageChannel{
|
||||
connectionID: connectionID,
|
||||
// allow some buffer to avoid blocked writes
|
||||
msgChan: make(chan proto.Message, size),
|
||||
}
|
||||
@@ -71,3 +88,7 @@ func (m *MessageChannel) Close() {
|
||||
m.onClose()
|
||||
}
|
||||
}
|
||||
|
||||
func (m *MessageChannel) ConnectionID() livekit.ConnectionID {
|
||||
return m.connectionID
|
||||
}
|
||||
|
||||
@@ -1,3 +1,17 @@
|
||||
// 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 routing_test
|
||||
|
||||
import (
|
||||
@@ -11,7 +25,7 @@ import (
|
||||
|
||||
func TestMessageChannel_WriteMessageClosed(t *testing.T) {
|
||||
// ensure it doesn't panic when written to after closing
|
||||
m := routing.NewMessageChannel(routing.DefaultMessageChannelSize)
|
||||
m := routing.NewMessageChannel(livekit.ConnectionID("test"), routing.DefaultMessageChannelSize)
|
||||
go func() {
|
||||
for msg := range m.ReadChan() {
|
||||
if msg == nil {
|
||||
|
||||
@@ -1,3 +1,17 @@
|
||||
// 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 routing
|
||||
|
||||
import (
|
||||
|
||||
+33
-1
@@ -1,3 +1,17 @@
|
||||
// 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 routing
|
||||
|
||||
import (
|
||||
@@ -98,16 +112,24 @@ func publishSignalMessage(rc redis.UniversalClient, nodeID livekit.NodeID, conne
|
||||
type RTCNodeSink struct {
|
||||
rc redis.UniversalClient
|
||||
nodeID livekit.NodeID
|
||||
connectionID livekit.ConnectionID
|
||||
participantKey livekit.ParticipantKey
|
||||
participantKeyB62 livekit.ParticipantKey
|
||||
isClosed atomic.Bool
|
||||
onClose func()
|
||||
}
|
||||
|
||||
func NewRTCNodeSink(rc redis.UniversalClient, nodeID livekit.NodeID, participantKey livekit.ParticipantKey, participantKeyB62 livekit.ParticipantKey) *RTCNodeSink {
|
||||
func NewRTCNodeSink(
|
||||
rc redis.UniversalClient,
|
||||
nodeID livekit.NodeID,
|
||||
connectionID livekit.ConnectionID,
|
||||
participantKey livekit.ParticipantKey,
|
||||
participantKeyB62 livekit.ParticipantKey,
|
||||
) *RTCNodeSink {
|
||||
return &RTCNodeSink{
|
||||
rc: rc,
|
||||
nodeID: nodeID,
|
||||
connectionID: connectionID,
|
||||
participantKey: participantKey,
|
||||
participantKeyB62: participantKeyB62,
|
||||
}
|
||||
@@ -137,6 +159,12 @@ func (s *RTCNodeSink) OnClose(f func()) {
|
||||
s.onClose = f
|
||||
}
|
||||
|
||||
func (s *RTCNodeSink) ConnectionID() livekit.ConnectionID {
|
||||
return s.connectionID
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
type SignalNodeSink struct {
|
||||
rc redis.UniversalClient
|
||||
nodeID livekit.NodeID
|
||||
@@ -177,3 +205,7 @@ func (s *SignalNodeSink) IsClosed() bool {
|
||||
func (s *SignalNodeSink) OnClose(f func()) {
|
||||
s.onClose = f
|
||||
}
|
||||
|
||||
func (s *SignalNodeSink) ConnectionID() livekit.ConnectionID {
|
||||
return s.connectionID
|
||||
}
|
||||
|
||||
+35
-15
@@ -1,3 +1,17 @@
|
||||
// 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 routing
|
||||
|
||||
import (
|
||||
@@ -150,13 +164,19 @@ func (r *RedisRouter) StartParticipantSignal(ctx context.Context, roomName livek
|
||||
}
|
||||
|
||||
if r.usePSRPCSignal {
|
||||
return r.StartParticipantSignalWithNodeID(ctx, roomName, pi, livekit.NodeID(rtcNode.Id))
|
||||
connectionID, reqSink, resSource, err = r.StartParticipantSignalWithNodeID(ctx, roomName, pi, livekit.NodeID(rtcNode.Id))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// map signal & rtc nodes
|
||||
err = r.setParticipantSignalNode(connectionID, r.currentNode.Id)
|
||||
return
|
||||
}
|
||||
|
||||
// create a new connection id
|
||||
connectionID = livekit.ConnectionID(utils.NewGuid("CO_"))
|
||||
pKey := participantKeyLegacy(roomName, pi.Identity)
|
||||
pKeyB62 := participantKey(roomName, pi.Identity)
|
||||
pKey := ParticipantKeyLegacy(roomName, pi.Identity)
|
||||
pKeyB62 := ParticipantKey(roomName, pi.Identity)
|
||||
|
||||
// map signal & rtc nodes
|
||||
if err = r.setParticipantSignalNode(connectionID, r.currentNode.Id); err != nil {
|
||||
@@ -167,7 +187,7 @@ func (r *RedisRouter) StartParticipantSignal(ctx context.Context, roomName livek
|
||||
// set up response channel before sending StartSession and be ready to receive responses.
|
||||
resChan := r.getOrCreateMessageChannel(r.responseChannels, string(connectionID))
|
||||
|
||||
sink := NewRTCNodeSink(r.rc, livekit.NodeID(rtcNode.Id), pKey, pKeyB62)
|
||||
sink := NewRTCNodeSink(r.rc, livekit.NodeID(rtcNode.Id), connectionID, pKey, pKeyB62)
|
||||
|
||||
// serialize claims
|
||||
ss, err := pi.ToStartSession(roomName, connectionID)
|
||||
@@ -185,16 +205,16 @@ func (r *RedisRouter) StartParticipantSignal(ctx context.Context, roomName livek
|
||||
}
|
||||
|
||||
func (r *RedisRouter) WriteParticipantRTC(_ context.Context, roomName livekit.RoomName, identity livekit.ParticipantIdentity, msg *livekit.RTCNodeMessage) error {
|
||||
pkey := participantKeyLegacy(roomName, identity)
|
||||
pkeyB62 := participantKey(roomName, identity)
|
||||
pkey := ParticipantKeyLegacy(roomName, identity)
|
||||
pkeyB62 := ParticipantKey(roomName, identity)
|
||||
rtcNode, err := r.getParticipantRTCNode(pkey, pkeyB62)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rtcSink := NewRTCNodeSink(r.rc, livekit.NodeID(rtcNode), pkey, pkeyB62)
|
||||
msg.ParticipantKey = string(participantKeyLegacy(roomName, identity))
|
||||
msg.ParticipantKeyB62 = string(participantKey(roomName, identity))
|
||||
rtcSink := NewRTCNodeSink(r.rc, livekit.NodeID(rtcNode), livekit.ConnectionID("ephemeral"), pkey, pkeyB62)
|
||||
msg.ParticipantKey = string(ParticipantKeyLegacy(roomName, identity))
|
||||
msg.ParticipantKeyB62 = string(ParticipantKey(roomName, identity))
|
||||
return r.writeRTCMessage(rtcSink, msg)
|
||||
}
|
||||
|
||||
@@ -203,13 +223,13 @@ func (r *RedisRouter) WriteRoomRTC(ctx context.Context, roomName livekit.RoomNam
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
msg.ParticipantKey = string(participantKeyLegacy(roomName, ""))
|
||||
msg.ParticipantKeyB62 = string(participantKey(roomName, ""))
|
||||
msg.ParticipantKey = string(ParticipantKeyLegacy(roomName, ""))
|
||||
msg.ParticipantKeyB62 = string(ParticipantKey(roomName, ""))
|
||||
return r.WriteNodeRTC(ctx, node.Id, msg)
|
||||
}
|
||||
|
||||
func (r *RedisRouter) WriteNodeRTC(_ context.Context, rtcNodeID string, msg *livekit.RTCNodeMessage) error {
|
||||
rtcSink := NewRTCNodeSink(r.rc, livekit.NodeID(rtcNodeID), livekit.ParticipantKey(msg.ParticipantKey), livekit.ParticipantKey(msg.ParticipantKeyB62))
|
||||
rtcSink := NewRTCNodeSink(r.rc, livekit.NodeID(rtcNodeID), livekit.ConnectionID("ephemeral"), livekit.ParticipantKey(msg.ParticipantKey), livekit.ParticipantKey(msg.ParticipantKeyB62))
|
||||
return r.writeRTCMessage(rtcSink, msg)
|
||||
}
|
||||
|
||||
@@ -229,7 +249,7 @@ func (r *RedisRouter) startParticipantRTC(ss *livekit.StartSession, participantK
|
||||
return err
|
||||
}
|
||||
|
||||
if err := r.setParticipantRTCNode(participantKey, participantKeyB62, rtcNode.Id); err != nil {
|
||||
if err := r.SetParticipantRTCNode(participantKey, participantKeyB62, rtcNode.Id); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -328,7 +348,7 @@ func (r *RedisRouter) Stop() {
|
||||
r.cancel()
|
||||
}
|
||||
|
||||
func (r *RedisRouter) setParticipantRTCNode(participantKey livekit.ParticipantKey, participantKeyB62 livekit.ParticipantKey, nodeID string) error {
|
||||
func (r *RedisRouter) SetParticipantRTCNode(participantKey livekit.ParticipantKey, participantKeyB62 livekit.ParticipantKey, nodeID string) error {
|
||||
var err error
|
||||
if participantKey != "" {
|
||||
err1 := r.rc.Set(r.ctx, participantRTCKey(participantKey), nodeID, participantMappingTTL).Err()
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"sync"
|
||||
|
||||
"github.com/livekit/livekit-server/pkg/routing"
|
||||
"github.com/livekit/protocol/livekit"
|
||||
"google.golang.org/protobuf/reflect/protoreflect"
|
||||
)
|
||||
|
||||
@@ -13,6 +14,16 @@ type FakeMessageSink struct {
|
||||
closeMutex sync.RWMutex
|
||||
closeArgsForCall []struct {
|
||||
}
|
||||
ConnectionIDStub func() livekit.ConnectionID
|
||||
connectionIDMutex sync.RWMutex
|
||||
connectionIDArgsForCall []struct {
|
||||
}
|
||||
connectionIDReturns struct {
|
||||
result1 livekit.ConnectionID
|
||||
}
|
||||
connectionIDReturnsOnCall map[int]struct {
|
||||
result1 livekit.ConnectionID
|
||||
}
|
||||
IsClosedStub func() bool
|
||||
isClosedMutex sync.RWMutex
|
||||
isClosedArgsForCall []struct {
|
||||
@@ -62,6 +73,59 @@ func (fake *FakeMessageSink) CloseCalls(stub func()) {
|
||||
fake.CloseStub = stub
|
||||
}
|
||||
|
||||
func (fake *FakeMessageSink) ConnectionID() livekit.ConnectionID {
|
||||
fake.connectionIDMutex.Lock()
|
||||
ret, specificReturn := fake.connectionIDReturnsOnCall[len(fake.connectionIDArgsForCall)]
|
||||
fake.connectionIDArgsForCall = append(fake.connectionIDArgsForCall, struct {
|
||||
}{})
|
||||
stub := fake.ConnectionIDStub
|
||||
fakeReturns := fake.connectionIDReturns
|
||||
fake.recordInvocation("ConnectionID", []interface{}{})
|
||||
fake.connectionIDMutex.Unlock()
|
||||
if stub != nil {
|
||||
return stub()
|
||||
}
|
||||
if specificReturn {
|
||||
return ret.result1
|
||||
}
|
||||
return fakeReturns.result1
|
||||
}
|
||||
|
||||
func (fake *FakeMessageSink) ConnectionIDCallCount() int {
|
||||
fake.connectionIDMutex.RLock()
|
||||
defer fake.connectionIDMutex.RUnlock()
|
||||
return len(fake.connectionIDArgsForCall)
|
||||
}
|
||||
|
||||
func (fake *FakeMessageSink) ConnectionIDCalls(stub func() livekit.ConnectionID) {
|
||||
fake.connectionIDMutex.Lock()
|
||||
defer fake.connectionIDMutex.Unlock()
|
||||
fake.ConnectionIDStub = stub
|
||||
}
|
||||
|
||||
func (fake *FakeMessageSink) ConnectionIDReturns(result1 livekit.ConnectionID) {
|
||||
fake.connectionIDMutex.Lock()
|
||||
defer fake.connectionIDMutex.Unlock()
|
||||
fake.ConnectionIDStub = nil
|
||||
fake.connectionIDReturns = struct {
|
||||
result1 livekit.ConnectionID
|
||||
}{result1}
|
||||
}
|
||||
|
||||
func (fake *FakeMessageSink) ConnectionIDReturnsOnCall(i int, result1 livekit.ConnectionID) {
|
||||
fake.connectionIDMutex.Lock()
|
||||
defer fake.connectionIDMutex.Unlock()
|
||||
fake.ConnectionIDStub = nil
|
||||
if fake.connectionIDReturnsOnCall == nil {
|
||||
fake.connectionIDReturnsOnCall = make(map[int]struct {
|
||||
result1 livekit.ConnectionID
|
||||
})
|
||||
}
|
||||
fake.connectionIDReturnsOnCall[i] = struct {
|
||||
result1 livekit.ConnectionID
|
||||
}{result1}
|
||||
}
|
||||
|
||||
func (fake *FakeMessageSink) IsClosed() bool {
|
||||
fake.isClosedMutex.Lock()
|
||||
ret, specificReturn := fake.isClosedReturnsOnCall[len(fake.isClosedArgsForCall)]
|
||||
@@ -181,6 +245,8 @@ func (fake *FakeMessageSink) Invocations() map[string][][]interface{} {
|
||||
defer fake.invocationsMutex.RUnlock()
|
||||
fake.closeMutex.RLock()
|
||||
defer fake.closeMutex.RUnlock()
|
||||
fake.connectionIDMutex.RLock()
|
||||
defer fake.connectionIDMutex.RUnlock()
|
||||
fake.isClosedMutex.RLock()
|
||||
defer fake.isClosedMutex.RUnlock()
|
||||
fake.writeMessageMutex.RLock()
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"sync"
|
||||
|
||||
"github.com/livekit/livekit-server/pkg/routing"
|
||||
"github.com/livekit/protocol/livekit"
|
||||
"google.golang.org/protobuf/reflect/protoreflect"
|
||||
)
|
||||
|
||||
@@ -13,6 +14,16 @@ type FakeMessageSource struct {
|
||||
closeMutex sync.RWMutex
|
||||
closeArgsForCall []struct {
|
||||
}
|
||||
ConnectionIDStub func() livekit.ConnectionID
|
||||
connectionIDMutex sync.RWMutex
|
||||
connectionIDArgsForCall []struct {
|
||||
}
|
||||
connectionIDReturns struct {
|
||||
result1 livekit.ConnectionID
|
||||
}
|
||||
connectionIDReturnsOnCall map[int]struct {
|
||||
result1 livekit.ConnectionID
|
||||
}
|
||||
IsClosedStub func() bool
|
||||
isClosedMutex sync.RWMutex
|
||||
isClosedArgsForCall []struct {
|
||||
@@ -61,6 +72,59 @@ func (fake *FakeMessageSource) CloseCalls(stub func()) {
|
||||
fake.CloseStub = stub
|
||||
}
|
||||
|
||||
func (fake *FakeMessageSource) ConnectionID() livekit.ConnectionID {
|
||||
fake.connectionIDMutex.Lock()
|
||||
ret, specificReturn := fake.connectionIDReturnsOnCall[len(fake.connectionIDArgsForCall)]
|
||||
fake.connectionIDArgsForCall = append(fake.connectionIDArgsForCall, struct {
|
||||
}{})
|
||||
stub := fake.ConnectionIDStub
|
||||
fakeReturns := fake.connectionIDReturns
|
||||
fake.recordInvocation("ConnectionID", []interface{}{})
|
||||
fake.connectionIDMutex.Unlock()
|
||||
if stub != nil {
|
||||
return stub()
|
||||
}
|
||||
if specificReturn {
|
||||
return ret.result1
|
||||
}
|
||||
return fakeReturns.result1
|
||||
}
|
||||
|
||||
func (fake *FakeMessageSource) ConnectionIDCallCount() int {
|
||||
fake.connectionIDMutex.RLock()
|
||||
defer fake.connectionIDMutex.RUnlock()
|
||||
return len(fake.connectionIDArgsForCall)
|
||||
}
|
||||
|
||||
func (fake *FakeMessageSource) ConnectionIDCalls(stub func() livekit.ConnectionID) {
|
||||
fake.connectionIDMutex.Lock()
|
||||
defer fake.connectionIDMutex.Unlock()
|
||||
fake.ConnectionIDStub = stub
|
||||
}
|
||||
|
||||
func (fake *FakeMessageSource) ConnectionIDReturns(result1 livekit.ConnectionID) {
|
||||
fake.connectionIDMutex.Lock()
|
||||
defer fake.connectionIDMutex.Unlock()
|
||||
fake.ConnectionIDStub = nil
|
||||
fake.connectionIDReturns = struct {
|
||||
result1 livekit.ConnectionID
|
||||
}{result1}
|
||||
}
|
||||
|
||||
func (fake *FakeMessageSource) ConnectionIDReturnsOnCall(i int, result1 livekit.ConnectionID) {
|
||||
fake.connectionIDMutex.Lock()
|
||||
defer fake.connectionIDMutex.Unlock()
|
||||
fake.ConnectionIDStub = nil
|
||||
if fake.connectionIDReturnsOnCall == nil {
|
||||
fake.connectionIDReturnsOnCall = make(map[int]struct {
|
||||
result1 livekit.ConnectionID
|
||||
})
|
||||
}
|
||||
fake.connectionIDReturnsOnCall[i] = struct {
|
||||
result1 livekit.ConnectionID
|
||||
}{result1}
|
||||
}
|
||||
|
||||
func (fake *FakeMessageSource) IsClosed() bool {
|
||||
fake.isClosedMutex.Lock()
|
||||
ret, specificReturn := fake.isClosedReturnsOnCall[len(fake.isClosedArgsForCall)]
|
||||
@@ -172,6 +236,8 @@ func (fake *FakeMessageSource) Invocations() map[string][][]interface{} {
|
||||
defer fake.invocationsMutex.RUnlock()
|
||||
fake.closeMutex.RLock()
|
||||
defer fake.closeMutex.RUnlock()
|
||||
fake.connectionIDMutex.RLock()
|
||||
defer fake.connectionIDMutex.RUnlock()
|
||||
fake.isClosedMutex.RLock()
|
||||
defer fake.isClosedMutex.RUnlock()
|
||||
fake.readChanMutex.RLock()
|
||||
|
||||
@@ -10,6 +10,16 @@ import (
|
||||
)
|
||||
|
||||
type FakeSignalClient struct {
|
||||
ActiveCountStub func() int
|
||||
activeCountMutex sync.RWMutex
|
||||
activeCountArgsForCall []struct {
|
||||
}
|
||||
activeCountReturns struct {
|
||||
result1 int
|
||||
}
|
||||
activeCountReturnsOnCall map[int]struct {
|
||||
result1 int
|
||||
}
|
||||
StartParticipantSignalStub func(context.Context, livekit.RoomName, routing.ParticipantInit, livekit.NodeID) (livekit.ConnectionID, routing.MessageSink, routing.MessageSource, error)
|
||||
startParticipantSignalMutex sync.RWMutex
|
||||
startParticipantSignalArgsForCall []struct {
|
||||
@@ -34,6 +44,59 @@ type FakeSignalClient struct {
|
||||
invocationsMutex sync.RWMutex
|
||||
}
|
||||
|
||||
func (fake *FakeSignalClient) ActiveCount() int {
|
||||
fake.activeCountMutex.Lock()
|
||||
ret, specificReturn := fake.activeCountReturnsOnCall[len(fake.activeCountArgsForCall)]
|
||||
fake.activeCountArgsForCall = append(fake.activeCountArgsForCall, struct {
|
||||
}{})
|
||||
stub := fake.ActiveCountStub
|
||||
fakeReturns := fake.activeCountReturns
|
||||
fake.recordInvocation("ActiveCount", []interface{}{})
|
||||
fake.activeCountMutex.Unlock()
|
||||
if stub != nil {
|
||||
return stub()
|
||||
}
|
||||
if specificReturn {
|
||||
return ret.result1
|
||||
}
|
||||
return fakeReturns.result1
|
||||
}
|
||||
|
||||
func (fake *FakeSignalClient) ActiveCountCallCount() int {
|
||||
fake.activeCountMutex.RLock()
|
||||
defer fake.activeCountMutex.RUnlock()
|
||||
return len(fake.activeCountArgsForCall)
|
||||
}
|
||||
|
||||
func (fake *FakeSignalClient) ActiveCountCalls(stub func() int) {
|
||||
fake.activeCountMutex.Lock()
|
||||
defer fake.activeCountMutex.Unlock()
|
||||
fake.ActiveCountStub = stub
|
||||
}
|
||||
|
||||
func (fake *FakeSignalClient) ActiveCountReturns(result1 int) {
|
||||
fake.activeCountMutex.Lock()
|
||||
defer fake.activeCountMutex.Unlock()
|
||||
fake.ActiveCountStub = nil
|
||||
fake.activeCountReturns = struct {
|
||||
result1 int
|
||||
}{result1}
|
||||
}
|
||||
|
||||
func (fake *FakeSignalClient) ActiveCountReturnsOnCall(i int, result1 int) {
|
||||
fake.activeCountMutex.Lock()
|
||||
defer fake.activeCountMutex.Unlock()
|
||||
fake.ActiveCountStub = nil
|
||||
if fake.activeCountReturnsOnCall == nil {
|
||||
fake.activeCountReturnsOnCall = make(map[int]struct {
|
||||
result1 int
|
||||
})
|
||||
}
|
||||
fake.activeCountReturnsOnCall[i] = struct {
|
||||
result1 int
|
||||
}{result1}
|
||||
}
|
||||
|
||||
func (fake *FakeSignalClient) StartParticipantSignal(arg1 context.Context, arg2 livekit.RoomName, arg3 routing.ParticipantInit, arg4 livekit.NodeID) (livekit.ConnectionID, routing.MessageSink, routing.MessageSource, error) {
|
||||
fake.startParticipantSignalMutex.Lock()
|
||||
ret, specificReturn := fake.startParticipantSignalReturnsOnCall[len(fake.startParticipantSignalArgsForCall)]
|
||||
@@ -110,6 +173,8 @@ func (fake *FakeSignalClient) StartParticipantSignalReturnsOnCall(i int, result1
|
||||
func (fake *FakeSignalClient) Invocations() map[string][][]interface{} {
|
||||
fake.invocationsMutex.RLock()
|
||||
defer fake.invocationsMutex.RUnlock()
|
||||
fake.activeCountMutex.RLock()
|
||||
defer fake.activeCountMutex.RUnlock()
|
||||
fake.startParticipantSignalMutex.RLock()
|
||||
defer fake.startParticipantSignalMutex.RUnlock()
|
||||
copiedInvocations := map[string][][]interface{}{}
|
||||
|
||||
@@ -1,3 +1,17 @@
|
||||
// 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 selector
|
||||
|
||||
import (
|
||||
|
||||
@@ -1,3 +1,17 @@
|
||||
// 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 selector
|
||||
|
||||
import (
|
||||
|
||||
@@ -1,3 +1,17 @@
|
||||
// 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 selector_test
|
||||
|
||||
import (
|
||||
|
||||
@@ -1,3 +1,17 @@
|
||||
// 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 selector
|
||||
|
||||
import "errors"
|
||||
|
||||
@@ -1,3 +1,17 @@
|
||||
// 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 selector
|
||||
|
||||
import (
|
||||
|
||||
@@ -1,3 +1,17 @@
|
||||
// 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 selector
|
||||
|
||||
import (
|
||||
|
||||
@@ -1,3 +1,17 @@
|
||||
// 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 selector_test
|
||||
|
||||
import (
|
||||
|
||||
@@ -1,3 +1,17 @@
|
||||
// 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 selector_test
|
||||
|
||||
import (
|
||||
|
||||
@@ -1,3 +1,17 @@
|
||||
// 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 selector
|
||||
|
||||
import (
|
||||
|
||||
@@ -1,3 +1,17 @@
|
||||
// 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 selector_test
|
||||
|
||||
import (
|
||||
|
||||
@@ -1,3 +1,17 @@
|
||||
// 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 selector
|
||||
|
||||
import (
|
||||
|
||||
@@ -1,3 +1,17 @@
|
||||
// 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 selector_test
|
||||
|
||||
import (
|
||||
|
||||
+280
-31
@@ -1,23 +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 routing
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"go.uber.org/atomic"
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
"github.com/livekit/livekit-server/pkg/config"
|
||||
"github.com/livekit/livekit-server/pkg/telemetry/prometheus"
|
||||
"github.com/livekit/protocol/livekit"
|
||||
"github.com/livekit/protocol/logger"
|
||||
"github.com/livekit/protocol/rpc"
|
||||
"github.com/livekit/protocol/utils"
|
||||
"github.com/livekit/psrpc"
|
||||
"github.com/livekit/psrpc/middleware"
|
||||
"github.com/livekit/psrpc/pkg/middleware"
|
||||
)
|
||||
|
||||
var ErrSignalWriteFailed = errors.New("signal write failed")
|
||||
var ErrSignalMessageDropped = errors.New("signal message dropped")
|
||||
|
||||
//go:generate go run github.com/maxbrunsfeld/counterfeiter/v6 -generate
|
||||
|
||||
//counterfeiter:generate . SignalClient
|
||||
type SignalClient interface {
|
||||
ActiveCount() int
|
||||
StartParticipantSignal(ctx context.Context, roomName livekit.RoomName, pi ParticipantInit, nodeID livekit.NodeID) (connectionID livekit.ConnectionID, reqSink MessageSink, resSource MessageSource, err error)
|
||||
}
|
||||
|
||||
@@ -25,15 +48,16 @@ type signalClient struct {
|
||||
nodeID livekit.NodeID
|
||||
config config.SignalRelayConfig
|
||||
client rpc.TypedSignalClient
|
||||
active atomic.Int32
|
||||
}
|
||||
|
||||
func NewSignalClient(nodeID livekit.NodeID, bus psrpc.MessageBus, config config.SignalRelayConfig) (SignalClient, error) {
|
||||
ri := middleware.NewStreamRetryInterceptorFactory(middleware.RetryOptions{
|
||||
MaxAttempts: config.MaxAttempts,
|
||||
Timeout: config.Timeout,
|
||||
Backoff: config.Backoff,
|
||||
})
|
||||
c, err := rpc.NewTypedSignalClient(nodeID, bus, psrpc.WithClientStreamInterceptors(ri))
|
||||
c, err := rpc.NewTypedSignalClient(
|
||||
nodeID,
|
||||
bus,
|
||||
middleware.WithClientMetrics(prometheus.PSRPCMetricsObserver{}),
|
||||
psrpc.WithClientChannelSize(config.StreamBufferSize),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -45,6 +69,10 @@ func NewSignalClient(nodeID livekit.NodeID, bus psrpc.MessageBus, config config.
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (r *signalClient) ActiveCount() int {
|
||||
return int(r.active.Load())
|
||||
}
|
||||
|
||||
func (r *signalClient) StartParticipantSignal(
|
||||
ctx context.Context,
|
||||
roomName livekit.RoomName,
|
||||
@@ -62,60 +90,281 @@ func (r *signalClient) StartParticipantSignal(
|
||||
return
|
||||
}
|
||||
|
||||
logger.Debugw(
|
||||
"starting signal connection",
|
||||
l := logger.GetLogger().WithValues(
|
||||
"room", roomName,
|
||||
"reqNodeID", nodeID,
|
||||
"participant", pi.Identity,
|
||||
"connectionID", connectionID,
|
||||
"connID", connectionID,
|
||||
)
|
||||
|
||||
l.Debugw("starting signal connection")
|
||||
|
||||
stream, err := r.client.RelaySignal(ctx, nodeID)
|
||||
if err != nil {
|
||||
prometheus.MessageCounter.WithLabelValues("signal", "failure").Add(1)
|
||||
return
|
||||
}
|
||||
|
||||
err = stream.Send(&rpc.RelaySignalRequest{StartSession: ss})
|
||||
if err != nil {
|
||||
stream.Close(err)
|
||||
prometheus.MessageCounter.WithLabelValues("signal", "failure").Add(1)
|
||||
return
|
||||
}
|
||||
|
||||
resChan := NewDefaultMessageChannel()
|
||||
sink := NewSignalMessageSink(SignalSinkParams[*rpc.RelaySignalRequest, *rpc.RelaySignalResponse]{
|
||||
Logger: l,
|
||||
Stream: stream,
|
||||
Config: r.config,
|
||||
Writer: signalRequestMessageWriter{},
|
||||
CloseOnFailure: true,
|
||||
BlockOnClose: true,
|
||||
ConnectionID: connectionID,
|
||||
})
|
||||
resChan := NewDefaultMessageChannel(connectionID)
|
||||
|
||||
go func() {
|
||||
var err error
|
||||
for msg := range stream.Channel() {
|
||||
if err = resChan.WriteMessage(msg.Response); err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
r.active.Inc()
|
||||
defer r.active.Dec()
|
||||
|
||||
logger.Debugw("participant signal stream closed",
|
||||
"error", err,
|
||||
"room", ss.RoomName,
|
||||
"participant", ss.Identity,
|
||||
"connectionID", connectionID,
|
||||
err := CopySignalStreamToMessageChannel[*rpc.RelaySignalRequest, *rpc.RelaySignalResponse](
|
||||
stream,
|
||||
resChan,
|
||||
signalResponseMessageReader{},
|
||||
r.config,
|
||||
)
|
||||
l.Infow("signal stream closed", "error", err)
|
||||
|
||||
resChan.Close()
|
||||
}()
|
||||
|
||||
return connectionID, &relaySignalRequestSink{stream}, resChan, nil
|
||||
return connectionID, sink, resChan, nil
|
||||
}
|
||||
|
||||
type relaySignalRequestSink struct {
|
||||
psrpc.ClientStream[*rpc.RelaySignalRequest, *rpc.RelaySignalResponse]
|
||||
type signalRequestMessageWriter struct{}
|
||||
|
||||
func (e signalRequestMessageWriter) Write(seq uint64, close bool, msgs []proto.Message) *rpc.RelaySignalRequest {
|
||||
r := &rpc.RelaySignalRequest{
|
||||
Seq: seq,
|
||||
Requests: make([]*livekit.SignalRequest, 0, len(msgs)),
|
||||
Close: close,
|
||||
}
|
||||
for _, m := range msgs {
|
||||
r.Requests = append(r.Requests, m.(*livekit.SignalRequest))
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
func (s *relaySignalRequestSink) Close() {
|
||||
s.ClientStream.Close(nil)
|
||||
type signalResponseMessageReader struct{}
|
||||
|
||||
func (e signalResponseMessageReader) Read(rm *rpc.RelaySignalResponse) ([]proto.Message, error) {
|
||||
msgs := make([]proto.Message, 0, len(rm.Responses))
|
||||
for _, m := range rm.Responses {
|
||||
msgs = append(msgs, m)
|
||||
}
|
||||
return msgs, nil
|
||||
}
|
||||
|
||||
func (s *relaySignalRequestSink) IsClosed() bool {
|
||||
return s.Context().Err() != nil
|
||||
type RelaySignalMessage interface {
|
||||
proto.Message
|
||||
GetSeq() uint64
|
||||
GetClose() bool
|
||||
}
|
||||
|
||||
func (s *relaySignalRequestSink) WriteMessage(msg proto.Message) error {
|
||||
return s.Send(&rpc.RelaySignalRequest{Request: msg.(*livekit.SignalRequest)})
|
||||
type SignalMessageWriter[SendType RelaySignalMessage] interface {
|
||||
Write(seq uint64, close bool, msgs []proto.Message) SendType
|
||||
}
|
||||
|
||||
type SignalMessageReader[RecvType RelaySignalMessage] interface {
|
||||
Read(msg RecvType) ([]proto.Message, error)
|
||||
}
|
||||
|
||||
func CopySignalStreamToMessageChannel[SendType, RecvType RelaySignalMessage](
|
||||
stream psrpc.Stream[SendType, RecvType],
|
||||
ch *MessageChannel,
|
||||
reader SignalMessageReader[RecvType],
|
||||
config config.SignalRelayConfig,
|
||||
) error {
|
||||
r := &signalMessageReader[SendType, RecvType]{
|
||||
reader: reader,
|
||||
config: config,
|
||||
}
|
||||
for msg := range stream.Channel() {
|
||||
res, err := r.Read(msg)
|
||||
if err != nil {
|
||||
prometheus.MessageCounter.WithLabelValues("signal", "failure").Add(1)
|
||||
return err
|
||||
}
|
||||
|
||||
for _, r := range res {
|
||||
if err = ch.WriteMessage(r); err != nil {
|
||||
prometheus.MessageCounter.WithLabelValues("signal", "failure").Add(1)
|
||||
return err
|
||||
}
|
||||
prometheus.MessageCounter.WithLabelValues("signal", "success").Add(1)
|
||||
}
|
||||
|
||||
if msg.GetClose() {
|
||||
return stream.Close(nil)
|
||||
}
|
||||
}
|
||||
return stream.Err()
|
||||
}
|
||||
|
||||
type signalMessageReader[SendType, RecvType RelaySignalMessage] struct {
|
||||
seq uint64
|
||||
reader SignalMessageReader[RecvType]
|
||||
config config.SignalRelayConfig
|
||||
}
|
||||
|
||||
func (r *signalMessageReader[SendType, RecvType]) Read(msg RecvType) ([]proto.Message, error) {
|
||||
res, err := r.reader.Read(msg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if r.seq < msg.GetSeq() {
|
||||
return nil, ErrSignalMessageDropped
|
||||
}
|
||||
if r.seq > msg.GetSeq() {
|
||||
n := int(r.seq - msg.GetSeq())
|
||||
if n > len(res) {
|
||||
n = len(res)
|
||||
}
|
||||
res = res[n:]
|
||||
}
|
||||
r.seq += uint64(len(res))
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
type SignalSinkParams[SendType, RecvType RelaySignalMessage] struct {
|
||||
Stream psrpc.Stream[SendType, RecvType]
|
||||
Logger logger.Logger
|
||||
Config config.SignalRelayConfig
|
||||
Writer SignalMessageWriter[SendType]
|
||||
CloseOnFailure bool
|
||||
BlockOnClose bool
|
||||
ConnectionID livekit.ConnectionID
|
||||
}
|
||||
|
||||
func NewSignalMessageSink[SendType, RecvType RelaySignalMessage](params SignalSinkParams[SendType, RecvType]) MessageSink {
|
||||
return &signalMessageSink[SendType, RecvType]{
|
||||
SignalSinkParams: params,
|
||||
}
|
||||
}
|
||||
|
||||
type signalMessageSink[SendType, RecvType RelaySignalMessage] struct {
|
||||
SignalSinkParams[SendType, RecvType]
|
||||
|
||||
mu sync.Mutex
|
||||
seq uint64
|
||||
queue []proto.Message
|
||||
writing bool
|
||||
draining bool
|
||||
}
|
||||
|
||||
func (s *signalMessageSink[SendType, RecvType]) Close() {
|
||||
s.mu.Lock()
|
||||
s.draining = true
|
||||
if !s.writing {
|
||||
s.writing = true
|
||||
go s.write()
|
||||
}
|
||||
s.mu.Unlock()
|
||||
|
||||
// conditionally block while closing to wait for outgoing messages to drain
|
||||
//
|
||||
// on media the signal sink shares a goroutine with other signal connection
|
||||
// attempts from the same participant so blocking delays establishing new
|
||||
// sessions during reconnect.
|
||||
//
|
||||
// on controller closing without waiting for the outstanding messages to
|
||||
// drain causes leave messages to be dropped from the write queue. when
|
||||
// this happens other participants in the room aren't notified about the
|
||||
// departure until the participant times out.
|
||||
if s.BlockOnClose {
|
||||
<-s.Stream.Context().Done()
|
||||
}
|
||||
}
|
||||
|
||||
func (s *signalMessageSink[SendType, RecvType]) IsClosed() bool {
|
||||
return s.Stream.Err() != nil
|
||||
}
|
||||
|
||||
func (s *signalMessageSink[SendType, RecvType]) write() {
|
||||
interval := s.Config.MinRetryInterval
|
||||
deadline := time.Now().Add(s.Config.RetryTimeout)
|
||||
var err error
|
||||
|
||||
s.mu.Lock()
|
||||
for {
|
||||
close := s.draining
|
||||
if (!close && len(s.queue) == 0) || s.IsClosed() {
|
||||
break
|
||||
}
|
||||
msg, n := s.Writer.Write(s.seq, close, s.queue), len(s.queue)
|
||||
s.mu.Unlock()
|
||||
|
||||
err = s.Stream.Send(msg, psrpc.WithTimeout(interval))
|
||||
if err != nil {
|
||||
if time.Now().After(deadline) {
|
||||
s.Logger.Warnw("could not send signal message", err)
|
||||
|
||||
s.mu.Lock()
|
||||
s.seq += uint64(len(s.queue))
|
||||
s.queue = nil
|
||||
break
|
||||
}
|
||||
|
||||
interval *= 2
|
||||
if interval > s.Config.MaxRetryInterval {
|
||||
interval = s.Config.MaxRetryInterval
|
||||
}
|
||||
}
|
||||
|
||||
s.mu.Lock()
|
||||
if err == nil {
|
||||
interval = s.Config.MinRetryInterval
|
||||
deadline = time.Now().Add(s.Config.RetryTimeout)
|
||||
|
||||
s.seq += uint64(n)
|
||||
s.queue = s.queue[n:]
|
||||
|
||||
if close {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
s.writing = false
|
||||
if s.draining {
|
||||
s.Stream.Close(nil)
|
||||
}
|
||||
if err != nil && s.CloseOnFailure {
|
||||
s.Stream.Close(ErrSignalWriteFailed)
|
||||
}
|
||||
s.mu.Unlock()
|
||||
}
|
||||
|
||||
func (s *signalMessageSink[SendType, RecvType]) WriteMessage(msg proto.Message) error {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
if err := s.Stream.Err(); err != nil {
|
||||
return err
|
||||
} else if s.draining {
|
||||
return psrpc.ErrStreamClosed
|
||||
}
|
||||
|
||||
s.queue = append(s.queue, msg)
|
||||
if !s.writing {
|
||||
s.writing = true
|
||||
go s.write()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *signalMessageSink[SendType, RecvType]) ConnectionID() livekit.ConnectionID {
|
||||
return s.SignalSinkParams.ConnectionID
|
||||
}
|
||||
|
||||
+16
-2
@@ -1,3 +1,17 @@
|
||||
// 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 routing
|
||||
|
||||
import (
|
||||
@@ -9,7 +23,7 @@ import (
|
||||
"github.com/livekit/protocol/livekit"
|
||||
)
|
||||
|
||||
func participantKeyLegacy(roomName livekit.RoomName, identity livekit.ParticipantIdentity) livekit.ParticipantKey {
|
||||
func ParticipantKeyLegacy(roomName livekit.RoomName, identity livekit.ParticipantIdentity) livekit.ParticipantKey {
|
||||
return livekit.ParticipantKey(string(roomName) + "|" + string(identity))
|
||||
}
|
||||
|
||||
@@ -25,7 +39,7 @@ func parseParticipantKeyLegacy(pkey livekit.ParticipantKey) (roomName livekit.Ro
|
||||
return
|
||||
}
|
||||
|
||||
func participantKey(roomName livekit.RoomName, identity livekit.ParticipantIdentity) livekit.ParticipantKey {
|
||||
func ParticipantKey(roomName livekit.RoomName, identity livekit.ParticipantIdentity) livekit.ParticipantKey {
|
||||
return livekit.ParticipantKey(encode(string(roomName), string(identity)))
|
||||
}
|
||||
|
||||
|
||||
@@ -1,15 +1,30 @@
|
||||
// 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 routing
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/livekit/protocol/livekit"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/livekit/protocol/livekit"
|
||||
)
|
||||
|
||||
func TestUtils_ParticipantKey(t *testing.T) {
|
||||
// encode/decode empty
|
||||
encoded := participantKey("", "")
|
||||
encoded := ParticipantKey("", "")
|
||||
roomName, identity, err := parseParticipantKey(encoded)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, livekit.RoomName(""), roomName)
|
||||
@@ -20,28 +35,28 @@ func TestUtils_ParticipantKey(t *testing.T) {
|
||||
require.Error(t, err)
|
||||
|
||||
// encode/decode without delimiter
|
||||
encoded = participantKey("room1", "identity1")
|
||||
encoded = ParticipantKey("room1", "identity1")
|
||||
roomName, identity, err = parseParticipantKey(encoded)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, livekit.RoomName("room1"), roomName)
|
||||
require.Equal(t, livekit.ParticipantIdentity("identity1"), identity)
|
||||
|
||||
// encode/decode with delimiter in roomName
|
||||
encoded = participantKey("room1|alter_room1", "identity1")
|
||||
encoded = ParticipantKey("room1|alter_room1", "identity1")
|
||||
roomName, identity, err = parseParticipantKey(encoded)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, livekit.RoomName("room1|alter_room1"), roomName)
|
||||
require.Equal(t, livekit.ParticipantIdentity("identity1"), identity)
|
||||
|
||||
// encode/decode with delimiter in identity
|
||||
encoded = participantKey("room1", "identity1|alter-identity1")
|
||||
encoded = ParticipantKey("room1", "identity1|alter-identity1")
|
||||
roomName, identity, err = parseParticipantKey(encoded)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, livekit.RoomName("room1"), roomName)
|
||||
require.Equal(t, livekit.ParticipantIdentity("identity1|alter-identity1"), identity)
|
||||
|
||||
// encode/decode with delimiter in both and multiple delimiters in both
|
||||
encoded = participantKey("room1|alter_room1|again_room1", "identity1|alter-identity1|again-identity1")
|
||||
encoded = ParticipantKey("room1|alter_room1|again_room1", "identity1|alter-identity1|again-identity1")
|
||||
roomName, identity, err = parseParticipantKey(encoded)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, livekit.RoomName("room1|alter_room1|again_room1"), roomName)
|
||||
|
||||
@@ -1,3 +1,17 @@
|
||||
// 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 (
|
||||
|
||||
+32
-358
@@ -1,43 +1,40 @@
|
||||
// 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 (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"net"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/pion/ice/v2"
|
||||
"github.com/pion/sdp/v3"
|
||||
"github.com/pion/webrtc/v3"
|
||||
|
||||
"github.com/livekit/livekit-server/pkg/config"
|
||||
logging "github.com/livekit/livekit-server/pkg/logger"
|
||||
"github.com/livekit/livekit-server/pkg/sfu/buffer"
|
||||
dd "github.com/livekit/livekit-server/pkg/sfu/dependencydescriptor"
|
||||
"github.com/livekit/protocol/logger"
|
||||
"github.com/livekit/mediatransportutil/pkg/rtcconfig"
|
||||
)
|
||||
|
||||
const (
|
||||
minUDPBufferSize = 5_000_000
|
||||
defaultUDPBufferSize = 16_777_216
|
||||
frameMarking = "urn:ietf:params:rtp-hdrext:framemarking"
|
||||
frameMarking = "urn:ietf:params:rtp-hdrext:framemarking"
|
||||
)
|
||||
|
||||
type WebRTCConfig struct {
|
||||
Configuration webrtc.Configuration
|
||||
SettingEngine webrtc.SettingEngine
|
||||
Receiver ReceiverConfig
|
||||
BufferFactory *buffer.Factory
|
||||
UDPMux ice.UDPMux
|
||||
TCPMuxListener *net.TCPListener
|
||||
Publisher DirectionConfig
|
||||
Subscriber DirectionConfig
|
||||
NAT1To1IPs []string
|
||||
UseMDNS bool
|
||||
rtcconfig.WebRTCConfig
|
||||
|
||||
BufferFactory *buffer.Factory
|
||||
Receiver ReceiverConfig
|
||||
Publisher DirectionConfig
|
||||
Subscriber DirectionConfig
|
||||
}
|
||||
|
||||
type ReceiverConfig struct {
|
||||
@@ -60,138 +57,21 @@ type DirectionConfig struct {
|
||||
StrictACKs bool
|
||||
}
|
||||
|
||||
const (
|
||||
// number of packets to buffer up
|
||||
readBufferSize = 50
|
||||
|
||||
writeBufferSizeInBytes = 4 * 1024 * 1024
|
||||
)
|
||||
|
||||
func NewWebRTCConfig(conf *config.Config, externalIP string) (*WebRTCConfig, error) {
|
||||
func NewWebRTCConfig(conf *config.Config) (*WebRTCConfig, error) {
|
||||
rtcConf := conf.RTC
|
||||
c := webrtc.Configuration{
|
||||
SDPSemantics: webrtc.SDPSemanticsUnifiedPlan,
|
||||
}
|
||||
s := webrtc.SettingEngine{
|
||||
LoggerFactory: logging.NewLoggerFactory(logger.GetLogger()),
|
||||
|
||||
webRTCConfig, err := rtcconfig.NewWebRTCConfig(&rtcConf.RTCConfig, conf.Development)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var ifFilter func(string) bool
|
||||
if len(rtcConf.Interfaces.Includes) != 0 || len(rtcConf.Interfaces.Excludes) != 0 {
|
||||
ifFilter = InterfaceFilterFromConf(rtcConf.Interfaces)
|
||||
s.SetInterfaceFilter(ifFilter)
|
||||
}
|
||||
|
||||
var ipFilter func(net.IP) bool
|
||||
if len(rtcConf.IPs.Includes) != 0 || len(rtcConf.IPs.Excludes) != 0 {
|
||||
filter, err := IPFilterFromConf(rtcConf.IPs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ipFilter = filter
|
||||
s.SetIPFilter(filter)
|
||||
}
|
||||
|
||||
if !rtcConf.UseMDNS {
|
||||
s.SetICEMulticastDNSMode(ice.MulticastDNSModeDisabled)
|
||||
}
|
||||
|
||||
var nat1to1IPs []string
|
||||
// force it to the node IPs that the user has set
|
||||
if externalIP != "" && (conf.RTC.UseExternalIP || (conf.RTC.NodeIP != "" && !conf.RTC.NodeIPAutoGenerated)) {
|
||||
if conf.RTC.UseExternalIP {
|
||||
ips, err := getNAT1to1IPsForConf(conf, ipFilter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(ips) == 0 {
|
||||
logger.Infow("no external IPs found, using node IP for NAT1To1Ips", "ip", externalIP)
|
||||
s.SetNAT1To1IPs([]string{externalIP}, webrtc.ICECandidateTypeHost)
|
||||
} else {
|
||||
logger.Infow("using external IPs", "ips", ips)
|
||||
s.SetNAT1To1IPs(ips, webrtc.ICECandidateTypeHost)
|
||||
}
|
||||
nat1to1IPs = ips
|
||||
} else {
|
||||
s.SetNAT1To1IPs([]string{externalIP}, webrtc.ICECandidateTypeHost)
|
||||
}
|
||||
}
|
||||
// we don't want to use active TCP on a server, clients should be dialing
|
||||
webRTCConfig.SettingEngine.DisableActiveTCP(true)
|
||||
|
||||
if rtcConf.PacketBufferSize == 0 {
|
||||
rtcConf.PacketBufferSize = 500
|
||||
}
|
||||
|
||||
var udpMux ice.UDPMux
|
||||
var err error
|
||||
networkTypes := make([]webrtc.NetworkType, 0, 4)
|
||||
|
||||
if !rtcConf.ForceTCP {
|
||||
networkTypes = append(networkTypes,
|
||||
webrtc.NetworkTypeUDP4, webrtc.NetworkTypeUDP6,
|
||||
)
|
||||
if rtcConf.ICEPortRangeStart != 0 && rtcConf.ICEPortRangeEnd != 0 {
|
||||
if err := s.SetEphemeralUDPPortRange(uint16(rtcConf.ICEPortRangeStart), uint16(rtcConf.ICEPortRangeEnd)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else if rtcConf.UDPPort != 0 {
|
||||
opts := []ice.UDPMuxFromPortOption{
|
||||
ice.UDPMuxFromPortWithReadBufferSize(defaultUDPBufferSize),
|
||||
ice.UDPMuxFromPortWithWriteBufferSize(defaultUDPBufferSize),
|
||||
ice.UDPMuxFromPortWithLogger(s.LoggerFactory.NewLogger("udp_mux")),
|
||||
}
|
||||
if rtcConf.EnableLoopbackCandidate {
|
||||
opts = append(opts, ice.UDPMuxFromPortWithLoopback())
|
||||
}
|
||||
if ipFilter != nil {
|
||||
opts = append(opts, ice.UDPMuxFromPortWithIPFilter(ipFilter))
|
||||
}
|
||||
if ifFilter != nil {
|
||||
opts = append(opts, ice.UDPMuxFromPortWithInterfaceFilter(ifFilter))
|
||||
}
|
||||
udpMux, err := ice.NewMultiUDPMuxFromPort(int(rtcConf.UDPPort), opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s.SetICEUDPMux(udpMux)
|
||||
if !conf.Development {
|
||||
checkUDPReadBuffer()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// use TCP mux when it's set
|
||||
var tcpListener *net.TCPListener
|
||||
if rtcConf.TCPPort != 0 {
|
||||
networkTypes = append(networkTypes,
|
||||
webrtc.NetworkTypeTCP4, webrtc.NetworkTypeTCP6,
|
||||
)
|
||||
tcpListener, err = net.ListenTCP("tcp", &net.TCPAddr{
|
||||
Port: int(rtcConf.TCPPort),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tcpMux := ice.NewTCPMuxDefault(ice.TCPMuxParams{
|
||||
Logger: s.LoggerFactory.NewLogger("tcp_mux"),
|
||||
Listener: tcpListener,
|
||||
ReadBufferSize: readBufferSize,
|
||||
WriteBufferSize: writeBufferSizeInBytes,
|
||||
})
|
||||
|
||||
s.SetICETCPMux(tcpMux)
|
||||
}
|
||||
|
||||
if len(networkTypes) == 0 {
|
||||
return nil, errors.New("TCP is forced but not configured")
|
||||
}
|
||||
s.SetNetworkTypes(networkTypes)
|
||||
|
||||
if rtcConf.EnableLoopbackCandidate {
|
||||
s.SetIncludeLoopbackCandidate(true)
|
||||
}
|
||||
|
||||
// publisher configuration
|
||||
publisherConfig := DirectionConfig{
|
||||
StrictACKs: true, // publisher is dialed, and will always reply with ACK
|
||||
@@ -244,32 +124,13 @@ func NewWebRTCConfig(conf *config.Config, externalIP string) (*WebRTCConfig, err
|
||||
subscriberConfig.RTCPFeedback.Video = append(subscriberConfig.RTCPFeedback.Video, webrtc.RTCPFeedback{Type: webrtc.TypeRTCPFBGoogREMB})
|
||||
}
|
||||
|
||||
if rtcConf.UseICELite {
|
||||
s.SetLite(true)
|
||||
} else if rtcConf.NodeIP == "" && !rtcConf.UseExternalIP {
|
||||
// use STUN servers for server to support NAT
|
||||
// when deployed in production, we expect UseExternalIP to be used, and ports accessible
|
||||
// this is not compatible with ICE Lite
|
||||
// Do not automatically add STUN servers if nodeIP is set
|
||||
if len(rtcConf.STUNServers) > 0 {
|
||||
c.ICEServers = []webrtc.ICEServer{iceServerForStunServers(rtcConf.STUNServers)}
|
||||
} else {
|
||||
c.ICEServers = []webrtc.ICEServer{iceServerForStunServers(config.DefaultStunServers)}
|
||||
}
|
||||
}
|
||||
|
||||
return &WebRTCConfig{
|
||||
Configuration: c,
|
||||
SettingEngine: s,
|
||||
WebRTCConfig: *webRTCConfig,
|
||||
Receiver: ReceiverConfig{
|
||||
PacketBufferSize: rtcConf.PacketBufferSize,
|
||||
},
|
||||
UDPMux: udpMux,
|
||||
TCPMuxListener: tcpListener,
|
||||
Publisher: publisherConfig,
|
||||
Subscriber: subscriberConfig,
|
||||
NAT1To1IPs: nat1to1IPs,
|
||||
UseMDNS: rtcConf.UseMDNS,
|
||||
Publisher: publisherConfig,
|
||||
Subscriber: subscriberConfig,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -277,190 +138,3 @@ func (c *WebRTCConfig) SetBufferFactory(factory *buffer.Factory) {
|
||||
c.BufferFactory = factory
|
||||
c.SettingEngine.BufferFactory = factory.GetOrNew
|
||||
}
|
||||
|
||||
func iceServerForStunServers(servers []string) webrtc.ICEServer {
|
||||
iceServer := webrtc.ICEServer{}
|
||||
for _, stunServer := range servers {
|
||||
iceServer.URLs = append(iceServer.URLs, fmt.Sprintf("stun:%s", stunServer))
|
||||
}
|
||||
return iceServer
|
||||
}
|
||||
|
||||
func getNAT1to1IPsForConf(conf *config.Config, ipFilter func(net.IP) bool) ([]string, error) {
|
||||
stunServers := conf.RTC.STUNServers
|
||||
if len(stunServers) == 0 {
|
||||
stunServers = config.DefaultStunServers
|
||||
}
|
||||
localIPs, err := config.GetLocalIPAddresses(conf.RTC.EnableLoopbackCandidate)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
type ipmapping struct {
|
||||
externalIP string
|
||||
localIP string
|
||||
}
|
||||
addrCh := make(chan ipmapping, len(localIPs))
|
||||
|
||||
var udpPorts []int
|
||||
if conf.RTC.ICEPortRangeStart != 0 && conf.RTC.ICEPortRangeEnd != 0 {
|
||||
portRangeStart, portRangeEnd := uint16(conf.RTC.ICEPortRangeStart), uint16(conf.RTC.ICEPortRangeEnd)
|
||||
for i := 0; i < 5; i++ {
|
||||
udpPorts = append(udpPorts, rand.Intn(int(portRangeEnd-portRangeStart))+int(portRangeStart))
|
||||
}
|
||||
} else if conf.RTC.UDPPort != 0 {
|
||||
udpPorts = append(udpPorts, int(conf.RTC.UDPPort))
|
||||
} else {
|
||||
udpPorts = append(udpPorts, 0)
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
for _, ip := range localIPs {
|
||||
if ipFilter != nil && !ipFilter(net.ParseIP(ip)) {
|
||||
continue
|
||||
}
|
||||
|
||||
wg.Add(1)
|
||||
go func(localIP string) {
|
||||
defer wg.Done()
|
||||
for _, port := range udpPorts {
|
||||
addr, err := config.GetExternalIP(ctx, stunServers, &net.UDPAddr{IP: net.ParseIP(localIP), Port: port})
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "address already in use") {
|
||||
logger.Debugw("failed to get external ip, address already in use", "local", localIP, "port", port)
|
||||
continue
|
||||
}
|
||||
logger.Infow("failed to get external ip", "local", localIP, "err", err)
|
||||
return
|
||||
}
|
||||
addrCh <- ipmapping{externalIP: addr, localIP: localIP}
|
||||
return
|
||||
}
|
||||
logger.Infow("failed to get external ip after all ports tried", "local", localIP, "ports", udpPorts)
|
||||
}(ip)
|
||||
}
|
||||
|
||||
var firstResloved bool
|
||||
natMapping := make(map[string]string)
|
||||
timeout := time.NewTimer(5 * time.Second)
|
||||
defer timeout.Stop()
|
||||
|
||||
done:
|
||||
for {
|
||||
select {
|
||||
case mapping := <-addrCh:
|
||||
if !firstResloved {
|
||||
firstResloved = true
|
||||
timeout.Reset(1 * time.Second)
|
||||
}
|
||||
if local, ok := natMapping[mapping.externalIP]; ok {
|
||||
logger.Infow("external ip already solved, ignore duplicate",
|
||||
"external", mapping.externalIP,
|
||||
"local", local,
|
||||
"ignore", mapping.localIP)
|
||||
} else {
|
||||
natMapping[mapping.externalIP] = mapping.localIP
|
||||
}
|
||||
|
||||
case <-timeout.C:
|
||||
break done
|
||||
}
|
||||
}
|
||||
cancel()
|
||||
wg.Wait()
|
||||
|
||||
if len(natMapping) == 0 {
|
||||
// no external ip resolved
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// mapping unresolved local ip to itself
|
||||
for _, local := range localIPs {
|
||||
var found bool
|
||||
for _, localIPMapping := range natMapping {
|
||||
if local == localIPMapping {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
natMapping[local] = local
|
||||
}
|
||||
}
|
||||
|
||||
nat1to1IPs := make([]string, 0, len(natMapping))
|
||||
for external, local := range natMapping {
|
||||
nat1to1IPs = append(nat1to1IPs, fmt.Sprintf("%s/%s", external, local))
|
||||
}
|
||||
return nat1to1IPs, nil
|
||||
}
|
||||
|
||||
func InterfaceFilterFromConf(ifs config.InterfacesConfig) func(string) bool {
|
||||
includes := ifs.Includes
|
||||
excludes := ifs.Excludes
|
||||
return func(s string) bool {
|
||||
// filter by include interfaces
|
||||
if len(includes) > 0 {
|
||||
for _, iface := range includes {
|
||||
if iface == s {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// filter by exclude interfaces
|
||||
if len(excludes) > 0 {
|
||||
for _, iface := range excludes {
|
||||
if iface == s {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
func IPFilterFromConf(ips config.IPsConfig) (func(ip net.IP) bool, error) {
|
||||
var ipnets [2][]*net.IPNet
|
||||
var err error
|
||||
for i, ips := range [][]string{ips.Includes, ips.Excludes} {
|
||||
ipnets[i], err = func(fromIPs []string) ([]*net.IPNet, error) {
|
||||
var toNets []*net.IPNet
|
||||
for _, ip := range fromIPs {
|
||||
_, ipnet, err := net.ParseCIDR(ip)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
toNets = append(toNets, ipnet)
|
||||
}
|
||||
return toNets, nil
|
||||
}(ips)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
includes, excludes := ipnets[0], ipnets[1]
|
||||
|
||||
return func(ip net.IP) bool {
|
||||
if len(includes) > 0 {
|
||||
for _, ipn := range includes {
|
||||
if ipn.Contains(ip) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
if len(excludes) > 0 {
|
||||
for _, ipn := range excludes {
|
||||
if ipn.Contains(ip) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -1,3 +1,17 @@
|
||||
// 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 (
|
||||
@@ -88,7 +102,7 @@ func (d *DynacastManager) Close() {
|
||||
}
|
||||
}
|
||||
|
||||
// THere are situations like track unmute or streaming from a sifferent node
|
||||
// THere are situations like track unmute or streaming from a different node
|
||||
// where subscribed quality needs to sent to the provider immediately.
|
||||
// This bypasses any debouncing and forces a subscribed quality update
|
||||
// with immediate effect.
|
||||
|
||||
@@ -1,3 +1,17 @@
|
||||
// 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 (
|
||||
@@ -6,10 +20,11 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/livekit/livekit-server/pkg/rtc/types"
|
||||
"github.com/livekit/protocol/livekit"
|
||||
"github.com/pion/webrtc/v3"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/livekit/livekit-server/pkg/rtc/types"
|
||||
"github.com/livekit/protocol/livekit"
|
||||
)
|
||||
|
||||
func TestSubscribedMaxQuality(t *testing.T) {
|
||||
|
||||
@@ -1,3 +1,17 @@
|
||||
// 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 (
|
||||
|
||||
+20
-5
@@ -1,3 +1,17 @@
|
||||
// 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"
|
||||
@@ -14,9 +28,10 @@ var (
|
||||
ErrMissingGrants = errors.New("VideoGrant is missing")
|
||||
|
||||
// Track subscription related
|
||||
ErrNoTrackPermission = errors.New("participant is not allowed to subscribe to this track")
|
||||
ErrNoSubscribePermission = errors.New("participant is not given permission to subscribe to tracks")
|
||||
ErrTrackNotFound = errors.New("track cannot be found")
|
||||
ErrTrackNotAttached = errors.New("track is not yet attached")
|
||||
ErrTrackNotBound = errors.New("track not bound")
|
||||
ErrNoTrackPermission = errors.New("participant is not allowed to subscribe to this track")
|
||||
ErrNoSubscribePermission = errors.New("participant is not given permission to subscribe to tracks")
|
||||
ErrTrackNotFound = errors.New("track cannot be found")
|
||||
ErrTrackNotAttached = errors.New("track is not yet attached")
|
||||
ErrTrackNotBound = errors.New("track not bound")
|
||||
ErrSubscriptionLimitExceeded = errors.New("participant has exceeded its subscription limit")
|
||||
)
|
||||
|
||||
+21
-7
@@ -1,3 +1,17 @@
|
||||
// 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 (
|
||||
@@ -21,7 +35,7 @@ func newMockParticipant(identity livekit.ParticipantIdentity, protocol types.Pro
|
||||
p.StateReturns(livekit.ParticipantInfo_JOINED)
|
||||
p.ProtocolVersionReturns(protocol)
|
||||
p.CanSubscribeReturns(true)
|
||||
p.CanPublishReturns(!hidden)
|
||||
p.CanPublishSourceReturns(!hidden)
|
||||
p.CanPublishDataReturns(!hidden)
|
||||
p.HiddenReturns(hidden)
|
||||
p.ToProtoReturns(&livekit.ParticipantInfo{
|
||||
@@ -31,7 +45,7 @@ func newMockParticipant(identity livekit.ParticipantIdentity, protocol types.Pro
|
||||
IsPublisher: publisher,
|
||||
})
|
||||
|
||||
p.SetMetadataStub = func(m string) {
|
||||
p.SetMetadataCalls(func(m string) {
|
||||
var f func(participant types.LocalParticipant)
|
||||
if p.OnParticipantUpdateCallCount() > 0 {
|
||||
f = p.OnParticipantUpdateArgsForCall(p.OnParticipantUpdateCallCount() - 1)
|
||||
@@ -39,7 +53,7 @@ func newMockParticipant(identity livekit.ParticipantIdentity, protocol types.Pro
|
||||
if f != nil {
|
||||
f(p)
|
||||
}
|
||||
}
|
||||
})
|
||||
updateTrack := func() {
|
||||
var f func(participant types.LocalParticipant, track types.MediaTrack)
|
||||
if p.OnTrackUpdatedCallCount() > 0 {
|
||||
@@ -50,12 +64,12 @@ func newMockParticipant(identity livekit.ParticipantIdentity, protocol types.Pro
|
||||
}
|
||||
}
|
||||
|
||||
p.SetTrackMutedStub = func(sid livekit.TrackID, muted bool, fromServer bool) {
|
||||
p.SetTrackMutedCalls(func(sid livekit.TrackID, muted bool, fromServer bool) {
|
||||
updateTrack()
|
||||
}
|
||||
p.AddTrackStub = func(req *livekit.AddTrackRequest) {
|
||||
})
|
||||
p.AddTrackCalls(func(req *livekit.AddTrackRequest) {
|
||||
updateTrack()
|
||||
}
|
||||
})
|
||||
|
||||
return p
|
||||
}
|
||||
|
||||
+26
-14
@@ -1,3 +1,17 @@
|
||||
// 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 (
|
||||
@@ -16,7 +30,7 @@ func registerCodecs(me *webrtc.MediaEngine, codecs []*livekit.Codec, rtcpFeedbac
|
||||
opusCodec := opusCodecCapability
|
||||
opusCodec.RTCPFeedback = rtcpFeedback.Audio
|
||||
var opusPayload webrtc.PayloadType
|
||||
if isCodecEnabled(codecs, opusCodec) {
|
||||
if IsCodecEnabled(codecs, opusCodec) {
|
||||
opusPayload = 111
|
||||
if err := me.RegisterCodec(webrtc.RTPCodecParameters{
|
||||
RTPCodecCapability: opusCodec,
|
||||
@@ -25,7 +39,7 @@ func registerCodecs(me *webrtc.MediaEngine, codecs []*livekit.Codec, rtcpFeedbac
|
||||
return err
|
||||
}
|
||||
|
||||
if isCodecEnabled(codecs, redCodecCapability) {
|
||||
if IsCodecEnabled(codecs, redCodecCapability) {
|
||||
if err := me.RegisterCodec(webrtc.RTPCodecParameters{
|
||||
RTPCodecCapability: redCodecCapability,
|
||||
PayloadType: 63,
|
||||
@@ -40,16 +54,14 @@ func registerCodecs(me *webrtc.MediaEngine, codecs []*livekit.Codec, rtcpFeedbac
|
||||
RTPCodecCapability: webrtc.RTPCodecCapability{MimeType: webrtc.MimeTypeVP8, ClockRate: 90000, RTCPFeedback: rtcpFeedback.Video},
|
||||
PayloadType: 96,
|
||||
},
|
||||
/*
|
||||
{
|
||||
RTPCodecCapability: webrtc.RTPCodecCapability{MimeType: webrtc.MimeTypeVP9, ClockRate: 90000, SDPFmtpLine: "profile-id=0", RTCPFeedback: rtcpFeedback.Video},
|
||||
PayloadType: 98,
|
||||
},
|
||||
{
|
||||
RTPCodecCapability: webrtc.RTPCodecCapability{MimeType: webrtc.MimeTypeVP9, ClockRate: 90000, SDPFmtpLine: "profile-id=1", RTCPFeedback: rtcpFeedback.Video},
|
||||
PayloadType: 100,
|
||||
},
|
||||
*/
|
||||
{
|
||||
RTPCodecCapability: webrtc.RTPCodecCapability{MimeType: webrtc.MimeTypeVP9, ClockRate: 90000, SDPFmtpLine: "profile-id=0", RTCPFeedback: rtcpFeedback.Video},
|
||||
PayloadType: 98,
|
||||
},
|
||||
{
|
||||
RTPCodecCapability: webrtc.RTPCodecCapability{MimeType: webrtc.MimeTypeVP9, ClockRate: 90000, SDPFmtpLine: "profile-id=1", RTCPFeedback: rtcpFeedback.Video},
|
||||
PayloadType: 100,
|
||||
},
|
||||
{
|
||||
RTPCodecCapability: webrtc.RTPCodecCapability{MimeType: webrtc.MimeTypeH264, ClockRate: 90000, SDPFmtpLine: "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f", RTCPFeedback: rtcpFeedback.Video},
|
||||
PayloadType: 125,
|
||||
@@ -67,7 +79,7 @@ func registerCodecs(me *webrtc.MediaEngine, codecs []*livekit.Codec, rtcpFeedbac
|
||||
PayloadType: 35,
|
||||
},
|
||||
} {
|
||||
if isCodecEnabled(codecs, codec.RTPCodecCapability) {
|
||||
if IsCodecEnabled(codecs, codec.RTPCodecCapability) {
|
||||
if err := me.RegisterCodec(codec, webrtc.RTPCodecTypeVideo); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -105,7 +117,7 @@ func createMediaEngine(codecs []*livekit.Codec, config DirectionConfig) (*webrtc
|
||||
return me, nil
|
||||
}
|
||||
|
||||
func isCodecEnabled(codecs []*livekit.Codec, cap webrtc.RTPCodecCapability) bool {
|
||||
func IsCodecEnabled(codecs []*livekit.Codec, cap webrtc.RTPCodecCapability) bool {
|
||||
for _, codec := range codecs {
|
||||
if !strings.EqualFold(codec.Mime, cap.MimeType) {
|
||||
continue
|
||||
|
||||
@@ -1,3 +1,17 @@
|
||||
// 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 (
|
||||
@@ -12,15 +26,15 @@ import (
|
||||
func TestIsCodecEnabled(t *testing.T) {
|
||||
t.Run("empty fmtp requirement should match all", func(t *testing.T) {
|
||||
enabledCodecs := []*livekit.Codec{{Mime: "video/h264"}}
|
||||
require.True(t, isCodecEnabled(enabledCodecs, webrtc.RTPCodecCapability{MimeType: webrtc.MimeTypeH264, SDPFmtpLine: "special"}))
|
||||
require.True(t, isCodecEnabled(enabledCodecs, webrtc.RTPCodecCapability{MimeType: webrtc.MimeTypeH264}))
|
||||
require.False(t, isCodecEnabled(enabledCodecs, webrtc.RTPCodecCapability{MimeType: webrtc.MimeTypeVP8}))
|
||||
require.True(t, IsCodecEnabled(enabledCodecs, webrtc.RTPCodecCapability{MimeType: webrtc.MimeTypeH264, SDPFmtpLine: "special"}))
|
||||
require.True(t, IsCodecEnabled(enabledCodecs, webrtc.RTPCodecCapability{MimeType: webrtc.MimeTypeH264}))
|
||||
require.False(t, IsCodecEnabled(enabledCodecs, webrtc.RTPCodecCapability{MimeType: webrtc.MimeTypeVP8}))
|
||||
})
|
||||
|
||||
t.Run("when fmtp is provided, require match", func(t *testing.T) {
|
||||
enabledCodecs := []*livekit.Codec{{Mime: "video/h264", FmtpLine: "special"}}
|
||||
require.True(t, isCodecEnabled(enabledCodecs, webrtc.RTPCodecCapability{MimeType: webrtc.MimeTypeH264, SDPFmtpLine: "special"}))
|
||||
require.False(t, isCodecEnabled(enabledCodecs, webrtc.RTPCodecCapability{MimeType: webrtc.MimeTypeH264}))
|
||||
require.False(t, isCodecEnabled(enabledCodecs, webrtc.RTPCodecCapability{MimeType: webrtc.MimeTypeVP8}))
|
||||
require.True(t, IsCodecEnabled(enabledCodecs, webrtc.RTPCodecCapability{MimeType: webrtc.MimeTypeH264, SDPFmtpLine: "special"}))
|
||||
require.False(t, IsCodecEnabled(enabledCodecs, webrtc.RTPCodecCapability{MimeType: webrtc.MimeTypeH264}))
|
||||
require.False(t, IsCodecEnabled(enabledCodecs, webrtc.RTPCodecCapability{MimeType: webrtc.MimeTypeVP8}))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,3 +1,17 @@
|
||||
// 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 (
|
||||
|
||||
@@ -1,3 +1,17 @@
|
||||
// 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 (
|
||||
@@ -190,6 +204,7 @@ func (t *MediaTrack) SetPendingCodecSid(codecs []*livekit.SimulcastCodec) {
|
||||
}
|
||||
}
|
||||
t.params.TrackInfo = ti
|
||||
t.MediaTrackReceiver.UpdateTrackInfo(ti)
|
||||
}
|
||||
|
||||
// AddReceiver adds a new RTP receiver to the track, returns true when receiver represents a new codec
|
||||
|
||||
@@ -1,3 +1,17 @@
|
||||
// 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 (
|
||||
@@ -98,4 +112,65 @@ func TestGetQualityForDimension(t *testing.T) {
|
||||
require.Equal(t, livekit.VideoQuality_MEDIUM, mt.GetQualityForDimension(800, 500))
|
||||
require.Equal(t, livekit.VideoQuality_HIGH, mt.GetQualityForDimension(1000, 700))
|
||||
})
|
||||
|
||||
t.Run("highest layer with smallest dimensions", func(t *testing.T) {
|
||||
mt := NewMediaTrack(MediaTrackParams{TrackInfo: &livekit.TrackInfo{
|
||||
Type: livekit.TrackType_VIDEO,
|
||||
Width: 1080,
|
||||
Height: 720,
|
||||
Layers: []*livekit.VideoLayer{
|
||||
{
|
||||
Quality: livekit.VideoQuality_LOW,
|
||||
Width: 480,
|
||||
Height: 270,
|
||||
},
|
||||
{
|
||||
Quality: livekit.VideoQuality_MEDIUM,
|
||||
Width: 1080,
|
||||
Height: 720,
|
||||
},
|
||||
{
|
||||
Quality: livekit.VideoQuality_HIGH,
|
||||
Width: 1080,
|
||||
Height: 720,
|
||||
},
|
||||
},
|
||||
}})
|
||||
|
||||
require.Equal(t, livekit.VideoQuality_LOW, mt.GetQualityForDimension(120, 120))
|
||||
require.Equal(t, livekit.VideoQuality_LOW, mt.GetQualityForDimension(300, 300))
|
||||
require.Equal(t, livekit.VideoQuality_HIGH, mt.GetQualityForDimension(800, 500))
|
||||
require.Equal(t, livekit.VideoQuality_HIGH, mt.GetQualityForDimension(1000, 700))
|
||||
require.Equal(t, livekit.VideoQuality_HIGH, mt.GetQualityForDimension(1200, 800))
|
||||
|
||||
mt = NewMediaTrack(MediaTrackParams{TrackInfo: &livekit.TrackInfo{
|
||||
Type: livekit.TrackType_VIDEO,
|
||||
Width: 1080,
|
||||
Height: 720,
|
||||
Layers: []*livekit.VideoLayer{
|
||||
{
|
||||
Quality: livekit.VideoQuality_LOW,
|
||||
Width: 480,
|
||||
Height: 270,
|
||||
},
|
||||
{
|
||||
Quality: livekit.VideoQuality_MEDIUM,
|
||||
Width: 480,
|
||||
Height: 270,
|
||||
},
|
||||
{
|
||||
Quality: livekit.VideoQuality_HIGH,
|
||||
Width: 1080,
|
||||
Height: 720,
|
||||
},
|
||||
},
|
||||
}})
|
||||
|
||||
require.Equal(t, livekit.VideoQuality_MEDIUM, mt.GetQualityForDimension(120, 120))
|
||||
require.Equal(t, livekit.VideoQuality_MEDIUM, mt.GetQualityForDimension(300, 300))
|
||||
require.Equal(t, livekit.VideoQuality_HIGH, mt.GetQualityForDimension(800, 500))
|
||||
require.Equal(t, livekit.VideoQuality_HIGH, mt.GetQualityForDimension(1000, 700))
|
||||
require.Equal(t, livekit.VideoQuality_HIGH, mt.GetQualityForDimension(1200, 800))
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
@@ -1,3 +1,17 @@
|
||||
// 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 (
|
||||
@@ -19,6 +33,7 @@ import (
|
||||
"github.com/livekit/livekit-server/pkg/rtc/types"
|
||||
"github.com/livekit/livekit-server/pkg/sfu"
|
||||
"github.com/livekit/livekit-server/pkg/sfu/buffer"
|
||||
"github.com/livekit/livekit-server/pkg/sfu/dependencydescriptor"
|
||||
"github.com/livekit/livekit-server/pkg/telemetry"
|
||||
)
|
||||
|
||||
@@ -54,7 +69,7 @@ func (m mediaTrackReceiverState) String() string {
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------
|
||||
// -----------------------------------------------------
|
||||
|
||||
type simulcastReceiver struct {
|
||||
sfu.TrackReceiver
|
||||
@@ -206,6 +221,15 @@ func (t *MediaTrackReceiver) SetupReceiver(receiver sfu.TrackReceiver, priority
|
||||
}
|
||||
|
||||
func (t *MediaTrackReceiver) SetPotentialCodecs(codecs []webrtc.RTPCodecParameters, headers []webrtc.RTPHeaderExtensionParameter) {
|
||||
// The potential codecs have not published yet, so we can't get the actual Extensions, the client/browser uses same extensions
|
||||
// for all video codecs so we assume they will have same extensions as the primary codec except for the dependency descriptor
|
||||
// that is munged in svc codec.
|
||||
headersWithoutDD := make([]webrtc.RTPHeaderExtensionParameter, 0, len(headers))
|
||||
for _, h := range headers {
|
||||
if h.URI != dependencydescriptor.ExtensionUrl {
|
||||
headersWithoutDD = append(headersWithoutDD, h)
|
||||
}
|
||||
}
|
||||
t.lock.Lock()
|
||||
t.potentialCodecs = codecs
|
||||
for i, c := range codecs {
|
||||
@@ -217,8 +241,12 @@ func (t *MediaTrackReceiver) SetPotentialCodecs(codecs []webrtc.RTPCodecParamete
|
||||
}
|
||||
}
|
||||
if !exist {
|
||||
extHeaders := headers
|
||||
if !sfu.IsSvcCodec(c.MimeType) {
|
||||
extHeaders = headersWithoutDD
|
||||
}
|
||||
t.receivers = append(t.receivers, &simulcastReceiver{
|
||||
TrackReceiver: NewDummyReceiver(livekit.TrackID(t.trackInfo.Sid), string(t.PublisherID()), c, headers),
|
||||
TrackReceiver: NewDummyReceiver(livekit.TrackID(t.trackInfo.Sid), string(t.PublisherID()), c, extHeaders),
|
||||
priority: i,
|
||||
})
|
||||
}
|
||||
@@ -240,7 +268,7 @@ func (t *MediaTrackReceiver) SetLayerSsrc(mime string, rid string, ssrc uint32)
|
||||
defer t.lock.Unlock()
|
||||
|
||||
layer := buffer.RidToSpatialLayer(rid, t.params.TrackInfo)
|
||||
if layer == sfu.InvalidLayerSpatial {
|
||||
if layer == buffer.InvalidLayerSpatial {
|
||||
// non-simulcast case will not have `rid`
|
||||
layer = 0
|
||||
}
|
||||
@@ -667,11 +695,13 @@ func (t *MediaTrackReceiver) GetQualityForDimension(width, height uint32) liveki
|
||||
})
|
||||
}
|
||||
|
||||
// finds the lowest layer that could satisfy client demands
|
||||
// finds the highest layer with smallest dimensions that still satisfy client demands
|
||||
requestedSize = uint32(float32(requestedSize) * layerSelectionTolerance)
|
||||
for i, s := range layerSizes {
|
||||
quality = livekit.VideoQuality(i)
|
||||
if s >= requestedSize {
|
||||
if i == len(layerSizes)-1 {
|
||||
break
|
||||
} else if s >= requestedSize && s != layerSizes[i+1] {
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -788,4 +818,11 @@ func (t *MediaTrackReceiver) GetTemporalLayerForSpatialFps(spatial int32, fps ui
|
||||
return buffer.DefaultMaxLayerTemporal
|
||||
}
|
||||
|
||||
func (t *MediaTrackReceiver) IsEncrypted() bool {
|
||||
t.lock.RLock()
|
||||
defer t.lock.RUnlock()
|
||||
|
||||
return t.trackInfo.Encryption != livekit.Encryption_NONE
|
||||
}
|
||||
|
||||
// ---------------------------
|
||||
|
||||
@@ -1,3 +1,17 @@
|
||||
// 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 (
|
||||
@@ -98,12 +112,18 @@ func (t *MediaTrackSubscriptions) AddSubscriber(sub types.LocalParticipant, wr *
|
||||
for _, c := range codecs {
|
||||
c.RTCPFeedback = rtcpFeedback
|
||||
}
|
||||
var trailer []byte
|
||||
if t.params.MediaTrack.IsEncrypted() {
|
||||
trailer = sub.GetTrailer()
|
||||
}
|
||||
downTrack, err := sfu.NewDownTrack(
|
||||
codecs,
|
||||
wr,
|
||||
sub.GetBufferFactory(),
|
||||
subscriberID,
|
||||
t.params.ReceiverConfig.PacketBufferSize,
|
||||
sub.GetPacer(),
|
||||
trailer,
|
||||
LoggerWithTrack(sub.GetLogger(), trackID, t.params.IsRelayed),
|
||||
)
|
||||
if err != nil {
|
||||
@@ -127,7 +147,11 @@ func (t *MediaTrackSubscriptions) AddSubscriber(sub types.LocalParticipant, wr *
|
||||
// Bind callback can happen from replaceTrack, so set it up early
|
||||
var reusingTransceiver atomic.Bool
|
||||
var dtState sfu.DownTrackState
|
||||
downTrack.OnBinding(func() {
|
||||
downTrack.OnBinding(func(err error) {
|
||||
if err != nil {
|
||||
go subTrack.Bound(err)
|
||||
return
|
||||
}
|
||||
wr.DetermineReceiver(downTrack.Codec())
|
||||
if reusingTransceiver.Load() {
|
||||
downTrack.SeedState(dtState)
|
||||
@@ -141,7 +165,7 @@ func (t *MediaTrackSubscriptions) AddSubscriber(sub types.LocalParticipant, wr *
|
||||
)
|
||||
}
|
||||
|
||||
go subTrack.Bound()
|
||||
go subTrack.Bound(nil)
|
||||
|
||||
subTrack.SetPublisherMuted(t.params.MediaTrack.IsMuted())
|
||||
})
|
||||
|
||||
+395
-220
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,21 @@
|
||||
// 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"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -175,6 +190,32 @@ func TestTrackPublishing(t *testing.T) {
|
||||
// check SID is the same
|
||||
require.Equal(t, p.pendingTracks["cid"].trackInfos[0].Sid, p.pendingTracks["cid"].trackInfos[1].Sid)
|
||||
})
|
||||
|
||||
t.Run("should not allow adding disallowed sources", func(t *testing.T) {
|
||||
p := newParticipantForTest("test")
|
||||
p.SetPermission(&livekit.ParticipantPermission{
|
||||
CanPublish: true,
|
||||
CanPublishSources: []livekit.TrackSource{
|
||||
livekit.TrackSource_CAMERA,
|
||||
},
|
||||
})
|
||||
sink := p.params.Sink.(*routingfakes.FakeMessageSink)
|
||||
p.AddTrack(&livekit.AddTrackRequest{
|
||||
Cid: "cid",
|
||||
Name: "webcam",
|
||||
Source: livekit.TrackSource_CAMERA,
|
||||
Type: livekit.TrackType_VIDEO,
|
||||
})
|
||||
require.Equal(t, 1, sink.WriteMessageCallCount())
|
||||
|
||||
p.AddTrack(&livekit.AddTrackRequest{
|
||||
Cid: "cid2",
|
||||
Name: "rejected source",
|
||||
Type: livekit.TrackType_AUDIO,
|
||||
Source: livekit.TrackSource_MICROPHONE,
|
||||
})
|
||||
require.Equal(t, 1, sink.WriteMessageCallCount())
|
||||
})
|
||||
}
|
||||
|
||||
func TestOutOfOrderUpdates(t *testing.T) {
|
||||
@@ -202,7 +243,7 @@ func TestOutOfOrderUpdates(t *testing.T) {
|
||||
func TestDisconnectTiming(t *testing.T) {
|
||||
t.Run("Negotiate doesn't panic after channel closed", func(t *testing.T) {
|
||||
p := newParticipantForTest("test")
|
||||
msg := routing.NewMessageChannel(routing.DefaultMessageChannelSize)
|
||||
msg := routing.NewMessageChannel(livekit.ConnectionID("test"), routing.DefaultMessageChannelSize)
|
||||
p.params.Sink = msg
|
||||
go func() {
|
||||
for msg := range msg.ReadChan() {
|
||||
@@ -361,7 +402,7 @@ func TestSetStableTrackID(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestDisableCodecs(t *testing.T) {
|
||||
participant := newParticipantForTestWithOpts(livekit.ParticipantIdentity("123"), &participantOpts{
|
||||
participant := newParticipantForTestWithOpts("123", &participantOpts{
|
||||
publisher: false,
|
||||
clientConf: &livekit.ClientConfiguration{
|
||||
DisabledCodecs: &livekit.DisabledCodecs{
|
||||
@@ -395,7 +436,7 @@ func TestDisableCodecs(t *testing.T) {
|
||||
participant.SetResponseSink(sink)
|
||||
var answer webrtc.SessionDescription
|
||||
var answerReceived atomic.Bool
|
||||
sink.WriteMessageStub = func(msg proto.Message) error {
|
||||
sink.WriteMessageCalls(func(msg proto.Message) error {
|
||||
if res, ok := msg.(*livekit.SignalResponse); ok {
|
||||
if res.GetAnswer() != nil {
|
||||
answer = FromProtoSessionDescription(res.GetAnswer())
|
||||
@@ -403,7 +444,7 @@ func TestDisableCodecs(t *testing.T) {
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
})
|
||||
participant.HandleOffer(sdp)
|
||||
|
||||
testutils.WithTimeout(t, func() string {
|
||||
@@ -425,6 +466,184 @@ func TestDisableCodecs(t *testing.T) {
|
||||
require.False(t, found264)
|
||||
}
|
||||
|
||||
func TestPreferVideoCodecForPublisher(t *testing.T) {
|
||||
participant := newParticipantForTestWithOpts("123", &participantOpts{
|
||||
publisher: true,
|
||||
})
|
||||
participant.SetMigrateState(types.MigrateStateComplete)
|
||||
|
||||
pc, err := webrtc.NewPeerConnection(webrtc.Configuration{})
|
||||
require.NoError(t, err)
|
||||
defer pc.Close()
|
||||
|
||||
for i := 0; i < 2; i++ {
|
||||
// publish h264 track without client preferred codec
|
||||
trackCid := fmt.Sprintf("preferh264video%d", i)
|
||||
participant.AddTrack(&livekit.AddTrackRequest{
|
||||
Type: livekit.TrackType_VIDEO,
|
||||
Name: "video",
|
||||
Width: 1280,
|
||||
Height: 720,
|
||||
Source: livekit.TrackSource_CAMERA,
|
||||
SimulcastCodecs: []*livekit.SimulcastCodec{
|
||||
{
|
||||
Codec: "h264",
|
||||
Cid: trackCid,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
track, err := webrtc.NewTrackLocalStaticRTP(webrtc.RTPCodecCapability{MimeType: "video/vp8"}, trackCid, trackCid)
|
||||
require.NoError(t, err)
|
||||
transceiver, err := pc.AddTransceiverFromTrack(track, webrtc.RTPTransceiverInit{Direction: webrtc.RTPTransceiverDirectionSendrecv})
|
||||
require.NoError(t, err)
|
||||
sdp, err := pc.CreateOffer(nil)
|
||||
require.NoError(t, err)
|
||||
pc.SetLocalDescription(sdp)
|
||||
codecs := transceiver.Receiver().GetParameters().Codecs
|
||||
|
||||
// h264 should not be preferred
|
||||
require.NotEqual(t, codecs[0].MimeType, "video/h264")
|
||||
|
||||
sink := &routingfakes.FakeMessageSink{}
|
||||
participant.SetResponseSink(sink)
|
||||
var answer webrtc.SessionDescription
|
||||
var answerReceived atomic.Bool
|
||||
sink.WriteMessageCalls(func(msg proto.Message) error {
|
||||
if res, ok := msg.(*livekit.SignalResponse); ok {
|
||||
if res.GetAnswer() != nil {
|
||||
answer = FromProtoSessionDescription(res.GetAnswer())
|
||||
pc.SetRemoteDescription(answer)
|
||||
answerReceived.Store(true)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
participant.HandleOffer(sdp)
|
||||
|
||||
require.Eventually(t, func() bool { return answerReceived.Load() }, 5*time.Second, 10*time.Millisecond)
|
||||
|
||||
var h264Preferred bool
|
||||
parsed, err := answer.Unmarshal()
|
||||
require.NoError(t, err)
|
||||
var videoSectionIndex int
|
||||
for _, m := range parsed.MediaDescriptions {
|
||||
if m.MediaName.Media == "video" {
|
||||
if videoSectionIndex == i {
|
||||
codecs, err := codecsFromMediaDescription(m)
|
||||
require.NoError(t, err)
|
||||
if strings.EqualFold(codecs[0].Name, "h264") {
|
||||
h264Preferred = true
|
||||
break
|
||||
}
|
||||
}
|
||||
videoSectionIndex++
|
||||
}
|
||||
}
|
||||
|
||||
require.Truef(t, h264Preferred, "h264 should be preferred for video section %d, answer sdp: \n%s", i, answer.SDP)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPreferAudioCodecForRed(t *testing.T) {
|
||||
participant := newParticipantForTestWithOpts("123", &participantOpts{
|
||||
publisher: true,
|
||||
})
|
||||
participant.SetMigrateState(types.MigrateStateComplete)
|
||||
|
||||
me := webrtc.MediaEngine{}
|
||||
me.RegisterDefaultCodecs()
|
||||
require.NoError(t, me.RegisterCodec(webrtc.RTPCodecParameters{
|
||||
RTPCodecCapability: redCodecCapability,
|
||||
PayloadType: 63,
|
||||
}, webrtc.RTPCodecTypeAudio))
|
||||
|
||||
api := webrtc.NewAPI(webrtc.WithMediaEngine(&me))
|
||||
pc, err := api.NewPeerConnection(webrtc.Configuration{})
|
||||
require.NoError(t, err)
|
||||
defer pc.Close()
|
||||
|
||||
for i, disableRed := range []bool{false, true} {
|
||||
t.Run(fmt.Sprintf("disableRed=%v", disableRed), func(t *testing.T) {
|
||||
trackCid := fmt.Sprintf("audiotrack%d", i)
|
||||
participant.AddTrack(&livekit.AddTrackRequest{
|
||||
Type: livekit.TrackType_AUDIO,
|
||||
DisableRed: disableRed,
|
||||
Cid: trackCid,
|
||||
})
|
||||
track, err := webrtc.NewTrackLocalStaticRTP(webrtc.RTPCodecCapability{MimeType: "audio/opus"}, trackCid, trackCid)
|
||||
require.NoError(t, err)
|
||||
transceiver, err := pc.AddTransceiverFromTrack(track, webrtc.RTPTransceiverInit{Direction: webrtc.RTPTransceiverDirectionSendrecv})
|
||||
require.NoError(t, err)
|
||||
codecs := transceiver.Sender().GetParameters().Codecs
|
||||
for i, c := range codecs {
|
||||
if c.MimeType == "audio/opus" && i != 0 {
|
||||
codecs[0], codecs[i] = codecs[i], codecs[0]
|
||||
break
|
||||
}
|
||||
}
|
||||
transceiver.SetCodecPreferences(codecs)
|
||||
sdp, err := pc.CreateOffer(nil)
|
||||
require.NoError(t, err)
|
||||
pc.SetLocalDescription(sdp)
|
||||
// opus should be preferred
|
||||
require.Equal(t, codecs[0].MimeType, "audio/opus", sdp)
|
||||
|
||||
sink := &routingfakes.FakeMessageSink{}
|
||||
participant.SetResponseSink(sink)
|
||||
var answer webrtc.SessionDescription
|
||||
var answerReceived atomic.Bool
|
||||
sink.WriteMessageCalls(func(msg proto.Message) error {
|
||||
if res, ok := msg.(*livekit.SignalResponse); ok {
|
||||
if res.GetAnswer() != nil {
|
||||
answer = FromProtoSessionDescription(res.GetAnswer())
|
||||
pc.SetRemoteDescription(answer)
|
||||
answerReceived.Store(true)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
participant.HandleOffer(sdp)
|
||||
|
||||
require.Eventually(t, func() bool { return answerReceived.Load() }, 5*time.Second, 10*time.Millisecond)
|
||||
|
||||
var redPreferred bool
|
||||
parsed, err := answer.Unmarshal()
|
||||
require.NoError(t, err)
|
||||
var audioSectionIndex int
|
||||
for _, m := range parsed.MediaDescriptions {
|
||||
if m.MediaName.Media == "audio" {
|
||||
if audioSectionIndex == i {
|
||||
codecs, err := codecsFromMediaDescription(m)
|
||||
require.NoError(t, err)
|
||||
// nack is always enabled. if red is preferred, server will not generate nack request
|
||||
var nackEnabled bool
|
||||
for _, c := range codecs {
|
||||
if c.Name == "opus" {
|
||||
for _, fb := range c.RTCPFeedback {
|
||||
if strings.Contains(fb, "nack") {
|
||||
nackEnabled = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
require.True(t, nackEnabled, "nack should be enabled for opus")
|
||||
|
||||
if strings.EqualFold(codecs[0].Name, "red") {
|
||||
redPreferred = true
|
||||
break
|
||||
}
|
||||
}
|
||||
audioSectionIndex++
|
||||
}
|
||||
}
|
||||
require.Equalf(t, !disableRed, redPreferred, "offer : \n%s\nanswer sdp: \n%s", sdp, answer.SDP)
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
type participantOpts struct {
|
||||
permissions *livekit.ParticipantPermission
|
||||
protocolVersion types.ProtocolVersion
|
||||
@@ -444,7 +663,7 @@ func newParticipantForTestWithOpts(identity livekit.ParticipantIdentity, opts *p
|
||||
// disable mux, it doesn't play too well with unit test
|
||||
conf.RTC.UDPPort = 0
|
||||
conf.RTC.TCPPort = 0
|
||||
rtcConf, err := NewWebRTCConfig(conf, "")
|
||||
rtcConf, err := NewWebRTCConfig(conf)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -478,6 +697,7 @@ func newParticipantForTestWithOpts(identity livekit.ParticipantIdentity, opts *p
|
||||
ClientInfo: ClientInfo{ClientInfo: opts.clientInfo},
|
||||
Logger: LoggerWithParticipant(logger.GetLogger(), identity, sid, false),
|
||||
Telemetry: &telemetryfakes.FakeTelemetryService{},
|
||||
VersionGenerator: utils.NewDefaultTimedVersionGenerator(),
|
||||
})
|
||||
p.isPublisher.Store(opts.publisher)
|
||||
p.updateState(livekit.ParticipantInfo_ACTIVE)
|
||||
|
||||
@@ -1,3 +1,17 @@
|
||||
// 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 (
|
||||
@@ -112,8 +126,15 @@ func (p *ParticipantImpl) setCodecPreferencesVideoForPublisher(offer webrtc.Sess
|
||||
continue
|
||||
}
|
||||
|
||||
var info *livekit.TrackInfo
|
||||
p.pendingTracksLock.RLock()
|
||||
_, info := p.getPendingTrack(streamID, livekit.TrackType_VIDEO)
|
||||
mt := p.getPublishedTrackBySdpCid(streamID)
|
||||
if mt != nil {
|
||||
info = mt.ToProto()
|
||||
} else {
|
||||
_, info = p.getPendingTrack(streamID, livekit.TrackType_VIDEO)
|
||||
}
|
||||
|
||||
if info == nil {
|
||||
p.pendingTracksLock.RUnlock()
|
||||
continue
|
||||
@@ -131,8 +152,8 @@ func (p *ParticipantImpl) setCodecPreferencesVideoForPublisher(offer webrtc.Sess
|
||||
p.pendingTracksLock.RUnlock()
|
||||
|
||||
mime = strings.ToUpper(mime)
|
||||
// remove dd extension if av1 not preferred
|
||||
if !strings.Contains(mime, "AV1") {
|
||||
// remove dd extension if av1/vp9 not preferred
|
||||
if !strings.Contains(strings.ToLower(mime), "av1") && !strings.Contains(strings.ToLower(mime), "vp9") {
|
||||
for i, attr := range unmatchVideo.Attributes {
|
||||
if strings.Contains(attr.Value, dd.ExtensionUrl) {
|
||||
unmatchVideo.Attributes[i] = unmatchVideo.Attributes[len(unmatchVideo.Attributes)-1]
|
||||
|
||||
@@ -1,33 +1,43 @@
|
||||
// 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"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/pion/webrtc/v3"
|
||||
|
||||
"github.com/livekit/protocol/livekit"
|
||||
"github.com/livekit/psrpc"
|
||||
|
||||
"github.com/livekit/livekit-server/pkg/routing"
|
||||
"github.com/livekit/livekit-server/pkg/rtc/types"
|
||||
)
|
||||
|
||||
func (p *ParticipantImpl) getResponseSink() routing.MessageSink {
|
||||
if !p.resSinkValid.Load() {
|
||||
return nil
|
||||
}
|
||||
sink := p.resSink.Load()
|
||||
if s, ok := sink.(routing.MessageSink); ok {
|
||||
return s
|
||||
}
|
||||
return nil
|
||||
p.resSinkMu.Lock()
|
||||
defer p.resSinkMu.Unlock()
|
||||
return p.resSink
|
||||
}
|
||||
|
||||
func (p *ParticipantImpl) SetResponseSink(sink routing.MessageSink) {
|
||||
p.resSinkValid.Store(sink != nil)
|
||||
if sink != nil {
|
||||
// cannot store nil into atomic.Value
|
||||
p.resSink.Store(sink)
|
||||
}
|
||||
p.resSinkMu.Lock()
|
||||
defer p.resSinkMu.Unlock()
|
||||
p.resSink = sink
|
||||
}
|
||||
|
||||
func (p *ParticipantImpl) SendJoinResponse(joinResponse *livekit.JoinResponse) error {
|
||||
@@ -90,6 +100,10 @@ func (p *ParticipantImpl) SendParticipantUpdate(participantsToUpdate []*livekit.
|
||||
isValid = false
|
||||
}
|
||||
}
|
||||
if pi.Permission != nil && pi.Permission.Hidden && pi.Sid != string(p.params.SID) {
|
||||
p.params.Logger.Debugw("skipping hidden participant update", "otherParticipant", pi.Identity)
|
||||
isValid = false
|
||||
}
|
||||
if isValid {
|
||||
p.updateCache.Add(pID, participantUpdateInfo{version: pi.Version, state: pi.State, updatedAt: time.Now()})
|
||||
validUpdates = append(validUpdates, pi)
|
||||
@@ -179,7 +193,9 @@ func (p *ParticipantImpl) SendRefreshToken(token string) error {
|
||||
})
|
||||
}
|
||||
|
||||
func (p *ParticipantImpl) SendReconnectResponse(reconnectResponse *livekit.ReconnectResponse) error {
|
||||
func (p *ParticipantImpl) HandleReconnectAndSendResponse(reconnectReason livekit.ReconnectReason, reconnectResponse *livekit.ReconnectResponse) error {
|
||||
p.TransportManager.HandleClientReconnect(reconnectReason)
|
||||
|
||||
if !p.params.ClientInfo.CanHandleReconnectResponse() {
|
||||
return nil
|
||||
}
|
||||
@@ -265,12 +281,16 @@ func (p *ParticipantImpl) writeMessage(msg *livekit.SignalResponse) error {
|
||||
|
||||
sink := p.getResponseSink()
|
||||
if sink == nil {
|
||||
p.params.Logger.Infow("could not send message to participant", "messageType", fmt.Sprintf("%T", msg.Message))
|
||||
p.params.Logger.Debugw("could not send message to participant", "messageType", fmt.Sprintf("%T", msg.Message))
|
||||
return nil
|
||||
}
|
||||
|
||||
err := sink.WriteMessage(msg)
|
||||
if err != nil {
|
||||
if errors.Is(err, psrpc.Canceled) {
|
||||
p.params.Logger.Debugw("could not send message to participant",
|
||||
"error", err, "messageType", fmt.Sprintf("%T", msg.Message))
|
||||
return nil
|
||||
} else if err != nil {
|
||||
p.params.Logger.Warnw("could not send message to participant", err,
|
||||
"messageType", fmt.Sprintf("%T", msg.Message))
|
||||
return err
|
||||
@@ -279,10 +299,10 @@ func (p *ParticipantImpl) writeMessage(msg *livekit.SignalResponse) error {
|
||||
}
|
||||
|
||||
// closes signal connection to notify client to resume/reconnect
|
||||
func (p *ParticipantImpl) CloseSignalConnection() {
|
||||
func (p *ParticipantImpl) CloseSignalConnection(reason types.SignallingCloseReason) {
|
||||
sink := p.getResponseSink()
|
||||
if sink != nil {
|
||||
p.params.Logger.Infow("closing signal connection")
|
||||
p.params.Logger.Infow("closing signal connection", "reason", reason, "connID", sink.ConnectionID())
|
||||
sink.Close()
|
||||
p.SetResponseSink(nil)
|
||||
}
|
||||
|
||||
+204
-94
@@ -1,3 +1,17 @@
|
||||
// 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 (
|
||||
@@ -27,14 +41,19 @@ import (
|
||||
|
||||
const (
|
||||
DefaultEmptyTimeout = 5 * 60 // 5m
|
||||
DefaultRoomDepartureGrace = 20
|
||||
AudioLevelQuantization = 8 // ideally power of 2 to minimize float decimal
|
||||
AudioLevelQuantization = 8 // ideally power of 2 to minimize float decimal
|
||||
invAudioLevelQuantization = 1.0 / AudioLevelQuantization
|
||||
subscriberUpdateInterval = 3 * time.Second
|
||||
|
||||
dataForwardLoadBalanceThreshold = 20
|
||||
)
|
||||
|
||||
var (
|
||||
// var to allow unit test override
|
||||
RoomDepartureGrace uint32 = 20
|
||||
roomUpdateInterval = 5 * time.Second // frequency to update room participant counts
|
||||
)
|
||||
|
||||
type broadcastOptions struct {
|
||||
skipSource bool
|
||||
immediate bool
|
||||
@@ -43,9 +62,10 @@ type broadcastOptions struct {
|
||||
type Room struct {
|
||||
lock sync.RWMutex
|
||||
|
||||
protoRoom *livekit.Room
|
||||
internal *livekit.RoomInternal
|
||||
Logger logger.Logger
|
||||
protoRoom *livekit.Room
|
||||
internal *livekit.RoomInternal
|
||||
protoProxy *utils.ProtoProxy[*livekit.Room]
|
||||
Logger logger.Logger
|
||||
|
||||
config WebRTCConfig
|
||||
audioConfig *config.AudioConfig
|
||||
@@ -71,8 +91,10 @@ type Room struct {
|
||||
leftAt atomic.Int64
|
||||
closed chan struct{}
|
||||
|
||||
trailer []byte
|
||||
|
||||
onParticipantChanged func(p types.LocalParticipant)
|
||||
onMetadataUpdate func(metadata string)
|
||||
onRoomUpdated func()
|
||||
onClose func()
|
||||
}
|
||||
|
||||
@@ -105,7 +127,9 @@ func NewRoom(
|
||||
bufferFactory: buffer.NewFactoryOfBufferFactory(config.Receiver.PacketBufferSize),
|
||||
batchedUpdates: make(map[livekit.ParticipantIdentity]*livekit.ParticipantInfo),
|
||||
closed: make(chan struct{}),
|
||||
trailer: []byte(utils.RandomSecret()),
|
||||
}
|
||||
r.protoProxy = utils.NewProtoProxy[*livekit.Room](roomUpdateInterval, r.updateProto)
|
||||
if r.protoRoom.EmptyTimeout == 0 {
|
||||
r.protoRoom.EmptyTimeout = DefaultEmptyTimeout
|
||||
}
|
||||
@@ -115,16 +139,13 @@ func NewRoom(
|
||||
|
||||
go r.audioUpdateWorker()
|
||||
go r.connectionQualityWorker()
|
||||
go r.subscriberBroadcastWorker()
|
||||
go r.changeUpdateWorker()
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
func (r *Room) ToProto() *livekit.Room {
|
||||
r.lock.RLock()
|
||||
defer r.lock.RUnlock()
|
||||
|
||||
return proto.Clone(r.protoRoom).(*livekit.Room)
|
||||
return r.protoProxy.Get()
|
||||
}
|
||||
|
||||
func (r *Room) Name() livekit.RoomName {
|
||||
@@ -135,6 +156,15 @@ func (r *Room) ID() livekit.RoomID {
|
||||
return livekit.RoomID(r.protoRoom.Sid)
|
||||
}
|
||||
|
||||
func (r *Room) Trailer() []byte {
|
||||
r.lock.RLock()
|
||||
defer r.lock.RUnlock()
|
||||
|
||||
trailer := make([]byte, len(r.trailer))
|
||||
copy(trailer, r.trailer)
|
||||
return trailer
|
||||
}
|
||||
|
||||
func (r *Room) GetParticipant(identity livekit.ParticipantIdentity) types.LocalParticipant {
|
||||
r.lock.RLock()
|
||||
defer r.lock.RUnlock()
|
||||
@@ -240,14 +270,13 @@ func (r *Room) Join(participant types.LocalParticipant, requestSource routing.Me
|
||||
}
|
||||
|
||||
if r.protoRoom.MaxParticipants > 0 && !participant.IsRecorder() {
|
||||
participantCount := 0
|
||||
numParticipants := uint32(0)
|
||||
for _, p := range r.participants {
|
||||
if !p.IsRecorder() {
|
||||
participantCount++
|
||||
numParticipants++
|
||||
}
|
||||
}
|
||||
|
||||
if participantCount >= int(r.protoRoom.MaxParticipants) {
|
||||
if numParticipants >= r.protoRoom.MaxParticipants {
|
||||
return ErrMaxParticipantsExceeded
|
||||
}
|
||||
}
|
||||
@@ -255,9 +284,6 @@ func (r *Room) Join(participant types.LocalParticipant, requestSource routing.Me
|
||||
if r.FirstJoinedAt() == 0 {
|
||||
r.joinedAt.Store(time.Now().Unix())
|
||||
}
|
||||
if !participant.Hidden() {
|
||||
r.protoRoom.NumParticipants++
|
||||
}
|
||||
|
||||
// it's important to set this before connection, we don't want to miss out on any published tracks
|
||||
participant.OnTrackPublished(r.onTrackPublished)
|
||||
@@ -280,10 +306,15 @@ func (r *Room) Join(participant types.LocalParticipant, requestSource routing.Me
|
||||
// start the workers once connectivity is established
|
||||
p.Start()
|
||||
|
||||
r.telemetry.ParticipantActive(context.Background(), r.ToProto(), p.ToProto(), &livekit.AnalyticsClientMeta{
|
||||
ClientConnectTime: uint32(time.Since(p.ConnectedAt()).Milliseconds()),
|
||||
ConnectionType: string(p.GetICEConnectionType()),
|
||||
})
|
||||
r.telemetry.ParticipantActive(context.Background(),
|
||||
r.ToProto(),
|
||||
p.ToProto(),
|
||||
&livekit.AnalyticsClientMeta{
|
||||
ClientConnectTime: uint32(time.Since(p.ConnectedAt()).Milliseconds()),
|
||||
ConnectionType: string(p.GetICEConnectionType()),
|
||||
},
|
||||
false,
|
||||
)
|
||||
} else if state == livekit.ParticipantInfo_DISCONNECTED {
|
||||
// remove participant from room
|
||||
go r.RemoveParticipant(p.Identity(), p.ID(), types.ParticipantCloseReasonStateDisconnected)
|
||||
@@ -336,7 +367,9 @@ func (r *Room) Join(participant types.LocalParticipant, requestSource routing.Me
|
||||
|
||||
if participant.IsRecorder() && !r.protoRoom.ActiveRecording {
|
||||
r.protoRoom.ActiveRecording = true
|
||||
r.sendRoomUpdateLocked()
|
||||
r.protoProxy.MarkDirty(true)
|
||||
} else {
|
||||
r.protoProxy.MarkDirty(false)
|
||||
}
|
||||
|
||||
r.participants[participant.Identity()] = participant
|
||||
@@ -398,28 +431,26 @@ func (r *Room) GetParticipantRequestSource(identity livekit.ParticipantIdentity)
|
||||
func (r *Room) ResumeParticipant(p types.LocalParticipant, requestSource routing.MessageSource, responseSink routing.MessageSink, iceServers []*livekit.ICEServer, reason livekit.ReconnectReason) error {
|
||||
r.ReplaceParticipantRequestSource(p.Identity(), requestSource)
|
||||
// close previous sink, and link to new one
|
||||
p.CloseSignalConnection()
|
||||
p.CloseSignalConnection(types.SignallingCloseReasonResume)
|
||||
p.SetResponseSink(responseSink)
|
||||
|
||||
p.SetSignalSourceValid(true)
|
||||
|
||||
if err := p.SendReconnectResponse(&livekit.ReconnectResponse{
|
||||
if err := p.HandleReconnectAndSendResponse(reason, &livekit.ReconnectResponse{
|
||||
IceServers: iceServers,
|
||||
ClientConfiguration: p.GetClientConfiguration(),
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
updates := ToProtoParticipants(r.GetParticipants())
|
||||
// include the local participant's info as well, since metadata could have been changed
|
||||
updates := r.getOtherParticipantInfo("")
|
||||
if err := p.SendParticipantUpdate(updates); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
r.lock.RLock()
|
||||
p.SendRoomUpdate(r.protoRoom)
|
||||
r.lock.RUnlock()
|
||||
|
||||
p.ICERestart(nil, reason)
|
||||
_ = p.SendRoomUpdate(r.ToProto())
|
||||
p.ICERestart(nil)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -441,6 +472,7 @@ func (r *Room) RemoveParticipant(identity livekit.ParticipantIdentity, pID livek
|
||||
}
|
||||
}
|
||||
|
||||
immediateChange := false
|
||||
if (p != nil && p.IsRecorder()) || r.protoRoom.ActiveRecording {
|
||||
activeRecording := false
|
||||
for _, op := range r.participants {
|
||||
@@ -452,10 +484,12 @@ func (r *Room) RemoveParticipant(identity livekit.ParticipantIdentity, pID livek
|
||||
|
||||
if r.protoRoom.ActiveRecording != activeRecording {
|
||||
r.protoRoom.ActiveRecording = activeRecording
|
||||
r.sendRoomUpdateLocked()
|
||||
immediateChange = true
|
||||
|
||||
}
|
||||
}
|
||||
r.lock.Unlock()
|
||||
r.protoProxy.MarkDirty(immediateChange)
|
||||
|
||||
if !ok {
|
||||
return
|
||||
@@ -464,6 +498,11 @@ func (r *Room) RemoveParticipant(identity livekit.ParticipantIdentity, pID livek
|
||||
// send broadcast only if it's not already closed
|
||||
sendUpdates := !p.IsDisconnected()
|
||||
|
||||
// remove all published tracks
|
||||
for _, t := range p.GetPublishedTracks() {
|
||||
r.trackManager.RemoveTrack(t)
|
||||
}
|
||||
|
||||
p.OnTrackUpdated(nil)
|
||||
p.OnTrackPublished(nil)
|
||||
p.OnTrackUnpublished(nil)
|
||||
@@ -474,13 +513,9 @@ func (r *Room) RemoveParticipant(identity livekit.ParticipantIdentity, pID livek
|
||||
|
||||
// close participant as well
|
||||
r.Logger.Debugw("closing participant for removal", "pID", p.ID(), "participant", p.Identity())
|
||||
_ = p.Close(true, reason)
|
||||
_ = p.Close(true, reason, false)
|
||||
|
||||
r.lock.RLock()
|
||||
if len(r.participants) == 0 {
|
||||
r.leftAt.Store(time.Now().Unix())
|
||||
}
|
||||
r.lock.RUnlock()
|
||||
r.leftAt.Store(time.Now().Unix())
|
||||
|
||||
if sendUpdates {
|
||||
if r.onParticipantChanged != nil {
|
||||
@@ -517,11 +552,51 @@ func (r *Room) UpdateSubscriptions(
|
||||
}
|
||||
|
||||
func (r *Room) SyncState(participant types.LocalParticipant, state *livekit.SyncState) error {
|
||||
pLogger := participant.GetLogger()
|
||||
pLogger.Infow("setting sync state", "state", state)
|
||||
|
||||
shouldReconnect := false
|
||||
pubTracks := state.GetPublishTracks()
|
||||
existingPubTracks := participant.GetPublishedTracks()
|
||||
for _, pubTrack := range pubTracks {
|
||||
// client may not have sent TrackInfo for each published track
|
||||
ti := pubTrack.Track
|
||||
if ti == nil {
|
||||
pLogger.Warnw("TrackInfo not sent during resume", nil)
|
||||
shouldReconnect = true
|
||||
break
|
||||
}
|
||||
|
||||
found := false
|
||||
for _, existingPubTrack := range existingPubTracks {
|
||||
if existingPubTrack.ID() == livekit.TrackID(ti.Sid) {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
pLogger.Warnw("unknown track during resume", nil, "trackID", ti.Sid)
|
||||
shouldReconnect = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if shouldReconnect {
|
||||
pLogger.Warnw("unable to resume due to missing published tracks, starting full reconnect", nil)
|
||||
participant.IssueFullReconnect(types.ParticipantCloseReasonPublicationError)
|
||||
return nil
|
||||
}
|
||||
|
||||
r.UpdateSubscriptions(
|
||||
participant,
|
||||
livekit.StringsAsTrackIDs(state.Subscription.TrackSids),
|
||||
state.Subscription.ParticipantTracks,
|
||||
state.Subscription.Subscribe,
|
||||
)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Room) UpdateSubscriptionPermission(participant types.LocalParticipant, subscriptionPermission *livekit.SubscriptionPermission) error {
|
||||
if err := participant.UpdateSubscriptionPermission(subscriptionPermission, nil, r.GetParticipant, r.GetParticipantByID); err != nil {
|
||||
if err := participant.UpdateSubscriptionPermission(subscriptionPermission, utils.TimedVersion{}, r.GetParticipant, r.GetParticipantByID); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, track := range participant.GetPublishedTracks() {
|
||||
@@ -530,20 +605,6 @@ func (r *Room) UpdateSubscriptionPermission(participant types.LocalParticipant,
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Room) RemoveDisallowedSubscriptions(sub types.LocalParticipant, disallowedSubscriptions map[livekit.TrackID]livekit.ParticipantID) {
|
||||
for trackID, publisherID := range disallowedSubscriptions {
|
||||
pub := r.GetParticipantByID(publisherID)
|
||||
if pub == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
track := pub.GetPublishedTrack(trackID)
|
||||
if track != nil {
|
||||
track.RemoveSubscriber(sub.ID(), false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Room) UpdateVideoLayers(participant types.Participant, updateVideoLayers *livekit.UpdateVideoLayers) error {
|
||||
return participant.UpdateVideoLayers(updateVideoLayers)
|
||||
}
|
||||
@@ -597,16 +658,15 @@ func (r *Room) CloseIfEmpty() {
|
||||
}
|
||||
}
|
||||
|
||||
timeout := r.protoRoom.EmptyTimeout
|
||||
var timeout uint32
|
||||
var elapsed int64
|
||||
if r.FirstJoinedAt() > 0 {
|
||||
// exit 20s after
|
||||
if r.FirstJoinedAt() > 0 && r.LastLeftAt() > 0 {
|
||||
elapsed = time.Now().Unix() - r.LastLeftAt()
|
||||
if timeout > DefaultRoomDepartureGrace {
|
||||
timeout = DefaultRoomDepartureGrace
|
||||
}
|
||||
// need to give time in case participant is reconnecting
|
||||
timeout = RoomDepartureGrace
|
||||
} else {
|
||||
elapsed = time.Now().Unix() - r.protoRoom.CreationTime
|
||||
timeout = r.protoRoom.EmptyTimeout
|
||||
}
|
||||
r.lock.Unlock()
|
||||
|
||||
@@ -628,8 +688,9 @@ func (r *Room) Close() {
|
||||
r.lock.Unlock()
|
||||
r.Logger.Infow("closing room")
|
||||
for _, p := range r.GetParticipants() {
|
||||
_ = p.Close(true, types.ParticipantCloseReasonRoomClose)
|
||||
_ = p.Close(true, types.ParticipantCloseReasonRoomClose, false)
|
||||
}
|
||||
r.protoProxy.Stop()
|
||||
if r.onClose != nil {
|
||||
r.onClose()
|
||||
}
|
||||
@@ -657,38 +718,43 @@ func (r *Room) SetMetadata(metadata string) {
|
||||
r.lock.Lock()
|
||||
r.protoRoom.Metadata = metadata
|
||||
r.lock.Unlock()
|
||||
r.protoProxy.MarkDirty(true)
|
||||
}
|
||||
|
||||
r.lock.RLock()
|
||||
r.sendRoomUpdateLocked()
|
||||
r.lock.RUnlock()
|
||||
|
||||
if r.onMetadataUpdate != nil {
|
||||
r.onMetadataUpdate(metadata)
|
||||
func (r *Room) UpdateParticipantMetadata(participant types.LocalParticipant, name string, metadata string) {
|
||||
if metadata != "" {
|
||||
participant.SetMetadata(metadata)
|
||||
}
|
||||
if name != "" {
|
||||
participant.SetName(name)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Room) sendRoomUpdateLocked() {
|
||||
func (r *Room) sendRoomUpdate() {
|
||||
roomInfo := r.ToProto()
|
||||
// Send update to participants
|
||||
for _, p := range r.participants {
|
||||
if !p.IsReady() {
|
||||
for _, p := range r.GetParticipants() {
|
||||
// new participants receive the update as part of JoinResponse
|
||||
// skip inactive participants
|
||||
if p.State() != livekit.ParticipantInfo_ACTIVE {
|
||||
continue
|
||||
}
|
||||
|
||||
err := p.SendRoomUpdate(r.protoRoom)
|
||||
err := p.SendRoomUpdate(roomInfo)
|
||||
if err != nil {
|
||||
r.Logger.Warnw("failed to send room update", err, "participant", p.Identity())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Room) OnMetadataUpdate(f func(metadata string)) {
|
||||
r.onMetadataUpdate = f
|
||||
func (r *Room) OnRoomUpdated(f func()) {
|
||||
r.onRoomUpdated = f
|
||||
}
|
||||
|
||||
func (r *Room) SimulateScenario(participant types.LocalParticipant, simulateScenario *livekit.SimulateScenario) error {
|
||||
switch scenario := simulateScenario.Scenario.(type) {
|
||||
case *livekit.SimulateScenario_SpeakerUpdate:
|
||||
r.Logger.Infow("simulating speaker update", "participant", participant.Identity())
|
||||
r.Logger.Infow("simulating speaker update", "participant", participant.Identity(), "duration", scenario.SpeakerUpdate)
|
||||
go func() {
|
||||
<-time.After(time.Duration(scenario.SpeakerUpdate) * time.Second)
|
||||
r.sendSpeakerChanges([]*livekit.SpeakerInfo{{
|
||||
@@ -705,31 +771,49 @@ func (r *Room) SimulateScenario(participant types.LocalParticipant, simulateScen
|
||||
case *livekit.SimulateScenario_Migration:
|
||||
r.Logger.Infow("simulating migration", "participant", participant.Identity())
|
||||
// drop participant without necessarily cleaning up
|
||||
if err := participant.Close(false, types.ParticipantCloseReasonSimulateMigration); err != nil {
|
||||
if err := participant.Close(false, types.ParticipantCloseReasonSimulateMigration, true); err != nil {
|
||||
return err
|
||||
}
|
||||
case *livekit.SimulateScenario_NodeFailure:
|
||||
r.Logger.Infow("simulating node failure", "participant", participant.Identity())
|
||||
// drop participant without necessarily cleaning up
|
||||
if err := participant.Close(false, types.ParticipantCloseReasonSimulateNodeFailure); err != nil {
|
||||
if err := participant.Close(false, types.ParticipantCloseReasonSimulateNodeFailure, true); err != nil {
|
||||
return err
|
||||
}
|
||||
case *livekit.SimulateScenario_ServerLeave:
|
||||
r.Logger.Infow("simulating server leave", "participant", participant.Identity())
|
||||
if err := participant.Close(true, types.ParticipantCloseReasonSimulateServerLeave); err != nil {
|
||||
if err := participant.Close(true, types.ParticipantCloseReasonSimulateServerLeave, false); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case *livekit.SimulateScenario_SwitchCandidateProtocol:
|
||||
r.Logger.Infow("simulating switch candidate protocol", "participant", participant.Identity())
|
||||
participant.ICERestart(&livekit.ICEConfig{
|
||||
PreferenceSubscriber: livekit.ICECandidateType(scenario.SwitchCandidateProtocol),
|
||||
PreferencePublisher: livekit.ICECandidateType(scenario.SwitchCandidateProtocol),
|
||||
}, livekit.ReconnectReason_RR_SWITCH_CANDIDATE)
|
||||
})
|
||||
case *livekit.SimulateScenario_SubscriberBandwidth:
|
||||
if scenario.SubscriberBandwidth > 0 {
|
||||
r.Logger.Infow("simulating subscriber bandwidth start", "participant", participant.Identity(), "bandwidth", scenario.SubscriberBandwidth)
|
||||
} else {
|
||||
r.Logger.Infow("simulating subscriber bandwidth end", "participant", participant.Identity())
|
||||
}
|
||||
participant.SetSubscriberChannelCapacity(scenario.SubscriberBandwidth)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Room) getOtherParticipantInfo(identity livekit.ParticipantIdentity) []*livekit.ParticipantInfo {
|
||||
participants := r.GetParticipants()
|
||||
pi := make([]*livekit.ParticipantInfo, 0, len(participants))
|
||||
for _, p := range participants {
|
||||
if !p.Hidden() && p.Identity() != identity {
|
||||
pi = append(pi, p.ToProto())
|
||||
}
|
||||
}
|
||||
|
||||
return pi
|
||||
}
|
||||
|
||||
// checks if participant should be autosubscribed to new tracks, assumes lock is already acquired
|
||||
func (r *Room) autoSubscribe(participant types.LocalParticipant) bool {
|
||||
opts := r.participantOpts[participant.Identity()]
|
||||
@@ -750,19 +834,20 @@ func (r *Room) createJoinResponseLocked(participant types.LocalParticipant, iceS
|
||||
}
|
||||
|
||||
return &livekit.JoinResponse{
|
||||
Room: r.protoRoom,
|
||||
Room: r.ToProto(),
|
||||
Participant: participant.ToProto(),
|
||||
OtherParticipants: otherParticipants,
|
||||
ServerVersion: r.serverInfo.Version,
|
||||
ServerRegion: r.serverInfo.Region,
|
||||
IceServers: iceServers,
|
||||
// indicates both server and client support subscriber as primary
|
||||
SubscriberPrimary: participant.SubscriberAsPrimary(),
|
||||
ClientConfiguration: participant.GetClientConfiguration(),
|
||||
// sane defaults for ping interval & timeout
|
||||
PingInterval: 10,
|
||||
PingTimeout: 20,
|
||||
ServerInfo: r.serverInfo,
|
||||
PingInterval: 10,
|
||||
PingTimeout: 20,
|
||||
ServerInfo: r.serverInfo,
|
||||
ServerVersion: r.serverInfo.Version,
|
||||
ServerRegion: r.serverInfo.Region,
|
||||
SifTrailer: r.trailer,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -838,6 +923,7 @@ func (r *Room) onTrackUnpublished(p types.LocalParticipant, track types.MediaTra
|
||||
}
|
||||
|
||||
func (r *Room) onParticipantUpdate(p types.LocalParticipant) {
|
||||
r.protoProxy.MarkDirty(false)
|
||||
// immediately notify when permissions or metadata changed
|
||||
r.broadcastParticipantState(p, broadcastOptions{immediate: true})
|
||||
if r.onParticipantChanged != nil {
|
||||
@@ -993,15 +1079,39 @@ func (r *Room) pushAndDequeueUpdates(pi *livekit.ParticipantInfo, isImmediate bo
|
||||
return updates
|
||||
}
|
||||
|
||||
func (r *Room) subscriberBroadcastWorker() {
|
||||
ticker := time.NewTicker(subscriberUpdateInterval)
|
||||
defer ticker.Stop()
|
||||
func (r *Room) updateProto() *livekit.Room {
|
||||
r.lock.RLock()
|
||||
room := proto.Clone(r.protoRoom).(*livekit.Room)
|
||||
r.lock.RUnlock()
|
||||
|
||||
room.NumPublishers = 0
|
||||
room.NumParticipants = 0
|
||||
for _, p := range r.GetParticipants() {
|
||||
if !p.IsRecorder() {
|
||||
room.NumParticipants++
|
||||
}
|
||||
if p.IsPublisher() {
|
||||
room.NumPublishers++
|
||||
}
|
||||
}
|
||||
|
||||
return room
|
||||
}
|
||||
|
||||
func (r *Room) changeUpdateWorker() {
|
||||
subTicker := time.NewTicker(subscriberUpdateInterval)
|
||||
defer subTicker.Stop()
|
||||
|
||||
for !r.IsClosed() {
|
||||
select {
|
||||
case <-r.closed:
|
||||
return
|
||||
case <-ticker.C:
|
||||
case <-r.protoProxy.Updated():
|
||||
if r.onRoomUpdated != nil {
|
||||
r.onRoomUpdated()
|
||||
}
|
||||
r.sendRoomUpdate()
|
||||
case <-subTicker.C:
|
||||
r.batchedUpdatesMu.Lock()
|
||||
updatesMap := r.batchedUpdates
|
||||
r.batchedUpdates = make(map[livekit.ParticipantIdentity]*livekit.ParticipantInfo)
|
||||
@@ -1162,11 +1272,11 @@ func BroadcastDataPacketForRoom(r types.Room, source types.LocalParticipant, dp
|
||||
var dpData []byte
|
||||
|
||||
participants := r.GetLocalParticipants()
|
||||
cap := len(dest)
|
||||
if cap == 0 {
|
||||
cap = len(participants)
|
||||
capacity := len(dest)
|
||||
if capacity == 0 {
|
||||
capacity = len(participants)
|
||||
}
|
||||
destParticpants := make([]types.LocalParticipant, 0, cap)
|
||||
destParticipants := make([]types.LocalParticipant, 0, capacity)
|
||||
|
||||
for _, op := range participants {
|
||||
if op.State() != livekit.ParticipantInfo_ACTIVE {
|
||||
@@ -1195,10 +1305,10 @@ func BroadcastDataPacketForRoom(r types.Room, source types.LocalParticipant, dp
|
||||
return
|
||||
}
|
||||
}
|
||||
destParticpants = append(destParticpants, op)
|
||||
destParticipants = append(destParticipants, op)
|
||||
}
|
||||
|
||||
utils.ParallelExec(destParticpants, dataForwardLoadBalanceThreshold, 1, func(op types.LocalParticipant) {
|
||||
utils.ParallelExec(destParticipants, dataForwardLoadBalanceThreshold, 1, func(op types.LocalParticipant) {
|
||||
err := op.SendDataPacket(dp, dpData)
|
||||
if err != nil && !errors.Is(err, io.ErrClosedPipe) {
|
||||
op.GetLogger().Infow("send data packet error", "error", err)
|
||||
|
||||
@@ -1,3 +1,17 @@
|
||||
// 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 (
|
||||
|
||||
+50
-8
@@ -1,3 +1,17 @@
|
||||
// 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 (
|
||||
@@ -14,7 +28,6 @@ import (
|
||||
"github.com/livekit/protocol/webhook"
|
||||
|
||||
"github.com/livekit/livekit-server/pkg/config"
|
||||
serverlogger "github.com/livekit/livekit-server/pkg/logger"
|
||||
"github.com/livekit/livekit-server/pkg/rtc/types"
|
||||
"github.com/livekit/livekit-server/pkg/rtc/types/typesfakes"
|
||||
"github.com/livekit/livekit-server/pkg/sfu/audio"
|
||||
@@ -30,9 +43,12 @@ const (
|
||||
)
|
||||
|
||||
func init() {
|
||||
serverlogger.InitFromConfig(config.LoggingConfig{
|
||||
config.InitLoggerFromConfig(config.LoggingConfig{
|
||||
Config: logger.Config{Level: "debug"},
|
||||
})
|
||||
// allow immediate closure in testing
|
||||
RoomDepartureGrace = 1
|
||||
roomUpdateInterval = defaultDelay
|
||||
}
|
||||
|
||||
var iceServersForRoom = []*livekit.ICEServer{{Urls: []string{"stun:stun.l.google.com:19302"}}}
|
||||
@@ -59,11 +75,11 @@ func TestJoinedState(t *testing.T) {
|
||||
require.LessOrEqual(t, s, rm.LastLeftAt())
|
||||
})
|
||||
|
||||
t.Run("LastLeftAt should not be set when there are still participants in the room", func(t *testing.T) {
|
||||
t.Run("LastLeftAt should be set when there are still participants in the room", func(t *testing.T) {
|
||||
rm := newRoomWithParticipants(t, testRoomOpts{num: 2})
|
||||
p0 := rm.GetParticipants()[0]
|
||||
rm.RemoveParticipant(p0.Identity(), p0.ID(), types.ParticipantCloseReasonClientRequestLeave)
|
||||
require.EqualValues(t, 0, rm.LastLeftAt())
|
||||
require.Greater(t, rm.LastLeftAt(), int64(0))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -138,7 +154,9 @@ func TestRoomJoin(t *testing.T) {
|
||||
|
||||
t.Run("cannot exceed max participants", func(t *testing.T) {
|
||||
rm := newRoomWithParticipants(t, testRoomOpts{num: 1})
|
||||
rm.lock.Lock()
|
||||
rm.protoRoom.MaxParticipants = 1
|
||||
rm.lock.Unlock()
|
||||
p := newMockParticipant("second", types.ProtocolVersion(0), false, false)
|
||||
|
||||
err := rm.Join(p, nil, nil, iceServersForRoom)
|
||||
@@ -345,11 +363,13 @@ func TestRoomClosure(t *testing.T) {
|
||||
isClosed = true
|
||||
})
|
||||
p := rm.GetParticipants()[0]
|
||||
rm.lock.Lock()
|
||||
// allows immediate close after
|
||||
rm.protoRoom.EmptyTimeout = 0
|
||||
rm.lock.Unlock()
|
||||
rm.RemoveParticipant(p.Identity(), p.ID(), types.ParticipantCloseReasonClientRequestLeave)
|
||||
|
||||
time.Sleep(defaultDelay)
|
||||
time.Sleep(time.Duration(RoomDepartureGrace)*time.Second + defaultDelay)
|
||||
|
||||
rm.CloseIfEmpty()
|
||||
require.Len(t, rm.GetParticipants(), 0)
|
||||
@@ -375,7 +395,9 @@ func TestRoomClosure(t *testing.T) {
|
||||
rm.OnClose(func() {
|
||||
isClosed = true
|
||||
})
|
||||
rm.lock.Lock()
|
||||
rm.protoRoom.EmptyTimeout = 1
|
||||
rm.lock.Unlock()
|
||||
|
||||
time.Sleep(1010 * time.Millisecond)
|
||||
rm.CloseIfEmpty()
|
||||
@@ -643,7 +665,7 @@ func TestHiddenParticipants(t *testing.T) {
|
||||
require.Len(t, res.OtherParticipants, 2)
|
||||
require.Len(t, rm.GetParticipants(), 4)
|
||||
require.NotEmpty(t, res.IceServers)
|
||||
require.Equal(t, "testregion", res.ServerRegion)
|
||||
require.Equal(t, "testregion", res.ServerInfo.Region)
|
||||
})
|
||||
|
||||
t.Run("hidden participant subscribes to tracks", func(t *testing.T) {
|
||||
@@ -663,15 +685,35 @@ func TestHiddenParticipants(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRoomUpdate(t *testing.T) {
|
||||
t.Run("updates are sent when participant joined", func(t *testing.T) {
|
||||
rm := newRoomWithParticipants(t, testRoomOpts{num: 1})
|
||||
defer rm.Close()
|
||||
|
||||
p1 := rm.GetParticipants()[0].(*typesfakes.FakeLocalParticipant)
|
||||
require.Equal(t, 0, p1.SendRoomUpdateCallCount())
|
||||
|
||||
p2 := newMockParticipant("p2", types.CurrentProtocol, false, false)
|
||||
require.NoError(t, rm.Join(p2, nil, nil, iceServersForRoom))
|
||||
|
||||
// p1 should have received an update
|
||||
time.Sleep(2 * defaultDelay)
|
||||
require.LessOrEqual(t, 1, p1.SendRoomUpdateCallCount())
|
||||
require.EqualValues(t, 2, p1.SendRoomUpdateArgsForCall(p1.SendRoomUpdateCallCount()-1).NumParticipants)
|
||||
})
|
||||
|
||||
t.Run("participants should receive metadata update", func(t *testing.T) {
|
||||
rm := newRoomWithParticipants(t, testRoomOpts{num: 2})
|
||||
defer rm.Close()
|
||||
|
||||
rm.SetMetadata("test metadata...")
|
||||
|
||||
// callbacks are updated from goroutine
|
||||
time.Sleep(2 * defaultDelay)
|
||||
|
||||
for _, op := range rm.GetParticipants() {
|
||||
fp := op.(*typesfakes.FakeLocalParticipant)
|
||||
require.Equal(t, 1, fp.SendRoomUpdateCallCount())
|
||||
// room updates are now sent for both participant joining and room metadata
|
||||
require.GreaterOrEqual(t, fp.SendRoomUpdateCallCount(), 1)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -699,7 +741,7 @@ func newRoomWithParticipants(t *testing.T, opts testRoomOpts) *Room {
|
||||
NodeId: "testnode",
|
||||
Region: "testregion",
|
||||
},
|
||||
telemetry.NewTelemetryService(webhook.NewNotifier("", "", nil), &telemetryfakes.FakeAnalyticsService{}),
|
||||
telemetry.NewTelemetryService(webhook.NewDefaultNotifier("", "", nil), &telemetryfakes.FakeAnalyticsService{}),
|
||||
nil,
|
||||
)
|
||||
for i := 0; i < opts.num+opts.numHidden; i++ {
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
//go:build !windows
|
||||
// +build !windows
|
||||
|
||||
package rtc
|
||||
|
||||
import (
|
||||
"net"
|
||||
"syscall"
|
||||
|
||||
"github.com/livekit/protocol/logger"
|
||||
)
|
||||
|
||||
func checkUDPReadBuffer() {
|
||||
val, err := getUDPReadBuffer()
|
||||
if err == nil {
|
||||
if val < minUDPBufferSize {
|
||||
logger.Warnw("UDP receive buffer is too small for a production set-up", nil,
|
||||
"current", val,
|
||||
"suggested", minUDPBufferSize)
|
||||
} else {
|
||||
logger.Debugw("UDP receive buffer size", "current", val)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getUDPReadBuffer() (int, error) {
|
||||
conn, err := net.ListenUDP("udp4", nil)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer func() { _ = conn.Close() }()
|
||||
_ = conn.SetReadBuffer(defaultUDPBufferSize)
|
||||
fd, err := conn.File()
|
||||
if err != nil {
|
||||
return 0, nil
|
||||
}
|
||||
defer func() { _ = fd.Close() }()
|
||||
|
||||
return syscall.GetsockoptInt(int(fd.Fd()), syscall.SOL_SOCKET, syscall.SO_RCVBUF)
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package rtc
|
||||
|
||||
func checkUDPReadBuffer() {
|
||||
}
|
||||
@@ -1,3 +1,17 @@
|
||||
// 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 (
|
||||
@@ -10,7 +24,7 @@ import (
|
||||
func HandleParticipantSignal(room types.Room, participant types.LocalParticipant, req *livekit.SignalRequest, pLogger logger.Logger) error {
|
||||
participant.UpdateLastSeenSignal()
|
||||
|
||||
switch msg := req.Message.(type) {
|
||||
switch msg := req.GetMessage().(type) {
|
||||
case *livekit.SignalRequest_Offer:
|
||||
participant.HandleOffer(FromProtoSessionDescription(msg.Offer))
|
||||
case *livekit.SignalRequest_Answer:
|
||||
@@ -73,6 +87,11 @@ func HandleParticipantSignal(room types.Room, participant types.LocalParticipant
|
||||
if msg.PingReq.Rtt > 0 {
|
||||
participant.UpdateSignalingRTT(uint32(msg.PingReq.Rtt))
|
||||
}
|
||||
|
||||
case *livekit.SignalRequest_UpdateMetadata:
|
||||
if participant.ClaimGrants().Video.GetCanUpdateOwnMetadata() {
|
||||
room.UpdateParticipantMetadata(participant, msg.UpdateMetadata.Name, msg.UpdateMetadata.Metadata)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,3 +1,17 @@
|
||||
// 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 (
|
||||
@@ -40,7 +54,7 @@ type SubscribedTrack struct {
|
||||
needsNegotiation atomic.Bool
|
||||
|
||||
bindLock sync.Mutex
|
||||
onBindCallbacks []func()
|
||||
onBindCallbacks []func(error)
|
||||
onClose atomic.Value // func(bool)
|
||||
bound atomic.Bool
|
||||
|
||||
@@ -61,7 +75,7 @@ func NewSubscribedTrack(params SubscribedTrackParams) *SubscribedTrack {
|
||||
return s
|
||||
}
|
||||
|
||||
func (t *SubscribedTrack) AddOnBind(f func()) {
|
||||
func (t *SubscribedTrack) AddOnBind(f func(error)) {
|
||||
t.bindLock.Lock()
|
||||
bound := t.bound.Load()
|
||||
if !bound {
|
||||
@@ -71,19 +85,21 @@ func (t *SubscribedTrack) AddOnBind(f func()) {
|
||||
|
||||
if bound {
|
||||
// fire immediately, do not need to persist since bind is a one time event
|
||||
go f()
|
||||
go f(nil)
|
||||
}
|
||||
}
|
||||
|
||||
// for DownTrack callback to notify us that it's bound
|
||||
func (t *SubscribedTrack) Bound() {
|
||||
func (t *SubscribedTrack) Bound(err error) {
|
||||
t.bindLock.Lock()
|
||||
t.bound.Store(true)
|
||||
if err == nil {
|
||||
t.bound.Store(true)
|
||||
}
|
||||
callbacks := t.onBindCallbacks
|
||||
t.onBindCallbacks = nil
|
||||
t.bindLock.Unlock()
|
||||
|
||||
if t.MediaTrack().Kind() == livekit.TrackType_VIDEO {
|
||||
if err == nil && t.MediaTrack().Kind() == livekit.TrackType_VIDEO {
|
||||
// When AdaptiveStream is enabled, default the subscriber to LOW quality stream
|
||||
// we would want LOW instead of OFF for a couple of reasons
|
||||
// 1. when a subscriber unsubscribes from a track, we would forget their previously defined settings
|
||||
@@ -107,7 +123,7 @@ func (t *SubscribedTrack) Bound() {
|
||||
}
|
||||
|
||||
for _, cb := range callbacks {
|
||||
go cb()
|
||||
go cb(err)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+180
-56
@@ -40,6 +40,11 @@ var (
|
||||
// amount of time to try otherwise before flagging subscription as failed
|
||||
subscriptionTimeout = iceFailedTimeout
|
||||
trackRemoveGracePeriod = time.Second
|
||||
maxUnsubscribeWait = time.Second
|
||||
)
|
||||
|
||||
const (
|
||||
trackIDForReconcileSubscriptions = livekit.TrackID("subscriptions_reconcile")
|
||||
)
|
||||
|
||||
type SubscriptionManagerParams struct {
|
||||
@@ -48,34 +53,37 @@ type SubscriptionManagerParams struct {
|
||||
TrackResolver types.MediaTrackResolver
|
||||
OnTrackSubscribed func(subTrack types.SubscribedTrack)
|
||||
OnTrackUnsubscribed func(subTrack types.SubscribedTrack)
|
||||
OnSubscriptionError func(trackID livekit.TrackID)
|
||||
OnSubscriptionError func(trackID livekit.TrackID, fatal bool, err error)
|
||||
Telemetry telemetry.TelemetryService
|
||||
|
||||
SubscriptionLimitVideo, SubscriptionLimitAudio int32
|
||||
}
|
||||
|
||||
// SubscriptionManager manages a participant's subscriptions
|
||||
type SubscriptionManager struct {
|
||||
params SubscriptionManagerParams
|
||||
lock sync.RWMutex
|
||||
subscriptions map[livekit.TrackID]*trackSubscription
|
||||
subscribedTo map[livekit.ParticipantID]map[livekit.TrackID]struct{}
|
||||
// keeps track of tracks that are already queued for reconcile to avoid duplicating reconcile requests
|
||||
pendingReconcile map[livekit.TrackID]struct{}
|
||||
reconcileCh chan livekit.TrackID
|
||||
closeCh chan struct{}
|
||||
doneCh chan struct{}
|
||||
params SubscriptionManagerParams
|
||||
lock sync.RWMutex
|
||||
subscriptions map[livekit.TrackID]*trackSubscription
|
||||
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{}
|
||||
|
||||
onSubscribeStatusChanged func(publisherID livekit.ParticipantID, subscribed bool)
|
||||
}
|
||||
|
||||
func NewSubscriptionManager(params SubscriptionManagerParams) *SubscriptionManager {
|
||||
m := &SubscriptionManager{
|
||||
params: params,
|
||||
subscriptions: make(map[livekit.TrackID]*trackSubscription),
|
||||
subscribedTo: make(map[livekit.ParticipantID]map[livekit.TrackID]struct{}),
|
||||
pendingReconcile: make(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]*trackSubscription),
|
||||
subscribedTo: make(map[livekit.ParticipantID]map[livekit.TrackID]struct{}),
|
||||
reconcileCh: make(chan livekit.TrackID, 50),
|
||||
closeCh: make(chan struct{}),
|
||||
doneCh: make(chan struct{}),
|
||||
}
|
||||
|
||||
go m.reconcileWorker()
|
||||
@@ -96,6 +104,7 @@ func (m *SubscriptionManager) Close(willBeResumed bool) {
|
||||
subTracks := m.GetSubscribedTracks()
|
||||
downTracksToClose := make([]*sfu.DownTrack, 0, len(subTracks))
|
||||
for _, st := range subTracks {
|
||||
m.setDesired(st.ID(), false)
|
||||
dt := st.DownTrack()
|
||||
// nil check exists primarily for tests
|
||||
if dt != nil {
|
||||
@@ -103,8 +112,15 @@ func (m *SubscriptionManager) Close(willBeResumed bool) {
|
||||
}
|
||||
}
|
||||
|
||||
for _, dt := range downTracksToClose {
|
||||
dt.CloseWithFlush(!willBeResumed)
|
||||
if willBeResumed {
|
||||
for _, dt := range downTracksToClose {
|
||||
dt.CloseWithFlush(false)
|
||||
}
|
||||
} else {
|
||||
// flush blocks, so execute in parallel
|
||||
for _, dt := range downTracksToClose {
|
||||
go dt.CloseWithFlush(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,17 +134,19 @@ func (m *SubscriptionManager) isClosed() bool {
|
||||
}
|
||||
|
||||
func (m *SubscriptionManager) SubscribeToTrack(trackID livekit.TrackID) {
|
||||
m.lock.Lock()
|
||||
sub, ok := m.subscriptions[trackID]
|
||||
if !ok {
|
||||
sub, desireChanged := m.setDesired(trackID, true)
|
||||
if sub == nil {
|
||||
sLogger := m.params.Logger.WithValues(
|
||||
"trackID", trackID,
|
||||
)
|
||||
sub = newTrackSubscription(m.params.Participant.ID(), trackID, sLogger)
|
||||
|
||||
m.lock.Lock()
|
||||
m.subscriptions[trackID] = sub
|
||||
m.lock.Unlock()
|
||||
|
||||
sub, desireChanged = m.setDesired(trackID, true)
|
||||
}
|
||||
desireChanged := sub.setDesired(true)
|
||||
m.lock.Unlock()
|
||||
if desireChanged {
|
||||
sub.logger.Infow("subscribing to track")
|
||||
}
|
||||
@@ -138,17 +156,13 @@ func (m *SubscriptionManager) SubscribeToTrack(trackID livekit.TrackID) {
|
||||
}
|
||||
|
||||
func (m *SubscriptionManager) UnsubscribeFromTrack(trackID livekit.TrackID) {
|
||||
m.lock.Lock()
|
||||
sub, ok := m.subscriptions[trackID]
|
||||
m.lock.Unlock()
|
||||
if !ok {
|
||||
sub, desireChanged := m.setDesired(trackID, false)
|
||||
if sub == nil || !desireChanged {
|
||||
return
|
||||
}
|
||||
|
||||
if sub.setDesired(false) {
|
||||
sub.logger.Infow("unsubscribing from track")
|
||||
m.queueReconcile(trackID)
|
||||
}
|
||||
sub.logger.Infow("unsubscribing from track")
|
||||
m.queueReconcile(trackID)
|
||||
}
|
||||
|
||||
func (m *SubscriptionManager) GetSubscribedTracks() []types.SubscribedTrack {
|
||||
@@ -240,6 +254,18 @@ func (m *SubscriptionManager) WaitUntilSubscribed(timeout time.Duration) error {
|
||||
return context.DeadlineExceeded
|
||||
}
|
||||
|
||||
func (m *SubscriptionManager) setDesired(trackID livekit.TrackID, desired bool) (*trackSubscription, bool) {
|
||||
m.lock.RLock()
|
||||
defer m.lock.RUnlock()
|
||||
|
||||
sub, ok := m.subscriptions[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() {
|
||||
@@ -252,7 +278,7 @@ func (m *SubscriptionManager) reconcileSubscriptions() {
|
||||
var needsToReconcile []*trackSubscription
|
||||
m.lock.RLock()
|
||||
for _, sub := range m.subscriptions {
|
||||
if sub.needsSubscribe() || sub.needsUnsubscribe() || sub.needsBind() {
|
||||
if sub.needsSubscribe() || sub.needsUnsubscribe() || sub.needsBind() || sub.needsCleanup() {
|
||||
needsToReconcile = append(needsToReconcile, sub)
|
||||
}
|
||||
}
|
||||
@@ -268,6 +294,15 @@ func (m *SubscriptionManager) reconcileSubscription(s *trackSubscription) {
|
||||
return
|
||||
}
|
||||
if s.needsSubscribe() {
|
||||
if m.pendingUnsubscribes.Load() != 0 && s.durationSinceStart() < maxUnsubscribeWait {
|
||||
// enqueue this in a bit, after pending unsubscribes are complete
|
||||
go func() {
|
||||
time.Sleep(time.Duration(sfu.RTPBlankFramesCloseSeconds * float32(time.Second)))
|
||||
m.queueReconcile(s.trackID)
|
||||
}()
|
||||
return
|
||||
}
|
||||
|
||||
numAttempts := s.getNumAttempts()
|
||||
if numAttempts == 0 {
|
||||
m.params.Telemetry.TrackSubscribeRequested(
|
||||
@@ -282,26 +317,28 @@ func (m *SubscriptionManager) reconcileSubscription(s *trackSubscription) {
|
||||
s.recordAttempt(false)
|
||||
|
||||
switch err {
|
||||
case ErrNoTrackPermission, ErrNoSubscribePermission, ErrNoReceiver, ErrNotOpen, ErrTrackNotAttached:
|
||||
case ErrNoTrackPermission, ErrNoSubscribePermission, ErrNoReceiver, ErrNotOpen, ErrTrackNotAttached, ErrSubscriptionLimitExceeded:
|
||||
// these are errors that are outside of our control, so we'll keep trying
|
||||
// - ErrNoTrackPermission: publisher did not grant subscriber permission, may change any moment
|
||||
// - ErrNoSubscribePermission: participant was not granted canSubscribe, may change any moment
|
||||
// - ErrNoReceiver: Track is in the process of closing (another local track published to the same instance)
|
||||
// - ErrTrackNotAttached: Remote Track that is not attached, but may be attached later
|
||||
// - ErrNotOpen: Track is closing or already closed
|
||||
// - ErrSubscriptionLimitExceeded: the participant have reached the limit of subscriptions, wait for the other subscription to be unsubscribed
|
||||
// We'll still log an event to reflect this in telemetry since it's been too long
|
||||
if s.durationSinceStart() > subscriptionTimeout {
|
||||
s.maybeRecordError(m.params.Telemetry, m.params.Participant.ID(), err, true)
|
||||
}
|
||||
case ErrTrackNotFound:
|
||||
// source track was never published or closed
|
||||
// if after timeout, we'd unsubscribe from it.
|
||||
// if after timeout we'd unsubscribe from it.
|
||||
// this is the *only* case we'd change desired state
|
||||
if s.durationSinceStart() > notFoundTimeout {
|
||||
s.maybeRecordError(m.params.Telemetry, m.params.Participant.ID(), err, true)
|
||||
s.logger.Infow("unsubscribing from track after notFoundTimeout", "error", err)
|
||||
s.setDesired(false)
|
||||
m.queueReconcile(s.trackID)
|
||||
m.params.OnSubscriptionError(s.trackID, false, err)
|
||||
}
|
||||
default:
|
||||
// all other errors
|
||||
@@ -310,7 +347,7 @@ func (m *SubscriptionManager) reconcileSubscription(s *trackSubscription) {
|
||||
"attempt", numAttempts,
|
||||
)
|
||||
s.maybeRecordError(m.params.Telemetry, m.params.Participant.ID(), err, false)
|
||||
m.params.OnSubscriptionError(s.trackID)
|
||||
m.params.OnSubscriptionError(s.trackID, true, err)
|
||||
} else {
|
||||
s.logger.Debugw("failed to subscribe, retrying",
|
||||
"error", err,
|
||||
@@ -332,6 +369,7 @@ func (m *SubscriptionManager) reconcileSubscription(s *trackSubscription) {
|
||||
// successfully unsubscribed, remove from map
|
||||
m.lock.Lock()
|
||||
if !s.isDesired() {
|
||||
s.logger.Debugw("unsubscribe removing subscription")
|
||||
delete(m.subscriptions, s.trackID)
|
||||
}
|
||||
m.lock.Unlock()
|
||||
@@ -346,20 +384,22 @@ func (m *SubscriptionManager) reconcileSubscription(s *trackSubscription) {
|
||||
if s.durationSinceStart() > subscriptionTimeout {
|
||||
s.logger.Errorw("track not bound after timeout", nil)
|
||||
s.maybeRecordError(m.params.Telemetry, m.params.Participant.ID(), ErrTrackNotBound, false)
|
||||
m.params.OnSubscriptionError(s.trackID)
|
||||
m.params.OnSubscriptionError(s.trackID, true, ErrTrackNotBound)
|
||||
}
|
||||
}
|
||||
|
||||
if s.needsCleanup() {
|
||||
m.lock.Lock()
|
||||
if !s.isDesired() {
|
||||
s.logger.Debugw("cleanup removing subscription")
|
||||
delete(m.subscriptions, s.trackID)
|
||||
}
|
||||
m.lock.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
// trigger an immediate reconciliation, when trackID is empty, will reconcile all subscriptions
|
||||
func (m *SubscriptionManager) queueReconcile(trackID livekit.TrackID) {
|
||||
m.lock.Lock()
|
||||
if _, ok := m.pendingReconcile[trackID]; ok {
|
||||
// already reconciled
|
||||
m.lock.Unlock()
|
||||
return
|
||||
}
|
||||
m.lock.Unlock()
|
||||
select {
|
||||
case m.reconcileCh <- trackID:
|
||||
default:
|
||||
@@ -381,7 +421,6 @@ func (m *SubscriptionManager) reconcileWorker() {
|
||||
case trackID := <-m.reconcileCh:
|
||||
m.lock.Lock()
|
||||
s := m.subscriptions[trackID]
|
||||
delete(m.pendingReconcile, trackID)
|
||||
m.lock.Unlock()
|
||||
if s != nil {
|
||||
m.reconcileSubscription(s)
|
||||
@@ -392,6 +431,21 @@ func (m *SubscriptionManager) reconcileWorker() {
|
||||
}
|
||||
}
|
||||
|
||||
func (m *SubscriptionManager) hasCapacityForSubscription(kind livekit.TrackType) bool {
|
||||
switch kind {
|
||||
case livekit.TrackType_VIDEO:
|
||||
if m.params.SubscriptionLimitVideo > 0 && m.subscribedVideoCount.Load() >= m.params.SubscriptionLimitVideo {
|
||||
return false
|
||||
}
|
||||
|
||||
case livekit.TrackType_AUDIO:
|
||||
if m.params.SubscriptionLimitAudio > 0 && m.subscribedAudioCount.Load() >= m.params.SubscriptionLimitAudio {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (m *SubscriptionManager) subscribe(s *trackSubscription) error {
|
||||
s.logger.Debugw("executing subscribe")
|
||||
|
||||
@@ -399,7 +453,12 @@ func (m *SubscriptionManager) subscribe(s *trackSubscription) error {
|
||||
return ErrNoSubscribePermission
|
||||
}
|
||||
|
||||
res := m.params.TrackResolver(m.params.Participant.Identity(), s.trackID)
|
||||
if kind, ok := s.getKind(); ok && !m.hasCapacityForSubscription(kind) {
|
||||
return ErrSubscriptionLimitExceeded
|
||||
}
|
||||
|
||||
trackID := s.trackID
|
||||
res := m.params.TrackResolver(m.params.Participant.Identity(), trackID)
|
||||
s.logger.Debugw("resolved track", "result", res)
|
||||
|
||||
if res.TrackChangedNotifier != nil && s.setChangedNotifier(res.TrackChangedNotifier) {
|
||||
@@ -407,18 +466,18 @@ func (m *SubscriptionManager) subscribe(s *trackSubscription) error {
|
||||
// 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(m.params.Participant.ID()), func() {
|
||||
m.queueReconcile(s.trackID)
|
||||
m.queueReconcile(trackID)
|
||||
})
|
||||
}
|
||||
if res.TrackRemovedNotifier != nil && s.setRemovedNotifier(res.TrackRemovedNotifier) {
|
||||
res.TrackRemovedNotifier.AddObserver(string(m.params.Participant.ID()), func() {
|
||||
// re-resolve the track in case the same track had been re-published
|
||||
res := m.params.TrackResolver(m.params.Participant.Identity(), s.trackID)
|
||||
res := m.params.TrackResolver(m.params.Participant.Identity(), trackID)
|
||||
if res.Track != nil {
|
||||
// do not unsubscribe, track is still available
|
||||
return
|
||||
}
|
||||
s.handleSourceTrackRemoved()
|
||||
m.handleSourceTrackRemoved(trackID)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -426,18 +485,23 @@ func (m *SubscriptionManager) subscribe(s *trackSubscription) error {
|
||||
if track == nil {
|
||||
return ErrTrackNotFound
|
||||
}
|
||||
s.trySetKind(track.Kind())
|
||||
if !m.hasCapacityForSubscription(track.Kind()) {
|
||||
return ErrSubscriptionLimitExceeded
|
||||
}
|
||||
|
||||
s.setPublisher(res.PublisherIdentity, res.PublisherID)
|
||||
|
||||
// since hasPermission defaults to true, we will want to send a message to the client the first time
|
||||
// that we discover permissions were denied
|
||||
permChanged := s.setHasPermission(res.HasPermission)
|
||||
if permChanged {
|
||||
m.params.Participant.SubscriptionPermissionUpdate(s.getPublisherID(), s.trackID, res.HasPermission)
|
||||
m.params.Participant.SubscriptionPermissionUpdate(s.getPublisherID(), trackID, res.HasPermission)
|
||||
}
|
||||
if !res.HasPermission {
|
||||
return ErrNoTrackPermission
|
||||
}
|
||||
|
||||
s.setPublisher(res.PublisherIdentity, res.PublisherID)
|
||||
subTrack, err := track.AddSubscriber(m.params.Participant)
|
||||
if err != nil && err != errAlreadySubscribed {
|
||||
// ignore already subscribed error
|
||||
@@ -447,12 +511,26 @@ func (m *SubscriptionManager) subscribe(s *trackSubscription) error {
|
||||
subTrack.OnClose(func(willBeResumed bool) {
|
||||
m.handleSubscribedTrackClose(s, willBeResumed)
|
||||
})
|
||||
subTrack.AddOnBind(func() {
|
||||
subTrack.AddOnBind(func(err error) {
|
||||
if err != nil {
|
||||
s.logger.Infow("failed to bind track", "err", err)
|
||||
s.maybeRecordError(m.params.Telemetry, m.params.Participant.ID(), err, true)
|
||||
m.UnsubscribeFromTrack(trackID)
|
||||
m.params.OnSubscriptionError(trackID, false, err)
|
||||
return
|
||||
}
|
||||
s.setBound()
|
||||
s.maybeRecordSuccess(m.params.Telemetry, m.params.Participant.ID())
|
||||
})
|
||||
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)
|
||||
}
|
||||
@@ -460,6 +538,8 @@ func (m *SubscriptionManager) subscribe(s *trackSubscription) error {
|
||||
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()
|
||||
@@ -471,7 +551,7 @@ func (m *SubscriptionManager) subscribe(s *trackSubscription) error {
|
||||
m.subscribedTo[publisherID] = pTracks
|
||||
firstSubscribe = true
|
||||
}
|
||||
pTracks[s.trackID] = struct{}{}
|
||||
pTracks[trackID] = struct{}{}
|
||||
m.lock.Unlock()
|
||||
|
||||
if changedCB != nil && firstSubscribe {
|
||||
@@ -491,11 +571,25 @@ func (m *SubscriptionManager) unsubscribe(s *trackSubscription) error {
|
||||
|
||||
track := subTrack.MediaTrack()
|
||||
pID := m.params.Participant.ID()
|
||||
track.RemoveSubscriber(pID, false)
|
||||
m.pendingUnsubscribes.Inc()
|
||||
go func() {
|
||||
defer m.pendingUnsubscribes.Dec()
|
||||
track.RemoveSubscriber(pID, false)
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *SubscriptionManager) handleSourceTrackRemoved(trackID livekit.TrackID) {
|
||||
m.lock.Lock()
|
||||
sub := m.subscriptions[trackID]
|
||||
m.lock.Unlock()
|
||||
|
||||
if sub != nil {
|
||||
sub.handleSourceTrackRemoved()
|
||||
}
|
||||
}
|
||||
|
||||
// DownTrack closing is how the publisher signifies that the subscription is no longer fulfilled
|
||||
// this could be due to a few reasons:
|
||||
// - subscriber-initiated unsubscribe
|
||||
@@ -512,6 +606,14 @@ func (m *SubscriptionManager) handleSubscribedTrackClose(s *trackSubscription, w
|
||||
}
|
||||
s.setSubscribedTrack(nil)
|
||||
|
||||
var relieveFromLimits bool
|
||||
switch subTrack.MediaTrack().Kind() {
|
||||
case livekit.TrackType_VIDEO:
|
||||
relieveFromLimits = m.params.SubscriptionLimitVideo > 0 && m.subscribedVideoCount.Dec() == m.params.SubscriptionLimitVideo-1
|
||||
case livekit.TrackType_AUDIO:
|
||||
relieveFromLimits = m.params.SubscriptionLimitAudio > 0 && m.subscribedAudioCount.Dec() == m.params.SubscriptionLimitAudio-1
|
||||
}
|
||||
|
||||
// remove from subscribedTo
|
||||
publisherID := s.getPublisherID()
|
||||
lastSubscription := false
|
||||
@@ -581,7 +683,11 @@ func (m *SubscriptionManager) handleSubscribedTrackClose(s *trackSubscription, w
|
||||
|
||||
m.params.Participant.Negotiate(false)
|
||||
}
|
||||
m.queueReconcile(s.trackID)
|
||||
if relieveFromLimits {
|
||||
m.queueReconcile(trackIDForReconcileSubscriptions)
|
||||
} else {
|
||||
m.queueReconcile(s.trackID)
|
||||
}
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------------------------
|
||||
@@ -603,6 +709,7 @@ type trackSubscription struct {
|
||||
eventSent atomic.Bool
|
||||
numAttempts atomic.Int32
|
||||
bound bool
|
||||
kind atomic.Pointer[livekit.TrackType]
|
||||
|
||||
// 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
|
||||
@@ -705,6 +812,18 @@ func (s *trackSubscription) setSubscribedTrack(track types.SubscribedTrack) {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *trackSubscription) trySetKind(kind livekit.TrackType) {
|
||||
s.kind.CompareAndSwap(nil, &kind)
|
||||
}
|
||||
|
||||
func (s *trackSubscription) getKind() (livekit.TrackType, bool) {
|
||||
kind := s.kind.Load()
|
||||
if kind == nil {
|
||||
return livekit.TrackType_AUDIO, false
|
||||
}
|
||||
return *kind, true
|
||||
}
|
||||
|
||||
func (s *trackSubscription) getSubscribedTrack() types.SubscribedTrack {
|
||||
s.lock.RLock()
|
||||
defer s.lock.RUnlock()
|
||||
@@ -739,7 +858,6 @@ func (s *trackSubscription) setRemovedNotifier(notifier types.ChangeNotifier) bo
|
||||
|
||||
func (s *trackSubscription) setRemovedNotifierLocked(notifier types.ChangeNotifier) bool {
|
||||
if s.removedNotifier == notifier {
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -865,3 +983,9 @@ func (s *trackSubscription) needsBind() bool {
|
||||
defer s.lock.RUnlock()
|
||||
return s.desired && s.subscribedTrack != nil && !s.bound
|
||||
}
|
||||
|
||||
func (s *trackSubscription) needsCleanup() bool {
|
||||
s.lock.RLock()
|
||||
defer s.lock.RUnlock()
|
||||
return !s.desired && s.subscribedTrack == nil
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ func init() {
|
||||
}
|
||||
|
||||
const (
|
||||
subSettleTimeout = 300 * time.Millisecond
|
||||
subSettleTimeout = 600 * time.Millisecond
|
||||
subCheckInterval = 10 * time.Millisecond
|
||||
)
|
||||
|
||||
@@ -54,7 +54,7 @@ func TestSubscribe(t *testing.T) {
|
||||
sm.params.OnTrackSubscribed = func(subTrack types.SubscribedTrack) {
|
||||
subCount.Add(1)
|
||||
}
|
||||
sm.params.OnSubscriptionError = func(trackID livekit.TrackID) {
|
||||
sm.params.OnSubscriptionError = func(trackID livekit.TrackID, fatal bool, err error) {
|
||||
failed.Store(true)
|
||||
}
|
||||
numParticipantSubscribed := atomic.Int32{}
|
||||
@@ -76,7 +76,10 @@ func TestSubscribe(t *testing.T) {
|
||||
|
||||
require.NotNil(t, s.getSubscribedTrack())
|
||||
require.Len(t, sm.GetSubscribedTracks(), 1)
|
||||
require.Len(t, sm.GetSubscribedParticipants(), 1)
|
||||
|
||||
require.Eventually(t, func() bool {
|
||||
return len(sm.GetSubscribedParticipants()) == 1
|
||||
}, subSettleTimeout, subCheckInterval, "GetSubscribedParticipants should have returned one item")
|
||||
require.Equal(t, "pubID", string(sm.GetSubscribedParticipants()[0]))
|
||||
|
||||
// ensure telemetry events are sent
|
||||
@@ -85,7 +88,6 @@ func TestSubscribe(t *testing.T) {
|
||||
|
||||
// ensure bound
|
||||
setTestSubscribedTrackBound(t, s.getSubscribedTrack())
|
||||
|
||||
require.Eventually(t, func() bool {
|
||||
return !s.needsBind()
|
||||
}, subSettleTimeout, subCheckInterval, "track was not bound")
|
||||
@@ -96,16 +98,22 @@ func TestSubscribe(t *testing.T) {
|
||||
time.Sleep(notFoundTimeout)
|
||||
require.False(t, failed.Load())
|
||||
|
||||
resolver.SetPause(true)
|
||||
// ensure its resilience after being closed
|
||||
setTestSubscribedTrackClosed(t, s.getSubscribedTrack(), false)
|
||||
require.True(t, s.needsSubscribe())
|
||||
require.Eventually(t, func() bool {
|
||||
return s.needsSubscribe()
|
||||
}, subSettleTimeout, subCheckInterval, "needs subscribe did not persist across track close")
|
||||
resolver.SetPause(false)
|
||||
|
||||
require.Eventually(t, func() bool {
|
||||
return s.isDesired() && !s.needsSubscribe()
|
||||
}, subSettleTimeout, subCheckInterval, "track was not resubscribed")
|
||||
|
||||
// was subscribed twice, unsubscribed once (due to close)
|
||||
require.Equal(t, int32(2), numParticipantSubscribed.Load())
|
||||
require.Eventually(t, func() bool {
|
||||
return numParticipantSubscribed.Load() == 2
|
||||
}, subSettleTimeout, subCheckInterval, "participant subscribe status was not updated twice")
|
||||
require.Equal(t, int32(1), numParticipantUnsubscribed.Load())
|
||||
})
|
||||
|
||||
@@ -115,7 +123,7 @@ func TestSubscribe(t *testing.T) {
|
||||
resolver := newTestResolver(false, true, "pub", "pubID")
|
||||
sm.params.TrackResolver = resolver.Resolve
|
||||
failed := atomic.Bool{}
|
||||
sm.params.OnSubscriptionError = func(trackID livekit.TrackID) {
|
||||
sm.params.OnSubscriptionError = func(trackID livekit.TrackID, fatal bool, err error) {
|
||||
failed.Store(true)
|
||||
}
|
||||
|
||||
@@ -156,7 +164,7 @@ func TestSubscribe(t *testing.T) {
|
||||
resolver := newTestResolver(true, true, "pub", "pubID")
|
||||
sm.params.TrackResolver = resolver.Resolve
|
||||
failed := atomic.Bool{}
|
||||
sm.params.OnSubscriptionError = func(trackID livekit.TrackID) {
|
||||
sm.params.OnSubscriptionError = func(trackID livekit.TrackID, fatal bool, err error) {
|
||||
failed.Store(true)
|
||||
}
|
||||
|
||||
@@ -209,9 +217,9 @@ func TestUnsubscribe(t *testing.T) {
|
||||
st.OnClose(func(willBeResumed bool) {
|
||||
sm.handleSubscribedTrackClose(s, willBeResumed)
|
||||
})
|
||||
res.Track.(*typesfakes.FakeMediaTrack).RemoveSubscriberStub = func(pID livekit.ParticipantID, willBeResumed bool) {
|
||||
res.Track.(*typesfakes.FakeMediaTrack).RemoveSubscriberCalls(func(pID livekit.ParticipantID, willBeResumed bool) {
|
||||
setTestSubscribedTrackClosed(t, st, willBeResumed)
|
||||
}
|
||||
})
|
||||
|
||||
sm.lock.Lock()
|
||||
sm.subscriptions["track"] = s
|
||||
@@ -225,14 +233,23 @@ func TestUnsubscribe(t *testing.T) {
|
||||
require.False(t, s.isDesired())
|
||||
|
||||
require.Eventually(t, func() bool {
|
||||
return !s.needsUnsubscribe()
|
||||
if s.needsUnsubscribe() {
|
||||
return false
|
||||
}
|
||||
if sm.pendingUnsubscribes.Load() != 0 {
|
||||
return false
|
||||
}
|
||||
sm.lock.RLock()
|
||||
subLen := len(sm.subscriptions)
|
||||
sm.lock.RUnlock()
|
||||
if subLen != 0 {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}, subSettleTimeout, subCheckInterval, "Track was not unsubscribed")
|
||||
|
||||
// no traces should be left
|
||||
require.Len(t, sm.GetSubscribedTracks(), 0)
|
||||
sm.lock.RLock()
|
||||
require.Len(t, sm.subscriptions, 0)
|
||||
sm.lock.RUnlock()
|
||||
require.False(t, res.TrackChangedNotifier.HasObservers())
|
||||
|
||||
tm := sm.params.Telemetry.(*telemetryfakes.FakeTelemetryService)
|
||||
@@ -269,14 +286,16 @@ func TestSubscribeStatusChanged(t *testing.T) {
|
||||
st2.OnClose(func(willBeResumed bool) {
|
||||
sm.handleSubscribedTrackClose(s2, willBeResumed)
|
||||
})
|
||||
st1.MediaTrack().(*typesfakes.FakeMediaTrack).RemoveSubscriberStub = func(pID livekit.ParticipantID, willBeResumed bool) {
|
||||
st1.MediaTrack().(*typesfakes.FakeMediaTrack).RemoveSubscriberCalls(func(pID livekit.ParticipantID, willBeResumed bool) {
|
||||
setTestSubscribedTrackClosed(t, st1, willBeResumed)
|
||||
}
|
||||
st2.MediaTrack().(*typesfakes.FakeMediaTrack).RemoveSubscriberStub = func(pID livekit.ParticipantID, willBeResumed bool) {
|
||||
})
|
||||
st2.MediaTrack().(*typesfakes.FakeMediaTrack).RemoveSubscriberCalls(func(pID livekit.ParticipantID, willBeResumed bool) {
|
||||
setTestSubscribedTrackClosed(t, st2, willBeResumed)
|
||||
}
|
||||
})
|
||||
|
||||
require.Equal(t, int32(1), numParticipantSubscribed.Load())
|
||||
require.Eventually(t, func() bool {
|
||||
return numParticipantSubscribed.Load() == 1
|
||||
}, subSettleTimeout, subCheckInterval, "should be subscribed to publisher")
|
||||
require.Equal(t, int32(0), numParticipantUnsubscribed.Load())
|
||||
require.True(t, sm.IsSubscribedTo("pubID"))
|
||||
|
||||
@@ -292,7 +311,9 @@ func TestSubscribeStatusChanged(t *testing.T) {
|
||||
require.Eventually(t, func() bool {
|
||||
return !s1.needsUnsubscribe()
|
||||
}, subSettleTimeout, subCheckInterval, "track1 should be unsubscribed")
|
||||
require.Equal(t, int32(1), numParticipantUnsubscribed.Load())
|
||||
require.Eventually(t, func() bool {
|
||||
return numParticipantUnsubscribed.Load() == 1
|
||||
}, subSettleTimeout, subCheckInterval, "should be subscribed to publisher")
|
||||
require.False(t, sm.IsSubscribedTo("pubID"))
|
||||
}
|
||||
|
||||
@@ -319,14 +340,123 @@ func TestUpdateSettingsBeforeSubscription(t *testing.T) {
|
||||
}, subSettleTimeout, subCheckInterval, "Track should be subscribed")
|
||||
|
||||
st := s.getSubscribedTrack().(*typesfakes.FakeSubscribedTrack)
|
||||
require.Equal(t, 1, st.UpdateSubscriberSettingsCallCount())
|
||||
require.Eventually(t, func() bool {
|
||||
return st.UpdateSubscriberSettingsCallCount() == 1
|
||||
}, subSettleTimeout, subCheckInterval, "UpdateSubscriberSettings should be called once")
|
||||
|
||||
applied := st.UpdateSubscriberSettingsArgsForCall(0)
|
||||
require.Equal(t, settings.Disabled, applied.Disabled)
|
||||
require.Equal(t, settings.Width, applied.Width)
|
||||
require.Equal(t, settings.Height, applied.Height)
|
||||
}
|
||||
|
||||
func TestSubscriptionLimits(t *testing.T) {
|
||||
sm := newTestSubscriptionManagerWithParams(t, testSubscriptionParams{
|
||||
SubscriptionLimitAudio: 1,
|
||||
SubscriptionLimitVideo: 1,
|
||||
})
|
||||
defer sm.Close(false)
|
||||
resolver := newTestResolver(true, true, "pub", "pubID")
|
||||
sm.params.TrackResolver = resolver.Resolve
|
||||
subCount := atomic.Int32{}
|
||||
failed := atomic.Bool{}
|
||||
sm.params.OnTrackSubscribed = func(subTrack types.SubscribedTrack) {
|
||||
subCount.Add(1)
|
||||
}
|
||||
sm.params.OnSubscriptionError = func(trackID livekit.TrackID, fatal bool, err error) {
|
||||
failed.Store(true)
|
||||
}
|
||||
numParticipantSubscribed := atomic.Int32{}
|
||||
numParticipantUnsubscribed := atomic.Int32{}
|
||||
sm.OnSubscribeStatusChanged(func(pubID livekit.ParticipantID, subscribed bool) {
|
||||
if subscribed {
|
||||
numParticipantSubscribed.Add(1)
|
||||
} else {
|
||||
numParticipantUnsubscribed.Add(1)
|
||||
}
|
||||
})
|
||||
|
||||
sm.SubscribeToTrack("track")
|
||||
s := sm.subscriptions["track"]
|
||||
require.True(t, s.isDesired())
|
||||
require.Eventually(t, func() bool {
|
||||
return subCount.Load() == 1
|
||||
}, subSettleTimeout, subCheckInterval, "track was not subscribed")
|
||||
|
||||
require.NotNil(t, s.getSubscribedTrack())
|
||||
require.Len(t, sm.GetSubscribedTracks(), 1)
|
||||
|
||||
require.Eventually(t, func() bool {
|
||||
return len(sm.GetSubscribedParticipants()) == 1
|
||||
}, subSettleTimeout, subCheckInterval, "GetSubscribedParticipants should have returned one item")
|
||||
require.Equal(t, "pubID", string(sm.GetSubscribedParticipants()[0]))
|
||||
|
||||
// ensure telemetry events are sent
|
||||
tm := sm.params.Telemetry.(*telemetryfakes.FakeTelemetryService)
|
||||
require.Equal(t, 1, tm.TrackSubscribeRequestedCallCount())
|
||||
|
||||
// ensure bound
|
||||
setTestSubscribedTrackBound(t, s.getSubscribedTrack())
|
||||
require.Eventually(t, func() bool {
|
||||
return !s.needsBind()
|
||||
}, subSettleTimeout, subCheckInterval, "track was not bound")
|
||||
|
||||
// telemetry event should have been sent
|
||||
require.Equal(t, 1, tm.TrackSubscribedCallCount())
|
||||
|
||||
// reach subscription limit, subscribe pending
|
||||
sm.SubscribeToTrack("track2")
|
||||
s2 := sm.subscriptions["track2"]
|
||||
time.Sleep(subscriptionTimeout * 2)
|
||||
require.True(t, s2.needsSubscribe())
|
||||
require.Equal(t, 2, tm.TrackSubscribeRequestedCallCount())
|
||||
require.Equal(t, 1, tm.TrackSubscribeFailedCallCount())
|
||||
require.Len(t, sm.GetSubscribedTracks(), 1)
|
||||
|
||||
// unsubscribe track1, then track2 should be subscribed
|
||||
sm.UnsubscribeFromTrack("track")
|
||||
require.False(t, s.isDesired())
|
||||
require.True(t, s.needsUnsubscribe())
|
||||
// wait for unsubscribe to take effect
|
||||
time.Sleep(reconcileInterval)
|
||||
setTestSubscribedTrackClosed(t, s.getSubscribedTrack(), false)
|
||||
require.Nil(t, s.getSubscribedTrack())
|
||||
|
||||
time.Sleep(reconcileInterval)
|
||||
require.True(t, s2.isDesired())
|
||||
require.False(t, s2.needsSubscribe())
|
||||
require.EqualValues(t, 2, subCount.Load())
|
||||
require.NotNil(t, s2.getSubscribedTrack())
|
||||
require.Equal(t, 2, tm.TrackSubscribeRequestedCallCount())
|
||||
require.Len(t, sm.GetSubscribedTracks(), 1)
|
||||
|
||||
// ensure bound
|
||||
setTestSubscribedTrackBound(t, s2.getSubscribedTrack())
|
||||
require.Eventually(t, func() bool {
|
||||
return !s2.needsBind()
|
||||
}, subSettleTimeout, subCheckInterval, "track was not bound")
|
||||
|
||||
// subscribe to track1 again, which should pending
|
||||
sm.SubscribeToTrack("track")
|
||||
s = sm.subscriptions["track"]
|
||||
require.True(t, s.isDesired())
|
||||
time.Sleep(subscriptionTimeout * 2)
|
||||
require.True(t, s.needsSubscribe())
|
||||
require.Equal(t, 3, tm.TrackSubscribeRequestedCallCount())
|
||||
require.Equal(t, 2, tm.TrackSubscribeFailedCallCount())
|
||||
require.Len(t, sm.GetSubscribedTracks(), 1)
|
||||
}
|
||||
|
||||
type testSubscriptionParams struct {
|
||||
SubscriptionLimitAudio int32
|
||||
SubscriptionLimitVideo int32
|
||||
}
|
||||
|
||||
func newTestSubscriptionManager(t *testing.T) *SubscriptionManager {
|
||||
return newTestSubscriptionManagerWithParams(t, testSubscriptionParams{})
|
||||
}
|
||||
|
||||
func newTestSubscriptionManagerWithParams(t *testing.T, params testSubscriptionParams) *SubscriptionManager {
|
||||
p := &typesfakes.FakeLocalParticipant{}
|
||||
p.CanSubscribeReturns(true)
|
||||
p.IDReturns("subID")
|
||||
@@ -336,11 +466,13 @@ func newTestSubscriptionManager(t *testing.T) *SubscriptionManager {
|
||||
Logger: logger.GetLogger(),
|
||||
OnTrackSubscribed: func(subTrack types.SubscribedTrack) {},
|
||||
OnTrackUnsubscribed: func(subTrack types.SubscribedTrack) {},
|
||||
OnSubscriptionError: func(trackID livekit.TrackID) {},
|
||||
OnSubscriptionError: func(trackID livekit.TrackID, fatal bool, err error) {},
|
||||
TrackResolver: func(identity livekit.ParticipantIdentity, trackID livekit.TrackID) types.MediaResolverResult {
|
||||
return types.MediaResolverResult{}
|
||||
},
|
||||
Telemetry: &telemetryfakes.FakeTelemetryService{},
|
||||
Telemetry: &telemetryfakes.FakeTelemetryService{},
|
||||
SubscriptionLimitAudio: params.SubscriptionLimitAudio,
|
||||
SubscriptionLimitVideo: params.SubscriptionLimitVideo,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -350,6 +482,8 @@ type testResolver struct {
|
||||
hasTrack bool
|
||||
pubIdentity livekit.ParticipantIdentity
|
||||
pubID livekit.ParticipantID
|
||||
|
||||
paused bool
|
||||
}
|
||||
|
||||
func newTestResolver(hasPermission bool, hasTrack bool, pubIdentity livekit.ParticipantIdentity, pubID livekit.ParticipantID) *testResolver {
|
||||
@@ -361,6 +495,12 @@ func newTestResolver(hasPermission bool, hasTrack bool, pubIdentity livekit.Part
|
||||
}
|
||||
}
|
||||
|
||||
func (t *testResolver) SetPause(paused bool) {
|
||||
t.lock.Lock()
|
||||
defer t.lock.Unlock()
|
||||
t.paused = paused
|
||||
}
|
||||
|
||||
func (t *testResolver) Resolve(identity livekit.ParticipantIdentity, trackID livekit.TrackID) types.MediaResolverResult {
|
||||
t.lock.Lock()
|
||||
defer t.lock.Unlock()
|
||||
@@ -371,7 +511,7 @@ func (t *testResolver) Resolve(identity livekit.ParticipantIdentity, trackID liv
|
||||
PublisherID: t.pubID,
|
||||
PublisherIdentity: t.pubIdentity,
|
||||
}
|
||||
if t.hasTrack {
|
||||
if t.hasTrack && !t.paused {
|
||||
mt := &typesfakes.FakeMediaTrack{}
|
||||
st := &typesfakes.FakeSubscribedTrack{}
|
||||
st.IDReturns(trackID)
|
||||
@@ -389,7 +529,7 @@ func setTestSubscribedTrackBound(t *testing.T, st types.SubscribedTrack) {
|
||||
require.True(t, ok)
|
||||
|
||||
for i := 0; i < fst.AddOnBindCallCount(); i++ {
|
||||
fst.AddOnBindArgsForCall(i)()
|
||||
fst.AddOnBindArgsForCall(i)(nil)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,17 @@
|
||||
// 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 supervisor
|
||||
|
||||
import (
|
||||
|
||||
@@ -1,3 +1,17 @@
|
||||
// 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 supervisor
|
||||
|
||||
import (
|
||||
@@ -34,7 +48,7 @@ type PublicationMonitor struct {
|
||||
params PublicationMonitorParams
|
||||
|
||||
lock sync.RWMutex
|
||||
desiredPublishes deque.Deque
|
||||
desiredPublishes deque.Deque[*publish]
|
||||
|
||||
isConnected bool
|
||||
|
||||
@@ -128,7 +142,7 @@ func (p *PublicationMonitor) Check() error {
|
||||
p.lock.RLock()
|
||||
var pub *publish
|
||||
if p.desiredPublishes.Len() > 0 {
|
||||
pub = p.desiredPublishes.Front().(*publish)
|
||||
pub = p.desiredPublishes.Front()
|
||||
}
|
||||
|
||||
isMuted := p.isMuted
|
||||
@@ -160,7 +174,7 @@ func (p *PublicationMonitor) update() {
|
||||
for {
|
||||
var pub *publish
|
||||
if p.desiredPublishes.Len() > 0 {
|
||||
pub = p.desiredPublishes.PopFront().(*publish)
|
||||
pub = p.desiredPublishes.PopFront()
|
||||
}
|
||||
|
||||
if pub == nil {
|
||||
|
||||
+121
-50
@@ -1,3 +1,17 @@
|
||||
// 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 (
|
||||
@@ -8,6 +22,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/bep/debounce"
|
||||
"github.com/pion/dtls/v2/pkg/crypto/elliptic"
|
||||
"github.com/pion/ice/v2"
|
||||
"github.com/pion/interceptor"
|
||||
"github.com/pion/interceptor/pkg/cc"
|
||||
@@ -21,12 +36,14 @@ import (
|
||||
|
||||
"github.com/livekit/protocol/livekit"
|
||||
"github.com/livekit/protocol/logger"
|
||||
"github.com/livekit/protocol/logger/pionlogger"
|
||||
lksdp "github.com/livekit/protocol/sdp"
|
||||
"github.com/livekit/protocol/utils"
|
||||
|
||||
"github.com/livekit/livekit-server/pkg/config"
|
||||
serverlogger "github.com/livekit/livekit-server/pkg/logger"
|
||||
"github.com/livekit/livekit-server/pkg/rtc/types"
|
||||
"github.com/livekit/livekit-server/pkg/sfu"
|
||||
"github.com/livekit/livekit-server/pkg/sfu/pacer"
|
||||
"github.com/livekit/livekit-server/pkg/sfu/streamallocator"
|
||||
"github.com/livekit/livekit-server/pkg/telemetry"
|
||||
"github.com/livekit/livekit-server/pkg/telemetry/prometheus"
|
||||
)
|
||||
@@ -49,6 +66,8 @@ const (
|
||||
minConnectTimeoutAfterICE = 10 * time.Second
|
||||
maxConnectTimeoutAfterICE = 20 * time.Second // max duration for waiting pc to connect after ICE is connected
|
||||
|
||||
maxICECandidates = 20
|
||||
|
||||
shortConnectionThreshold = 90 * time.Second
|
||||
)
|
||||
|
||||
@@ -182,7 +201,10 @@ type PCTransport struct {
|
||||
onNegotiationFailed func()
|
||||
|
||||
// stream allocator for subscriber PC
|
||||
streamAllocator *sfu.StreamAllocator
|
||||
streamAllocator *streamallocator.StreamAllocator
|
||||
|
||||
// only for subscriber PC
|
||||
pacer pacer.Pacer
|
||||
|
||||
previousAnswer *webrtc.SessionDescription
|
||||
// track id -> description map in previous offer sdp
|
||||
@@ -208,10 +230,10 @@ type PCTransport struct {
|
||||
pendingRestartIceOffer *webrtc.SessionDescription
|
||||
|
||||
// for cleaner logging
|
||||
allowedLocalCandidates []string
|
||||
allowedRemoteCandidates []string
|
||||
filteredLocalCandidates []string
|
||||
filteredRemoteCandidates []string
|
||||
allowedLocalCandidates *utils.DedupedSlice[string]
|
||||
allowedRemoteCandidates *utils.DedupedSlice[string]
|
||||
filteredLocalCandidates *utils.DedupedSlice[string]
|
||||
filteredRemoteCandidates *utils.DedupedSlice[string]
|
||||
}
|
||||
|
||||
type TransportParams struct {
|
||||
@@ -231,9 +253,7 @@ type TransportParams struct {
|
||||
}
|
||||
|
||||
func newPeerConnection(params TransportParams, onBandwidthEstimator func(estimator cc.BandwidthEstimator)) (*webrtc.PeerConnection, *webrtc.MediaEngine, error) {
|
||||
directionConfig := params.DirectionConfig
|
||||
|
||||
me, err := createMediaEngine(params.EnabledCodecs, directionConfig)
|
||||
me, err := createMediaEngine(params.EnabledCodecs, params.DirectionConfig)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
@@ -241,6 +261,10 @@ func newPeerConnection(params TransportParams, onBandwidthEstimator func(estimat
|
||||
se := params.Config.SettingEngine
|
||||
se.DisableMediaEngineCopy(true)
|
||||
|
||||
// Change elliptic curve to improve connectivity
|
||||
// https://github.com/pion/dtls/pull/474
|
||||
se.SetDTLSEllipticCurves(elliptic.X25519, elliptic.P384, elliptic.P256)
|
||||
|
||||
//
|
||||
// Disable SRTP replay protection (https://datatracker.ietf.org/doc/html/rfc3711#page-15).
|
||||
// Needed due to lack of RTX stream support in Pion.
|
||||
@@ -293,28 +317,14 @@ func newPeerConnection(params TransportParams, onBandwidthEstimator func(estimat
|
||||
}
|
||||
}
|
||||
|
||||
lf := serverlogger.NewLoggerFactory(params.Logger)
|
||||
lf := pionlogger.NewLoggerFactory(params.Logger)
|
||||
if lf != nil {
|
||||
se.LoggerFactory = lf
|
||||
}
|
||||
|
||||
ir := &interceptor.Registry{}
|
||||
if params.IsSendSide {
|
||||
isSendSideBWE := false
|
||||
for _, ext := range directionConfig.RTPHeaderExtension.Video {
|
||||
if ext == sdp.TransportCCURI {
|
||||
isSendSideBWE = true
|
||||
break
|
||||
}
|
||||
}
|
||||
for _, ext := range directionConfig.RTPHeaderExtension.Audio {
|
||||
if ext == sdp.TransportCCURI {
|
||||
isSendSideBWE = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if isSendSideBWE {
|
||||
if params.CongestionControlConfig.UseSendSideBWE {
|
||||
gf, err := cc.NewInterceptor(func() (cc.BandwidthEstimator, error) {
|
||||
return gcc.NewSendSideBWE(
|
||||
gcc.SendSideBWEInitialBitrate(1*1000*1000),
|
||||
@@ -364,13 +374,18 @@ func NewPCTransport(params TransportParams) (*PCTransport, error) {
|
||||
eventCh: make(chan event, 50),
|
||||
previousTrackDescription: make(map[string]*trackDescription),
|
||||
canReuseTransceiver: true,
|
||||
allowedLocalCandidates: utils.NewDedupedSlice[string](maxICECandidates),
|
||||
allowedRemoteCandidates: utils.NewDedupedSlice[string](maxICECandidates),
|
||||
filteredLocalCandidates: utils.NewDedupedSlice[string](maxICECandidates),
|
||||
filteredRemoteCandidates: utils.NewDedupedSlice[string](maxICECandidates),
|
||||
}
|
||||
if params.IsSendSide {
|
||||
t.streamAllocator = sfu.NewStreamAllocator(sfu.StreamAllocatorParams{
|
||||
t.streamAllocator = streamallocator.NewStreamAllocator(streamallocator.StreamAllocatorParams{
|
||||
Config: params.CongestionControlConfig,
|
||||
Logger: params.Logger,
|
||||
})
|
||||
t.streamAllocator.Start()
|
||||
t.pacer = pacer.NewPassThrough(params.Logger)
|
||||
}
|
||||
|
||||
if err := t.createPeerConnection(); err != nil {
|
||||
@@ -409,6 +424,10 @@ func (t *PCTransport) createPeerConnection() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *PCTransport) GetPacer() pacer.Pacer {
|
||||
return t.pacer
|
||||
}
|
||||
|
||||
func (t *PCTransport) SetSignalingRTT(rtt uint32) {
|
||||
t.signalingRTT.Store(rtt)
|
||||
}
|
||||
@@ -495,7 +514,7 @@ func (t *PCTransport) resetShortConn() {
|
||||
t.lock.Unlock()
|
||||
}
|
||||
|
||||
func (t *PCTransport) isShortConnection(at time.Time) (bool, time.Duration) {
|
||||
func (t *PCTransport) IsShortConnection(at time.Time) (bool, time.Duration) {
|
||||
t.lock.RLock()
|
||||
defer t.lock.RUnlock()
|
||||
|
||||
@@ -568,7 +587,7 @@ func (t *PCTransport) handleConnectionFailed(forceShortConn bool) {
|
||||
isShort := forceShortConn
|
||||
if !isShort {
|
||||
var duration time.Duration
|
||||
isShort, duration = t.isShortConnection(time.Now())
|
||||
isShort, duration = t.IsShortConnection(time.Now())
|
||||
if isShort {
|
||||
pair, err := t.getSelectedPair()
|
||||
if err != nil {
|
||||
@@ -599,6 +618,11 @@ func (t *PCTransport) onICEConnectionStateChange(state webrtc.ICEConnectionState
|
||||
|
||||
case webrtc.ICEConnectionStateChecking:
|
||||
t.setICEStartedAt(time.Now())
|
||||
|
||||
case webrtc.ICEConnectionStateDisconnected:
|
||||
fallthrough
|
||||
case webrtc.ICEConnectionStateFailed:
|
||||
t.params.Logger.Infow("ice connection state change unexpected", "state", state.String())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -614,6 +638,7 @@ func (t *PCTransport) onPeerConnectionStateChange(state webrtc.PeerConnectionSta
|
||||
}
|
||||
|
||||
t.maybeNotifyFullyEstablished()
|
||||
t.logICECandidates()
|
||||
}
|
||||
case webrtc.PeerConnectionStateFailed:
|
||||
t.params.Logger.Infow("peer connection failed")
|
||||
@@ -893,6 +918,9 @@ func (t *PCTransport) Close() {
|
||||
if t.streamAllocator != nil {
|
||||
t.streamAllocator.Stop()
|
||||
}
|
||||
if t.pacer != nil {
|
||||
t.pacer.Stop()
|
||||
}
|
||||
|
||||
_ = t.pc.Close()
|
||||
|
||||
@@ -1085,7 +1113,7 @@ func (t *PCTransport) ResetShortConnOnICERestart() {
|
||||
t.resetShortConnOnICERestart.Store(true)
|
||||
}
|
||||
|
||||
func (t *PCTransport) OnStreamStateChange(f func(update *sfu.StreamStateUpdate) error) {
|
||||
func (t *PCTransport) OnStreamStateChange(f func(update *streamallocator.StreamStateUpdate) error) {
|
||||
if t.streamAllocator == nil {
|
||||
return
|
||||
}
|
||||
@@ -1098,7 +1126,7 @@ func (t *PCTransport) AddTrackToStreamAllocator(subTrack types.SubscribedTrack)
|
||||
return
|
||||
}
|
||||
|
||||
t.streamAllocator.AddTrack(subTrack.DownTrack(), sfu.AddTrackParams{
|
||||
t.streamAllocator.AddTrack(subTrack.DownTrack(), streamallocator.AddTrackParams{
|
||||
Source: subTrack.MediaTrack().Source(),
|
||||
IsSimulcast: subTrack.MediaTrack().IsSimulcast(),
|
||||
PublisherID: subTrack.MediaTrack().PublisherID(),
|
||||
@@ -1113,6 +1141,22 @@ func (t *PCTransport) RemoveTrackFromStreamAllocator(subTrack types.SubscribedTr
|
||||
t.streamAllocator.RemoveTrack(subTrack.DownTrack())
|
||||
}
|
||||
|
||||
func (t *PCTransport) SetAllowPauseOfStreamAllocator(allowPause bool) {
|
||||
if t.streamAllocator == nil {
|
||||
return
|
||||
}
|
||||
|
||||
t.streamAllocator.SetAllowPause(allowPause)
|
||||
}
|
||||
|
||||
func (t *PCTransport) SetChannelCapacityOfStreamAllocator(channelCapacity int64) {
|
||||
if t.streamAllocator == nil {
|
||||
return
|
||||
}
|
||||
|
||||
t.streamAllocator.SetChannelCapacity(channelCapacity)
|
||||
}
|
||||
|
||||
func (t *PCTransport) GetICEConnectionType() types.ICEConnectionType {
|
||||
unknown := types.ICEConnectionTypeUnknown
|
||||
if t.pc == nil {
|
||||
@@ -1130,7 +1174,7 @@ func (t *PCTransport) GetICEConnectionType() types.ICEConnectionType {
|
||||
// Pion would have created a prflx candidate with the same address as the relay candidate.
|
||||
// to report an accurate connection type, we'll compare to see if existing relay candidates match
|
||||
t.lock.RLock()
|
||||
allowedRemoteCandidates := t.allowedRemoteCandidates
|
||||
allowedRemoteCandidates := t.allowedRemoteCandidates.Get()
|
||||
t.lock.RUnlock()
|
||||
|
||||
for _, ci := range allowedRemoteCandidates {
|
||||
@@ -1263,7 +1307,7 @@ func (t *PCTransport) initPCWithPreviousAnswer(previousAnswer webrtc.SessionDesc
|
||||
}
|
||||
tr.SetMid(mid)
|
||||
|
||||
// save mid -> senders for migration resue
|
||||
// save mid -> senders for migration reuse
|
||||
sender := tr.Sender()
|
||||
senders[mid] = sender
|
||||
|
||||
@@ -1294,7 +1338,7 @@ func (t *PCTransport) SetPreviousSdp(offer, answer *webrtc.SessionDescription) {
|
||||
}
|
||||
return
|
||||
} else if offer != nil {
|
||||
// in migration case, can't reuse tranceiver before negotiated except track subscribed at previous node
|
||||
// in migration case, can't reuse transceiver before negotiated except track subscribed at previous node
|
||||
t.canReuseTransceiver = false
|
||||
if err := t.parseTrackMid(*offer, senders); err != nil {
|
||||
t.params.Logger.Errorw("parse previous offer failed", err, "offer", offer.SDP)
|
||||
@@ -1318,12 +1362,12 @@ func (t *PCTransport) parseTrackMid(offer webrtc.SessionDescription, senders map
|
||||
}
|
||||
|
||||
if split := strings.Split(msid, " "); len(split) == 2 {
|
||||
trackid := split[1]
|
||||
trackID := split[1]
|
||||
mid := lksdp.GetMidValue(m)
|
||||
if mid == "" {
|
||||
return ErrMidNotFound
|
||||
}
|
||||
t.previousTrackDescription[trackid] = &trackDescription{
|
||||
t.previousTrackDescription[trackID] = &trackDescription{
|
||||
mid: mid,
|
||||
sender: senders[mid],
|
||||
}
|
||||
@@ -1349,6 +1393,11 @@ func (t *PCTransport) postEvent(event event) {
|
||||
|
||||
func (t *PCTransport) processEvents() {
|
||||
for event := range t.eventCh {
|
||||
if t.isClosed.Load() {
|
||||
// just drain the channel without processing events
|
||||
continue
|
||||
}
|
||||
|
||||
err := t.handleEvent(&event)
|
||||
if err != nil {
|
||||
t.params.Logger.Errorw("error handling event", err, "event", event.String())
|
||||
@@ -1445,12 +1494,12 @@ func (t *PCTransport) clearLocalDescriptionSent() {
|
||||
t.cacheLocalCandidates = true
|
||||
t.cachedLocalCandidates = nil
|
||||
|
||||
t.allowedLocalCandidates = nil
|
||||
t.allowedLocalCandidates.Clear()
|
||||
t.lock.Lock()
|
||||
t.allowedRemoteCandidates = nil
|
||||
t.allowedRemoteCandidates.Clear()
|
||||
t.lock.Unlock()
|
||||
t.filteredLocalCandidates = nil
|
||||
t.filteredRemoteCandidates = nil
|
||||
t.filteredLocalCandidates.Clear()
|
||||
t.filteredRemoteCandidates.Clear()
|
||||
}
|
||||
|
||||
func (t *PCTransport) handleLocalICECandidate(e *event) error {
|
||||
@@ -1460,7 +1509,7 @@ func (t *PCTransport) handleLocalICECandidate(e *event) error {
|
||||
if t.preferTCP.Load() && c != nil && c.Protocol != webrtc.ICEProtocolTCP {
|
||||
cstr := c.String()
|
||||
t.params.Logger.Debugw("filtering out local candidate", "candidate", cstr)
|
||||
t.filteredLocalCandidates = append(t.filteredLocalCandidates, cstr)
|
||||
t.filteredLocalCandidates.Add(cstr)
|
||||
filtered = true
|
||||
}
|
||||
|
||||
@@ -1469,7 +1518,7 @@ func (t *PCTransport) handleLocalICECandidate(e *event) error {
|
||||
}
|
||||
|
||||
if c != nil {
|
||||
t.allowedLocalCandidates = append(t.allowedLocalCandidates, c.String())
|
||||
t.allowedLocalCandidates.Add(c.String())
|
||||
}
|
||||
if t.cacheLocalCandidates {
|
||||
t.cachedLocalCandidates = append(t.cachedLocalCandidates, c)
|
||||
@@ -1489,7 +1538,7 @@ func (t *PCTransport) handleRemoteICECandidate(e *event) error {
|
||||
filtered := false
|
||||
if t.preferTCP.Load() && !strings.Contains(c.Candidate, "tcp") {
|
||||
t.params.Logger.Debugw("filtering out remote candidate", "candidate", c.Candidate)
|
||||
t.filteredRemoteCandidates = append(t.filteredRemoteCandidates, c.Candidate)
|
||||
t.filteredRemoteCandidates.Add(c.Candidate)
|
||||
filtered = true
|
||||
}
|
||||
|
||||
@@ -1498,7 +1547,7 @@ func (t *PCTransport) handleRemoteICECandidate(e *event) error {
|
||||
}
|
||||
|
||||
t.lock.Lock()
|
||||
t.allowedRemoteCandidates = append(t.allowedRemoteCandidates, c.Candidate)
|
||||
t.allowedRemoteCandidates.Add(c.Candidate)
|
||||
t.lock.Unlock()
|
||||
|
||||
if t.pc.RemoteDescription() == nil {
|
||||
@@ -1516,10 +1565,10 @@ func (t *PCTransport) handleRemoteICECandidate(e *event) error {
|
||||
func (t *PCTransport) handleLogICECandidates(e *event) error {
|
||||
t.params.Logger.Infow(
|
||||
"ice candidates",
|
||||
"lc", t.allowedLocalCandidates,
|
||||
"rc", t.allowedRemoteCandidates,
|
||||
"lc (filtered)", t.filteredLocalCandidates,
|
||||
"rc (filtered)", t.filteredRemoteCandidates,
|
||||
"lc", t.allowedLocalCandidates.Get(),
|
||||
"rc", t.allowedRemoteCandidates.Get(),
|
||||
"lc (filtered)", t.filteredLocalCandidates.Get(),
|
||||
"rc (filtered)", t.filteredRemoteCandidates.Get(),
|
||||
)
|
||||
|
||||
return nil
|
||||
@@ -1589,6 +1638,13 @@ func (t *PCTransport) setupSignalStateCheckTimer() {
|
||||
failed := t.negotiationState != NegotiationStateNone
|
||||
|
||||
if t.negotiateCounter.Load() == negotiateVersion && failed {
|
||||
t.params.Logger.Infow(
|
||||
"negotiation timed out",
|
||||
"localCurrent", t.pc.CurrentLocalDescription(),
|
||||
"localPending", t.pc.PendingLocalDescription(),
|
||||
"remoteCurrent", t.pc.CurrentRemoteDescription(),
|
||||
"remotePending", t.pc.PendingRemoteDescription(),
|
||||
)
|
||||
if onNegotiationFailed := t.getOnNegotiationFailed(); onNegotiationFailed != nil {
|
||||
onNegotiationFailed()
|
||||
}
|
||||
@@ -1844,7 +1900,13 @@ func (t *PCTransport) handleRemoteOfferReceived(sd *webrtc.SessionDescription) e
|
||||
|
||||
func (t *PCTransport) handleRemoteAnswerReceived(sd *webrtc.SessionDescription) error {
|
||||
if err := t.setRemoteDescription(*sd); err != nil {
|
||||
return err
|
||||
// Pion will call RTPSender.Send method for each new added Downtrack, and return error if the DownTrack.Bind
|
||||
// returns error. In case of Downtrack.Bind returns ErrUnsupportedCodec, the signal state will be stable as negotiation is aleady compelted
|
||||
// before startRTPSenders, and the peerconnection state can be recovered by next negotiation which will be triggered
|
||||
// by the SubscriptionManager unsubscribe the failure DownTrack. So don't treat this error as negotiation failure.
|
||||
if !errors.Is(err, webrtc.ErrUnsupportedCodec) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
t.clearSignalStateCheckTimer()
|
||||
@@ -1937,7 +1999,16 @@ func configureAudioTransceiver(tr *webrtc.RTPTransceiver, stereo bool, nack bool
|
||||
c.SDPFmtpLine += ";sprop-stereo=1"
|
||||
}
|
||||
if nack {
|
||||
c.RTCPFeedback = append(c.RTCPFeedback, webrtc.RTCPFeedback{Type: webrtc.TypeRTCPFBNACK})
|
||||
var nackFound bool
|
||||
for _, fb := range c.RTCPFeedback {
|
||||
if fb.Type == webrtc.TypeRTCPFBNACK {
|
||||
nackFound = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !nackFound {
|
||||
c.RTCPFeedback = append(c.RTCPFeedback, webrtc.RTCPFeedback{Type: webrtc.TypeRTCPFBNACK})
|
||||
}
|
||||
}
|
||||
}
|
||||
configCodecs = append(configCodecs, c)
|
||||
|
||||
@@ -1,3 +1,17 @@
|
||||
// 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 (
|
||||
@@ -246,7 +260,7 @@ func TestFirstAnswerMissedDuringICERestart(t *testing.T) {
|
||||
// exchange ICE
|
||||
handleICEExchange(t, transportA, transportB)
|
||||
|
||||
// first anwser missed
|
||||
// first answer missed
|
||||
var firstAnswerReceived atomic.Bool
|
||||
transportB.OnAnswer(func(sd webrtc.SessionDescription) error {
|
||||
if firstAnswerReceived.Load() {
|
||||
@@ -506,3 +520,40 @@ func connectTransports(t *testing.T, offerer, answerer *PCTransport, isICERestar
|
||||
return answerer.pc.ICEConnectionState() == webrtc.ICEConnectionStateConnected
|
||||
}, 10*time.Second, time.Millisecond*10, "answerer did not become connected")
|
||||
}
|
||||
|
||||
func TestConfigureAudioTransceiver(t *testing.T) {
|
||||
pc, err := webrtc.NewPeerConnection(webrtc.Configuration{})
|
||||
require.NoError(t, err)
|
||||
defer pc.Close()
|
||||
|
||||
for _, testcase := range []struct {
|
||||
nack bool
|
||||
stereo bool
|
||||
}{
|
||||
{false, false},
|
||||
{true, false},
|
||||
{false, true},
|
||||
{true, true},
|
||||
} {
|
||||
t.Run(fmt.Sprintf("nack=%v,stereo=%v", testcase.nack, testcase.stereo), func(t *testing.T) {
|
||||
tr, err := pc.AddTransceiverFromKind(webrtc.RTPCodecTypeAudio, webrtc.RtpTransceiverInit{Direction: webrtc.RTPTransceiverDirectionSendonly})
|
||||
require.NoError(t, err)
|
||||
|
||||
configureAudioTransceiver(tr, testcase.stereo, testcase.nack)
|
||||
codecs := tr.Sender().GetParameters().Codecs
|
||||
for _, codec := range codecs {
|
||||
if strings.Contains(codec.MimeType, webrtc.MimeTypeOpus) {
|
||||
require.Equal(t, testcase.stereo, strings.Contains(codec.SDPFmtpLine, "sprop-stereo=1"))
|
||||
var nackEnabled bool
|
||||
for _, fb := range codec.RTCPFeedback {
|
||||
if fb.Type == webrtc.TypeRTCPFBNACK {
|
||||
nackEnabled = true
|
||||
break
|
||||
}
|
||||
}
|
||||
require.Equal(t, testcase.nack, nackEnabled)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
+114
-41
@@ -1,3 +1,17 @@
|
||||
// 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 (
|
||||
@@ -17,6 +31,8 @@ import (
|
||||
"github.com/livekit/livekit-server/pkg/config"
|
||||
"github.com/livekit/livekit-server/pkg/rtc/types"
|
||||
"github.com/livekit/livekit-server/pkg/sfu"
|
||||
"github.com/livekit/livekit-server/pkg/sfu/pacer"
|
||||
"github.com/livekit/livekit-server/pkg/sfu/streamallocator"
|
||||
"github.com/livekit/livekit-server/pkg/telemetry"
|
||||
"github.com/livekit/protocol/livekit"
|
||||
"github.com/livekit/protocol/logger"
|
||||
@@ -33,22 +49,23 @@ const (
|
||||
)
|
||||
|
||||
type TransportManagerParams struct {
|
||||
Identity livekit.ParticipantIdentity
|
||||
SID livekit.ParticipantID
|
||||
SubscriberAsPrimary bool
|
||||
Config *WebRTCConfig
|
||||
ProtocolVersion types.ProtocolVersion
|
||||
Telemetry telemetry.TelemetryService
|
||||
CongestionControlConfig config.CongestionControlConfig
|
||||
EnabledCodecs []*livekit.Codec
|
||||
SimTracks map[uint32]SimulcastTrackInfo
|
||||
ClientConf *livekit.ClientConfiguration
|
||||
ClientInfo ClientInfo
|
||||
Migration bool
|
||||
AllowTCPFallback bool
|
||||
TCPFallbackRTTThreshold int
|
||||
TURNSEnabled bool
|
||||
Logger logger.Logger
|
||||
Identity livekit.ParticipantIdentity
|
||||
SID livekit.ParticipantID
|
||||
SubscriberAsPrimary bool
|
||||
Config *WebRTCConfig
|
||||
ProtocolVersion types.ProtocolVersion
|
||||
Telemetry telemetry.TelemetryService
|
||||
CongestionControlConfig config.CongestionControlConfig
|
||||
EnabledCodecs []*livekit.Codec
|
||||
SimTracks map[uint32]SimulcastTrackInfo
|
||||
ClientConf *livekit.ClientConfiguration
|
||||
ClientInfo ClientInfo
|
||||
Migration bool
|
||||
AllowTCPFallback bool
|
||||
TCPFallbackRTTThreshold int
|
||||
AllowUDPUnstableFallback bool
|
||||
TURNSEnabled bool
|
||||
Logger logger.Logger
|
||||
}
|
||||
|
||||
type TransportManager struct {
|
||||
@@ -93,18 +110,32 @@ func NewTransportManager(params TransportManagerParams) (*TransportManager, erro
|
||||
}
|
||||
t.mediaLossProxy.OnMediaLossUpdate(t.onMediaLossUpdate)
|
||||
|
||||
enabledCodecs := make([]*livekit.Codec, 0, len(params.EnabledCodecs))
|
||||
for _, c := range params.EnabledCodecs {
|
||||
var disabled bool
|
||||
for _, disableCodec := range params.ClientConf.GetDisabledCodecs().GetCodecs() {
|
||||
subscribeCodecs := make([]*livekit.Codec, 0, len(params.EnabledCodecs))
|
||||
publishCodecs := make([]*livekit.Codec, 0, len(params.EnabledCodecs))
|
||||
shouldDisable := func(c *livekit.Codec, disabledCodecs []*livekit.Codec) bool {
|
||||
for _, disableCodec := range disabledCodecs {
|
||||
// disable codec's fmtp is empty means disable this codec entirely
|
||||
if strings.EqualFold(c.Mime, disableCodec.Mime) && (disableCodec.FmtpLine == "" || disableCodec.FmtpLine == c.FmtpLine) {
|
||||
disabled = true
|
||||
break
|
||||
return true
|
||||
}
|
||||
}
|
||||
if !disabled {
|
||||
enabledCodecs = append(enabledCodecs, c)
|
||||
return false
|
||||
}
|
||||
for _, c := range params.EnabledCodecs {
|
||||
var publishDisabled bool
|
||||
var subscribeDisabled bool
|
||||
if shouldDisable(c, params.ClientConf.GetDisabledCodecs().GetCodecs()) {
|
||||
publishDisabled = true
|
||||
subscribeDisabled = true
|
||||
}
|
||||
if shouldDisable(c, params.ClientConf.GetDisabledCodecs().GetPublish()) {
|
||||
publishDisabled = true
|
||||
}
|
||||
if !publishDisabled {
|
||||
publishCodecs = append(publishCodecs, c)
|
||||
}
|
||||
if !subscribeDisabled {
|
||||
subscribeCodecs = append(subscribeCodecs, c)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,7 +147,7 @@ func NewTransportManager(params TransportManagerParams) (*TransportManager, erro
|
||||
DirectionConfig: params.Config.Publisher,
|
||||
CongestionControlConfig: params.CongestionControlConfig,
|
||||
Telemetry: params.Telemetry,
|
||||
EnabledCodecs: enabledCodecs,
|
||||
EnabledCodecs: publishCodecs,
|
||||
Logger: LoggerWithPCTarget(params.Logger, livekit.SignalTarget_PUBLISHER),
|
||||
SimTracks: params.SimTracks,
|
||||
ClientInfo: params.ClientInfo,
|
||||
@@ -148,7 +179,7 @@ func NewTransportManager(params TransportManagerParams) (*TransportManager, erro
|
||||
DirectionConfig: params.Config.Subscriber,
|
||||
CongestionControlConfig: params.CongestionControlConfig,
|
||||
Telemetry: params.Telemetry,
|
||||
EnabledCodecs: enabledCodecs,
|
||||
EnabledCodecs: subscribeCodecs,
|
||||
Logger: LoggerWithPCTarget(params.Logger, livekit.SignalTarget_SUBSCRIBER),
|
||||
ClientInfo: params.ClientInfo,
|
||||
IsOfferer: true,
|
||||
@@ -188,10 +219,6 @@ func (t *TransportManager) Close() {
|
||||
t.subscriber.Close()
|
||||
}
|
||||
|
||||
func (t *TransportManager) HaveAllTransportEverConnected() bool {
|
||||
return t.publisher.HasEverConnected() && t.subscriber.HasEverConnected()
|
||||
}
|
||||
|
||||
func (t *TransportManager) SubscriberClose() {
|
||||
t.subscriber.Close()
|
||||
}
|
||||
@@ -215,6 +242,10 @@ func (t *TransportManager) OnPublisherTrack(f func(track *webrtc.TrackRemote, rt
|
||||
t.publisher.OnTrack(f)
|
||||
}
|
||||
|
||||
func (t *TransportManager) HasPublisherEverConnected() bool {
|
||||
return t.publisher.HasEverConnected()
|
||||
}
|
||||
|
||||
func (t *TransportManager) IsPublisherEstablished() bool {
|
||||
return t.publisher.IsEstablished()
|
||||
}
|
||||
@@ -243,7 +274,7 @@ func (t *TransportManager) OnSubscriberInitialConnected(f func()) {
|
||||
t.onSubscriberInitialConnected = f
|
||||
}
|
||||
|
||||
func (t *TransportManager) OnSubscriberStreamStateChange(f func(update *sfu.StreamStateUpdate) error) {
|
||||
func (t *TransportManager) OnSubscriberStreamStateChange(f func(update *streamallocator.StreamStateUpdate) error) {
|
||||
t.subscriber.OnStreamStateChange(f)
|
||||
}
|
||||
|
||||
@@ -267,6 +298,10 @@ func (t *TransportManager) WriteSubscriberRTCP(pkts []rtcp.Packet) error {
|
||||
return t.subscriber.WriteRTCP(pkts)
|
||||
}
|
||||
|
||||
func (t *TransportManager) GetSubscriberPacer() pacer.Pacer {
|
||||
return t.subscriber.GetPacer()
|
||||
}
|
||||
|
||||
func (t *TransportManager) OnPrimaryTransportInitialConnected(f func()) {
|
||||
t.onPrimaryTransportInitialConnected = f
|
||||
}
|
||||
@@ -363,7 +398,7 @@ func (t *TransportManager) GetUnmatchMediaForOffer(offer webrtc.SessionDescripti
|
||||
answer := lastAnswer.(webrtc.SessionDescription)
|
||||
parsedAnswer, err1 := answer.Unmarshal()
|
||||
if err1 != nil {
|
||||
// should not happend
|
||||
// should not happen
|
||||
t.params.Logger.Errorw("failed to parse last answer", err)
|
||||
return
|
||||
}
|
||||
@@ -456,15 +491,41 @@ func (t *TransportManager) NegotiateSubscriber(force bool) {
|
||||
t.subscriber.Negotiate(force)
|
||||
}
|
||||
|
||||
func (t *TransportManager) ICERestart(iceConfig *livekit.ICEConfig, resetShortConnection bool) {
|
||||
if iceConfig != nil {
|
||||
t.SetICEConfig(iceConfig)
|
||||
func (t *TransportManager) HandleClientReconnect(reason livekit.ReconnectReason) {
|
||||
var (
|
||||
isShort bool
|
||||
duration time.Duration
|
||||
resetShortConnection bool
|
||||
)
|
||||
switch reason {
|
||||
case livekit.ReconnectReason_RR_PUBLISHER_FAILED:
|
||||
resetShortConnection = true
|
||||
isShort, duration = t.publisher.IsShortConnection(time.Now())
|
||||
|
||||
case livekit.ReconnectReason_RR_SUBSCRIBER_FAILED:
|
||||
resetShortConnection = true
|
||||
isShort, duration = t.subscriber.IsShortConnection(time.Now())
|
||||
}
|
||||
|
||||
if isShort {
|
||||
t.lock.Lock()
|
||||
t.resetTransportConfigureLocked(false)
|
||||
t.lock.Unlock()
|
||||
t.params.Logger.Infow("short connection by client ice restart", "duration", duration, "reason", reason)
|
||||
t.handleConnectionFailed(isShort)
|
||||
}
|
||||
|
||||
if resetShortConnection {
|
||||
t.publisher.ResetShortConnOnICERestart()
|
||||
t.subscriber.ResetShortConnOnICERestart()
|
||||
}
|
||||
}
|
||||
|
||||
func (t *TransportManager) ICERestart(iceConfig *livekit.ICEConfig) {
|
||||
if iceConfig != nil {
|
||||
t.SetICEConfig(iceConfig)
|
||||
}
|
||||
|
||||
t.subscriber.ICERestart()
|
||||
}
|
||||
|
||||
@@ -478,14 +539,18 @@ func (t *TransportManager) SetICEConfig(iceConfig *livekit.ICEConfig) {
|
||||
t.configureICE(iceConfig, true)
|
||||
}
|
||||
|
||||
func (t *TransportManager) resetTransportConfigureLocked(reconfigured bool) {
|
||||
t.failureCount = 0
|
||||
t.isTransportReconfigured = reconfigured
|
||||
t.udpLossUnstableCount = 0
|
||||
t.lastFailure = time.Time{}
|
||||
}
|
||||
|
||||
func (t *TransportManager) configureICE(iceConfig *livekit.ICEConfig, reset bool) {
|
||||
t.lock.Lock()
|
||||
isEqual := proto.Equal(t.iceConfig, iceConfig)
|
||||
if reset || !isEqual {
|
||||
t.failureCount = 0
|
||||
t.isTransportReconfigured = !reset
|
||||
t.udpLossUnstableCount = 0
|
||||
t.lastFailure = time.Time{}
|
||||
t.resetTransportConfigureLocked(!reset)
|
||||
}
|
||||
|
||||
if isEqual {
|
||||
@@ -552,7 +617,7 @@ func (t *TransportManager) handleConnectionFailed(isShortLived bool) {
|
||||
}
|
||||
|
||||
//
|
||||
// Checking only `PreferenceSubcriber` field although any connection failure (PUBLISHER OR SUBSCRIBER) will
|
||||
// Checking only `PreferenceSubscriber` field although any connection failure (PUBLISHER OR SUBSCRIBER) will
|
||||
// flow through here.
|
||||
//
|
||||
// As both transports are switched to the same type on any failure, checking just subscriber should be fine.
|
||||
@@ -679,7 +744,7 @@ func (t *TransportManager) OnReceiverReport(dt *sfu.DownTrack, report *rtcp.Rece
|
||||
}
|
||||
|
||||
func (t *TransportManager) onMediaLossUpdate(loss uint8) {
|
||||
if t.params.TCPFallbackRTTThreshold == 0 {
|
||||
if t.params.TCPFallbackRTTThreshold == 0 || !t.params.AllowUDPUnstableFallback {
|
||||
return
|
||||
}
|
||||
t.lock.Lock()
|
||||
@@ -751,3 +816,11 @@ func (t *TransportManager) SetSignalSourceValid(valid bool) {
|
||||
t.signalSourceValid.Store(valid)
|
||||
t.params.Logger.Debugw("signal source valid", "valid", valid)
|
||||
}
|
||||
|
||||
func (t *TransportManager) SetSubscriberAllowPause(allowPause bool) {
|
||||
t.subscriber.SetAllowPauseOfStreamAllocator(allowPause)
|
||||
}
|
||||
|
||||
func (t *TransportManager) SetSubscriberChannelCapacity(channelCapacity int64) {
|
||||
t.subscriber.SetChannelCapacityOfStreamAllocator(channelCapacity)
|
||||
}
|
||||
|
||||
+83
-11
@@ -1,3 +1,17 @@
|
||||
// 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 types
|
||||
|
||||
import (
|
||||
@@ -10,10 +24,12 @@ import (
|
||||
"github.com/livekit/protocol/auth"
|
||||
"github.com/livekit/protocol/livekit"
|
||||
"github.com/livekit/protocol/logger"
|
||||
"github.com/livekit/protocol/utils"
|
||||
|
||||
"github.com/livekit/livekit-server/pkg/routing"
|
||||
"github.com/livekit/livekit-server/pkg/sfu"
|
||||
"github.com/livekit/livekit-server/pkg/sfu/buffer"
|
||||
"github.com/livekit/livekit-server/pkg/sfu/pacer"
|
||||
)
|
||||
|
||||
//go:generate go run github.com/maxbrunsfeld/counterfeiter/v6 -generate
|
||||
@@ -85,6 +101,7 @@ const (
|
||||
ParticipantCloseReasonMigrationRequested
|
||||
ParticipantCloseReasonOvercommitted
|
||||
ParticipantCloseReasonPublicationError
|
||||
ParticipantCloseReasonSubscriptionError
|
||||
)
|
||||
|
||||
func (p ParticipantCloseReason) String() string {
|
||||
@@ -129,6 +146,8 @@ func (p ParticipantCloseReason) String() string {
|
||||
return "OVERCOMMITTED"
|
||||
case ParticipantCloseReasonPublicationError:
|
||||
return "PUBLICATION_ERROR"
|
||||
case ParticipantCloseReasonSubscriptionError:
|
||||
return "SUBSCRIPTION_ERROR"
|
||||
default:
|
||||
return fmt.Sprintf("%d", int(p))
|
||||
}
|
||||
@@ -159,7 +178,7 @@ func (p ParticipantCloseReason) ToDisconnectReason() livekit.DisconnectReason {
|
||||
return livekit.DisconnectReason_SERVER_SHUTDOWN
|
||||
case ParticipantCloseReasonOvercommitted:
|
||||
return livekit.DisconnectReason_SERVER_SHUTDOWN
|
||||
case ParticipantCloseReasonNegotiateFailed, ParticipantCloseReasonPublicationError:
|
||||
case ParticipantCloseReasonNegotiateFailed, ParticipantCloseReasonPublicationError, ParticipantCloseReasonSubscriptionError:
|
||||
return livekit.DisconnectReason_STATE_MISMATCH
|
||||
default:
|
||||
// the other types will map to unknown reason
|
||||
@@ -169,17 +188,57 @@ func (p ParticipantCloseReason) ToDisconnectReason() livekit.DisconnectReason {
|
||||
|
||||
// ---------------------------------------------
|
||||
|
||||
type SignallingCloseReason int
|
||||
|
||||
const (
|
||||
SignallingCloseReasonUnknown SignallingCloseReason = iota
|
||||
SignallingCloseReasonMigration
|
||||
SignallingCloseReasonResume
|
||||
SignallingCloseReasonTransportFailure
|
||||
SignallingCloseReasonFullReconnectPublicationError
|
||||
SignallingCloseReasonFullReconnectSubscriptionError
|
||||
SignallingCloseReasonFullReconnectNegotiateFailed
|
||||
SignallingCloseReasonParticipantClose
|
||||
)
|
||||
|
||||
func (s SignallingCloseReason) String() string {
|
||||
switch s {
|
||||
case SignallingCloseReasonUnknown:
|
||||
return "UNKNOWN"
|
||||
case SignallingCloseReasonMigration:
|
||||
return "MIGRATION"
|
||||
case SignallingCloseReasonResume:
|
||||
return "RESUME"
|
||||
case SignallingCloseReasonTransportFailure:
|
||||
return "TRANSPORT_FAILURE"
|
||||
case SignallingCloseReasonFullReconnectPublicationError:
|
||||
return "FULL_RECONNECT_PUBLICATION_ERROR"
|
||||
case SignallingCloseReasonFullReconnectSubscriptionError:
|
||||
return "FULL_RECONNECT_SUBSCRIPTION_ERROR"
|
||||
case SignallingCloseReasonFullReconnectNegotiateFailed:
|
||||
return "FULL_RECONNECT_NEGOTIATE_FAILED"
|
||||
case SignallingCloseReasonParticipantClose:
|
||||
return "PARTICIPANT_CLOSE"
|
||||
default:
|
||||
return fmt.Sprintf("%d", int(s))
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------
|
||||
|
||||
//counterfeiter:generate . Participant
|
||||
type Participant interface {
|
||||
ID() livekit.ParticipantID
|
||||
Identity() livekit.ParticipantIdentity
|
||||
State() livekit.ParticipantInfo_State
|
||||
|
||||
CanSkipBroadcast() bool
|
||||
ToProto() *livekit.ParticipantInfo
|
||||
|
||||
SetName(name string)
|
||||
SetMetadata(metadata string)
|
||||
|
||||
IsPublisher() bool
|
||||
GetPublishedTrack(sid livekit.TrackID) MediaTrack
|
||||
GetPublishedTracks() []MediaTrack
|
||||
RemovePublishedTrack(track MediaTrack, willBeResumed bool, shouldClose bool)
|
||||
@@ -193,14 +252,14 @@ type Participant interface {
|
||||
IsRecorder() bool
|
||||
|
||||
Start()
|
||||
Close(sendLeave bool, reason ParticipantCloseReason) error
|
||||
Close(sendLeave bool, reason ParticipantCloseReason, isExpectedToResume bool) error
|
||||
|
||||
SubscriptionPermission() (*livekit.SubscriptionPermission, *livekit.TimedVersion)
|
||||
SubscriptionPermission() (*livekit.SubscriptionPermission, utils.TimedVersion)
|
||||
|
||||
// updates from remotes
|
||||
UpdateSubscriptionPermission(
|
||||
subscriptionPermission *livekit.SubscriptionPermission,
|
||||
timedVersion *livekit.TimedVersion,
|
||||
timedVersion utils.TimedVersion,
|
||||
resolverByIdentity func(participantIdentity livekit.ParticipantIdentity) LocalParticipant,
|
||||
resolverBySid func(participantID livekit.ParticipantID) LocalParticipant,
|
||||
) error
|
||||
@@ -229,7 +288,10 @@ type AddTrackParams struct {
|
||||
type LocalParticipant interface {
|
||||
Participant
|
||||
|
||||
ToProtoWithVersion() (*livekit.ParticipantInfo, utils.TimedVersion)
|
||||
|
||||
// getters
|
||||
GetTrailer() []byte
|
||||
GetLogger() logger.Logger
|
||||
GetAdaptiveStream() bool
|
||||
ProtocolVersion() ProtocolVersion
|
||||
@@ -239,19 +301,21 @@ type LocalParticipant interface {
|
||||
IsDisconnected() bool
|
||||
IsIdle() bool
|
||||
SubscriberAsPrimary() bool
|
||||
GetClientInfo() *livekit.ClientInfo
|
||||
GetClientConfiguration() *livekit.ClientConfiguration
|
||||
GetICEConnectionType() ICEConnectionType
|
||||
GetBufferFactory() *buffer.Factory
|
||||
|
||||
SetResponseSink(sink routing.MessageSink)
|
||||
CloseSignalConnection()
|
||||
CloseSignalConnection(reason SignallingCloseReason)
|
||||
UpdateLastSeenSignal()
|
||||
SetSignalSourceValid(valid bool)
|
||||
HandleSignalSourceClose()
|
||||
|
||||
// permissions
|
||||
ClaimGrants() *auth.ClaimGrants
|
||||
SetPermission(permission *livekit.ParticipantPermission) bool
|
||||
CanPublish() bool
|
||||
CanPublishSource(source livekit.TrackSource) bool
|
||||
CanSubscribe() bool
|
||||
CanPublishData() bool
|
||||
|
||||
@@ -263,7 +327,7 @@ type LocalParticipant interface {
|
||||
|
||||
HandleAnswer(sdp webrtc.SessionDescription)
|
||||
Negotiate(force bool)
|
||||
ICERestart(iceConfig *livekit.ICEConfig, reason livekit.ReconnectReason)
|
||||
ICERestart(iceConfig *livekit.ICEConfig)
|
||||
AddTrackToSubscriber(trackLocal webrtc.TrackLocal, params AddTrackParams) (*webrtc.RTPSender, *webrtc.RTPTransceiver, error)
|
||||
AddTransceiverFromTrackToSubscriber(trackLocal webrtc.TrackLocal, params AddTrackParams) (*webrtc.RTPSender, *webrtc.RTPTransceiver, error)
|
||||
RemoveTrackFromSubscriber(sender *webrtc.RTPSender) error
|
||||
@@ -281,7 +345,6 @@ type LocalParticipant interface {
|
||||
// returns list of participant identities that the current participant is subscribed to
|
||||
GetSubscribedParticipants() []livekit.ParticipantID
|
||||
IsSubscribedTo(sid livekit.ParticipantID) bool
|
||||
IsPublisher() bool
|
||||
|
||||
GetAudioLevel() (smoothedLevel float64, active bool)
|
||||
GetConnectionQuality() *livekit.ConnectionQualityInfo
|
||||
@@ -295,7 +358,7 @@ type LocalParticipant interface {
|
||||
SendConnectionQualityUpdate(update *livekit.ConnectionQualityUpdate) error
|
||||
SubscriptionPermissionUpdate(publisherID livekit.ParticipantID, trackID livekit.TrackID, allowed bool)
|
||||
SendRefreshToken(token string) error
|
||||
SendReconnectResponse(reconnectResponse *livekit.ReconnectResponse) error
|
||||
HandleReconnectAndSendResponse(reconnectReason livekit.ReconnectReason, reconnectResponse *livekit.ReconnectResponse) error
|
||||
IssueFullReconnect(reason ParticipantCloseReason)
|
||||
|
||||
// callbacks
|
||||
@@ -311,7 +374,7 @@ type LocalParticipant interface {
|
||||
OnParticipantUpdate(callback func(LocalParticipant))
|
||||
OnDataPacket(callback func(LocalParticipant, *livekit.DataPacket))
|
||||
OnSubscribeStatusChanged(fn func(publisherID livekit.ParticipantID, subscribed bool))
|
||||
OnClose(callback func(LocalParticipant, map[livekit.TrackID]livekit.ParticipantID))
|
||||
OnClose(callback func(LocalParticipant))
|
||||
OnClaimsChanged(callback func(LocalParticipant))
|
||||
OnReceiverReport(dt *sfu.DownTrack, report *rtcp.ReceiverReport)
|
||||
|
||||
@@ -333,6 +396,12 @@ type LocalParticipant interface {
|
||||
|
||||
UpdateSubscribedQuality(nodeID livekit.NodeID, trackID livekit.TrackID, maxQualities []SubscribedCodecQuality) error
|
||||
UpdateMediaLoss(nodeID livekit.NodeID, trackID livekit.TrackID, fractionalLoss uint32) error
|
||||
|
||||
// down stream bandwidth management
|
||||
SetSubscriberAllowPause(allowPause bool)
|
||||
SetSubscriberChannelCapacity(channelCapacity int64)
|
||||
|
||||
GetPacer() pacer.Pacer
|
||||
}
|
||||
|
||||
// Room is a container of participants, and can provide room-level actions
|
||||
@@ -349,6 +418,7 @@ type Room interface {
|
||||
UpdateVideoLayers(participant Participant, updateVideoLayers *livekit.UpdateVideoLayers) error
|
||||
ResolveMediaTrackForSubscriber(subIdentity livekit.ParticipantIdentity, trackID livekit.TrackID) MediaResolverResult
|
||||
GetLocalParticipants() []LocalParticipant
|
||||
UpdateParticipantMetadata(participant LocalParticipant, name string, metadata string)
|
||||
}
|
||||
|
||||
// MediaTrack represents a media track
|
||||
@@ -394,6 +464,8 @@ type MediaTrack interface {
|
||||
|
||||
Receivers() []sfu.TrackReceiver
|
||||
ClearAllReceivers(willBeResumed bool)
|
||||
|
||||
IsEncrypted() bool
|
||||
}
|
||||
|
||||
//counterfeiter:generate . LocalMediaTrack
|
||||
@@ -416,7 +488,7 @@ type LocalMediaTrack interface {
|
||||
|
||||
//counterfeiter:generate . SubscribedTrack
|
||||
type SubscribedTrack interface {
|
||||
AddOnBind(f func())
|
||||
AddOnBind(f func(error))
|
||||
IsBound() bool
|
||||
Close(willBeResumed bool)
|
||||
OnClose(f func(willBeResumed bool))
|
||||
|
||||
@@ -1,3 +1,17 @@
|
||||
// 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 types
|
||||
|
||||
type ProtocolVersion int
|
||||
|
||||
@@ -128,6 +128,16 @@ type FakeLocalMediaTrack struct {
|
||||
iDReturnsOnCall map[int]struct {
|
||||
result1 livekit.TrackID
|
||||
}
|
||||
IsEncryptedStub func() bool
|
||||
isEncryptedMutex sync.RWMutex
|
||||
isEncryptedArgsForCall []struct {
|
||||
}
|
||||
isEncryptedReturns struct {
|
||||
result1 bool
|
||||
}
|
||||
isEncryptedReturnsOnCall map[int]struct {
|
||||
result1 bool
|
||||
}
|
||||
IsMutedStub func() bool
|
||||
isMutedMutex sync.RWMutex
|
||||
isMutedArgsForCall []struct {
|
||||
@@ -928,6 +938,59 @@ func (fake *FakeLocalMediaTrack) IDReturnsOnCall(i int, result1 livekit.TrackID)
|
||||
}{result1}
|
||||
}
|
||||
|
||||
func (fake *FakeLocalMediaTrack) IsEncrypted() bool {
|
||||
fake.isEncryptedMutex.Lock()
|
||||
ret, specificReturn := fake.isEncryptedReturnsOnCall[len(fake.isEncryptedArgsForCall)]
|
||||
fake.isEncryptedArgsForCall = append(fake.isEncryptedArgsForCall, struct {
|
||||
}{})
|
||||
stub := fake.IsEncryptedStub
|
||||
fakeReturns := fake.isEncryptedReturns
|
||||
fake.recordInvocation("IsEncrypted", []interface{}{})
|
||||
fake.isEncryptedMutex.Unlock()
|
||||
if stub != nil {
|
||||
return stub()
|
||||
}
|
||||
if specificReturn {
|
||||
return ret.result1
|
||||
}
|
||||
return fakeReturns.result1
|
||||
}
|
||||
|
||||
func (fake *FakeLocalMediaTrack) IsEncryptedCallCount() int {
|
||||
fake.isEncryptedMutex.RLock()
|
||||
defer fake.isEncryptedMutex.RUnlock()
|
||||
return len(fake.isEncryptedArgsForCall)
|
||||
}
|
||||
|
||||
func (fake *FakeLocalMediaTrack) IsEncryptedCalls(stub func() bool) {
|
||||
fake.isEncryptedMutex.Lock()
|
||||
defer fake.isEncryptedMutex.Unlock()
|
||||
fake.IsEncryptedStub = stub
|
||||
}
|
||||
|
||||
func (fake *FakeLocalMediaTrack) IsEncryptedReturns(result1 bool) {
|
||||
fake.isEncryptedMutex.Lock()
|
||||
defer fake.isEncryptedMutex.Unlock()
|
||||
fake.IsEncryptedStub = nil
|
||||
fake.isEncryptedReturns = struct {
|
||||
result1 bool
|
||||
}{result1}
|
||||
}
|
||||
|
||||
func (fake *FakeLocalMediaTrack) IsEncryptedReturnsOnCall(i int, result1 bool) {
|
||||
fake.isEncryptedMutex.Lock()
|
||||
defer fake.isEncryptedMutex.Unlock()
|
||||
fake.IsEncryptedStub = nil
|
||||
if fake.isEncryptedReturnsOnCall == nil {
|
||||
fake.isEncryptedReturnsOnCall = make(map[int]struct {
|
||||
result1 bool
|
||||
})
|
||||
}
|
||||
fake.isEncryptedReturnsOnCall[i] = struct {
|
||||
result1 bool
|
||||
}{result1}
|
||||
}
|
||||
|
||||
func (fake *FakeLocalMediaTrack) IsMuted() bool {
|
||||
fake.isMutedMutex.Lock()
|
||||
ret, specificReturn := fake.isMutedReturnsOnCall[len(fake.isMutedArgsForCall)]
|
||||
@@ -1947,6 +2010,8 @@ func (fake *FakeLocalMediaTrack) Invocations() map[string][][]interface{} {
|
||||
defer fake.hasSdpCidMutex.RUnlock()
|
||||
fake.iDMutex.RLock()
|
||||
defer fake.iDMutex.RUnlock()
|
||||
fake.isEncryptedMutex.RLock()
|
||||
defer fake.isEncryptedMutex.RUnlock()
|
||||
fake.isMutedMutex.RLock()
|
||||
defer fake.isMutedMutex.RUnlock()
|
||||
fake.isOpenMutex.RLock()
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user