直播:后台 JWT 推流、前台画中画;WebRTC 服务与 Nginx WebSocket 代理
Made-with: Cursor
This commit is contained in:
6
server/vendor/github.com/pion/webrtc/v3/.codacy.yaml
generated
vendored
Normal file
6
server/vendor/github.com/pion/webrtc/v3/.codacy.yaml
generated
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
# SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
exclude_paths:
|
||||
- examples/examples.json
|
||||
3
server/vendor/github.com/pion/webrtc/v3/.eslintrc.json
generated
vendored
Normal file
3
server/vendor/github.com/pion/webrtc/v3/.eslintrc.json
generated
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"extends": ["standard"]
|
||||
}
|
||||
28
server/vendor/github.com/pion/webrtc/v3/.gitignore
generated
vendored
Normal file
28
server/vendor/github.com/pion/webrtc/v3/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
# SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
### JetBrains IDE ###
|
||||
#####################
|
||||
.idea/
|
||||
|
||||
### Emacs Temporary Files ###
|
||||
#############################
|
||||
*~
|
||||
|
||||
### Folders ###
|
||||
###############
|
||||
bin/
|
||||
vendor/
|
||||
node_modules/
|
||||
|
||||
### Files ###
|
||||
#############
|
||||
*.ivf
|
||||
*.ogg
|
||||
tags
|
||||
cover.out
|
||||
*.sw[poe]
|
||||
*.wasm
|
||||
examples/sfu-ws/cert.pem
|
||||
examples/sfu-ws/key.pem
|
||||
wasm_exec.js
|
||||
137
server/vendor/github.com/pion/webrtc/v3/.golangci.yml
generated
vendored
Normal file
137
server/vendor/github.com/pion/webrtc/v3/.golangci.yml
generated
vendored
Normal file
@@ -0,0 +1,137 @@
|
||||
# SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
linters-settings:
|
||||
govet:
|
||||
check-shadowing: true
|
||||
misspell:
|
||||
locale: US
|
||||
exhaustive:
|
||||
default-signifies-exhaustive: true
|
||||
gomodguard:
|
||||
blocked:
|
||||
modules:
|
||||
- github.com/pkg/errors:
|
||||
recommendations:
|
||||
- errors
|
||||
forbidigo:
|
||||
forbid:
|
||||
- ^fmt.Print(f|ln)?$
|
||||
- ^log.(Panic|Fatal|Print)(f|ln)?$
|
||||
- ^os.Exit$
|
||||
- ^panic$
|
||||
- ^print(ln)?$
|
||||
|
||||
linters:
|
||||
enable:
|
||||
- asciicheck # Simple linter to check that your code does not contain non-ASCII identifiers
|
||||
- bidichk # Checks for dangerous unicode character sequences
|
||||
- bodyclose # checks whether HTTP response body is closed successfully
|
||||
- contextcheck # check the function whether use a non-inherited context
|
||||
- decorder # check declaration order and count of types, constants, variables and functions
|
||||
- depguard # Go linter that checks if package imports are in a list of acceptable packages
|
||||
- dogsled # Checks assignments with too many blank identifiers (e.g. x, _, _, _, := f())
|
||||
- dupl # Tool for code clone detection
|
||||
- durationcheck # check for two durations multiplied together
|
||||
- errcheck # Errcheck is a program for checking for unchecked errors in go programs. These unchecked errors can be critical bugs in some cases
|
||||
- errchkjson # Checks types passed to the json encoding functions. Reports unsupported types and optionally reports occations, where the check for the returned error can be omitted.
|
||||
- errname # Checks that sentinel errors are prefixed with the `Err` and error types are suffixed with the `Error`.
|
||||
- errorlint # errorlint is a linter for that can be used to find code that will cause problems with the error wrapping scheme introduced in Go 1.13.
|
||||
- exhaustive # check exhaustiveness of enum switch statements
|
||||
- exportloopref # checks for pointers to enclosing loop variables
|
||||
- forbidigo # Forbids identifiers
|
||||
- forcetypeassert # finds forced type assertions
|
||||
- gci # Gci control golang package import order and make it always deterministic.
|
||||
- gochecknoglobals # Checks that no globals are present in Go code
|
||||
- gochecknoinits # Checks that no init functions are present in Go code
|
||||
- gocognit # Computes and checks the cognitive complexity of functions
|
||||
- goconst # Finds repeated strings that could be replaced by a constant
|
||||
- gocritic # The most opinionated Go source code linter
|
||||
- godox # Tool for detection of FIXME, TODO and other comment keywords
|
||||
- goerr113 # Golang linter to check the errors handling expressions
|
||||
- gofmt # Gofmt checks whether code was gofmt-ed. By default this tool runs with -s option to check for code simplification
|
||||
- gofumpt # Gofumpt checks whether code was gofumpt-ed.
|
||||
- goheader # Checks is file header matches to pattern
|
||||
- goimports # Goimports does everything that gofmt does. Additionally it checks unused imports
|
||||
- gomoddirectives # Manage the use of 'replace', 'retract', and 'excludes' directives in go.mod.
|
||||
- gomodguard # Allow and block list linter for direct Go module dependencies. This is different from depguard where there are different block types for example version constraints and module recommendations.
|
||||
- goprintffuncname # Checks that printf-like functions are named with `f` at the end
|
||||
- gosec # Inspects source code for security problems
|
||||
- gosimple # Linter for Go source code that specializes in simplifying a code
|
||||
- govet # Vet examines Go source code and reports suspicious constructs, such as Printf calls whose arguments do not align with the format string
|
||||
- grouper # An analyzer to analyze expression groups.
|
||||
- importas # Enforces consistent import aliases
|
||||
- ineffassign # Detects when assignments to existing variables are not used
|
||||
- misspell # Finds commonly misspelled English words in comments
|
||||
- nakedret # Finds naked returns in functions greater than a specified function length
|
||||
- nilerr # Finds the code that returns nil even if it checks that the error is not nil.
|
||||
- nilnil # Checks that there is no simultaneous return of `nil` error and an invalid value.
|
||||
- noctx # noctx finds sending http request without context.Context
|
||||
- predeclared # find code that shadows one of Go's predeclared identifiers
|
||||
- revive # golint replacement, finds style mistakes
|
||||
- staticcheck # Staticcheck is a go vet on steroids, applying a ton of static analysis checks
|
||||
- stylecheck # Stylecheck is a replacement for golint
|
||||
- tagliatelle # Checks the struct tags.
|
||||
- tenv # tenv is analyzer that detects using os.Setenv instead of t.Setenv since Go1.17
|
||||
- tparallel # tparallel detects inappropriate usage of t.Parallel() method in your Go test codes
|
||||
- typecheck # Like the front-end of a Go compiler, parses and type-checks Go code
|
||||
- unconvert # Remove unnecessary type conversions
|
||||
- unparam # Reports unused function parameters
|
||||
- unused # Checks Go code for unused constants, variables, functions and types
|
||||
- wastedassign # wastedassign finds wasted assignment statements
|
||||
- whitespace # Tool for detection of leading and trailing whitespace
|
||||
disable:
|
||||
- containedctx # containedctx is a linter that detects struct contained context.Context field
|
||||
- cyclop # checks function and package cyclomatic complexity
|
||||
- exhaustivestruct # Checks if all struct's fields are initialized
|
||||
- funlen # Tool for detection of long functions
|
||||
- gocyclo # Computes and checks the cyclomatic complexity of functions
|
||||
- godot # Check if comments end in a period
|
||||
- gomnd # An analyzer to detect magic numbers.
|
||||
- ifshort # Checks that your code uses short syntax for if-statements whenever possible
|
||||
- ireturn # Accept Interfaces, Return Concrete Types
|
||||
- lll # Reports long lines
|
||||
- maintidx # maintidx measures the maintainability index of each function.
|
||||
- makezero # Finds slice declarations with non-zero initial length
|
||||
- maligned # Tool to detect Go structs that would take less memory if their fields were sorted
|
||||
- nestif # Reports deeply nested if statements
|
||||
- nlreturn # nlreturn checks for a new line before return and branch statements to increase code clarity
|
||||
- nolintlint # Reports ill-formed or insufficient nolint directives
|
||||
- paralleltest # paralleltest detects missing usage of t.Parallel() method in your Go test
|
||||
- prealloc # Finds slice declarations that could potentially be preallocated
|
||||
- promlinter # Check Prometheus metrics naming via promlint
|
||||
- rowserrcheck # checks whether Err of rows is checked successfully
|
||||
- sqlclosecheck # Checks that sql.Rows and sql.Stmt are closed.
|
||||
- testpackage # linter that makes you use a separate _test package
|
||||
- thelper # thelper detects golang test helpers without t.Helper() call and checks the consistency of test helpers
|
||||
- varnamelen # checks that the length of a variable's name matches its scope
|
||||
- wrapcheck # Checks that errors returned from external packages are wrapped
|
||||
- wsl # Whitespace Linter - Forces you to use empty lines!
|
||||
|
||||
issues:
|
||||
exclude-use-default: false
|
||||
exclude-rules:
|
||||
# Allow complex tests, better to be self contained
|
||||
- path: _test\.go
|
||||
linters:
|
||||
- gocognit
|
||||
- forbidigo
|
||||
|
||||
# Allow complex main function in examples
|
||||
- path: examples
|
||||
text: "of func `main` is high"
|
||||
linters:
|
||||
- gocognit
|
||||
|
||||
# Allow forbidden identifiers in examples
|
||||
- path: examples
|
||||
linters:
|
||||
- forbidigo
|
||||
|
||||
# Allow forbidden identifiers in CLI commands
|
||||
- path: cmd
|
||||
linters:
|
||||
- forbidigo
|
||||
|
||||
run:
|
||||
skip-dirs-use-default: false
|
||||
5
server/vendor/github.com/pion/webrtc/v3/.goreleaser.yml
generated
vendored
Normal file
5
server/vendor/github.com/pion/webrtc/v3/.goreleaser.yml
generated
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
# SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
builds:
|
||||
- skip: true
|
||||
229
server/vendor/github.com/pion/webrtc/v3/AUTHORS.txt
generated
vendored
Normal file
229
server/vendor/github.com/pion/webrtc/v3/AUTHORS.txt
generated
vendored
Normal file
@@ -0,0 +1,229 @@
|
||||
# Thank you to everyone that made Pion possible. If you are interested in contributing
|
||||
# we would love to have you https://github.com/pion/webrtc/wiki/Contributing
|
||||
#
|
||||
# This file is auto generated, using git to list all individuals contributors.
|
||||
# see https://github.com/pion/.goassets/blob/master/scripts/generate-authors.sh for the scripting
|
||||
a-wing <1@233.email>
|
||||
Aaron Boushley <boushley@pretzelaux.com>
|
||||
Aaron France <aaron.l.france@gmail.com>
|
||||
Adam Kiss <masterada@gmail.com>
|
||||
Aditya Kumar <k.aditya00@gmail.com>
|
||||
Adrian Cable <adrian.cable@gmail.com>
|
||||
adwpc <adwpc@hotmail.com>
|
||||
aggresss <aggresss@163.com>
|
||||
akil <akil@everycrave.me>
|
||||
Aleksandr Alekseev <alekseev-dev@yandex-team.ru>
|
||||
Aleksandr Razumov <ar@gortc.io>
|
||||
aler9 <46489434+aler9@users.noreply.github.com>
|
||||
Alex Browne <stephenalexbrowne@gmail.com>
|
||||
Alex Harford <harford@gmail.com>
|
||||
Alexey Khit <alexey.khit@gmail.com>
|
||||
AlexWoo(武杰) <wj19840501@gmail.com>
|
||||
Ali Error <alipadida@live.com>
|
||||
Andrew N. Shalaev <isqad88@gmail.com>
|
||||
Antoine Baché <antoine.bache@epitech.eu>
|
||||
Antoine Baché <antoine@tenten.app>
|
||||
Anton <leonardspbox@gmail.com>
|
||||
Artur Shellunts <shellunts.artur@gmail.com>
|
||||
Assad Obaid <assad@lap5cg901003r.se.axis.com>
|
||||
Ato Araki <ato.araki@gmail.com>
|
||||
Atsushi Watanabe <atsushi.w@ieee.org>
|
||||
backkem <mail@backkem.me>
|
||||
baiyufei <baiyufei@outlook.com>
|
||||
Bao Nguyen <bao@n4n.dev>
|
||||
Ben Weitzman <benweitzman@gmail.com>
|
||||
Benny Daon <benny@tuzig.com>
|
||||
bkim <bruce.kim.it@gmail.com>
|
||||
Bo Shi <boshi@mural.co>
|
||||
boks1971 <raja.gobi@tutanota.com>
|
||||
Brendan Rius <brendan.rius@gmail.com>
|
||||
brian <brian@cyalive.com>
|
||||
Bryan Phelps <bryan@coder.com>
|
||||
Cameron Elliott <cameron-elliott@users.noreply.github.com>
|
||||
Cecylia Bocovich <cohosh@torproject.org>
|
||||
Cedric Fung <cedric@vec.io>
|
||||
cgojin <gongjin21@hotmail.com>
|
||||
Chad Retz <chad.retz@stackpath.com>
|
||||
chenkaiC4 <chenkaic4@gmail.com>
|
||||
Chinmay Kousik <chinmaykousik1@gmail.com>
|
||||
Chris Hiszpanski <chris@hiszpanski.name>
|
||||
Christopher Fry <chris.fry@getcruise.com>
|
||||
Clayton McCray <cpmdmccray@gmail.com>
|
||||
cnderrauber <zengjie9004@gmail.com>
|
||||
cyannuk <cyannuk@issart.com>
|
||||
Daniele Sluijters <daenney@users.noreply.github.com>
|
||||
David <dz@livekit.io>
|
||||
David Hamilton <davidhamiltron@gmail.com>
|
||||
David Zhao <david@davidzhao.com>
|
||||
David Zhao <dz@livekit.io>
|
||||
david.s <david.s@hpcnt.com>
|
||||
Dean Sheather <dean@coder.com>
|
||||
decanus <7621705+decanus@users.noreply.github.com>
|
||||
Denis <Hixon10@yandex.ru>
|
||||
digitalix <digitalix4@gmail.com>
|
||||
do-hyung-kim <do_hyung.kim@navercorp.com>
|
||||
donotanswer <viktor.ferter@doclerholding.com>
|
||||
earle <aguilar@dm.ai>
|
||||
Egon Elbre <egonelbre@gmail.com>
|
||||
Eric Daniels <eric@erdaniels.com>
|
||||
Eric Fontaine <ericfontainejazz@gmail.com>
|
||||
Evan Sonderegger <evan@rpy.xyz>
|
||||
feixiao <feixiao2020@sina.com>
|
||||
Forest Johnson <forest.n.johnson@gmail.com>
|
||||
frank <frank@huzhedeMacBook-Pro.local>
|
||||
funvit <funvit@gmail.com>
|
||||
Gabor Pongracz <gabor.pongracz@proemergotech.com>
|
||||
Gareth Hayes <gareth.hayes@gmail.com>
|
||||
Guilherme <gqgs@protonmail.com>
|
||||
Guillaume Denis <gdenispro@gmail.com>
|
||||
Hanjun Kim <hallazzang@gmail.com>
|
||||
Hans Gylling <hans.gylling@axis.com>
|
||||
Hao <shell0fly@gmail.com>
|
||||
Hendrik Hofstadt <hendrik@nexantic.com>
|
||||
Henry <cryptix@riseup.net>
|
||||
Hongchao Ma <hongchao.ma@synaptop.com>
|
||||
Hugo Arregui <hugo.arregui@gmail.com>
|
||||
Hugo Arregui <hugo@decentraland.org>
|
||||
Ilya Mayorov <faroyam1@yandex.ru>
|
||||
imalic3 <manovisut.ktp@gmail.com>
|
||||
Ivan Egorov <vany.egorov@gmail.com>
|
||||
JacobZwang <59858341+JacobZwang@users.noreply.github.com>
|
||||
Jake B <doogie1012@gmail.com>
|
||||
Jamie Good <jamie.good@gmail.com>
|
||||
Jason <jabrady42@gmail.com>
|
||||
Jeff Tchang <jeff.tchang@gmail.com>
|
||||
jeremija
|
||||
Jerko Steiner <jerko.steiner@gmail.com>
|
||||
Jerry Tao <taojay315@gmail.com>
|
||||
jinleileiking <jinleileiking@gmail.com>
|
||||
John Berthels <john.berthels@gmail.com>
|
||||
John Bradley <jrb@turrettech.com>
|
||||
John Selbie <jselbie@gmail.com>
|
||||
JooYoung <qkdlql@naver.com>
|
||||
Jorropo <jorropo.pgm@gmail.com>
|
||||
Josh Bleecher Snyder <josharian@gmail.com>
|
||||
juberti <juberti@alphaexplorationco.com>
|
||||
Juliusz Chroboczek <jch@irif.fr>
|
||||
Justin Okamoto <jdokamoto@gmail.com>
|
||||
Justin Okamoto <justmoto@amazon.com>
|
||||
Kevin <kevmo314@gmail.com>
|
||||
Kevin Staunton-Lambert <kevin.staunton-lambert@metacdn.com>
|
||||
Kevin Wang <kevmo314@gmail.com>
|
||||
Konstantin Chugalinskiy <kchugalinskiy@yandex.ru>
|
||||
Konstantin Itskov <konstantin.itskov@kovits.com>
|
||||
krishna chiatanya <kittuov@gmail.com>
|
||||
Kunal <tekunalogy@gmail.com>
|
||||
Kunal Singh <kunalsin9h@gmail.com>
|
||||
Kuzmin Vladimir <vova-kyzmin@yandex.ru>
|
||||
lawl <github@dumbinter.net>
|
||||
Len <len@hpcnt.com>
|
||||
Leslie Wang <wqyuwss@gmail.com>
|
||||
lisa yan <lisa.yan@nokia-sbell.com>
|
||||
Lukas Herman <lherman.cs@gmail.com>
|
||||
Luke <luke@street.dev>
|
||||
Luke Curley <kixelated@gmail.com>
|
||||
Luke S <luke@street.dev>
|
||||
Magnus Wahlstrand <magnus.wahlstrand@gmail.com>
|
||||
Manish <itzmanish108@gmail.com>
|
||||
Markus Tzoe <chou.marcus@gmail.com>
|
||||
Marouane <6729798+nindolabs@users.noreply.github.com>
|
||||
Marouane <marouane@gamestream.biz>
|
||||
Masahiro Nakamura <13937915+tsuu32@users.noreply.github.com>
|
||||
Mathis Engelbart <mathis.engelbart@gmail.com>
|
||||
Max Hawkins <maxhawkins@gmail.com>
|
||||
mchlrhw <4028654+mchlrhw@users.noreply.github.com>
|
||||
Michael MacDonald <github@macdonald.cx>
|
||||
Michael MacDonald <mike.macdonald@savantsystems.com>
|
||||
Michiel De Backker <38858977+backkem@users.noreply.github.com>
|
||||
Mike Coleman <mc@fivebats.com>
|
||||
Mindgamesnl <matsmoolhuizen@gmail.com>
|
||||
mission-liao <missionaryliao@gmail.com>
|
||||
mohammadne <mohammadne@mail.ru>
|
||||
mr-shitij <21.shitijagrawal@gmail.com>
|
||||
mxmCherry <mxmCherry@gmail.com>
|
||||
Nam V. Do <vannam12a7@gmail.com>
|
||||
Nick Mykins <nmykins@digitalocean.com>
|
||||
Nicolas Menard <nicolas.p.menard@gmail.com>
|
||||
nindolabs <6729798+nindolabs@users.noreply.github.com>
|
||||
Norman Rasmussen <norman@rasmussen.co.za>
|
||||
notedit <notedit@gmail.com>
|
||||
o0olele <liangruipeng@ztgame.com>
|
||||
obasajujoshua31 <obasajujoshua31@gmail.com>
|
||||
Oleg Kovalov <iamolegkovalov@gmail.com>
|
||||
opennota <opennota@gmail.com>
|
||||
OrlandoCo <luisorlando.co@gmail.com>
|
||||
Pascal Benoit <pascal.benoit@acemediastools.fr>
|
||||
pascal-ace <47424881+pascal-ace@users.noreply.github.com>
|
||||
Patrice Ferlet <patrice.ferlet@smile.fr>
|
||||
Patrick Lange <mail@langep.com>
|
||||
Patryk Rogalski <digitalix4@gmail.com>
|
||||
Pieere Pi <pihuibin@hotmail.com>
|
||||
Pouget-Abadie <thomas.pougetabadie@gmail.com>
|
||||
q191201771 <191201771@qq.com>
|
||||
Quentin Renard <contact@asticode.com>
|
||||
Rafael Viscarra <rafael@viscarra.dev>
|
||||
rahulnakre <rahulnakre@gmail.com>
|
||||
Raphael Randschau <nicolai86@me.com>
|
||||
Raphael Randschau <nicolai86@users.noreply.github.com>
|
||||
Reese <3253971+figadore@users.noreply.github.com>
|
||||
rob <me@iamcalledrob.com>
|
||||
rob-deutsch <robzyb+altgithub@gmail.com>
|
||||
Robert Eperjesi <eperjesi@uber.com>
|
||||
Robin Raymond <robin-raymond@users.noreply.github.com>
|
||||
Roman Romanenko <roman.romanenko@aliexpress.ru>
|
||||
Roman Romanenko <romandafe94@gmail.com>
|
||||
ronan <ronan.jezequel@gmail.com>
|
||||
Ryan Shumate <Ryan.Shumate@garmin.com>
|
||||
salmān aljammāz <s@aljmz.com>
|
||||
Sam Lancia <sam@vaion.com>
|
||||
Sean <sean@pion.ly>
|
||||
Sean DuBois <duboisea@justin.tv>
|
||||
Sean DuBois <duboisea@twitch.tv>
|
||||
Sean DuBois <seaduboi@amazon.com>
|
||||
Sean DuBois <sean@pion.ly>
|
||||
Sean DuBois <sean@siobud.com>
|
||||
Sean DuBois <sean_dubois@apple.com>
|
||||
Sean Knight <git@seanknight.com>
|
||||
Sebastian Waisbrot <seppo0010@gmail.com>
|
||||
Sidney San Martín <sidney@s4y.us>
|
||||
Simon Cousineau <simon.cousineau@verkada.com>
|
||||
Simon Eisenmann <simon@longsleep.org>
|
||||
simonacca-fotokite <47634061+simonacca-fotokite@users.noreply.github.com>
|
||||
Simone Gotti <simone.gotti@gmail.com>
|
||||
Slugalisk <slugalisk@gmail.com>
|
||||
Somers Matthews <somersbmatthews@gmail.com>
|
||||
soolaugust <soolaugust@gmail.com>
|
||||
spaceCh1mp <drimboat@gmail.com>
|
||||
Steffen Vogel <post@steffenvogel.de>
|
||||
stephanrotolante <stephanrotolante@gmail.com>
|
||||
streamer45 <cstcld91@gmail.com>
|
||||
Suhas Gaddam <suhas.g.2011@gmail.com>
|
||||
Suzuki Takeo <takeo@stko.info>
|
||||
sylba2050 <masataka.hisasue@optim.co.jp>
|
||||
Tarrence van As <tarrencev@users.noreply.github.com>
|
||||
tarrencev <tarrence13@gmail.com>
|
||||
Thomas Miller <tmiv74@gmail.com>
|
||||
Tobias Fridén <tobias.friden@gmail.com>
|
||||
Tomek <tomek@pop-os.localdomain>
|
||||
treyhakanson <treyhakanson@gmail.com>
|
||||
Tristan Matthews <tmatth@videolan.org>
|
||||
Twometer <twometer@outlook.de>
|
||||
Vicken Simonian <vsimon@gmail.com>
|
||||
wattanakorn495 <wattanakorn.i@ku.th>
|
||||
Will Forcey <wsforc3y@gmail.com>
|
||||
Will Watson <william.a.watson@gmail.com>
|
||||
WofWca <wofwca@protonmail.com>
|
||||
Woodrow Douglass <wdouglass@carnegierobotics.com>
|
||||
xsbchen <xsbchen@qq.com>
|
||||
Yoon SeungYong <simon.y@hpcnt.com>
|
||||
Yuki Igarashi <me@bonprosoft.com>
|
||||
yusuke <yusuke.m99@gmail.com>
|
||||
Yutaka Takeda <yt0916@gmail.com>
|
||||
ZHENK <chengzhenyang@gmail.com>
|
||||
zhngs <zhangshuo19991204@163.com>
|
||||
zigazeljko <ziga.zeljko@gmail.com>
|
||||
Štefan Uram <SterverSVK@users.noreply.github.com>
|
||||
박종훈 <jonghun.park.194@gmail.com>
|
||||
|
||||
# List of contributors not appearing in Git history
|
||||
|
||||
43
server/vendor/github.com/pion/webrtc/v3/DESIGN.md
generated
vendored
Normal file
43
server/vendor/github.com/pion/webrtc/v3/DESIGN.md
generated
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
<h1 align="center">
|
||||
Design
|
||||
</h1>
|
||||
WebRTC is a powerful, but complicated technology you can build amazing things with, it comes with a steep learning curve though.
|
||||
Using WebRTC in the browser is easy, but outside the browser is more of a challenge. There are multiple libraries, and they all have
|
||||
varying levels of quality. Most are also difficult to build, and depend on libraries that aren't available in repos or portable.
|
||||
|
||||
Pion WebRTC aims to solve all that! Built in native Go you should be able to send and receive media and text from anywhere with minimal headache.
|
||||
These are the design principals that drive Pion WebRTC and hopefully convince you it is worth a try.
|
||||
|
||||
### Portable
|
||||
Pion WebRTC is written in Go and extremely portable. Anywhere Golang runs, Pion WebRTC should work as well! Instead of dealing with complicated
|
||||
cross-compiling of multiple libraries, you now can run anywhere with one `go build`
|
||||
|
||||
### Flexible
|
||||
When possible we leave all decisions to the user. When choice is possible (like what logging library is used) we defer to the developer.
|
||||
|
||||
### Simple API
|
||||
If you know how to use WebRTC in your browser, you know how to use Pion WebRTC.
|
||||
We try our best just to duplicate the Javascript API, so your code can look the same everywhere.
|
||||
|
||||
If this is your first time using WebRTC, don't worry! We have multiple [examples](https://github.com/pion/webrtc/tree/master/examples) and [GoDoc](https://pkg.go.dev/github.com/pion/webrtc/v3)
|
||||
|
||||
### Bring your own media
|
||||
Pion WebRTC doesn't make any assumptions about where your audio, video or text come from. You can use FFmpeg, GStreamer, MLT or just serve a video file.
|
||||
This library only serves to transport, not create media.
|
||||
|
||||
### Safe
|
||||
Golang provides a great foundation to build safe network services.
|
||||
Especially when running a networked service that is highly concurrent bugs can be devastating.
|
||||
|
||||
### Readable
|
||||
If code comes from an RFC we try to make sure everything is commented with a link to the spec.
|
||||
This makes learning and debugging easier, this WebRTC library was written to also serve as a guide for others.
|
||||
|
||||
### Tested
|
||||
Every commit is tested via travis-ci Go provides fantastic facilities for testing, and more will be added as time goes on.
|
||||
|
||||
### Shared libraries
|
||||
Every Pion project is built using shared libraries, allowing others to review and reuse our libraries.
|
||||
|
||||
### Community
|
||||
The most important part of Pion is the community. This projects only exist because of individual contributions. We aim to be radically open and do everything we can to support those that make Pion possible.
|
||||
9
server/vendor/github.com/pion/webrtc/v3/LICENSE
generated
vendored
Normal file
9
server/vendor/github.com/pion/webrtc/v3/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2023 The Pion community <https://pion.ly>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
129
server/vendor/github.com/pion/webrtc/v3/README.md
generated
vendored
Normal file
129
server/vendor/github.com/pion/webrtc/v3/README.md
generated
vendored
Normal file
@@ -0,0 +1,129 @@
|
||||
<h1 align="center">
|
||||
<a href="https://pion.ly"><img src="./.github/pion-gopher-webrtc.png" alt="Pion WebRTC" height="250px"></a>
|
||||
<br>
|
||||
Pion WebRTC
|
||||
<br>
|
||||
</h1>
|
||||
<h4 align="center">A pure Go implementation of the WebRTC API</h4>
|
||||
<p align="center">
|
||||
<a href="https://pion.ly"><img src="https://img.shields.io/badge/pion-webrtc-gray.svg?longCache=true&colorB=brightgreen" alt="Pion WebRTC"></a>
|
||||
<a href="https://sourcegraph.com/github.com/pion/webrtc?badge"><img src="https://sourcegraph.com/github.com/pion/webrtc/-/badge.svg" alt="Sourcegraph Widget"></a>
|
||||
<a href="https://pion.ly/slack"><img src="https://img.shields.io/badge/join-us%20on%20slack-gray.svg?longCache=true&logo=slack&colorB=brightgreen" alt="Slack Widget"></a>
|
||||
<a href="https://twitter.com/_pion?ref_src=twsrc%5Etfw"><img src="https://img.shields.io/twitter/url.svg?label=Follow%20%40_pion&style=social&url=https%3A%2F%2Ftwitter.com%2F_pion" alt="Twitter Widget"></a>
|
||||
<a href="https://github.com/pion/awesome-pion" alt="Awesome Pion"><img src="https://cdn.rawgit.com/sindresorhus/awesome/d7305f38d29fed78fa85652e3a63e154dd8e8829/media/badge.svg"></a>
|
||||
<br>
|
||||
<img alt="GitHub Workflow Status" src="https://img.shields.io/github/actions/workflow/status/pion/webrtc/test.yaml">
|
||||
<a href="https://pkg.go.dev/github.com/pion/webrtc/v3"><img src="https://pkg.go.dev/badge/github.com/pion/webrtc/v3.svg" alt="Go Reference"></a>
|
||||
<a href="https://codecov.io/gh/pion/webrtc"><img src="https://codecov.io/gh/pion/webrtc/branch/master/graph/badge.svg" alt="Coverage Status"></a>
|
||||
<a href="https://goreportcard.com/report/github.com/pion/webrtc/v3"><img src="https://goreportcard.com/badge/github.com/pion/webrtc/v3" alt="Go Report Card"></a>
|
||||
<a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="License: MIT"></a>
|
||||
</p>
|
||||
<br>
|
||||
|
||||
### Usage
|
||||
[Go Modules](https://blog.golang.org/using-go-modules) are mandatory for using Pion WebRTC. So make sure you set `export GO111MODULE=on`, and explicitly specify `/v2` or `/v3` when importing.
|
||||
|
||||
|
||||
**[example applications](examples/README.md)** contains code samples of common things people build with Pion WebRTC.
|
||||
|
||||
**[example-webrtc-applications](https://github.com/pion/example-webrtc-applications)** contains more full featured examples that use 3rd party libraries.
|
||||
|
||||
**[awesome-pion](https://github.com/pion/awesome-pion)** contains projects that have used Pion, and serve as real world examples of usage.
|
||||
|
||||
**[GoDoc](https://pkg.go.dev/github.com/pion/webrtc/v3)** is an auto generated API reference. All our Public APIs are commented.
|
||||
|
||||
**[FAQ](https://github.com/pion/webrtc/wiki/FAQ)** has answers to common questions. If you have a question not covered please ask in [Slack](https://pion.ly/slack) we are always looking to expand it.
|
||||
|
||||
Now go build something awesome! Here are some **ideas** to get your creative juices flowing:
|
||||
* Send a video file to multiple browser in real time for perfectly synchronized movie watching.
|
||||
* Send a webcam on an embedded device to your browser with no additional server required!
|
||||
* Securely send data between two servers, without using pub/sub.
|
||||
* Record your webcam and do special effects server side.
|
||||
* Build a conferencing application that processes audio/video and make decisions off of it.
|
||||
* Remotely control a robots and stream its cameras in realtime.
|
||||
|
||||
### Want to learn more about WebRTC?
|
||||
Join our [Office Hours](https://github.com/pion/webrtc/wiki/OfficeHours). Come hang out, ask questions, get help debugging and
|
||||
hear about the cool things being built with WebRTC. We also start every meeting with basic project planning.
|
||||
|
||||
Check out [WebRTC for the Curious](https://webrtcforthecurious.com). A book about WebRTC in depth, not just about the APIs.
|
||||
Learn the full details of ICE, SCTP, DTLS, SRTP, and how they work together to make up the WebRTC stack.
|
||||
|
||||
This is also a great resource if you are trying to debug. Learn the tools of the trade and how to approach WebRTC issues.
|
||||
|
||||
This book is vendor agnostic and will not have any Pion specific information.
|
||||
|
||||
### Features
|
||||
#### PeerConnection API
|
||||
* Go implementation of [webrtc-pc](https://w3c.github.io/webrtc-pc/) and [webrtc-stats](https://www.w3.org/TR/webrtc-stats/)
|
||||
* DataChannels
|
||||
* Send/Receive audio and video
|
||||
* Renegotiation
|
||||
* Plan-B and Unified Plan
|
||||
* [SettingEngine](https://pkg.go.dev/github.com/pion/webrtc/v3#SettingEngine) for Pion specific extensions
|
||||
|
||||
|
||||
#### Connectivity
|
||||
* Full ICE Agent
|
||||
* ICE Restart
|
||||
* Trickle ICE
|
||||
* STUN
|
||||
* TURN (UDP, TCP, DTLS and TLS)
|
||||
* mDNS candidates
|
||||
|
||||
#### DataChannels
|
||||
* Ordered/Unordered
|
||||
* Lossy/Lossless
|
||||
|
||||
#### Media
|
||||
* API with direct RTP/RTCP access
|
||||
* Opus, PCM, H264, VP8 and VP9 packetizer
|
||||
* API also allows developer to pass their own packetizer
|
||||
* IVF, Ogg, H264 and Matroska provided for easy sending and saving
|
||||
* [getUserMedia](https://github.com/pion/mediadevices) implementation (Requires Cgo)
|
||||
* Easy integration with x264, libvpx, GStreamer and ffmpeg.
|
||||
* [Simulcast](https://github.com/pion/webrtc/tree/master/examples/simulcast)
|
||||
* [SVC](https://github.com/pion/rtp/blob/master/codecs/vp9_packet.go#L138)
|
||||
* [NACK](https://github.com/pion/interceptor/pull/4)
|
||||
* [Sender/Receiver Reports](https://github.com/pion/interceptor/tree/master/pkg/report)
|
||||
* [Transport Wide Congestion Control Feedback](https://github.com/pion/interceptor/tree/master/pkg/twcc)
|
||||
* [Bandwidth Estimation](https://github.com/pion/webrtc/tree/master/examples/bandwidth-estimation-from-disk)
|
||||
|
||||
#### Security
|
||||
* TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 and TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA for DTLS v1.2
|
||||
* SRTP_AEAD_AES_256_GCM and SRTP_AES128_CM_HMAC_SHA1_80 for SRTP
|
||||
* Hardware acceleration available for GCM suites
|
||||
|
||||
#### Pure Go
|
||||
* No Cgo usage
|
||||
* Wide platform support
|
||||
* Windows, macOS, Linux, FreeBSD
|
||||
* iOS, Android
|
||||
* [WASM](https://github.com/pion/webrtc/wiki/WebAssembly-Development-and-Testing) see [examples](examples/README.md#webassembly)
|
||||
* 386, amd64, arm, mips, ppc64
|
||||
* Easy to build *Numbers generated on Intel(R) Core(TM) i5-2520M CPU @ 2.50GHz*
|
||||
* **Time to build examples/play-from-disk** - 0.66s user 0.20s system 306% cpu 0.279 total
|
||||
* **Time to run entire test suite** - 25.60s user 9.40s system 45% cpu 1:16.69 total
|
||||
* Tools to measure performance [provided](https://github.com/pion/rtsp-bench)
|
||||
|
||||
### Roadmap
|
||||
The library is in active development, please refer to the [roadmap](https://github.com/pion/webrtc/issues/9) to track our major milestones.
|
||||
We also maintain a list of [Big Ideas](https://github.com/pion/webrtc/wiki/Big-Ideas) these are things we want to build but don't have a clear plan or the resources yet.
|
||||
If you are looking to get involved this is a great place to get started! We would also love to hear your ideas! Even if you can't implement it yourself, it could inspire others.
|
||||
|
||||
### Sponsoring
|
||||
Work on Pion's congestion control and bandwidth estimation was funded through the [User-Operated Internet](https://nlnet.nl/useroperated/) fund, a fund established by [NLnet](https://nlnet.nl/) made possible by financial support from the [PKT Community](https://pkt.cash/)/[The Network Steward](https://pkt.cash/network-steward) and stichting [Technology Commons Trust](https://technologycommons.org/).
|
||||
|
||||
### Community
|
||||
Pion has an active community on the [Slack](https://pion.ly/slack).
|
||||
|
||||
Follow the [Pion Twitter](https://twitter.com/_pion) for project updates and important WebRTC news.
|
||||
|
||||
We are always looking to support **your projects**. Please reach out if you have something to build!
|
||||
If you need commercial support or don't want to use public methods you can contact us at [team@pion.ly](mailto:team@pion.ly)
|
||||
|
||||
### Contributing
|
||||
Check out the [contributing wiki](https://github.com/pion/webrtc/wiki/Contributing) to join the group of amazing people making this project possible: [AUTHORS.txt](./AUTHORS.txt)
|
||||
|
||||
### License
|
||||
MIT License - see [LICENSE](LICENSE) for full text
|
||||
77
server/vendor/github.com/pion/webrtc/v3/api.go
generated
vendored
Normal file
77
server/vendor/github.com/pion/webrtc/v3/api.go
generated
vendored
Normal file
@@ -0,0 +1,77 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
//go:build !js
|
||||
// +build !js
|
||||
|
||||
package webrtc
|
||||
|
||||
import (
|
||||
"github.com/pion/interceptor"
|
||||
"github.com/pion/logging"
|
||||
)
|
||||
|
||||
// API allows configuration of a PeerConnection
|
||||
// with APIs that are available in the standard. This
|
||||
// lets you set custom behavior via the SettingEngine, configure
|
||||
// codecs via the MediaEngine and define custom media behaviors via
|
||||
// Interceptors.
|
||||
type API struct {
|
||||
settingEngine *SettingEngine
|
||||
mediaEngine *MediaEngine
|
||||
interceptorRegistry *interceptor.Registry
|
||||
|
||||
interceptor interceptor.Interceptor // Generated per PeerConnection
|
||||
}
|
||||
|
||||
// NewAPI Creates a new API object for keeping semi-global settings to WebRTC objects
|
||||
func NewAPI(options ...func(*API)) *API {
|
||||
a := &API{
|
||||
interceptor: &interceptor.NoOp{},
|
||||
settingEngine: &SettingEngine{},
|
||||
mediaEngine: &MediaEngine{},
|
||||
interceptorRegistry: &interceptor.Registry{},
|
||||
}
|
||||
|
||||
for _, o := range options {
|
||||
o(a)
|
||||
}
|
||||
|
||||
if a.settingEngine.LoggerFactory == nil {
|
||||
a.settingEngine.LoggerFactory = logging.NewDefaultLoggerFactory()
|
||||
}
|
||||
|
||||
return a
|
||||
}
|
||||
|
||||
// WithMediaEngine allows providing a MediaEngine to the API.
|
||||
// Settings can be changed after passing the engine to an API.
|
||||
// When a PeerConnection is created the MediaEngine is copied
|
||||
// and no more changes can be made.
|
||||
func WithMediaEngine(m *MediaEngine) func(a *API) {
|
||||
return func(a *API) {
|
||||
a.mediaEngine = m
|
||||
if a.mediaEngine == nil {
|
||||
a.mediaEngine = &MediaEngine{}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// WithSettingEngine allows providing a SettingEngine to the API.
|
||||
// Settings should not be changed after passing the engine to an API.
|
||||
func WithSettingEngine(s SettingEngine) func(a *API) {
|
||||
return func(a *API) {
|
||||
a.settingEngine = &s
|
||||
}
|
||||
}
|
||||
|
||||
// WithInterceptorRegistry allows providing Interceptors to the API.
|
||||
// Settings should not be changed after passing the registry to an API.
|
||||
func WithInterceptorRegistry(ir *interceptor.Registry) func(a *API) {
|
||||
return func(a *API) {
|
||||
a.interceptorRegistry = ir
|
||||
if a.interceptorRegistry == nil {
|
||||
a.interceptorRegistry = &interceptor.Registry{}
|
||||
}
|
||||
}
|
||||
}
|
||||
35
server/vendor/github.com/pion/webrtc/v3/api_js.go
generated
vendored
Normal file
35
server/vendor/github.com/pion/webrtc/v3/api_js.go
generated
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
//go:build js && wasm
|
||||
// +build js,wasm
|
||||
|
||||
package webrtc
|
||||
|
||||
// API bundles the global funcions of the WebRTC and ORTC API.
|
||||
type API struct {
|
||||
settingEngine *SettingEngine
|
||||
}
|
||||
|
||||
// NewAPI Creates a new API object for keeping semi-global settings to WebRTC objects
|
||||
func NewAPI(options ...func(*API)) *API {
|
||||
a := &API{}
|
||||
|
||||
for _, o := range options {
|
||||
o(a)
|
||||
}
|
||||
|
||||
if a.settingEngine == nil {
|
||||
a.settingEngine = &SettingEngine{}
|
||||
}
|
||||
|
||||
return a
|
||||
}
|
||||
|
||||
// WithSettingEngine allows providing a SettingEngine to the API.
|
||||
// Settings should not be changed after passing the engine to an API.
|
||||
func WithSettingEngine(s SettingEngine) func(a *API) {
|
||||
return func(a *API) {
|
||||
a.settingEngine = &s
|
||||
}
|
||||
}
|
||||
31
server/vendor/github.com/pion/webrtc/v3/atomicbool.go
generated
vendored
Normal file
31
server/vendor/github.com/pion/webrtc/v3/atomicbool.go
generated
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package webrtc
|
||||
|
||||
import "sync/atomic"
|
||||
|
||||
type atomicBool struct {
|
||||
val int32
|
||||
}
|
||||
|
||||
func (b *atomicBool) set(value bool) { // nolint: unparam
|
||||
var i int32
|
||||
if value {
|
||||
i = 1
|
||||
}
|
||||
|
||||
atomic.StoreInt32(&(b.val), i)
|
||||
}
|
||||
|
||||
func (b *atomicBool) get() bool {
|
||||
return atomic.LoadInt32(&(b.val)) != 0
|
||||
}
|
||||
|
||||
func (b *atomicBool) swap(value bool) bool {
|
||||
var i int32
|
||||
if value {
|
||||
i = 1
|
||||
}
|
||||
return atomic.SwapInt32(&(b.val), i) != 0
|
||||
}
|
||||
81
server/vendor/github.com/pion/webrtc/v3/bundlepolicy.go
generated
vendored
Normal file
81
server/vendor/github.com/pion/webrtc/v3/bundlepolicy.go
generated
vendored
Normal file
@@ -0,0 +1,81 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package webrtc
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
// BundlePolicy affects which media tracks are negotiated if the remote
|
||||
// endpoint is not bundle-aware, and what ICE candidates are gathered. If the
|
||||
// remote endpoint is bundle-aware, all media tracks and data channels are
|
||||
// bundled onto the same transport.
|
||||
type BundlePolicy int
|
||||
|
||||
const (
|
||||
// BundlePolicyBalanced indicates to gather ICE candidates for each
|
||||
// media type in use (audio, video, and data). If the remote endpoint is
|
||||
// not bundle-aware, negotiate only one audio and video track on separate
|
||||
// transports.
|
||||
BundlePolicyBalanced BundlePolicy = iota + 1
|
||||
|
||||
// BundlePolicyMaxCompat indicates to gather ICE candidates for each
|
||||
// track. If the remote endpoint is not bundle-aware, negotiate all media
|
||||
// tracks on separate transports.
|
||||
BundlePolicyMaxCompat
|
||||
|
||||
// BundlePolicyMaxBundle indicates to gather ICE candidates for only
|
||||
// one track. If the remote endpoint is not bundle-aware, negotiate only
|
||||
// one media track.
|
||||
BundlePolicyMaxBundle
|
||||
)
|
||||
|
||||
// This is done this way because of a linter.
|
||||
const (
|
||||
bundlePolicyBalancedStr = "balanced"
|
||||
bundlePolicyMaxCompatStr = "max-compat"
|
||||
bundlePolicyMaxBundleStr = "max-bundle"
|
||||
)
|
||||
|
||||
func newBundlePolicy(raw string) BundlePolicy {
|
||||
switch raw {
|
||||
case bundlePolicyBalancedStr:
|
||||
return BundlePolicyBalanced
|
||||
case bundlePolicyMaxCompatStr:
|
||||
return BundlePolicyMaxCompat
|
||||
case bundlePolicyMaxBundleStr:
|
||||
return BundlePolicyMaxBundle
|
||||
default:
|
||||
return BundlePolicy(Unknown)
|
||||
}
|
||||
}
|
||||
|
||||
func (t BundlePolicy) String() string {
|
||||
switch t {
|
||||
case BundlePolicyBalanced:
|
||||
return bundlePolicyBalancedStr
|
||||
case BundlePolicyMaxCompat:
|
||||
return bundlePolicyMaxCompatStr
|
||||
case BundlePolicyMaxBundle:
|
||||
return bundlePolicyMaxBundleStr
|
||||
default:
|
||||
return ErrUnknownType.Error()
|
||||
}
|
||||
}
|
||||
|
||||
// UnmarshalJSON parses the JSON-encoded data and stores the result
|
||||
func (t *BundlePolicy) UnmarshalJSON(b []byte) error {
|
||||
var val string
|
||||
if err := json.Unmarshal(b, &val); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*t = newBundlePolicy(val)
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalJSON returns the JSON encoding
|
||||
func (t BundlePolicy) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(t.String())
|
||||
}
|
||||
234
server/vendor/github.com/pion/webrtc/v3/certificate.go
generated
vendored
Normal file
234
server/vendor/github.com/pion/webrtc/v3/certificate.go
generated
vendored
Normal file
@@ -0,0 +1,234 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
//go:build !js
|
||||
// +build !js
|
||||
|
||||
package webrtc
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/base64"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/pion/dtls/v2/pkg/crypto/fingerprint"
|
||||
"github.com/pion/webrtc/v3/pkg/rtcerr"
|
||||
)
|
||||
|
||||
// Certificate represents a x509Cert used to authenticate WebRTC communications.
|
||||
type Certificate struct {
|
||||
privateKey crypto.PrivateKey
|
||||
x509Cert *x509.Certificate
|
||||
statsID string
|
||||
}
|
||||
|
||||
// NewCertificate generates a new x509 compliant Certificate to be used
|
||||
// by DTLS for encrypting data sent over the wire. This method differs from
|
||||
// GenerateCertificate by allowing to specify a template x509.Certificate to
|
||||
// be used in order to define certificate parameters.
|
||||
func NewCertificate(key crypto.PrivateKey, tpl x509.Certificate) (*Certificate, error) {
|
||||
var err error
|
||||
var certDER []byte
|
||||
switch sk := key.(type) {
|
||||
case *rsa.PrivateKey:
|
||||
pk := sk.Public()
|
||||
tpl.SignatureAlgorithm = x509.SHA256WithRSA
|
||||
certDER, err = x509.CreateCertificate(rand.Reader, &tpl, &tpl, pk, sk)
|
||||
if err != nil {
|
||||
return nil, &rtcerr.UnknownError{Err: err}
|
||||
}
|
||||
case *ecdsa.PrivateKey:
|
||||
pk := sk.Public()
|
||||
tpl.SignatureAlgorithm = x509.ECDSAWithSHA256
|
||||
certDER, err = x509.CreateCertificate(rand.Reader, &tpl, &tpl, pk, sk)
|
||||
if err != nil {
|
||||
return nil, &rtcerr.UnknownError{Err: err}
|
||||
}
|
||||
default:
|
||||
return nil, &rtcerr.NotSupportedError{Err: ErrPrivateKeyType}
|
||||
}
|
||||
|
||||
cert, err := x509.ParseCertificate(certDER)
|
||||
if err != nil {
|
||||
return nil, &rtcerr.UnknownError{Err: err}
|
||||
}
|
||||
|
||||
return &Certificate{privateKey: key, x509Cert: cert, statsID: fmt.Sprintf("certificate-%d", time.Now().UnixNano())}, nil
|
||||
}
|
||||
|
||||
// Equals determines if two certificates are identical by comparing both the
|
||||
// secretKeys and x509Certificates.
|
||||
func (c Certificate) Equals(o Certificate) bool {
|
||||
switch cSK := c.privateKey.(type) {
|
||||
case *rsa.PrivateKey:
|
||||
if oSK, ok := o.privateKey.(*rsa.PrivateKey); ok {
|
||||
if cSK.N.Cmp(oSK.N) != 0 {
|
||||
return false
|
||||
}
|
||||
return c.x509Cert.Equal(o.x509Cert)
|
||||
}
|
||||
return false
|
||||
case *ecdsa.PrivateKey:
|
||||
if oSK, ok := o.privateKey.(*ecdsa.PrivateKey); ok {
|
||||
if cSK.X.Cmp(oSK.X) != 0 || cSK.Y.Cmp(oSK.Y) != 0 {
|
||||
return false
|
||||
}
|
||||
return c.x509Cert.Equal(o.x509Cert)
|
||||
}
|
||||
return false
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Expires returns the timestamp after which this certificate is no longer valid.
|
||||
func (c Certificate) Expires() time.Time {
|
||||
if c.x509Cert == nil {
|
||||
return time.Time{}
|
||||
}
|
||||
return c.x509Cert.NotAfter
|
||||
}
|
||||
|
||||
// GetFingerprints returns the list of certificate fingerprints, one of which
|
||||
// is computed with the digest algorithm used in the certificate signature.
|
||||
func (c Certificate) GetFingerprints() ([]DTLSFingerprint, error) {
|
||||
fingerprintAlgorithms := []crypto.Hash{crypto.SHA256}
|
||||
res := make([]DTLSFingerprint, len(fingerprintAlgorithms))
|
||||
|
||||
i := 0
|
||||
for _, algo := range fingerprintAlgorithms {
|
||||
name, err := fingerprint.StringFromHash(algo)
|
||||
if err != nil {
|
||||
// nolint
|
||||
return nil, fmt.Errorf("%w: %v", ErrFailedToGenerateCertificateFingerprint, err)
|
||||
}
|
||||
value, err := fingerprint.Fingerprint(c.x509Cert, algo)
|
||||
if err != nil {
|
||||
// nolint
|
||||
return nil, fmt.Errorf("%w: %v", ErrFailedToGenerateCertificateFingerprint, err)
|
||||
}
|
||||
res[i] = DTLSFingerprint{
|
||||
Algorithm: name,
|
||||
Value: value,
|
||||
}
|
||||
}
|
||||
|
||||
return res[:i+1], nil
|
||||
}
|
||||
|
||||
// GenerateCertificate causes the creation of an X.509 certificate and
|
||||
// corresponding private key.
|
||||
func GenerateCertificate(secretKey crypto.PrivateKey) (*Certificate, error) {
|
||||
// Max random value, a 130-bits integer, i.e 2^130 - 1
|
||||
maxBigInt := new(big.Int)
|
||||
/* #nosec */
|
||||
maxBigInt.Exp(big.NewInt(2), big.NewInt(130), nil).Sub(maxBigInt, big.NewInt(1))
|
||||
/* #nosec */
|
||||
serialNumber, err := rand.Int(rand.Reader, maxBigInt)
|
||||
if err != nil {
|
||||
return nil, &rtcerr.UnknownError{Err: err}
|
||||
}
|
||||
|
||||
return NewCertificate(secretKey, x509.Certificate{
|
||||
Issuer: pkix.Name{CommonName: generatedCertificateOrigin},
|
||||
NotBefore: time.Now().AddDate(0, 0, -1),
|
||||
NotAfter: time.Now().AddDate(0, 1, -1),
|
||||
SerialNumber: serialNumber,
|
||||
Version: 2,
|
||||
Subject: pkix.Name{CommonName: generatedCertificateOrigin},
|
||||
})
|
||||
}
|
||||
|
||||
// CertificateFromX509 creates a new WebRTC Certificate from a given PrivateKey and Certificate
|
||||
//
|
||||
// This can be used if you want to share a certificate across multiple PeerConnections
|
||||
func CertificateFromX509(privateKey crypto.PrivateKey, certificate *x509.Certificate) Certificate {
|
||||
return Certificate{privateKey, certificate, fmt.Sprintf("certificate-%d", time.Now().UnixNano())}
|
||||
}
|
||||
|
||||
func (c Certificate) collectStats(report *statsReportCollector) error {
|
||||
report.Collecting()
|
||||
|
||||
fingerPrintAlgo, err := c.GetFingerprints()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
base64Certificate := base64.RawURLEncoding.EncodeToString(c.x509Cert.Raw)
|
||||
|
||||
stats := CertificateStats{
|
||||
Timestamp: statsTimestampFrom(time.Now()),
|
||||
Type: StatsTypeCertificate,
|
||||
ID: c.statsID,
|
||||
Fingerprint: fingerPrintAlgo[0].Value,
|
||||
FingerprintAlgorithm: fingerPrintAlgo[0].Algorithm,
|
||||
Base64Certificate: base64Certificate,
|
||||
IssuerCertificateID: c.x509Cert.Issuer.String(),
|
||||
}
|
||||
|
||||
report.Collect(stats.ID, stats)
|
||||
return nil
|
||||
}
|
||||
|
||||
// CertificateFromPEM creates a fresh certificate based on a string containing
|
||||
// pem blocks fort the private key and x509 certificate
|
||||
func CertificateFromPEM(pems string) (*Certificate, error) {
|
||||
// decode & parse the certificate
|
||||
block, more := pem.Decode([]byte(pems))
|
||||
if block == nil || block.Type != "CERTIFICATE" {
|
||||
return nil, errCertificatePEMFormatError
|
||||
}
|
||||
certBytes := make([]byte, base64.StdEncoding.DecodedLen(len(block.Bytes)))
|
||||
n, err := base64.StdEncoding.Decode(certBytes, block.Bytes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decode ceritifcate: %w", err)
|
||||
}
|
||||
cert, err := x509.ParseCertificate(certBytes[:n])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed parsing ceritifcate: %w", err)
|
||||
}
|
||||
// decode & parse the private key
|
||||
block, _ = pem.Decode(more)
|
||||
if block == nil || block.Type != "PRIVATE KEY" {
|
||||
return nil, errCertificatePEMFormatError
|
||||
}
|
||||
privateKey, err := x509.ParsePKCS8PrivateKey(block.Bytes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to parse private key: %w", err)
|
||||
}
|
||||
x := CertificateFromX509(privateKey, cert)
|
||||
return &x, nil
|
||||
}
|
||||
|
||||
// PEM returns the certificate encoded as two pem block: once for the X509
|
||||
// certificate and the other for the private key
|
||||
func (c Certificate) PEM() (string, error) {
|
||||
// First write the X509 certificate
|
||||
var o strings.Builder
|
||||
xcertBytes := make(
|
||||
[]byte, base64.StdEncoding.EncodedLen(len(c.x509Cert.Raw)))
|
||||
base64.StdEncoding.Encode(xcertBytes, c.x509Cert.Raw)
|
||||
err := pem.Encode(&o, &pem.Block{Type: "CERTIFICATE", Bytes: xcertBytes})
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to pem encode the X certificate: %w", err)
|
||||
}
|
||||
// Next write the private key
|
||||
privBytes, err := x509.MarshalPKCS8PrivateKey(c.privateKey)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to marshal private key: %w", err)
|
||||
}
|
||||
err = pem.Encode(&o, &pem.Block{Type: "PRIVATE KEY", Bytes: privBytes})
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to encode private key: %w", err)
|
||||
}
|
||||
return o.String(), nil
|
||||
}
|
||||
22
server/vendor/github.com/pion/webrtc/v3/codecov.yml
generated
vendored
Normal file
22
server/vendor/github.com/pion/webrtc/v3/codecov.yml
generated
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
#
|
||||
# DO NOT EDIT THIS FILE
|
||||
#
|
||||
# It is automatically copied from https://github.com/pion/.goassets repository.
|
||||
#
|
||||
# SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
coverage:
|
||||
status:
|
||||
project:
|
||||
default:
|
||||
# Allow decreasing 2% of total coverage to avoid noise.
|
||||
threshold: 2%
|
||||
patch:
|
||||
default:
|
||||
target: 70%
|
||||
only_pulls: true
|
||||
|
||||
ignore:
|
||||
- "examples/*"
|
||||
- "examples/**/*"
|
||||
55
server/vendor/github.com/pion/webrtc/v3/configuration.go
generated
vendored
Normal file
55
server/vendor/github.com/pion/webrtc/v3/configuration.go
generated
vendored
Normal file
@@ -0,0 +1,55 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
//go:build !js
|
||||
// +build !js
|
||||
|
||||
package webrtc
|
||||
|
||||
// A Configuration defines how peer-to-peer communication via PeerConnection
|
||||
// is established or re-established.
|
||||
// Configurations may be set up once and reused across multiple connections.
|
||||
// Configurations are treated as readonly. As long as they are unmodified,
|
||||
// they are safe for concurrent use.
|
||||
type Configuration struct {
|
||||
// ICEServers defines a slice describing servers available to be used by
|
||||
// ICE, such as STUN and TURN servers.
|
||||
ICEServers []ICEServer `json:"iceServers,omitempty"`
|
||||
|
||||
// ICETransportPolicy indicates which candidates the ICEAgent is allowed
|
||||
// to use.
|
||||
ICETransportPolicy ICETransportPolicy `json:"iceTransportPolicy,omitempty"`
|
||||
|
||||
// BundlePolicy indicates which media-bundling policy to use when gathering
|
||||
// ICE candidates.
|
||||
BundlePolicy BundlePolicy `json:"bundlePolicy,omitempty"`
|
||||
|
||||
// RTCPMuxPolicy indicates which rtcp-mux policy to use when gathering ICE
|
||||
// candidates.
|
||||
RTCPMuxPolicy RTCPMuxPolicy `json:"rtcpMuxPolicy,omitempty"`
|
||||
|
||||
// PeerIdentity sets the target peer identity for the PeerConnection.
|
||||
// The PeerConnection will not establish a connection to a remote peer
|
||||
// unless it can be successfully authenticated with the provided name.
|
||||
PeerIdentity string `json:"peerIdentity,omitempty"`
|
||||
|
||||
// Certificates describes a set of certificates that the PeerConnection
|
||||
// uses to authenticate. Valid values for this parameter are created
|
||||
// through calls to the GenerateCertificate function. Although any given
|
||||
// DTLS connection will use only one certificate, this attribute allows the
|
||||
// caller to provide multiple certificates that support different
|
||||
// algorithms. The final certificate will be selected based on the DTLS
|
||||
// handshake, which establishes which certificates are allowed. The
|
||||
// PeerConnection implementation selects which of the certificates is
|
||||
// used for a given connection; how certificates are selected is outside
|
||||
// the scope of this specification. If this value is absent, then a default
|
||||
// set of certificates is generated for each PeerConnection instance.
|
||||
Certificates []Certificate `json:"certificates,omitempty"`
|
||||
|
||||
// ICECandidatePoolSize describes the size of the prefetched ICE pool.
|
||||
ICECandidatePoolSize uint8 `json:"iceCandidatePoolSize,omitempty"`
|
||||
|
||||
// SDPSemantics controls the type of SDP offers accepted by and
|
||||
// SDP answers generated by the PeerConnection.
|
||||
SDPSemantics SDPSemantics `json:"sdpSemantics,omitempty"`
|
||||
}
|
||||
27
server/vendor/github.com/pion/webrtc/v3/configuration_common.go
generated
vendored
Normal file
27
server/vendor/github.com/pion/webrtc/v3/configuration_common.go
generated
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package webrtc
|
||||
|
||||
import "strings"
|
||||
|
||||
// getICEServers side-steps the strict parsing mode of the ice package
|
||||
// (as defined in https://tools.ietf.org/html/rfc7064) by copying and then
|
||||
// stripping any erroneous queries from "stun(s):" URLs before parsing.
|
||||
func (c Configuration) getICEServers() []ICEServer {
|
||||
iceServers := append([]ICEServer{}, c.ICEServers...)
|
||||
|
||||
for iceServersIndex := range iceServers {
|
||||
iceServers[iceServersIndex].URLs = append([]string{}, iceServers[iceServersIndex].URLs...)
|
||||
|
||||
for urlsIndex, rawURL := range iceServers[iceServersIndex].URLs {
|
||||
if strings.HasPrefix(rawURL, "stun") {
|
||||
// strip the query from "stun(s):" if present
|
||||
parts := strings.Split(rawURL, "?")
|
||||
rawURL = parts[0]
|
||||
}
|
||||
iceServers[iceServersIndex].URLs[urlsIndex] = rawURL
|
||||
}
|
||||
}
|
||||
return iceServers
|
||||
}
|
||||
39
server/vendor/github.com/pion/webrtc/v3/configuration_js.go
generated
vendored
Normal file
39
server/vendor/github.com/pion/webrtc/v3/configuration_js.go
generated
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
//go:build js && wasm
|
||||
// +build js,wasm
|
||||
|
||||
package webrtc
|
||||
|
||||
// Configuration defines a set of parameters to configure how the
|
||||
// peer-to-peer communication via PeerConnection is established or
|
||||
// re-established.
|
||||
type Configuration struct {
|
||||
// ICEServers defines a slice describing servers available to be used by
|
||||
// ICE, such as STUN and TURN servers.
|
||||
ICEServers []ICEServer
|
||||
|
||||
// ICETransportPolicy indicates which candidates the ICEAgent is allowed
|
||||
// to use.
|
||||
ICETransportPolicy ICETransportPolicy
|
||||
|
||||
// BundlePolicy indicates which media-bundling policy to use when gathering
|
||||
// ICE candidates.
|
||||
BundlePolicy BundlePolicy
|
||||
|
||||
// RTCPMuxPolicy indicates which rtcp-mux policy to use when gathering ICE
|
||||
// candidates.
|
||||
RTCPMuxPolicy RTCPMuxPolicy
|
||||
|
||||
// PeerIdentity sets the target peer identity for the PeerConnection.
|
||||
// The PeerConnection will not establish a connection to a remote peer
|
||||
// unless it can be successfully authenticated with the provided name.
|
||||
PeerIdentity string
|
||||
|
||||
// Certificates are not supported in the JavaScript/Wasm bindings.
|
||||
// Certificates []Certificate
|
||||
|
||||
// ICECandidatePoolSize describes the size of the prefetched ICE pool.
|
||||
ICECandidatePoolSize uint8
|
||||
}
|
||||
52
server/vendor/github.com/pion/webrtc/v3/constants.go
generated
vendored
Normal file
52
server/vendor/github.com/pion/webrtc/v3/constants.go
generated
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package webrtc
|
||||
|
||||
import "github.com/pion/dtls/v2"
|
||||
|
||||
const (
|
||||
// Unknown defines default public constant to use for "enum" like struct
|
||||
// comparisons when no value was defined.
|
||||
Unknown = iota
|
||||
unknownStr = "unknown"
|
||||
|
||||
// Equal to UDP MTU
|
||||
receiveMTU = 1460
|
||||
|
||||
// simulcastProbeCount is the amount of RTP Packets
|
||||
// that handleUndeclaredSSRC will read and try to dispatch from
|
||||
// mid and rid values
|
||||
simulcastProbeCount = 10
|
||||
|
||||
// simulcastMaxProbeRoutines is how many active routines can be used to probe
|
||||
// If the total amount of incoming SSRCes exceeds this new requests will be ignored
|
||||
simulcastMaxProbeRoutines = 25
|
||||
|
||||
mediaSectionApplication = "application"
|
||||
|
||||
sdpAttributeRid = "rid"
|
||||
|
||||
sdpAttributeSimulcast = "simulcast"
|
||||
|
||||
rtpOutboundMTU = 1200
|
||||
|
||||
rtpPayloadTypeBitmask = 0x7F
|
||||
|
||||
incomingUnhandledRTPSsrc = "Incoming unhandled RTP ssrc(%d), OnTrack will not be fired. %v"
|
||||
|
||||
generatedCertificateOrigin = "WebRTC"
|
||||
|
||||
sdesRepairRTPStreamIDURI = "urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id"
|
||||
|
||||
// AttributeRtxPayloadType is the interceptor attribute added when Read() returns an RTX packet containing the RTX stream payload type
|
||||
AttributeRtxPayloadType = "rtx_payload_type"
|
||||
// AttributeRtxSsrc is the interceptor attribute added when Read() returns an RTX packet containing the RTX stream SSRC
|
||||
AttributeRtxSsrc = "rtx_ssrc"
|
||||
// AttributeRtxSequenceNumber is the interceptor attribute added when Read() returns an RTX packet containing the RTX stream sequence number
|
||||
AttributeRtxSequenceNumber = "rtx_sequence_number"
|
||||
)
|
||||
|
||||
func defaultSrtpProtectionProfiles() []dtls.SRTPProtectionProfile {
|
||||
return []dtls.SRTPProtectionProfile{dtls.SRTP_AEAD_AES_256_GCM, dtls.SRTP_AEAD_AES_128_GCM, dtls.SRTP_AES128_CM_HMAC_SHA1_80}
|
||||
}
|
||||
695
server/vendor/github.com/pion/webrtc/v3/datachannel.go
generated
vendored
Normal file
695
server/vendor/github.com/pion/webrtc/v3/datachannel.go
generated
vendored
Normal file
@@ -0,0 +1,695 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
//go:build !js
|
||||
// +build !js
|
||||
|
||||
package webrtc
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/pion/datachannel"
|
||||
"github.com/pion/logging"
|
||||
"github.com/pion/webrtc/v3/pkg/rtcerr"
|
||||
)
|
||||
|
||||
const dataChannelBufferSize = math.MaxUint16 // message size limit for Chromium
|
||||
var errSCTPNotEstablished = errors.New("SCTP not established")
|
||||
|
||||
// DataChannel represents a WebRTC DataChannel
|
||||
// The DataChannel interface represents a network channel
|
||||
// which can be used for bidirectional peer-to-peer transfers of arbitrary data
|
||||
type DataChannel struct {
|
||||
mu sync.RWMutex
|
||||
|
||||
statsID string
|
||||
label string
|
||||
ordered bool
|
||||
maxPacketLifeTime *uint16
|
||||
maxRetransmits *uint16
|
||||
protocol string
|
||||
negotiated bool
|
||||
id *uint16
|
||||
readyState atomic.Value // DataChannelState
|
||||
bufferedAmountLowThreshold uint64
|
||||
detachCalled bool
|
||||
readLoopActive chan struct{}
|
||||
isGracefulClosed bool
|
||||
|
||||
// The binaryType represents attribute MUST, on getting, return the value to
|
||||
// which it was last set. On setting, if the new value is either the string
|
||||
// "blob" or the string "arraybuffer", then set the IDL attribute to this
|
||||
// new value. Otherwise, throw a SyntaxError. When an DataChannel object
|
||||
// is created, the binaryType attribute MUST be initialized to the string
|
||||
// "blob". This attribute controls how binary data is exposed to scripts.
|
||||
// binaryType string
|
||||
|
||||
onMessageHandler func(DataChannelMessage)
|
||||
openHandlerOnce sync.Once
|
||||
onOpenHandler func()
|
||||
dialHandlerOnce sync.Once
|
||||
onDialHandler func()
|
||||
onCloseHandler func()
|
||||
onBufferedAmountLow func()
|
||||
onErrorHandler func(error)
|
||||
|
||||
sctpTransport *SCTPTransport
|
||||
dataChannel *datachannel.DataChannel
|
||||
|
||||
// A reference to the associated api object used by this datachannel
|
||||
api *API
|
||||
log logging.LeveledLogger
|
||||
}
|
||||
|
||||
// NewDataChannel creates a new DataChannel.
|
||||
// This constructor is part of the ORTC API. It is not
|
||||
// meant to be used together with the basic WebRTC API.
|
||||
func (api *API) NewDataChannel(transport *SCTPTransport, params *DataChannelParameters) (*DataChannel, error) {
|
||||
d, err := api.newDataChannel(params, nil, api.settingEngine.LoggerFactory.NewLogger("ortc"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = d.open(transport)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return d, nil
|
||||
}
|
||||
|
||||
// newDataChannel is an internal constructor for the data channel used to
|
||||
// create the DataChannel object before the networking is set up.
|
||||
func (api *API) newDataChannel(params *DataChannelParameters, sctpTransport *SCTPTransport, log logging.LeveledLogger) (*DataChannel, error) {
|
||||
// https://w3c.github.io/webrtc-pc/#peer-to-peer-data-api (Step #5)
|
||||
if len(params.Label) > 65535 {
|
||||
return nil, &rtcerr.TypeError{Err: ErrStringSizeLimit}
|
||||
}
|
||||
|
||||
d := &DataChannel{
|
||||
sctpTransport: sctpTransport,
|
||||
statsID: fmt.Sprintf("DataChannel-%d", time.Now().UnixNano()),
|
||||
label: params.Label,
|
||||
protocol: params.Protocol,
|
||||
negotiated: params.Negotiated,
|
||||
id: params.ID,
|
||||
ordered: params.Ordered,
|
||||
maxPacketLifeTime: params.MaxPacketLifeTime,
|
||||
maxRetransmits: params.MaxRetransmits,
|
||||
api: api,
|
||||
log: log,
|
||||
}
|
||||
|
||||
d.setReadyState(DataChannelStateConnecting)
|
||||
return d, nil
|
||||
}
|
||||
|
||||
// open opens the datachannel over the sctp transport
|
||||
func (d *DataChannel) open(sctpTransport *SCTPTransport) error {
|
||||
association := sctpTransport.association()
|
||||
if association == nil {
|
||||
return errSCTPNotEstablished
|
||||
}
|
||||
|
||||
d.mu.Lock()
|
||||
if d.sctpTransport != nil { // already open
|
||||
d.mu.Unlock()
|
||||
return nil
|
||||
}
|
||||
d.sctpTransport = sctpTransport
|
||||
var channelType datachannel.ChannelType
|
||||
var reliabilityParameter uint32
|
||||
|
||||
switch {
|
||||
case d.maxPacketLifeTime == nil && d.maxRetransmits == nil:
|
||||
if d.ordered {
|
||||
channelType = datachannel.ChannelTypeReliable
|
||||
} else {
|
||||
channelType = datachannel.ChannelTypeReliableUnordered
|
||||
}
|
||||
|
||||
case d.maxRetransmits != nil:
|
||||
reliabilityParameter = uint32(*d.maxRetransmits)
|
||||
if d.ordered {
|
||||
channelType = datachannel.ChannelTypePartialReliableRexmit
|
||||
} else {
|
||||
channelType = datachannel.ChannelTypePartialReliableRexmitUnordered
|
||||
}
|
||||
default:
|
||||
reliabilityParameter = uint32(*d.maxPacketLifeTime)
|
||||
if d.ordered {
|
||||
channelType = datachannel.ChannelTypePartialReliableTimed
|
||||
} else {
|
||||
channelType = datachannel.ChannelTypePartialReliableTimedUnordered
|
||||
}
|
||||
}
|
||||
|
||||
cfg := &datachannel.Config{
|
||||
ChannelType: channelType,
|
||||
Priority: datachannel.ChannelPriorityNormal,
|
||||
ReliabilityParameter: reliabilityParameter,
|
||||
Label: d.label,
|
||||
Protocol: d.protocol,
|
||||
Negotiated: d.negotiated,
|
||||
LoggerFactory: d.api.settingEngine.LoggerFactory,
|
||||
}
|
||||
|
||||
if d.id == nil {
|
||||
// avoid holding lock when generating ID, since id generation locks
|
||||
d.mu.Unlock()
|
||||
var dcID *uint16
|
||||
err := d.sctpTransport.generateAndSetDataChannelID(d.sctpTransport.dtlsTransport.role(), &dcID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
d.mu.Lock()
|
||||
d.id = dcID
|
||||
}
|
||||
dc, err := datachannel.Dial(association, *d.id, cfg)
|
||||
if err != nil {
|
||||
d.mu.Unlock()
|
||||
return err
|
||||
}
|
||||
|
||||
// bufferedAmountLowThreshold and onBufferedAmountLow might be set earlier
|
||||
dc.SetBufferedAmountLowThreshold(d.bufferedAmountLowThreshold)
|
||||
dc.OnBufferedAmountLow(d.onBufferedAmountLow)
|
||||
d.mu.Unlock()
|
||||
|
||||
d.onDial()
|
||||
d.handleOpen(dc, false, d.negotiated)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Transport returns the SCTPTransport instance the DataChannel is sending over.
|
||||
func (d *DataChannel) Transport() *SCTPTransport {
|
||||
d.mu.RLock()
|
||||
defer d.mu.RUnlock()
|
||||
|
||||
return d.sctpTransport
|
||||
}
|
||||
|
||||
// After onOpen is complete check that the user called detach
|
||||
// and provide an error message if the call was missed
|
||||
func (d *DataChannel) checkDetachAfterOpen() {
|
||||
d.mu.RLock()
|
||||
defer d.mu.RUnlock()
|
||||
|
||||
if d.api.settingEngine.detach.DataChannels && !d.detachCalled {
|
||||
d.log.Warn("webrtc.DetachDataChannels() enabled but didn't Detach, call Detach from OnOpen")
|
||||
}
|
||||
}
|
||||
|
||||
// OnOpen sets an event handler which is invoked when
|
||||
// the underlying data transport has been established (or re-established).
|
||||
func (d *DataChannel) OnOpen(f func()) {
|
||||
d.mu.Lock()
|
||||
d.openHandlerOnce = sync.Once{}
|
||||
d.onOpenHandler = f
|
||||
d.mu.Unlock()
|
||||
|
||||
if d.ReadyState() == DataChannelStateOpen {
|
||||
// If the data channel is already open, call the handler immediately.
|
||||
go d.openHandlerOnce.Do(func() {
|
||||
f()
|
||||
d.checkDetachAfterOpen()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (d *DataChannel) onOpen() {
|
||||
d.mu.RLock()
|
||||
handler := d.onOpenHandler
|
||||
if d.isGracefulClosed {
|
||||
d.mu.RUnlock()
|
||||
return
|
||||
}
|
||||
d.mu.RUnlock()
|
||||
|
||||
if handler != nil {
|
||||
go d.openHandlerOnce.Do(func() {
|
||||
handler()
|
||||
d.checkDetachAfterOpen()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// OnDial sets an event handler which is invoked when the
|
||||
// peer has been dialed, but before said peer has responsed
|
||||
func (d *DataChannel) OnDial(f func()) {
|
||||
d.mu.Lock()
|
||||
d.dialHandlerOnce = sync.Once{}
|
||||
d.onDialHandler = f
|
||||
d.mu.Unlock()
|
||||
|
||||
if d.ReadyState() == DataChannelStateOpen {
|
||||
// If the data channel is already open, call the handler immediately.
|
||||
go d.dialHandlerOnce.Do(f)
|
||||
}
|
||||
}
|
||||
|
||||
func (d *DataChannel) onDial() {
|
||||
d.mu.RLock()
|
||||
handler := d.onDialHandler
|
||||
if d.isGracefulClosed {
|
||||
d.mu.RUnlock()
|
||||
return
|
||||
}
|
||||
d.mu.RUnlock()
|
||||
|
||||
if handler != nil {
|
||||
go d.dialHandlerOnce.Do(handler)
|
||||
}
|
||||
}
|
||||
|
||||
// OnClose sets an event handler which is invoked when
|
||||
// the underlying data transport has been closed.
|
||||
// Note: Due to backwards compatibility, there is a chance that
|
||||
// OnClose can be called, even if the GracefulClose is used.
|
||||
// If this is the case for you, you can deregister OnClose
|
||||
// prior to GracefulClose.
|
||||
func (d *DataChannel) OnClose(f func()) {
|
||||
d.mu.Lock()
|
||||
defer d.mu.Unlock()
|
||||
d.onCloseHandler = f
|
||||
}
|
||||
|
||||
func (d *DataChannel) onClose() {
|
||||
d.mu.RLock()
|
||||
handler := d.onCloseHandler
|
||||
d.mu.RUnlock()
|
||||
|
||||
if handler != nil {
|
||||
go handler()
|
||||
}
|
||||
}
|
||||
|
||||
// OnMessage sets an event handler which is invoked on a binary
|
||||
// message arrival over the sctp transport from a remote peer.
|
||||
// OnMessage can currently receive messages up to 16384 bytes
|
||||
// in size. Check out the detach API if you want to use larger
|
||||
// message sizes. Note that browser support for larger messages
|
||||
// is also limited.
|
||||
func (d *DataChannel) OnMessage(f func(msg DataChannelMessage)) {
|
||||
d.mu.Lock()
|
||||
defer d.mu.Unlock()
|
||||
d.onMessageHandler = f
|
||||
}
|
||||
|
||||
func (d *DataChannel) onMessage(msg DataChannelMessage) {
|
||||
d.mu.RLock()
|
||||
handler := d.onMessageHandler
|
||||
if d.isGracefulClosed {
|
||||
d.mu.RUnlock()
|
||||
return
|
||||
}
|
||||
d.mu.RUnlock()
|
||||
|
||||
if handler == nil {
|
||||
return
|
||||
}
|
||||
handler(msg)
|
||||
}
|
||||
|
||||
func (d *DataChannel) handleOpen(dc *datachannel.DataChannel, isRemote, isAlreadyNegotiated bool) {
|
||||
d.mu.Lock()
|
||||
if d.isGracefulClosed {
|
||||
d.mu.Unlock()
|
||||
return
|
||||
}
|
||||
d.dataChannel = dc
|
||||
bufferedAmountLowThreshold := d.bufferedAmountLowThreshold
|
||||
onBufferedAmountLow := d.onBufferedAmountLow
|
||||
d.mu.Unlock()
|
||||
d.setReadyState(DataChannelStateOpen)
|
||||
|
||||
// Fire the OnOpen handler immediately not using pion/datachannel
|
||||
// * detached datachannels have no read loop, the user needs to read and query themselves
|
||||
// * remote datachannels should fire OnOpened. This isn't spec compliant, but we can't break behavior yet
|
||||
// * already negotiated datachannels should fire OnOpened
|
||||
if d.api.settingEngine.detach.DataChannels || isRemote || isAlreadyNegotiated {
|
||||
// bufferedAmountLowThreshold and onBufferedAmountLow might be set earlier
|
||||
d.dataChannel.SetBufferedAmountLowThreshold(bufferedAmountLowThreshold)
|
||||
d.dataChannel.OnBufferedAmountLow(onBufferedAmountLow)
|
||||
d.onOpen()
|
||||
} else {
|
||||
dc.OnOpen(func() {
|
||||
d.onOpen()
|
||||
})
|
||||
}
|
||||
|
||||
d.mu.Lock()
|
||||
defer d.mu.Unlock()
|
||||
|
||||
if d.isGracefulClosed {
|
||||
return
|
||||
}
|
||||
|
||||
if !d.api.settingEngine.detach.DataChannels {
|
||||
d.readLoopActive = make(chan struct{})
|
||||
go d.readLoop()
|
||||
}
|
||||
}
|
||||
|
||||
// OnError sets an event handler which is invoked when
|
||||
// the underlying data transport cannot be read.
|
||||
func (d *DataChannel) OnError(f func(err error)) {
|
||||
d.mu.Lock()
|
||||
defer d.mu.Unlock()
|
||||
d.onErrorHandler = f
|
||||
}
|
||||
|
||||
func (d *DataChannel) onError(err error) {
|
||||
d.mu.RLock()
|
||||
handler := d.onErrorHandler
|
||||
if d.isGracefulClosed {
|
||||
d.mu.RUnlock()
|
||||
return
|
||||
}
|
||||
d.mu.RUnlock()
|
||||
|
||||
if handler != nil {
|
||||
go handler(err)
|
||||
}
|
||||
}
|
||||
|
||||
// See https://github.com/pion/webrtc/issues/1516
|
||||
// nolint:gochecknoglobals
|
||||
var rlBufPool = sync.Pool{New: func() interface{} {
|
||||
return make([]byte, dataChannelBufferSize)
|
||||
}}
|
||||
|
||||
func (d *DataChannel) readLoop() {
|
||||
defer func() {
|
||||
d.mu.Lock()
|
||||
readLoopActive := d.readLoopActive
|
||||
d.mu.Unlock()
|
||||
defer close(readLoopActive)
|
||||
}()
|
||||
for {
|
||||
buffer := rlBufPool.Get().([]byte) //nolint:forcetypeassert
|
||||
n, isString, err := d.dataChannel.ReadDataChannel(buffer)
|
||||
if err != nil {
|
||||
rlBufPool.Put(buffer) // nolint:staticcheck
|
||||
d.setReadyState(DataChannelStateClosed)
|
||||
if !errors.Is(err, io.EOF) {
|
||||
d.onError(err)
|
||||
}
|
||||
d.onClose()
|
||||
return
|
||||
}
|
||||
|
||||
m := DataChannelMessage{Data: make([]byte, n), IsString: isString}
|
||||
copy(m.Data, buffer[:n])
|
||||
// The 'staticcheck' pragma is a false positive on the part of the CI linter.
|
||||
rlBufPool.Put(buffer) // nolint:staticcheck
|
||||
|
||||
// NB: Why was DataChannelMessage not passed as a pointer value?
|
||||
d.onMessage(m) // nolint:staticcheck
|
||||
}
|
||||
}
|
||||
|
||||
// Send sends the binary message to the DataChannel peer
|
||||
func (d *DataChannel) Send(data []byte) error {
|
||||
err := d.ensureOpen()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = d.dataChannel.WriteDataChannel(data, false)
|
||||
return err
|
||||
}
|
||||
|
||||
// SendText sends the text message to the DataChannel peer
|
||||
func (d *DataChannel) SendText(s string) error {
|
||||
err := d.ensureOpen()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = d.dataChannel.WriteDataChannel([]byte(s), true)
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *DataChannel) ensureOpen() error {
|
||||
d.mu.RLock()
|
||||
defer d.mu.RUnlock()
|
||||
if d.ReadyState() != DataChannelStateOpen {
|
||||
return io.ErrClosedPipe
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Detach allows you to detach the underlying datachannel. This provides
|
||||
// an idiomatic API to work with, however it disables the OnMessage callback.
|
||||
// Before calling Detach you have to enable this behavior by calling
|
||||
// webrtc.DetachDataChannels(). Combining detached and normal data channels
|
||||
// is not supported.
|
||||
// Please refer to the data-channels-detach example and the
|
||||
// pion/datachannel documentation for the correct way to handle the
|
||||
// resulting DataChannel object.
|
||||
func (d *DataChannel) Detach() (datachannel.ReadWriteCloser, error) {
|
||||
d.mu.Lock()
|
||||
defer d.mu.Unlock()
|
||||
|
||||
if !d.api.settingEngine.detach.DataChannels {
|
||||
return nil, errDetachNotEnabled
|
||||
}
|
||||
|
||||
if d.dataChannel == nil {
|
||||
return nil, errDetachBeforeOpened
|
||||
}
|
||||
|
||||
d.detachCalled = true
|
||||
|
||||
return d.dataChannel, nil
|
||||
}
|
||||
|
||||
// Close Closes the DataChannel. It may be called regardless of whether
|
||||
// the DataChannel object was created by this peer or the remote peer.
|
||||
func (d *DataChannel) Close() error {
|
||||
return d.close(false)
|
||||
}
|
||||
|
||||
// GracefulClose Closes the DataChannel. It may be called regardless of whether
|
||||
// the DataChannel object was created by this peer or the remote peer. It also waits
|
||||
// for any goroutines it started to complete. This is only safe to call outside of
|
||||
// DataChannel callbacks or if in a callback, in its own goroutine.
|
||||
func (d *DataChannel) GracefulClose() error {
|
||||
return d.close(true)
|
||||
}
|
||||
|
||||
// Normally, close only stops writes from happening, so graceful=true
|
||||
// will wait for reads to be finished based on underlying SCTP association
|
||||
// closure or a SCTP reset stream from the other side. This is safe to call
|
||||
// with graceful=true after tearing down a PeerConnection but not
|
||||
// necessarily before. For example, if you used a vnet and dropped all packets
|
||||
// right before closing the DataChannel, you'd need never see a reset stream.
|
||||
func (d *DataChannel) close(shouldGracefullyClose bool) error {
|
||||
d.mu.Lock()
|
||||
d.isGracefulClosed = true
|
||||
readLoopActive := d.readLoopActive
|
||||
if shouldGracefullyClose && readLoopActive != nil {
|
||||
defer func() {
|
||||
<-readLoopActive
|
||||
}()
|
||||
}
|
||||
haveSctpTransport := d.dataChannel != nil
|
||||
d.mu.Unlock()
|
||||
|
||||
if d.ReadyState() == DataChannelStateClosed {
|
||||
return nil
|
||||
}
|
||||
|
||||
d.setReadyState(DataChannelStateClosing)
|
||||
if !haveSctpTransport {
|
||||
return nil
|
||||
}
|
||||
|
||||
return d.dataChannel.Close()
|
||||
}
|
||||
|
||||
// Label represents a label that can be used to distinguish this
|
||||
// DataChannel object from other DataChannel objects. Scripts are
|
||||
// allowed to create multiple DataChannel objects with the same label.
|
||||
func (d *DataChannel) Label() string {
|
||||
d.mu.RLock()
|
||||
defer d.mu.RUnlock()
|
||||
|
||||
return d.label
|
||||
}
|
||||
|
||||
// Ordered returns true if the DataChannel is ordered, and false if
|
||||
// out-of-order delivery is allowed.
|
||||
func (d *DataChannel) Ordered() bool {
|
||||
d.mu.RLock()
|
||||
defer d.mu.RUnlock()
|
||||
|
||||
return d.ordered
|
||||
}
|
||||
|
||||
// MaxPacketLifeTime represents the length of the time window (msec) during
|
||||
// which transmissions and retransmissions may occur in unreliable mode.
|
||||
func (d *DataChannel) MaxPacketLifeTime() *uint16 {
|
||||
d.mu.RLock()
|
||||
defer d.mu.RUnlock()
|
||||
|
||||
return d.maxPacketLifeTime
|
||||
}
|
||||
|
||||
// MaxRetransmits represents the maximum number of retransmissions that are
|
||||
// attempted in unreliable mode.
|
||||
func (d *DataChannel) MaxRetransmits() *uint16 {
|
||||
d.mu.RLock()
|
||||
defer d.mu.RUnlock()
|
||||
|
||||
return d.maxRetransmits
|
||||
}
|
||||
|
||||
// Protocol represents the name of the sub-protocol used with this
|
||||
// DataChannel.
|
||||
func (d *DataChannel) Protocol() string {
|
||||
d.mu.RLock()
|
||||
defer d.mu.RUnlock()
|
||||
|
||||
return d.protocol
|
||||
}
|
||||
|
||||
// Negotiated represents whether this DataChannel was negotiated by the
|
||||
// application (true), or not (false).
|
||||
func (d *DataChannel) Negotiated() bool {
|
||||
d.mu.RLock()
|
||||
defer d.mu.RUnlock()
|
||||
|
||||
return d.negotiated
|
||||
}
|
||||
|
||||
// ID represents the ID for this DataChannel. The value is initially
|
||||
// null, which is what will be returned if the ID was not provided at
|
||||
// channel creation time, and the DTLS role of the SCTP transport has not
|
||||
// yet been negotiated. Otherwise, it will return the ID that was either
|
||||
// selected by the script or generated. After the ID is set to a non-null
|
||||
// value, it will not change.
|
||||
func (d *DataChannel) ID() *uint16 {
|
||||
d.mu.RLock()
|
||||
defer d.mu.RUnlock()
|
||||
|
||||
return d.id
|
||||
}
|
||||
|
||||
// ReadyState represents the state of the DataChannel object.
|
||||
func (d *DataChannel) ReadyState() DataChannelState {
|
||||
if v, ok := d.readyState.Load().(DataChannelState); ok {
|
||||
return v
|
||||
}
|
||||
return DataChannelState(0)
|
||||
}
|
||||
|
||||
// BufferedAmount represents the number of bytes of application data
|
||||
// (UTF-8 text and binary data) that have been queued using send(). Even
|
||||
// though the data transmission can occur in parallel, the returned value
|
||||
// MUST NOT be decreased before the current task yielded back to the event
|
||||
// loop to prevent race conditions. The value does not include framing
|
||||
// overhead incurred by the protocol, or buffering done by the operating
|
||||
// system or network hardware. The value of BufferedAmount slot will only
|
||||
// increase with each call to the send() method as long as the ReadyState is
|
||||
// open; however, BufferedAmount does not reset to zero once the channel
|
||||
// closes.
|
||||
func (d *DataChannel) BufferedAmount() uint64 {
|
||||
d.mu.RLock()
|
||||
defer d.mu.RUnlock()
|
||||
|
||||
if d.dataChannel == nil {
|
||||
return 0
|
||||
}
|
||||
return d.dataChannel.BufferedAmount()
|
||||
}
|
||||
|
||||
// BufferedAmountLowThreshold represents the threshold at which the
|
||||
// bufferedAmount is considered to be low. When the bufferedAmount decreases
|
||||
// from above this threshold to equal or below it, the bufferedamountlow
|
||||
// event fires. BufferedAmountLowThreshold is initially zero on each new
|
||||
// DataChannel, but the application may change its value at any time.
|
||||
// The threshold is set to 0 by default.
|
||||
func (d *DataChannel) BufferedAmountLowThreshold() uint64 {
|
||||
d.mu.RLock()
|
||||
defer d.mu.RUnlock()
|
||||
|
||||
if d.dataChannel == nil {
|
||||
return d.bufferedAmountLowThreshold
|
||||
}
|
||||
return d.dataChannel.BufferedAmountLowThreshold()
|
||||
}
|
||||
|
||||
// SetBufferedAmountLowThreshold is used to update the threshold.
|
||||
// See BufferedAmountLowThreshold().
|
||||
func (d *DataChannel) SetBufferedAmountLowThreshold(th uint64) {
|
||||
d.mu.Lock()
|
||||
defer d.mu.Unlock()
|
||||
|
||||
d.bufferedAmountLowThreshold = th
|
||||
|
||||
if d.dataChannel != nil {
|
||||
d.dataChannel.SetBufferedAmountLowThreshold(th)
|
||||
}
|
||||
}
|
||||
|
||||
// OnBufferedAmountLow sets an event handler which is invoked when
|
||||
// the number of bytes of outgoing data becomes lower than the
|
||||
// BufferedAmountLowThreshold.
|
||||
func (d *DataChannel) OnBufferedAmountLow(f func()) {
|
||||
d.mu.Lock()
|
||||
defer d.mu.Unlock()
|
||||
|
||||
d.onBufferedAmountLow = f
|
||||
if d.dataChannel != nil {
|
||||
d.dataChannel.OnBufferedAmountLow(f)
|
||||
}
|
||||
}
|
||||
|
||||
func (d *DataChannel) getStatsID() string {
|
||||
d.mu.Lock()
|
||||
defer d.mu.Unlock()
|
||||
return d.statsID
|
||||
}
|
||||
|
||||
func (d *DataChannel) collectStats(collector *statsReportCollector) {
|
||||
collector.Collecting()
|
||||
|
||||
d.mu.Lock()
|
||||
defer d.mu.Unlock()
|
||||
|
||||
stats := DataChannelStats{
|
||||
Timestamp: statsTimestampNow(),
|
||||
Type: StatsTypeDataChannel,
|
||||
ID: d.statsID,
|
||||
Label: d.label,
|
||||
Protocol: d.protocol,
|
||||
// TransportID string `json:"transportId"`
|
||||
State: d.ReadyState(),
|
||||
}
|
||||
|
||||
if d.id != nil {
|
||||
stats.DataChannelIdentifier = int32(*d.id)
|
||||
}
|
||||
|
||||
if d.dataChannel != nil {
|
||||
stats.MessagesSent = d.dataChannel.MessagesSent()
|
||||
stats.BytesSent = d.dataChannel.BytesSent()
|
||||
stats.MessagesReceived = d.dataChannel.MessagesReceived()
|
||||
stats.BytesReceived = d.dataChannel.BytesReceived()
|
||||
}
|
||||
|
||||
collector.Collect(stats.ID, stats)
|
||||
}
|
||||
|
||||
func (d *DataChannel) setReadyState(r DataChannelState) {
|
||||
d.readyState.Store(r)
|
||||
}
|
||||
323
server/vendor/github.com/pion/webrtc/v3/datachannel_js.go
generated
vendored
Normal file
323
server/vendor/github.com/pion/webrtc/v3/datachannel_js.go
generated
vendored
Normal file
@@ -0,0 +1,323 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
//go:build js && wasm
|
||||
// +build js,wasm
|
||||
|
||||
package webrtc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"syscall/js"
|
||||
|
||||
"github.com/pion/datachannel"
|
||||
)
|
||||
|
||||
const dataChannelBufferSize = 16384 // Lowest common denominator among browsers
|
||||
|
||||
// DataChannel represents a WebRTC DataChannel
|
||||
// The DataChannel interface represents a network channel
|
||||
// which can be used for bidirectional peer-to-peer transfers of arbitrary data
|
||||
type DataChannel struct {
|
||||
// Pointer to the underlying JavaScript RTCPeerConnection object.
|
||||
underlying js.Value
|
||||
|
||||
// Keep track of handlers/callbacks so we can call Release as required by the
|
||||
// syscall/js API. Initially nil.
|
||||
onOpenHandler *js.Func
|
||||
onCloseHandler *js.Func
|
||||
onMessageHandler *js.Func
|
||||
onBufferedAmountLow *js.Func
|
||||
|
||||
// A reference to the associated api object used by this datachannel
|
||||
api *API
|
||||
}
|
||||
|
||||
// OnOpen sets an event handler which is invoked when
|
||||
// the underlying data transport has been established (or re-established).
|
||||
func (d *DataChannel) OnOpen(f func()) {
|
||||
if d.onOpenHandler != nil {
|
||||
oldHandler := d.onOpenHandler
|
||||
defer oldHandler.Release()
|
||||
}
|
||||
onOpenHandler := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
|
||||
go f()
|
||||
return js.Undefined()
|
||||
})
|
||||
d.onOpenHandler = &onOpenHandler
|
||||
d.underlying.Set("onopen", onOpenHandler)
|
||||
}
|
||||
|
||||
// OnClose sets an event handler which is invoked when
|
||||
// the underlying data transport has been closed.
|
||||
func (d *DataChannel) OnClose(f func()) {
|
||||
if d.onCloseHandler != nil {
|
||||
oldHandler := d.onCloseHandler
|
||||
defer oldHandler.Release()
|
||||
}
|
||||
onCloseHandler := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
|
||||
go f()
|
||||
return js.Undefined()
|
||||
})
|
||||
d.onCloseHandler = &onCloseHandler
|
||||
d.underlying.Set("onclose", onCloseHandler)
|
||||
}
|
||||
|
||||
// OnMessage sets an event handler which is invoked on a binary message arrival
|
||||
// from a remote peer. Note that browsers may place limitations on message size.
|
||||
func (d *DataChannel) OnMessage(f func(msg DataChannelMessage)) {
|
||||
if d.onMessageHandler != nil {
|
||||
oldHandler := d.onMessageHandler
|
||||
defer oldHandler.Release()
|
||||
}
|
||||
onMessageHandler := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
|
||||
// pion/webrtc/projects/15
|
||||
data := args[0].Get("data")
|
||||
go func() {
|
||||
// valueToDataChannelMessage may block when handling 'Blob' data
|
||||
// so we need to call it from a new routine. See:
|
||||
// https://pkg.go.dev/syscall/js#FuncOf
|
||||
msg := valueToDataChannelMessage(data)
|
||||
f(msg)
|
||||
}()
|
||||
return js.Undefined()
|
||||
})
|
||||
d.onMessageHandler = &onMessageHandler
|
||||
d.underlying.Set("onmessage", onMessageHandler)
|
||||
}
|
||||
|
||||
// Send sends the binary message to the DataChannel peer
|
||||
func (d *DataChannel) Send(data []byte) (err error) {
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
err = recoveryToError(e)
|
||||
}
|
||||
}()
|
||||
array := js.Global().Get("Uint8Array").New(len(data))
|
||||
js.CopyBytesToJS(array, data)
|
||||
d.underlying.Call("send", array)
|
||||
return nil
|
||||
}
|
||||
|
||||
// SendText sends the text message to the DataChannel peer
|
||||
func (d *DataChannel) SendText(s string) (err error) {
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
err = recoveryToError(e)
|
||||
}
|
||||
}()
|
||||
d.underlying.Call("send", s)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Detach allows you to detach the underlying datachannel. This provides
|
||||
// an idiomatic API to work with, however it disables the OnMessage callback.
|
||||
// Before calling Detach you have to enable this behavior by calling
|
||||
// webrtc.DetachDataChannels(). Combining detached and normal data channels
|
||||
// is not supported.
|
||||
// Please reffer to the data-channels-detach example and the
|
||||
// pion/datachannel documentation for the correct way to handle the
|
||||
// resulting DataChannel object.
|
||||
func (d *DataChannel) Detach() (datachannel.ReadWriteCloser, error) {
|
||||
if !d.api.settingEngine.detach.DataChannels {
|
||||
return nil, fmt.Errorf("enable detaching by calling webrtc.DetachDataChannels()")
|
||||
}
|
||||
|
||||
detached := newDetachedDataChannel(d)
|
||||
return detached, nil
|
||||
}
|
||||
|
||||
// Close Closes the DataChannel. It may be called regardless of whether
|
||||
// the DataChannel object was created by this peer or the remote peer.
|
||||
func (d *DataChannel) Close() (err error) {
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
err = recoveryToError(e)
|
||||
}
|
||||
}()
|
||||
|
||||
d.underlying.Call("close")
|
||||
|
||||
// Release any handlers as required by the syscall/js API.
|
||||
if d.onOpenHandler != nil {
|
||||
d.onOpenHandler.Release()
|
||||
}
|
||||
if d.onCloseHandler != nil {
|
||||
d.onCloseHandler.Release()
|
||||
}
|
||||
if d.onMessageHandler != nil {
|
||||
d.onMessageHandler.Release()
|
||||
}
|
||||
if d.onBufferedAmountLow != nil {
|
||||
d.onBufferedAmountLow.Release()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Label represents a label that can be used to distinguish this
|
||||
// DataChannel object from other DataChannel objects. Scripts are
|
||||
// allowed to create multiple DataChannel objects with the same label.
|
||||
func (d *DataChannel) Label() string {
|
||||
return d.underlying.Get("label").String()
|
||||
}
|
||||
|
||||
// Ordered represents if the DataChannel is ordered, and false if
|
||||
// out-of-order delivery is allowed.
|
||||
func (d *DataChannel) Ordered() bool {
|
||||
ordered := d.underlying.Get("ordered")
|
||||
if ordered.IsUndefined() {
|
||||
return true // default is true
|
||||
}
|
||||
return ordered.Bool()
|
||||
}
|
||||
|
||||
// MaxPacketLifeTime represents the length of the time window (msec) during
|
||||
// which transmissions and retransmissions may occur in unreliable mode.
|
||||
func (d *DataChannel) MaxPacketLifeTime() *uint16 {
|
||||
if !d.underlying.Get("maxPacketLifeTime").IsUndefined() {
|
||||
return valueToUint16Pointer(d.underlying.Get("maxPacketLifeTime"))
|
||||
}
|
||||
|
||||
// See https://bugs.chromium.org/p/chromium/issues/detail?id=696681
|
||||
// Chrome calls this "maxRetransmitTime"
|
||||
return valueToUint16Pointer(d.underlying.Get("maxRetransmitTime"))
|
||||
}
|
||||
|
||||
// MaxRetransmits represents the maximum number of retransmissions that are
|
||||
// attempted in unreliable mode.
|
||||
func (d *DataChannel) MaxRetransmits() *uint16 {
|
||||
return valueToUint16Pointer(d.underlying.Get("maxRetransmits"))
|
||||
}
|
||||
|
||||
// Protocol represents the name of the sub-protocol used with this
|
||||
// DataChannel.
|
||||
func (d *DataChannel) Protocol() string {
|
||||
return d.underlying.Get("protocol").String()
|
||||
}
|
||||
|
||||
// Negotiated represents whether this DataChannel was negotiated by the
|
||||
// application (true), or not (false).
|
||||
func (d *DataChannel) Negotiated() bool {
|
||||
return d.underlying.Get("negotiated").Bool()
|
||||
}
|
||||
|
||||
// ID represents the ID for this DataChannel. The value is initially
|
||||
// null, which is what will be returned if the ID was not provided at
|
||||
// channel creation time. Otherwise, it will return the ID that was either
|
||||
// selected by the script or generated. After the ID is set to a non-null
|
||||
// value, it will not change.
|
||||
func (d *DataChannel) ID() *uint16 {
|
||||
return valueToUint16Pointer(d.underlying.Get("id"))
|
||||
}
|
||||
|
||||
// ReadyState represents the state of the DataChannel object.
|
||||
func (d *DataChannel) ReadyState() DataChannelState {
|
||||
return newDataChannelState(d.underlying.Get("readyState").String())
|
||||
}
|
||||
|
||||
// BufferedAmount represents the number of bytes of application data
|
||||
// (UTF-8 text and binary data) that have been queued using send(). Even
|
||||
// though the data transmission can occur in parallel, the returned value
|
||||
// MUST NOT be decreased before the current task yielded back to the event
|
||||
// loop to prevent race conditions. The value does not include framing
|
||||
// overhead incurred by the protocol, or buffering done by the operating
|
||||
// system or network hardware. The value of BufferedAmount slot will only
|
||||
// increase with each call to the send() method as long as the ReadyState is
|
||||
// open; however, BufferedAmount does not reset to zero once the channel
|
||||
// closes.
|
||||
func (d *DataChannel) BufferedAmount() uint64 {
|
||||
return uint64(d.underlying.Get("bufferedAmount").Int())
|
||||
}
|
||||
|
||||
// BufferedAmountLowThreshold represents the threshold at which the
|
||||
// bufferedAmount is considered to be low. When the bufferedAmount decreases
|
||||
// from above this threshold to equal or below it, the bufferedamountlow
|
||||
// event fires. BufferedAmountLowThreshold is initially zero on each new
|
||||
// DataChannel, but the application may change its value at any time.
|
||||
func (d *DataChannel) BufferedAmountLowThreshold() uint64 {
|
||||
return uint64(d.underlying.Get("bufferedAmountLowThreshold").Int())
|
||||
}
|
||||
|
||||
// SetBufferedAmountLowThreshold is used to update the threshold.
|
||||
// See BufferedAmountLowThreshold().
|
||||
func (d *DataChannel) SetBufferedAmountLowThreshold(th uint64) {
|
||||
d.underlying.Set("bufferedAmountLowThreshold", th)
|
||||
}
|
||||
|
||||
// OnBufferedAmountLow sets an event handler which is invoked when
|
||||
// the number of bytes of outgoing data becomes lower than the
|
||||
// BufferedAmountLowThreshold.
|
||||
func (d *DataChannel) OnBufferedAmountLow(f func()) {
|
||||
if d.onBufferedAmountLow != nil {
|
||||
oldHandler := d.onBufferedAmountLow
|
||||
defer oldHandler.Release()
|
||||
}
|
||||
onBufferedAmountLow := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
|
||||
go f()
|
||||
return js.Undefined()
|
||||
})
|
||||
d.onBufferedAmountLow = &onBufferedAmountLow
|
||||
d.underlying.Set("onbufferedamountlow", onBufferedAmountLow)
|
||||
}
|
||||
|
||||
// valueToDataChannelMessage converts the given value to a DataChannelMessage.
|
||||
// val should be obtained from MessageEvent.data where MessageEvent is received
|
||||
// via the RTCDataChannel.onmessage callback.
|
||||
func valueToDataChannelMessage(val js.Value) DataChannelMessage {
|
||||
// If val is of type string, the conversion is straightforward.
|
||||
if val.Type() == js.TypeString {
|
||||
return DataChannelMessage{
|
||||
IsString: true,
|
||||
Data: []byte(val.String()),
|
||||
}
|
||||
}
|
||||
|
||||
// For other types, we need to first determine val.constructor.name.
|
||||
constructorName := val.Get("constructor").Get("name").String()
|
||||
var data []byte
|
||||
switch constructorName {
|
||||
case "Uint8Array":
|
||||
// We can easily convert Uint8Array to []byte
|
||||
data = uint8ArrayValueToBytes(val)
|
||||
case "Blob":
|
||||
// Convert the Blob to an ArrayBuffer and then convert the ArrayBuffer
|
||||
// to a Uint8Array.
|
||||
// See: https://developer.mozilla.org/en-US/docs/Web/API/Blob
|
||||
|
||||
// The JavaScript API for reading from the Blob is asynchronous. We use a
|
||||
// channel to signal when reading is done.
|
||||
reader := js.Global().Get("FileReader").New()
|
||||
doneChan := make(chan struct{})
|
||||
reader.Call("addEventListener", "loadend", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
|
||||
go func() {
|
||||
// Signal that the FileReader is done reading/loading by sending through
|
||||
// the doneChan.
|
||||
doneChan <- struct{}{}
|
||||
}()
|
||||
return js.Undefined()
|
||||
}))
|
||||
|
||||
reader.Call("readAsArrayBuffer", val)
|
||||
|
||||
// Wait for the FileReader to finish reading/loading.
|
||||
<-doneChan
|
||||
|
||||
// At this point buffer.result is a typed array, which we know how to
|
||||
// handle.
|
||||
buffer := reader.Get("result")
|
||||
uint8Array := js.Global().Get("Uint8Array").New(buffer)
|
||||
data = uint8ArrayValueToBytes(uint8Array)
|
||||
default:
|
||||
// Assume we have an ArrayBufferView type which we can convert to a
|
||||
// Uint8Array in JavaScript.
|
||||
// See: https://developer.mozilla.org/en-US/docs/Web/API/ArrayBufferView
|
||||
uint8Array := js.Global().Get("Uint8Array").New(val)
|
||||
data = uint8ArrayValueToBytes(uint8Array)
|
||||
}
|
||||
|
||||
return DataChannelMessage{
|
||||
IsString: false,
|
||||
Data: data,
|
||||
}
|
||||
}
|
||||
75
server/vendor/github.com/pion/webrtc/v3/datachannel_js_detach.go
generated
vendored
Normal file
75
server/vendor/github.com/pion/webrtc/v3/datachannel_js_detach.go
generated
vendored
Normal file
@@ -0,0 +1,75 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
//go:build js && wasm
|
||||
// +build js,wasm
|
||||
|
||||
package webrtc
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
type detachedDataChannel struct {
|
||||
dc *DataChannel
|
||||
|
||||
read chan DataChannelMessage
|
||||
done chan struct{}
|
||||
}
|
||||
|
||||
func newDetachedDataChannel(dc *DataChannel) *detachedDataChannel {
|
||||
read := make(chan DataChannelMessage)
|
||||
done := make(chan struct{})
|
||||
|
||||
// Wire up callbacks
|
||||
dc.OnMessage(func(msg DataChannelMessage) {
|
||||
read <- msg // pion/webrtc/projects/15
|
||||
})
|
||||
|
||||
// pion/webrtc/projects/15
|
||||
|
||||
return &detachedDataChannel{
|
||||
dc: dc,
|
||||
read: read,
|
||||
done: done,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *detachedDataChannel) Read(p []byte) (int, error) {
|
||||
n, _, err := c.ReadDataChannel(p)
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (c *detachedDataChannel) ReadDataChannel(p []byte) (int, bool, error) {
|
||||
select {
|
||||
case <-c.done:
|
||||
return 0, false, errors.New("Reader closed")
|
||||
case msg := <-c.read:
|
||||
n := copy(p, msg.Data)
|
||||
if n < len(msg.Data) {
|
||||
return n, msg.IsString, errors.New("Read buffer to small")
|
||||
}
|
||||
return n, msg.IsString, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *detachedDataChannel) Write(p []byte) (n int, err error) {
|
||||
return c.WriteDataChannel(p, false)
|
||||
}
|
||||
|
||||
func (c *detachedDataChannel) WriteDataChannel(p []byte, isString bool) (n int, err error) {
|
||||
if isString {
|
||||
err = c.dc.SendText(string(p))
|
||||
return len(p), err
|
||||
}
|
||||
|
||||
err = c.dc.Send(p)
|
||||
|
||||
return len(p), err
|
||||
}
|
||||
|
||||
func (c *detachedDataChannel) Close() error {
|
||||
close(c.done)
|
||||
|
||||
return c.dc.Close()
|
||||
}
|
||||
36
server/vendor/github.com/pion/webrtc/v3/datachannelinit.go
generated
vendored
Normal file
36
server/vendor/github.com/pion/webrtc/v3/datachannelinit.go
generated
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package webrtc
|
||||
|
||||
// DataChannelInit can be used to configure properties of the underlying
|
||||
// channel such as data reliability.
|
||||
type DataChannelInit struct {
|
||||
// Ordered indicates if data is allowed to be delivered out of order. The
|
||||
// default value of true, guarantees that data will be delivered in order.
|
||||
Ordered *bool
|
||||
|
||||
// MaxPacketLifeTime limits the time (in milliseconds) during which the
|
||||
// channel will transmit or retransmit data if not acknowledged. This value
|
||||
// may be clamped if it exceeds the maximum value supported.
|
||||
MaxPacketLifeTime *uint16
|
||||
|
||||
// MaxRetransmits limits the number of times a channel will retransmit data
|
||||
// if not successfully delivered. This value may be clamped if it exceeds
|
||||
// the maximum value supported.
|
||||
MaxRetransmits *uint16
|
||||
|
||||
// Protocol describes the subprotocol name used for this channel.
|
||||
Protocol *string
|
||||
|
||||
// Negotiated describes if the data channel is created by the local peer or
|
||||
// the remote peer. The default value of false tells the user agent to
|
||||
// announce the channel in-band and instruct the other peer to dispatch a
|
||||
// corresponding DataChannel. If set to true, it is up to the application
|
||||
// to negotiate the channel and create an DataChannel with the same id
|
||||
// at the other peer.
|
||||
Negotiated *bool
|
||||
|
||||
// ID overrides the default selection of ID for this channel.
|
||||
ID *uint16
|
||||
}
|
||||
13
server/vendor/github.com/pion/webrtc/v3/datachannelmessage.go
generated
vendored
Normal file
13
server/vendor/github.com/pion/webrtc/v3/datachannelmessage.go
generated
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package webrtc
|
||||
|
||||
// DataChannelMessage represents a message received from the
|
||||
// data channel. IsString will be set to true if the incoming
|
||||
// message is of the string type. Otherwise the message is of
|
||||
// a binary type.
|
||||
type DataChannelMessage struct {
|
||||
IsString bool
|
||||
Data []byte
|
||||
}
|
||||
15
server/vendor/github.com/pion/webrtc/v3/datachannelparameters.go
generated
vendored
Normal file
15
server/vendor/github.com/pion/webrtc/v3/datachannelparameters.go
generated
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package webrtc
|
||||
|
||||
// DataChannelParameters describes the configuration of the DataChannel.
|
||||
type DataChannelParameters struct {
|
||||
Label string `json:"label"`
|
||||
Protocol string `json:"protocol"`
|
||||
ID *uint16 `json:"id"`
|
||||
Ordered bool `json:"ordered"`
|
||||
MaxPacketLifeTime *uint16 `json:"maxPacketLifeTime"`
|
||||
MaxRetransmits *uint16 `json:"maxRetransmits"`
|
||||
Negotiated bool `json:"negotiated"`
|
||||
}
|
||||
75
server/vendor/github.com/pion/webrtc/v3/datachannelstate.go
generated
vendored
Normal file
75
server/vendor/github.com/pion/webrtc/v3/datachannelstate.go
generated
vendored
Normal file
@@ -0,0 +1,75 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package webrtc
|
||||
|
||||
// DataChannelState indicates the state of a data channel.
|
||||
type DataChannelState int
|
||||
|
||||
const (
|
||||
// DataChannelStateConnecting indicates that the data channel is being
|
||||
// established. This is the initial state of DataChannel, whether created
|
||||
// with CreateDataChannel, or dispatched as a part of an DataChannelEvent.
|
||||
DataChannelStateConnecting DataChannelState = iota + 1
|
||||
|
||||
// DataChannelStateOpen indicates that the underlying data transport is
|
||||
// established and communication is possible.
|
||||
DataChannelStateOpen
|
||||
|
||||
// DataChannelStateClosing indicates that the procedure to close down the
|
||||
// underlying data transport has started.
|
||||
DataChannelStateClosing
|
||||
|
||||
// DataChannelStateClosed indicates that the underlying data transport
|
||||
// has been closed or could not be established.
|
||||
DataChannelStateClosed
|
||||
)
|
||||
|
||||
// This is done this way because of a linter.
|
||||
const (
|
||||
dataChannelStateConnectingStr = "connecting"
|
||||
dataChannelStateOpenStr = "open"
|
||||
dataChannelStateClosingStr = "closing"
|
||||
dataChannelStateClosedStr = "closed"
|
||||
)
|
||||
|
||||
func newDataChannelState(raw string) DataChannelState {
|
||||
switch raw {
|
||||
case dataChannelStateConnectingStr:
|
||||
return DataChannelStateConnecting
|
||||
case dataChannelStateOpenStr:
|
||||
return DataChannelStateOpen
|
||||
case dataChannelStateClosingStr:
|
||||
return DataChannelStateClosing
|
||||
case dataChannelStateClosedStr:
|
||||
return DataChannelStateClosed
|
||||
default:
|
||||
return DataChannelState(Unknown)
|
||||
}
|
||||
}
|
||||
|
||||
func (t DataChannelState) String() string {
|
||||
switch t {
|
||||
case DataChannelStateConnecting:
|
||||
return dataChannelStateConnectingStr
|
||||
case DataChannelStateOpen:
|
||||
return dataChannelStateOpenStr
|
||||
case DataChannelStateClosing:
|
||||
return dataChannelStateClosingStr
|
||||
case DataChannelStateClosed:
|
||||
return dataChannelStateClosedStr
|
||||
default:
|
||||
return ErrUnknownType.Error()
|
||||
}
|
||||
}
|
||||
|
||||
// MarshalText implements encoding.TextMarshaler
|
||||
func (t DataChannelState) MarshalText() ([]byte, error) {
|
||||
return []byte(t.String()), nil
|
||||
}
|
||||
|
||||
// UnmarshalText implements encoding.TextUnmarshaler
|
||||
func (t *DataChannelState) UnmarshalText(b []byte) error {
|
||||
*t = newDataChannelState(string(b))
|
||||
return nil
|
||||
}
|
||||
17
server/vendor/github.com/pion/webrtc/v3/dtlsfingerprint.go
generated
vendored
Normal file
17
server/vendor/github.com/pion/webrtc/v3/dtlsfingerprint.go
generated
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package webrtc
|
||||
|
||||
// DTLSFingerprint specifies the hash function algorithm and certificate
|
||||
// fingerprint as described in https://tools.ietf.org/html/rfc4572.
|
||||
type DTLSFingerprint struct {
|
||||
// Algorithm specifies one of the the hash function algorithms defined in
|
||||
// the 'Hash function Textual Names' registry.
|
||||
Algorithm string `json:"algorithm"`
|
||||
|
||||
// Value specifies the value of the certificate fingerprint in lowercase
|
||||
// hex string as expressed utilizing the syntax of 'fingerprint' in
|
||||
// https://tools.ietf.org/html/rfc4572#section-5.
|
||||
Value string `json:"value"`
|
||||
}
|
||||
10
server/vendor/github.com/pion/webrtc/v3/dtlsparameters.go
generated
vendored
Normal file
10
server/vendor/github.com/pion/webrtc/v3/dtlsparameters.go
generated
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package webrtc
|
||||
|
||||
// DTLSParameters holds information relating to DTLS configuration.
|
||||
type DTLSParameters struct {
|
||||
Role DTLSRole `json:"role"`
|
||||
Fingerprints []DTLSFingerprint `json:"fingerprints"`
|
||||
}
|
||||
95
server/vendor/github.com/pion/webrtc/v3/dtlsrole.go
generated
vendored
Normal file
95
server/vendor/github.com/pion/webrtc/v3/dtlsrole.go
generated
vendored
Normal file
@@ -0,0 +1,95 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package webrtc
|
||||
|
||||
import (
|
||||
"github.com/pion/sdp/v3"
|
||||
)
|
||||
|
||||
// DTLSRole indicates the role of the DTLS transport.
|
||||
type DTLSRole byte
|
||||
|
||||
const (
|
||||
// DTLSRoleAuto defines the DTLS role is determined based on
|
||||
// the resolved ICE role: the ICE controlled role acts as the DTLS
|
||||
// client and the ICE controlling role acts as the DTLS server.
|
||||
DTLSRoleAuto DTLSRole = iota + 1
|
||||
|
||||
// DTLSRoleClient defines the DTLS client role.
|
||||
DTLSRoleClient
|
||||
|
||||
// DTLSRoleServer defines the DTLS server role.
|
||||
DTLSRoleServer
|
||||
)
|
||||
|
||||
const (
|
||||
// https://tools.ietf.org/html/rfc5763
|
||||
/*
|
||||
The answerer MUST use either a
|
||||
setup attribute value of setup:active or setup:passive. Note that
|
||||
if the answerer uses setup:passive, then the DTLS handshake will
|
||||
not begin until the answerer is received, which adds additional
|
||||
latency. setup:active allows the answer and the DTLS handshake to
|
||||
occur in parallel. Thus, setup:active is RECOMMENDED.
|
||||
*/
|
||||
defaultDtlsRoleAnswer = DTLSRoleClient
|
||||
/*
|
||||
The endpoint that is the offerer MUST use the setup attribute
|
||||
value of setup:actpass and be prepared to receive a client_hello
|
||||
before it receives the answer.
|
||||
*/
|
||||
defaultDtlsRoleOffer = DTLSRoleAuto
|
||||
)
|
||||
|
||||
func (r DTLSRole) String() string {
|
||||
switch r {
|
||||
case DTLSRoleAuto:
|
||||
return "auto"
|
||||
case DTLSRoleClient:
|
||||
return "client"
|
||||
case DTLSRoleServer:
|
||||
return "server"
|
||||
default:
|
||||
return unknownStr
|
||||
}
|
||||
}
|
||||
|
||||
// Iterate a SessionDescription from a remote to determine if an explicit
|
||||
// role can been determined from it. The decision is made from the first role we we parse.
|
||||
// If no role can be found we return DTLSRoleAuto
|
||||
func dtlsRoleFromRemoteSDP(sessionDescription *sdp.SessionDescription) DTLSRole {
|
||||
if sessionDescription == nil {
|
||||
return DTLSRoleAuto
|
||||
}
|
||||
|
||||
for _, mediaSection := range sessionDescription.MediaDescriptions {
|
||||
for _, attribute := range mediaSection.Attributes {
|
||||
if attribute.Key == "setup" {
|
||||
switch attribute.Value {
|
||||
case sdp.ConnectionRoleActive.String():
|
||||
return DTLSRoleClient
|
||||
case sdp.ConnectionRolePassive.String():
|
||||
return DTLSRoleServer
|
||||
default:
|
||||
return DTLSRoleAuto
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return DTLSRoleAuto
|
||||
}
|
||||
|
||||
func connectionRoleFromDtlsRole(d DTLSRole) sdp.ConnectionRole {
|
||||
switch d {
|
||||
case DTLSRoleClient:
|
||||
return sdp.ConnectionRoleActive
|
||||
case DTLSRoleServer:
|
||||
return sdp.ConnectionRolePassive
|
||||
case DTLSRoleAuto:
|
||||
return sdp.ConnectionRoleActpass
|
||||
default:
|
||||
return sdp.ConnectionRole(0)
|
||||
}
|
||||
}
|
||||
522
server/vendor/github.com/pion/webrtc/v3/dtlstransport.go
generated
vendored
Normal file
522
server/vendor/github.com/pion/webrtc/v3/dtlstransport.go
generated
vendored
Normal file
@@ -0,0 +1,522 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
//go:build !js
|
||||
// +build !js
|
||||
|
||||
package webrtc
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/pion/dtls/v2"
|
||||
"github.com/pion/dtls/v2/pkg/crypto/fingerprint"
|
||||
"github.com/pion/interceptor"
|
||||
"github.com/pion/logging"
|
||||
"github.com/pion/rtcp"
|
||||
"github.com/pion/srtp/v2"
|
||||
"github.com/pion/webrtc/v3/internal/mux"
|
||||
"github.com/pion/webrtc/v3/internal/util"
|
||||
"github.com/pion/webrtc/v3/pkg/rtcerr"
|
||||
)
|
||||
|
||||
// DTLSTransport allows an application access to information about the DTLS
|
||||
// transport over which RTP and RTCP packets are sent and received by
|
||||
// RTPSender and RTPReceiver, as well other data such as SCTP packets sent
|
||||
// and received by data channels.
|
||||
type DTLSTransport struct {
|
||||
lock sync.RWMutex
|
||||
|
||||
iceTransport *ICETransport
|
||||
certificates []Certificate
|
||||
remoteParameters DTLSParameters
|
||||
remoteCertificate []byte
|
||||
state DTLSTransportState
|
||||
srtpProtectionProfile srtp.ProtectionProfile
|
||||
|
||||
onStateChangeHandler func(DTLSTransportState)
|
||||
|
||||
conn *dtls.Conn
|
||||
|
||||
srtpSession, srtcpSession atomic.Value
|
||||
srtpEndpoint, srtcpEndpoint *mux.Endpoint
|
||||
simulcastStreams []simulcastStreamPair
|
||||
srtpReady chan struct{}
|
||||
|
||||
dtlsMatcher mux.MatchFunc
|
||||
|
||||
api *API
|
||||
log logging.LeveledLogger
|
||||
}
|
||||
|
||||
type simulcastStreamPair struct {
|
||||
srtp *srtp.ReadStreamSRTP
|
||||
srtcp *srtp.ReadStreamSRTCP
|
||||
}
|
||||
|
||||
// NewDTLSTransport creates a new DTLSTransport.
|
||||
// This constructor is part of the ORTC API. It is not
|
||||
// meant to be used together with the basic WebRTC API.
|
||||
func (api *API) NewDTLSTransport(transport *ICETransport, certificates []Certificate) (*DTLSTransport, error) {
|
||||
t := &DTLSTransport{
|
||||
iceTransport: transport,
|
||||
api: api,
|
||||
state: DTLSTransportStateNew,
|
||||
dtlsMatcher: mux.MatchDTLS,
|
||||
srtpReady: make(chan struct{}),
|
||||
log: api.settingEngine.LoggerFactory.NewLogger("DTLSTransport"),
|
||||
}
|
||||
|
||||
if len(certificates) > 0 {
|
||||
now := time.Now()
|
||||
for _, x509Cert := range certificates {
|
||||
if !x509Cert.Expires().IsZero() && now.After(x509Cert.Expires()) {
|
||||
return nil, &rtcerr.InvalidAccessError{Err: ErrCertificateExpired}
|
||||
}
|
||||
t.certificates = append(t.certificates, x509Cert)
|
||||
}
|
||||
} else {
|
||||
sk, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
if err != nil {
|
||||
return nil, &rtcerr.UnknownError{Err: err}
|
||||
}
|
||||
certificate, err := GenerateCertificate(sk)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
t.certificates = []Certificate{*certificate}
|
||||
}
|
||||
|
||||
return t, nil
|
||||
}
|
||||
|
||||
// ICETransport returns the currently-configured *ICETransport or nil
|
||||
// if one has not been configured
|
||||
func (t *DTLSTransport) ICETransport() *ICETransport {
|
||||
t.lock.RLock()
|
||||
defer t.lock.RUnlock()
|
||||
return t.iceTransport
|
||||
}
|
||||
|
||||
// onStateChange requires the caller holds the lock
|
||||
func (t *DTLSTransport) onStateChange(state DTLSTransportState) {
|
||||
t.state = state
|
||||
handler := t.onStateChangeHandler
|
||||
if handler != nil {
|
||||
handler(state)
|
||||
}
|
||||
}
|
||||
|
||||
// OnStateChange sets a handler that is fired when the DTLS
|
||||
// connection state changes.
|
||||
func (t *DTLSTransport) OnStateChange(f func(DTLSTransportState)) {
|
||||
t.lock.Lock()
|
||||
defer t.lock.Unlock()
|
||||
t.onStateChangeHandler = f
|
||||
}
|
||||
|
||||
// State returns the current dtls transport state.
|
||||
func (t *DTLSTransport) State() DTLSTransportState {
|
||||
t.lock.RLock()
|
||||
defer t.lock.RUnlock()
|
||||
return t.state
|
||||
}
|
||||
|
||||
// WriteRTCP sends a user provided RTCP packet to the connected peer. If no peer is connected the
|
||||
// packet is discarded.
|
||||
func (t *DTLSTransport) WriteRTCP(pkts []rtcp.Packet) (int, error) {
|
||||
raw, err := rtcp.Marshal(pkts)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
srtcpSession, err := t.getSRTCPSession()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
writeStream, err := srtcpSession.OpenWriteStream()
|
||||
if err != nil {
|
||||
// nolint
|
||||
return 0, fmt.Errorf("%w: %v", errPeerConnWriteRTCPOpenWriteStream, err)
|
||||
}
|
||||
|
||||
return writeStream.Write(raw)
|
||||
}
|
||||
|
||||
// GetLocalParameters returns the DTLS parameters of the local DTLSTransport upon construction.
|
||||
func (t *DTLSTransport) GetLocalParameters() (DTLSParameters, error) {
|
||||
fingerprints := []DTLSFingerprint{}
|
||||
|
||||
for _, c := range t.certificates {
|
||||
prints, err := c.GetFingerprints()
|
||||
if err != nil {
|
||||
return DTLSParameters{}, err
|
||||
}
|
||||
|
||||
fingerprints = append(fingerprints, prints...)
|
||||
}
|
||||
|
||||
return DTLSParameters{
|
||||
Role: DTLSRoleAuto, // always returns the default role
|
||||
Fingerprints: fingerprints,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetRemoteCertificate returns the certificate chain in use by the remote side
|
||||
// returns an empty list prior to selection of the remote certificate
|
||||
func (t *DTLSTransport) GetRemoteCertificate() []byte {
|
||||
t.lock.RLock()
|
||||
defer t.lock.RUnlock()
|
||||
return t.remoteCertificate
|
||||
}
|
||||
|
||||
func (t *DTLSTransport) startSRTP() error {
|
||||
srtpConfig := &srtp.Config{
|
||||
Profile: t.srtpProtectionProfile,
|
||||
BufferFactory: t.api.settingEngine.BufferFactory,
|
||||
LoggerFactory: t.api.settingEngine.LoggerFactory,
|
||||
}
|
||||
if t.api.settingEngine.replayProtection.SRTP != nil {
|
||||
srtpConfig.RemoteOptions = append(
|
||||
srtpConfig.RemoteOptions,
|
||||
srtp.SRTPReplayProtection(*t.api.settingEngine.replayProtection.SRTP),
|
||||
)
|
||||
}
|
||||
|
||||
if t.api.settingEngine.disableSRTPReplayProtection {
|
||||
srtpConfig.RemoteOptions = append(
|
||||
srtpConfig.RemoteOptions,
|
||||
srtp.SRTPNoReplayProtection(),
|
||||
)
|
||||
}
|
||||
|
||||
if t.api.settingEngine.replayProtection.SRTCP != nil {
|
||||
srtpConfig.RemoteOptions = append(
|
||||
srtpConfig.RemoteOptions,
|
||||
srtp.SRTCPReplayProtection(*t.api.settingEngine.replayProtection.SRTCP),
|
||||
)
|
||||
}
|
||||
|
||||
if t.api.settingEngine.disableSRTCPReplayProtection {
|
||||
srtpConfig.RemoteOptions = append(
|
||||
srtpConfig.RemoteOptions,
|
||||
srtp.SRTCPNoReplayProtection(),
|
||||
)
|
||||
}
|
||||
|
||||
connState := t.conn.ConnectionState()
|
||||
err := srtpConfig.ExtractSessionKeysFromDTLS(&connState, t.role() == DTLSRoleClient)
|
||||
if err != nil {
|
||||
// nolint
|
||||
return fmt.Errorf("%w: %v", errDtlsKeyExtractionFailed, err)
|
||||
}
|
||||
|
||||
srtpSession, err := srtp.NewSessionSRTP(t.srtpEndpoint, srtpConfig)
|
||||
if err != nil {
|
||||
// nolint
|
||||
return fmt.Errorf("%w: %v", errFailedToStartSRTP, err)
|
||||
}
|
||||
|
||||
srtcpSession, err := srtp.NewSessionSRTCP(t.srtcpEndpoint, srtpConfig)
|
||||
if err != nil {
|
||||
// nolint
|
||||
return fmt.Errorf("%w: %v", errFailedToStartSRTCP, err)
|
||||
}
|
||||
|
||||
t.srtpSession.Store(srtpSession)
|
||||
t.srtcpSession.Store(srtcpSession)
|
||||
close(t.srtpReady)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *DTLSTransport) getSRTPSession() (*srtp.SessionSRTP, error) {
|
||||
if value, ok := t.srtpSession.Load().(*srtp.SessionSRTP); ok {
|
||||
return value, nil
|
||||
}
|
||||
|
||||
return nil, errDtlsTransportNotStarted
|
||||
}
|
||||
|
||||
func (t *DTLSTransport) getSRTCPSession() (*srtp.SessionSRTCP, error) {
|
||||
if value, ok := t.srtcpSession.Load().(*srtp.SessionSRTCP); ok {
|
||||
return value, nil
|
||||
}
|
||||
|
||||
return nil, errDtlsTransportNotStarted
|
||||
}
|
||||
|
||||
func (t *DTLSTransport) role() DTLSRole {
|
||||
// If remote has an explicit role use the inverse
|
||||
switch t.remoteParameters.Role {
|
||||
case DTLSRoleClient:
|
||||
return DTLSRoleServer
|
||||
case DTLSRoleServer:
|
||||
return DTLSRoleClient
|
||||
default:
|
||||
}
|
||||
|
||||
// If SettingEngine has an explicit role
|
||||
switch t.api.settingEngine.answeringDTLSRole {
|
||||
case DTLSRoleServer:
|
||||
return DTLSRoleServer
|
||||
case DTLSRoleClient:
|
||||
return DTLSRoleClient
|
||||
default:
|
||||
}
|
||||
|
||||
// Remote was auto and no explicit role was configured via SettingEngine
|
||||
if t.iceTransport.Role() == ICERoleControlling {
|
||||
return DTLSRoleServer
|
||||
}
|
||||
return defaultDtlsRoleAnswer
|
||||
}
|
||||
|
||||
// Start DTLS transport negotiation with the parameters of the remote DTLS transport
|
||||
func (t *DTLSTransport) Start(remoteParameters DTLSParameters) error {
|
||||
// Take lock and prepare connection, we must not hold the lock
|
||||
// when connecting
|
||||
prepareTransport := func() (DTLSRole, *dtls.Config, error) {
|
||||
t.lock.Lock()
|
||||
defer t.lock.Unlock()
|
||||
|
||||
if err := t.ensureICEConn(); err != nil {
|
||||
return DTLSRole(0), nil, err
|
||||
}
|
||||
|
||||
if t.state != DTLSTransportStateNew {
|
||||
return DTLSRole(0), nil, &rtcerr.InvalidStateError{Err: fmt.Errorf("%w: %s", errInvalidDTLSStart, t.state)}
|
||||
}
|
||||
|
||||
t.srtpEndpoint = t.iceTransport.newEndpoint(mux.MatchSRTP)
|
||||
t.srtcpEndpoint = t.iceTransport.newEndpoint(mux.MatchSRTCP)
|
||||
t.remoteParameters = remoteParameters
|
||||
|
||||
cert := t.certificates[0]
|
||||
t.onStateChange(DTLSTransportStateConnecting)
|
||||
|
||||
return t.role(), &dtls.Config{
|
||||
Certificates: []tls.Certificate{
|
||||
{
|
||||
Certificate: [][]byte{cert.x509Cert.Raw},
|
||||
PrivateKey: cert.privateKey,
|
||||
},
|
||||
},
|
||||
SRTPProtectionProfiles: func() []dtls.SRTPProtectionProfile {
|
||||
if len(t.api.settingEngine.srtpProtectionProfiles) > 0 {
|
||||
return t.api.settingEngine.srtpProtectionProfiles
|
||||
}
|
||||
|
||||
return defaultSrtpProtectionProfiles()
|
||||
}(),
|
||||
ClientAuth: dtls.RequireAnyClientCert,
|
||||
LoggerFactory: t.api.settingEngine.LoggerFactory,
|
||||
InsecureSkipVerify: !t.api.settingEngine.dtls.disableInsecureSkipVerify,
|
||||
}, nil
|
||||
}
|
||||
|
||||
var dtlsConn *dtls.Conn
|
||||
dtlsEndpoint := t.iceTransport.newEndpoint(mux.MatchDTLS)
|
||||
role, dtlsConfig, err := prepareTransport()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if t.api.settingEngine.replayProtection.DTLS != nil {
|
||||
dtlsConfig.ReplayProtectionWindow = int(*t.api.settingEngine.replayProtection.DTLS)
|
||||
}
|
||||
|
||||
if t.api.settingEngine.dtls.clientAuth != nil {
|
||||
dtlsConfig.ClientAuth = *t.api.settingEngine.dtls.clientAuth
|
||||
}
|
||||
|
||||
dtlsConfig.FlightInterval = t.api.settingEngine.dtls.retransmissionInterval
|
||||
dtlsConfig.InsecureSkipVerifyHello = t.api.settingEngine.dtls.insecureSkipHelloVerify
|
||||
dtlsConfig.EllipticCurves = t.api.settingEngine.dtls.ellipticCurves
|
||||
dtlsConfig.ConnectContextMaker = t.api.settingEngine.dtls.connectContextMaker
|
||||
dtlsConfig.ExtendedMasterSecret = t.api.settingEngine.dtls.extendedMasterSecret
|
||||
dtlsConfig.ClientCAs = t.api.settingEngine.dtls.clientCAs
|
||||
dtlsConfig.RootCAs = t.api.settingEngine.dtls.rootCAs
|
||||
dtlsConfig.KeyLogWriter = t.api.settingEngine.dtls.keyLogWriter
|
||||
|
||||
// Connect as DTLS Client/Server, function is blocking and we
|
||||
// must not hold the DTLSTransport lock
|
||||
if role == DTLSRoleClient {
|
||||
dtlsConn, err = dtls.Client(dtlsEndpoint, dtlsConfig)
|
||||
} else {
|
||||
dtlsConn, err = dtls.Server(dtlsEndpoint, dtlsConfig)
|
||||
}
|
||||
|
||||
// Re-take the lock, nothing beyond here is blocking
|
||||
t.lock.Lock()
|
||||
defer t.lock.Unlock()
|
||||
|
||||
if err != nil {
|
||||
t.onStateChange(DTLSTransportStateFailed)
|
||||
return err
|
||||
}
|
||||
|
||||
srtpProfile, ok := dtlsConn.SelectedSRTPProtectionProfile()
|
||||
if !ok {
|
||||
t.onStateChange(DTLSTransportStateFailed)
|
||||
return ErrNoSRTPProtectionProfile
|
||||
}
|
||||
|
||||
switch srtpProfile {
|
||||
case dtls.SRTP_AEAD_AES_128_GCM:
|
||||
t.srtpProtectionProfile = srtp.ProtectionProfileAeadAes128Gcm
|
||||
case dtls.SRTP_AEAD_AES_256_GCM:
|
||||
t.srtpProtectionProfile = srtp.ProtectionProfileAeadAes256Gcm
|
||||
case dtls.SRTP_AES128_CM_HMAC_SHA1_80:
|
||||
t.srtpProtectionProfile = srtp.ProtectionProfileAes128CmHmacSha1_80
|
||||
case dtls.SRTP_NULL_HMAC_SHA1_80:
|
||||
t.srtpProtectionProfile = srtp.ProtectionProfileNullHmacSha1_80
|
||||
default:
|
||||
t.onStateChange(DTLSTransportStateFailed)
|
||||
return ErrNoSRTPProtectionProfile
|
||||
}
|
||||
|
||||
// Check the fingerprint if a certificate was exchanged
|
||||
remoteCerts := dtlsConn.ConnectionState().PeerCertificates
|
||||
if len(remoteCerts) == 0 {
|
||||
t.onStateChange(DTLSTransportStateFailed)
|
||||
return errNoRemoteCertificate
|
||||
}
|
||||
t.remoteCertificate = remoteCerts[0]
|
||||
|
||||
if !t.api.settingEngine.disableCertificateFingerprintVerification {
|
||||
parsedRemoteCert, err := x509.ParseCertificate(t.remoteCertificate)
|
||||
if err != nil {
|
||||
if closeErr := dtlsConn.Close(); closeErr != nil {
|
||||
t.log.Error(err.Error())
|
||||
}
|
||||
|
||||
t.onStateChange(DTLSTransportStateFailed)
|
||||
return err
|
||||
}
|
||||
|
||||
if err = t.validateFingerPrint(parsedRemoteCert); err != nil {
|
||||
if closeErr := dtlsConn.Close(); closeErr != nil {
|
||||
t.log.Error(err.Error())
|
||||
}
|
||||
|
||||
t.onStateChange(DTLSTransportStateFailed)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
t.conn = dtlsConn
|
||||
t.onStateChange(DTLSTransportStateConnected)
|
||||
|
||||
return t.startSRTP()
|
||||
}
|
||||
|
||||
// Stop stops and closes the DTLSTransport object.
|
||||
func (t *DTLSTransport) Stop() error {
|
||||
t.lock.Lock()
|
||||
defer t.lock.Unlock()
|
||||
|
||||
// Try closing everything and collect the errors
|
||||
var closeErrs []error
|
||||
|
||||
if srtpSession, err := t.getSRTPSession(); err == nil && srtpSession != nil {
|
||||
closeErrs = append(closeErrs, srtpSession.Close())
|
||||
}
|
||||
|
||||
if srtcpSession, err := t.getSRTCPSession(); err == nil && srtcpSession != nil {
|
||||
closeErrs = append(closeErrs, srtcpSession.Close())
|
||||
}
|
||||
|
||||
for i := range t.simulcastStreams {
|
||||
closeErrs = append(closeErrs, t.simulcastStreams[i].srtp.Close())
|
||||
closeErrs = append(closeErrs, t.simulcastStreams[i].srtcp.Close())
|
||||
}
|
||||
|
||||
if t.conn != nil {
|
||||
// dtls connection may be closed on sctp close.
|
||||
if err := t.conn.Close(); err != nil && !errors.Is(err, dtls.ErrConnClosed) {
|
||||
closeErrs = append(closeErrs, err)
|
||||
}
|
||||
}
|
||||
t.onStateChange(DTLSTransportStateClosed)
|
||||
return util.FlattenErrs(closeErrs)
|
||||
}
|
||||
|
||||
func (t *DTLSTransport) validateFingerPrint(remoteCert *x509.Certificate) error {
|
||||
for _, fp := range t.remoteParameters.Fingerprints {
|
||||
hashAlgo, err := fingerprint.HashFromString(fp.Algorithm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
remoteValue, err := fingerprint.Fingerprint(remoteCert, hashAlgo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if strings.EqualFold(remoteValue, fp.Value) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return errNoMatchingCertificateFingerprint
|
||||
}
|
||||
|
||||
func (t *DTLSTransport) ensureICEConn() error {
|
||||
if t.iceTransport == nil {
|
||||
return errICEConnectionNotStarted
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *DTLSTransport) storeSimulcastStream(srtpReadStream *srtp.ReadStreamSRTP, srtcpReadStream *srtp.ReadStreamSRTCP) {
|
||||
t.lock.Lock()
|
||||
defer t.lock.Unlock()
|
||||
|
||||
t.simulcastStreams = append(t.simulcastStreams, simulcastStreamPair{srtpReadStream, srtcpReadStream})
|
||||
}
|
||||
|
||||
func (t *DTLSTransport) streamsForSSRC(ssrc SSRC, streamInfo interceptor.StreamInfo) (*srtp.ReadStreamSRTP, interceptor.RTPReader, *srtp.ReadStreamSRTCP, interceptor.RTCPReader, error) {
|
||||
srtpSession, err := t.getSRTPSession()
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, err
|
||||
}
|
||||
|
||||
rtpReadStream, err := srtpSession.OpenReadStream(uint32(ssrc))
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, err
|
||||
}
|
||||
|
||||
rtpInterceptor := t.api.interceptor.BindRemoteStream(&streamInfo, interceptor.RTPReaderFunc(func(in []byte, a interceptor.Attributes) (n int, attributes interceptor.Attributes, err error) {
|
||||
n, err = rtpReadStream.Read(in)
|
||||
return n, a, err
|
||||
}))
|
||||
|
||||
srtcpSession, err := t.getSRTCPSession()
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, err
|
||||
}
|
||||
|
||||
rtcpReadStream, err := srtcpSession.OpenReadStream(uint32(ssrc))
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, err
|
||||
}
|
||||
|
||||
rtcpInterceptor := t.api.interceptor.BindRTCPReader(interceptor.RTCPReaderFunc(func(in []byte, a interceptor.Attributes) (n int, attributes interceptor.Attributes, err error) {
|
||||
n, err = rtcpReadStream.Read(in)
|
||||
return n, a, err
|
||||
}))
|
||||
|
||||
return rtpReadStream, rtpInterceptor, rtcpReadStream, rtcpInterceptor, nil
|
||||
}
|
||||
31
server/vendor/github.com/pion/webrtc/v3/dtlstransport_js.go
generated
vendored
Normal file
31
server/vendor/github.com/pion/webrtc/v3/dtlstransport_js.go
generated
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
//go:build js && wasm
|
||||
// +build js,wasm
|
||||
|
||||
package webrtc
|
||||
|
||||
import "syscall/js"
|
||||
|
||||
// DTLSTransport allows an application access to information about the DTLS
|
||||
// transport over which RTP and RTCP packets are sent and received by
|
||||
// RTPSender and RTPReceiver, as well other data such as SCTP packets sent
|
||||
// and received by data channels.
|
||||
type DTLSTransport struct {
|
||||
// Pointer to the underlying JavaScript DTLSTransport object.
|
||||
underlying js.Value
|
||||
}
|
||||
|
||||
// ICETransport returns the currently-configured *ICETransport or nil
|
||||
// if one has not been configured
|
||||
func (r *DTLSTransport) ICETransport() *ICETransport {
|
||||
underlying := r.underlying.Get("iceTransport")
|
||||
if underlying.IsNull() || underlying.IsUndefined() {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &ICETransport{
|
||||
underlying: underlying,
|
||||
}
|
||||
}
|
||||
85
server/vendor/github.com/pion/webrtc/v3/dtlstransportstate.go
generated
vendored
Normal file
85
server/vendor/github.com/pion/webrtc/v3/dtlstransportstate.go
generated
vendored
Normal file
@@ -0,0 +1,85 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package webrtc
|
||||
|
||||
// DTLSTransportState indicates the DTLS transport establishment state.
|
||||
type DTLSTransportState int
|
||||
|
||||
const (
|
||||
// DTLSTransportStateNew indicates that DTLS has not started negotiating
|
||||
// yet.
|
||||
DTLSTransportStateNew DTLSTransportState = iota + 1
|
||||
|
||||
// DTLSTransportStateConnecting indicates that DTLS is in the process of
|
||||
// negotiating a secure connection and verifying the remote fingerprint.
|
||||
DTLSTransportStateConnecting
|
||||
|
||||
// DTLSTransportStateConnected indicates that DTLS has completed
|
||||
// negotiation of a secure connection and verified the remote fingerprint.
|
||||
DTLSTransportStateConnected
|
||||
|
||||
// DTLSTransportStateClosed indicates that the transport has been closed
|
||||
// intentionally as the result of receipt of a close_notify alert, or
|
||||
// calling close().
|
||||
DTLSTransportStateClosed
|
||||
|
||||
// DTLSTransportStateFailed indicates that the transport has failed as
|
||||
// the result of an error (such as receipt of an error alert or failure to
|
||||
// validate the remote fingerprint).
|
||||
DTLSTransportStateFailed
|
||||
)
|
||||
|
||||
// This is done this way because of a linter.
|
||||
const (
|
||||
dtlsTransportStateNewStr = "new"
|
||||
dtlsTransportStateConnectingStr = "connecting"
|
||||
dtlsTransportStateConnectedStr = "connected"
|
||||
dtlsTransportStateClosedStr = "closed"
|
||||
dtlsTransportStateFailedStr = "failed"
|
||||
)
|
||||
|
||||
func newDTLSTransportState(raw string) DTLSTransportState {
|
||||
switch raw {
|
||||
case dtlsTransportStateNewStr:
|
||||
return DTLSTransportStateNew
|
||||
case dtlsTransportStateConnectingStr:
|
||||
return DTLSTransportStateConnecting
|
||||
case dtlsTransportStateConnectedStr:
|
||||
return DTLSTransportStateConnected
|
||||
case dtlsTransportStateClosedStr:
|
||||
return DTLSTransportStateClosed
|
||||
case dtlsTransportStateFailedStr:
|
||||
return DTLSTransportStateFailed
|
||||
default:
|
||||
return DTLSTransportState(Unknown)
|
||||
}
|
||||
}
|
||||
|
||||
func (t DTLSTransportState) String() string {
|
||||
switch t {
|
||||
case DTLSTransportStateNew:
|
||||
return dtlsTransportStateNewStr
|
||||
case DTLSTransportStateConnecting:
|
||||
return dtlsTransportStateConnectingStr
|
||||
case DTLSTransportStateConnected:
|
||||
return dtlsTransportStateConnectedStr
|
||||
case DTLSTransportStateClosed:
|
||||
return dtlsTransportStateClosedStr
|
||||
case DTLSTransportStateFailed:
|
||||
return dtlsTransportStateFailedStr
|
||||
default:
|
||||
return ErrUnknownType.Error()
|
||||
}
|
||||
}
|
||||
|
||||
// MarshalText implements encoding.TextMarshaler
|
||||
func (t DTLSTransportState) MarshalText() ([]byte, error) {
|
||||
return []byte(t.String()), nil
|
||||
}
|
||||
|
||||
// UnmarshalText implements encoding.TextUnmarshaler
|
||||
func (t *DTLSTransportState) UnmarshalText(b []byte) error {
|
||||
*t = newDTLSTransportState(string(b))
|
||||
return nil
|
||||
}
|
||||
250
server/vendor/github.com/pion/webrtc/v3/errors.go
generated
vendored
Normal file
250
server/vendor/github.com/pion/webrtc/v3/errors.go
generated
vendored
Normal file
@@ -0,0 +1,250 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package webrtc
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrUnknownType indicates an error with Unknown info.
|
||||
ErrUnknownType = errors.New("unknown")
|
||||
|
||||
// ErrConnectionClosed indicates an operation executed after connection
|
||||
// has already been closed.
|
||||
ErrConnectionClosed = errors.New("connection closed")
|
||||
|
||||
// ErrDataChannelNotOpen indicates an operation executed when the data
|
||||
// channel is not (yet) open.
|
||||
ErrDataChannelNotOpen = errors.New("data channel not open")
|
||||
|
||||
// ErrCertificateExpired indicates that an x509 certificate has expired.
|
||||
ErrCertificateExpired = errors.New("x509Cert expired")
|
||||
|
||||
// ErrNoTurnCredentials indicates that a TURN server URL was provided
|
||||
// without required credentials.
|
||||
ErrNoTurnCredentials = errors.New("turn server credentials required")
|
||||
|
||||
// ErrTurnCredentials indicates that provided TURN credentials are partial
|
||||
// or malformed.
|
||||
ErrTurnCredentials = errors.New("invalid turn server credentials")
|
||||
|
||||
// ErrExistingTrack indicates that a track already exists.
|
||||
ErrExistingTrack = errors.New("track already exists")
|
||||
|
||||
// ErrPrivateKeyType indicates that a particular private key encryption
|
||||
// chosen to generate a certificate is not supported.
|
||||
ErrPrivateKeyType = errors.New("private key type not supported")
|
||||
|
||||
// ErrModifyingPeerIdentity indicates that an attempt to modify
|
||||
// PeerIdentity was made after PeerConnection has been initialized.
|
||||
ErrModifyingPeerIdentity = errors.New("peerIdentity cannot be modified")
|
||||
|
||||
// ErrModifyingCertificates indicates that an attempt to modify
|
||||
// Certificates was made after PeerConnection has been initialized.
|
||||
ErrModifyingCertificates = errors.New("certificates cannot be modified")
|
||||
|
||||
// ErrModifyingBundlePolicy indicates that an attempt to modify
|
||||
// BundlePolicy was made after PeerConnection has been initialized.
|
||||
ErrModifyingBundlePolicy = errors.New("bundle policy cannot be modified")
|
||||
|
||||
// ErrModifyingRTCPMuxPolicy indicates that an attempt to modify
|
||||
// RTCPMuxPolicy was made after PeerConnection has been initialized.
|
||||
ErrModifyingRTCPMuxPolicy = errors.New("rtcp mux policy cannot be modified")
|
||||
|
||||
// ErrModifyingICECandidatePoolSize indicates that an attempt to modify
|
||||
// ICECandidatePoolSize was made after PeerConnection has been initialized.
|
||||
ErrModifyingICECandidatePoolSize = errors.New("ice candidate pool size cannot be modified")
|
||||
|
||||
// ErrStringSizeLimit indicates that the character size limit of string is
|
||||
// exceeded. The limit is hardcoded to 65535 according to specifications.
|
||||
ErrStringSizeLimit = errors.New("data channel label exceeds size limit")
|
||||
|
||||
// ErrMaxDataChannelID indicates that the maximum number ID that could be
|
||||
// specified for a data channel has been exceeded.
|
||||
ErrMaxDataChannelID = errors.New("maximum number ID for datachannel specified")
|
||||
|
||||
// ErrNegotiatedWithoutID indicates that an attempt to create a data channel
|
||||
// was made while setting the negotiated option to true without providing
|
||||
// the negotiated channel ID.
|
||||
ErrNegotiatedWithoutID = errors.New("negotiated set without channel id")
|
||||
|
||||
// ErrRetransmitsOrPacketLifeTime indicates that an attempt to create a data
|
||||
// channel was made with both options MaxPacketLifeTime and MaxRetransmits
|
||||
// set together. Such configuration is not supported by the specification
|
||||
// and is mutually exclusive.
|
||||
ErrRetransmitsOrPacketLifeTime = errors.New("both MaxPacketLifeTime and MaxRetransmits was set")
|
||||
|
||||
// ErrCodecNotFound is returned when a codec search to the Media Engine fails
|
||||
ErrCodecNotFound = errors.New("codec not found")
|
||||
|
||||
// ErrNoRemoteDescription indicates that an operation was rejected because
|
||||
// the remote description is not set
|
||||
ErrNoRemoteDescription = errors.New("remote description is not set")
|
||||
|
||||
// ErrIncorrectSDPSemantics indicates that the PeerConnection was configured to
|
||||
// generate SDP Answers with different SDP Semantics than the received Offer
|
||||
ErrIncorrectSDPSemantics = errors.New("remote SessionDescription semantics does not match configuration")
|
||||
|
||||
// ErrIncorrectSignalingState indicates that the signaling state of PeerConnection is not correct
|
||||
ErrIncorrectSignalingState = errors.New("operation can not be run in current signaling state")
|
||||
|
||||
// ErrProtocolTooLarge indicates that value given for a DataChannelInit protocol is
|
||||
// longer then 65535 bytes
|
||||
ErrProtocolTooLarge = errors.New("protocol is larger then 65535 bytes")
|
||||
|
||||
// ErrSenderNotCreatedByConnection indicates RemoveTrack was called with a RtpSender not created
|
||||
// by this PeerConnection
|
||||
ErrSenderNotCreatedByConnection = errors.New("RtpSender not created by this PeerConnection")
|
||||
|
||||
// ErrSessionDescriptionNoFingerprint indicates SetRemoteDescription was called with a SessionDescription that has no
|
||||
// fingerprint
|
||||
ErrSessionDescriptionNoFingerprint = errors.New("SetRemoteDescription called with no fingerprint")
|
||||
|
||||
// ErrSessionDescriptionInvalidFingerprint indicates SetRemoteDescription was called with a SessionDescription that
|
||||
// has an invalid fingerprint
|
||||
ErrSessionDescriptionInvalidFingerprint = errors.New("SetRemoteDescription called with an invalid fingerprint")
|
||||
|
||||
// ErrSessionDescriptionConflictingFingerprints indicates SetRemoteDescription was called with a SessionDescription that
|
||||
// has an conflicting fingerprints
|
||||
ErrSessionDescriptionConflictingFingerprints = errors.New("SetRemoteDescription called with multiple conflicting fingerprint")
|
||||
|
||||
// ErrSessionDescriptionMissingIceUfrag indicates SetRemoteDescription was called with a SessionDescription that
|
||||
// is missing an ice-ufrag value
|
||||
ErrSessionDescriptionMissingIceUfrag = errors.New("SetRemoteDescription called with no ice-ufrag")
|
||||
|
||||
// ErrSessionDescriptionMissingIcePwd indicates SetRemoteDescription was called with a SessionDescription that
|
||||
// is missing an ice-pwd value
|
||||
ErrSessionDescriptionMissingIcePwd = errors.New("SetRemoteDescription called with no ice-pwd")
|
||||
|
||||
// ErrSessionDescriptionConflictingIceUfrag indicates SetRemoteDescription was called with a SessionDescription that
|
||||
// contains multiple conflicting ice-ufrag values
|
||||
ErrSessionDescriptionConflictingIceUfrag = errors.New("SetRemoteDescription called with multiple conflicting ice-ufrag values")
|
||||
|
||||
// ErrSessionDescriptionConflictingIcePwd indicates SetRemoteDescription was called with a SessionDescription that
|
||||
// contains multiple conflicting ice-pwd values
|
||||
ErrSessionDescriptionConflictingIcePwd = errors.New("SetRemoteDescription called with multiple conflicting ice-pwd values")
|
||||
|
||||
// ErrNoSRTPProtectionProfile indicates that the DTLS handshake completed and no SRTP Protection Profile was chosen
|
||||
ErrNoSRTPProtectionProfile = errors.New("DTLS Handshake completed and no SRTP Protection Profile was chosen")
|
||||
|
||||
// ErrFailedToGenerateCertificateFingerprint indicates that we failed to generate the fingerprint used for comparing certificates
|
||||
ErrFailedToGenerateCertificateFingerprint = errors.New("failed to generate certificate fingerprint")
|
||||
|
||||
// ErrNoCodecsAvailable indicates that operation isn't possible because the MediaEngine has no codecs available
|
||||
ErrNoCodecsAvailable = errors.New("operation failed no codecs are available")
|
||||
|
||||
// ErrUnsupportedCodec indicates the remote peer doesn't support the requested codec
|
||||
ErrUnsupportedCodec = errors.New("unable to start track, codec is not supported by remote")
|
||||
|
||||
// ErrSenderWithNoCodecs indicates that a RTPSender was created without any codecs. To send media the MediaEngine needs at
|
||||
// least one configured codec.
|
||||
ErrSenderWithNoCodecs = errors.New("unable to populate media section, RTPSender created with no codecs")
|
||||
|
||||
// ErrRTPSenderNewTrackHasIncorrectKind indicates that the new track is of a different kind than the previous/original
|
||||
ErrRTPSenderNewTrackHasIncorrectKind = errors.New("new track must be of the same kind as previous")
|
||||
|
||||
// ErrRTPSenderNewTrackHasIncorrectEnvelope indicates that the new track has a different envelope than the previous/original
|
||||
ErrRTPSenderNewTrackHasIncorrectEnvelope = errors.New("new track must have the same envelope as previous")
|
||||
|
||||
// ErrUnbindFailed indicates that a TrackLocal was not able to be unbind
|
||||
ErrUnbindFailed = errors.New("failed to unbind TrackLocal from PeerConnection")
|
||||
|
||||
// ErrNoPayloaderForCodec indicates that the requested codec does not have a payloader
|
||||
ErrNoPayloaderForCodec = errors.New("the requested codec does not have a payloader")
|
||||
|
||||
// ErrRegisterHeaderExtensionInvalidDirection indicates that a extension was registered with a direction besides `sendonly` or `recvonly`
|
||||
ErrRegisterHeaderExtensionInvalidDirection = errors.New("a header extension must be registered as 'recvonly', 'sendonly' or both")
|
||||
|
||||
// ErrSimulcastProbeOverflow indicates that too many Simulcast probe streams are in flight and the requested SSRC was ignored
|
||||
ErrSimulcastProbeOverflow = errors.New("simulcast probe limit has been reached, new SSRC has been discarded")
|
||||
|
||||
errDetachNotEnabled = errors.New("enable detaching by calling webrtc.DetachDataChannels()")
|
||||
errDetachBeforeOpened = errors.New("datachannel not opened yet, try calling Detach from OnOpen")
|
||||
errDtlsTransportNotStarted = errors.New("the DTLS transport has not started yet")
|
||||
errDtlsKeyExtractionFailed = errors.New("failed extracting keys from DTLS for SRTP")
|
||||
errFailedToStartSRTP = errors.New("failed to start SRTP")
|
||||
errFailedToStartSRTCP = errors.New("failed to start SRTCP")
|
||||
errInvalidDTLSStart = errors.New("attempted to start DTLSTransport that is not in new state")
|
||||
errNoRemoteCertificate = errors.New("peer didn't provide certificate via DTLS")
|
||||
errIdentityProviderNotImplemented = errors.New("identity provider is not implemented")
|
||||
errNoMatchingCertificateFingerprint = errors.New("remote certificate does not match any fingerprint")
|
||||
|
||||
errICEConnectionNotStarted = errors.New("ICE connection not started")
|
||||
errICECandidateTypeUnknown = errors.New("unknown candidate type")
|
||||
errICEInvalidConvertCandidateType = errors.New("cannot convert ice.CandidateType into webrtc.ICECandidateType, invalid type")
|
||||
errICEAgentNotExist = errors.New("ICEAgent does not exist")
|
||||
errICECandiatesCoversionFailed = errors.New("unable to convert ICE candidates to ICECandidates")
|
||||
errICERoleUnknown = errors.New("unknown ICE Role")
|
||||
errICEProtocolUnknown = errors.New("unknown protocol")
|
||||
errICEGathererNotStarted = errors.New("gatherer not started")
|
||||
|
||||
errNetworkTypeUnknown = errors.New("unknown network type")
|
||||
|
||||
errSDPDoesNotMatchOffer = errors.New("new sdp does not match previous offer")
|
||||
errSDPDoesNotMatchAnswer = errors.New("new sdp does not match previous answer")
|
||||
errPeerConnSDPTypeInvalidValue = errors.New("provided value is not a valid enum value of type SDPType")
|
||||
errPeerConnStateChangeInvalid = errors.New("invalid state change op")
|
||||
errPeerConnStateChangeUnhandled = errors.New("unhandled state change op")
|
||||
errPeerConnSDPTypeInvalidValueSetLocalDescription = errors.New("invalid SDP type supplied to SetLocalDescription()")
|
||||
errPeerConnRemoteDescriptionWithoutMidValue = errors.New("remoteDescription contained media section without mid value")
|
||||
errPeerConnRemoteDescriptionNil = errors.New("remoteDescription has not been set yet")
|
||||
errPeerConnSingleMediaSectionHasExplicitSSRC = errors.New("single media section has an explicit SSRC")
|
||||
errPeerConnRemoteSSRCAddTransceiver = errors.New("could not add transceiver for remote SSRC")
|
||||
errPeerConnSimulcastMidRTPExtensionRequired = errors.New("mid RTP Extensions required for Simulcast")
|
||||
errPeerConnSimulcastStreamIDRTPExtensionRequired = errors.New("stream id RTP Extensions required for Simulcast")
|
||||
errPeerConnSimulcastIncomingSSRCFailed = errors.New("incoming SSRC failed Simulcast probing")
|
||||
errPeerConnAddTransceiverFromKindOnlyAcceptsOne = errors.New("AddTransceiverFromKind only accepts one RTPTransceiverInit")
|
||||
errPeerConnAddTransceiverFromTrackOnlyAcceptsOne = errors.New("AddTransceiverFromTrack only accepts one RTPTransceiverInit")
|
||||
errPeerConnAddTransceiverFromKindSupport = errors.New("AddTransceiverFromKind currently only supports recvonly")
|
||||
errPeerConnAddTransceiverFromTrackSupport = errors.New("AddTransceiverFromTrack currently only supports sendonly and sendrecv")
|
||||
errPeerConnSetIdentityProviderNotImplemented = errors.New("TODO SetIdentityProvider")
|
||||
errPeerConnWriteRTCPOpenWriteStream = errors.New("WriteRTCP failed to open WriteStream")
|
||||
errPeerConnTranscieverMidNil = errors.New("cannot find transceiver with mid")
|
||||
|
||||
errRTPReceiverDTLSTransportNil = errors.New("DTLSTransport must not be nil")
|
||||
errRTPReceiverReceiveAlreadyCalled = errors.New("Receive has already been called")
|
||||
errRTPReceiverWithSSRCTrackStreamNotFound = errors.New("unable to find stream for Track with SSRC")
|
||||
errRTPReceiverForRIDTrackStreamNotFound = errors.New("no trackStreams found for RID")
|
||||
|
||||
errRTPSenderTrackNil = errors.New("Track must not be nil")
|
||||
errRTPSenderDTLSTransportNil = errors.New("DTLSTransport must not be nil")
|
||||
errRTPSenderSendAlreadyCalled = errors.New("Send has already been called")
|
||||
errRTPSenderStopped = errors.New("Sender has already been stopped")
|
||||
errRTPSenderTrackRemoved = errors.New("Sender Track has been removed or replaced to nil")
|
||||
errRTPSenderRidNil = errors.New("Sender cannot add encoding as rid is empty")
|
||||
errRTPSenderNoBaseEncoding = errors.New("Sender cannot add encoding as there is no base track")
|
||||
errRTPSenderBaseEncodingMismatch = errors.New("Sender cannot add encoding as provided track does not match base track")
|
||||
errRTPSenderRIDCollision = errors.New("Sender cannot encoding due to RID collision")
|
||||
errRTPSenderNoTrackForRID = errors.New("Sender does not have track for RID")
|
||||
|
||||
errRTPTransceiverCannotChangeMid = errors.New("errRTPSenderTrackNil")
|
||||
errRTPTransceiverSetSendingInvalidState = errors.New("invalid state change in RTPTransceiver.setSending")
|
||||
errRTPTransceiverCodecUnsupported = errors.New("unsupported codec type by this transceiver")
|
||||
|
||||
errSCTPTransportDTLS = errors.New("DTLS not established")
|
||||
|
||||
errSDPZeroTransceivers = errors.New("addTransceiverSDP() called with 0 transceivers")
|
||||
errSDPMediaSectionMediaDataChanInvalid = errors.New("invalid Media Section. Media + DataChannel both enabled")
|
||||
errSDPMediaSectionMultipleTrackInvalid = errors.New("invalid Media Section. Can not have multiple tracks in one MediaSection in UnifiedPlan")
|
||||
|
||||
errSettingEngineSetAnsweringDTLSRole = errors.New("SetAnsweringDTLSRole must DTLSRoleClient or DTLSRoleServer")
|
||||
|
||||
errSignalingStateCannotRollback = errors.New("can't rollback from stable state")
|
||||
errSignalingStateProposedTransitionInvalid = errors.New("invalid proposed signaling state transition")
|
||||
|
||||
errStatsICECandidateStateInvalid = errors.New("cannot convert to StatsICECandidatePairStateSucceeded invalid ice candidate state")
|
||||
|
||||
errInvalidICECredentialTypeString = errors.New("invalid ICECredentialType")
|
||||
errInvalidICEServer = errors.New("invalid ICEServer")
|
||||
|
||||
errICETransportNotInNew = errors.New("ICETransport can only be called in ICETransportStateNew")
|
||||
errICETransportClosed = errors.New("ICETransport closed")
|
||||
|
||||
errCertificatePEMFormatError = errors.New("bad Certificate PEM format")
|
||||
|
||||
errRTPTooShort = errors.New("not long enough to be a RTP Packet")
|
||||
|
||||
errExcessiveRetries = errors.New("excessive retries in CreateOffer")
|
||||
)
|
||||
27
server/vendor/github.com/pion/webrtc/v3/gathering_complete_promise.go
generated
vendored
Normal file
27
server/vendor/github.com/pion/webrtc/v3/gathering_complete_promise.go
generated
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package webrtc
|
||||
|
||||
import (
|
||||
"context"
|
||||
)
|
||||
|
||||
// GatheringCompletePromise is a Pion specific helper function that returns a channel that is closed when gathering is complete.
|
||||
// This function may be helpful in cases where you are unable to trickle your ICE Candidates.
|
||||
//
|
||||
// It is better to not use this function, and instead trickle candidates. If you use this function you will see longer connection startup times.
|
||||
// When the call is connected you will see no impact however.
|
||||
func GatheringCompletePromise(pc *PeerConnection) (gatherComplete <-chan struct{}) {
|
||||
gatheringComplete, done := context.WithCancel(context.Background())
|
||||
|
||||
// It's possible to miss the GatherComplete event since setGatherCompleteHandler is an atomic operation and the
|
||||
// promise might have been created after the gathering is finished. Therefore, we need to check if the ICE gathering
|
||||
// state has changed to complete so that we don't block the caller forever.
|
||||
pc.setGatherCompleteHandler(func() { done() })
|
||||
if pc.ICEGatheringState() == ICEGatheringStateComplete {
|
||||
done()
|
||||
}
|
||||
|
||||
return gatheringComplete.Done()
|
||||
}
|
||||
14
server/vendor/github.com/pion/webrtc/v3/ice_go.go
generated
vendored
Normal file
14
server/vendor/github.com/pion/webrtc/v3/ice_go.go
generated
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
//go:build !js
|
||||
// +build !js
|
||||
|
||||
package webrtc
|
||||
|
||||
// NewICETransport creates a new NewICETransport.
|
||||
// This constructor is part of the ORTC API. It is not
|
||||
// meant to be used together with the basic WebRTC API.
|
||||
func (api *API) NewICETransport(gatherer *ICEGatherer) *ICETransport {
|
||||
return NewICETransport(gatherer, api.settingEngine.LoggerFactory)
|
||||
}
|
||||
172
server/vendor/github.com/pion/webrtc/v3/icecandidate.go
generated
vendored
Normal file
172
server/vendor/github.com/pion/webrtc/v3/icecandidate.go
generated
vendored
Normal file
@@ -0,0 +1,172 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package webrtc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/pion/ice/v2"
|
||||
)
|
||||
|
||||
// ICECandidate represents a ice candidate
|
||||
type ICECandidate struct {
|
||||
statsID string
|
||||
Foundation string `json:"foundation"`
|
||||
Priority uint32 `json:"priority"`
|
||||
Address string `json:"address"`
|
||||
Protocol ICEProtocol `json:"protocol"`
|
||||
Port uint16 `json:"port"`
|
||||
Typ ICECandidateType `json:"type"`
|
||||
Component uint16 `json:"component"`
|
||||
RelatedAddress string `json:"relatedAddress"`
|
||||
RelatedPort uint16 `json:"relatedPort"`
|
||||
TCPType string `json:"tcpType"`
|
||||
}
|
||||
|
||||
// Conversion for package ice
|
||||
|
||||
func newICECandidatesFromICE(iceCandidates []ice.Candidate) ([]ICECandidate, error) {
|
||||
candidates := []ICECandidate{}
|
||||
|
||||
for _, i := range iceCandidates {
|
||||
c, err := newICECandidateFromICE(i)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
candidates = append(candidates, c)
|
||||
}
|
||||
|
||||
return candidates, nil
|
||||
}
|
||||
|
||||
func newICECandidateFromICE(i ice.Candidate) (ICECandidate, error) {
|
||||
typ, err := convertTypeFromICE(i.Type())
|
||||
if err != nil {
|
||||
return ICECandidate{}, err
|
||||
}
|
||||
protocol, err := NewICEProtocol(i.NetworkType().NetworkShort())
|
||||
if err != nil {
|
||||
return ICECandidate{}, err
|
||||
}
|
||||
|
||||
c := ICECandidate{
|
||||
statsID: i.ID(),
|
||||
Foundation: i.Foundation(),
|
||||
Priority: i.Priority(),
|
||||
Address: i.Address(),
|
||||
Protocol: protocol,
|
||||
Port: uint16(i.Port()),
|
||||
Component: i.Component(),
|
||||
Typ: typ,
|
||||
TCPType: i.TCPType().String(),
|
||||
}
|
||||
|
||||
if i.RelatedAddress() != nil {
|
||||
c.RelatedAddress = i.RelatedAddress().Address
|
||||
c.RelatedPort = uint16(i.RelatedAddress().Port)
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (c ICECandidate) toICE() (ice.Candidate, error) {
|
||||
candidateID := c.statsID
|
||||
switch c.Typ {
|
||||
case ICECandidateTypeHost:
|
||||
config := ice.CandidateHostConfig{
|
||||
CandidateID: candidateID,
|
||||
Network: c.Protocol.String(),
|
||||
Address: c.Address,
|
||||
Port: int(c.Port),
|
||||
Component: c.Component,
|
||||
TCPType: ice.NewTCPType(c.TCPType),
|
||||
Foundation: c.Foundation,
|
||||
Priority: c.Priority,
|
||||
}
|
||||
return ice.NewCandidateHost(&config)
|
||||
case ICECandidateTypeSrflx:
|
||||
config := ice.CandidateServerReflexiveConfig{
|
||||
CandidateID: candidateID,
|
||||
Network: c.Protocol.String(),
|
||||
Address: c.Address,
|
||||
Port: int(c.Port),
|
||||
Component: c.Component,
|
||||
Foundation: c.Foundation,
|
||||
Priority: c.Priority,
|
||||
RelAddr: c.RelatedAddress,
|
||||
RelPort: int(c.RelatedPort),
|
||||
}
|
||||
return ice.NewCandidateServerReflexive(&config)
|
||||
case ICECandidateTypePrflx:
|
||||
config := ice.CandidatePeerReflexiveConfig{
|
||||
CandidateID: candidateID,
|
||||
Network: c.Protocol.String(),
|
||||
Address: c.Address,
|
||||
Port: int(c.Port),
|
||||
Component: c.Component,
|
||||
Foundation: c.Foundation,
|
||||
Priority: c.Priority,
|
||||
RelAddr: c.RelatedAddress,
|
||||
RelPort: int(c.RelatedPort),
|
||||
}
|
||||
return ice.NewCandidatePeerReflexive(&config)
|
||||
case ICECandidateTypeRelay:
|
||||
config := ice.CandidateRelayConfig{
|
||||
CandidateID: candidateID,
|
||||
Network: c.Protocol.String(),
|
||||
Address: c.Address,
|
||||
Port: int(c.Port),
|
||||
Component: c.Component,
|
||||
Foundation: c.Foundation,
|
||||
Priority: c.Priority,
|
||||
RelAddr: c.RelatedAddress,
|
||||
RelPort: int(c.RelatedPort),
|
||||
}
|
||||
return ice.NewCandidateRelay(&config)
|
||||
default:
|
||||
return nil, fmt.Errorf("%w: %s", errICECandidateTypeUnknown, c.Typ)
|
||||
}
|
||||
}
|
||||
|
||||
func convertTypeFromICE(t ice.CandidateType) (ICECandidateType, error) {
|
||||
switch t {
|
||||
case ice.CandidateTypeHost:
|
||||
return ICECandidateTypeHost, nil
|
||||
case ice.CandidateTypeServerReflexive:
|
||||
return ICECandidateTypeSrflx, nil
|
||||
case ice.CandidateTypePeerReflexive:
|
||||
return ICECandidateTypePrflx, nil
|
||||
case ice.CandidateTypeRelay:
|
||||
return ICECandidateTypeRelay, nil
|
||||
default:
|
||||
return ICECandidateType(t), fmt.Errorf("%w: %s", errICECandidateTypeUnknown, t)
|
||||
}
|
||||
}
|
||||
|
||||
func (c ICECandidate) String() string {
|
||||
ic, err := c.toICE()
|
||||
if err != nil {
|
||||
return fmt.Sprintf("%#v failed to convert to ICE: %s", c, err)
|
||||
}
|
||||
return ic.String()
|
||||
}
|
||||
|
||||
// ToJSON returns an ICECandidateInit
|
||||
// as indicated by the spec https://w3c.github.io/webrtc-pc/#dom-rtcicecandidate-tojson
|
||||
func (c ICECandidate) ToJSON() ICECandidateInit {
|
||||
zeroVal := uint16(0)
|
||||
emptyStr := ""
|
||||
candidateStr := ""
|
||||
|
||||
candidate, err := c.toICE()
|
||||
if err == nil {
|
||||
candidateStr = candidate.Marshal()
|
||||
}
|
||||
|
||||
return ICECandidateInit{
|
||||
Candidate: fmt.Sprintf("candidate:%s", candidateStr),
|
||||
SDPMid: &emptyStr,
|
||||
SDPMLineIndex: &zeroVal,
|
||||
}
|
||||
}
|
||||
12
server/vendor/github.com/pion/webrtc/v3/icecandidateinit.go
generated
vendored
Normal file
12
server/vendor/github.com/pion/webrtc/v3/icecandidateinit.go
generated
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package webrtc
|
||||
|
||||
// ICECandidateInit is used to serialize ice candidates
|
||||
type ICECandidateInit struct {
|
||||
Candidate string `json:"candidate"`
|
||||
SDPMid *string `json:"sdpMid"`
|
||||
SDPMLineIndex *uint16 `json:"sdpMLineIndex"`
|
||||
UsernameFragment *string `json:"usernameFragment"`
|
||||
}
|
||||
32
server/vendor/github.com/pion/webrtc/v3/icecandidatepair.go
generated
vendored
Normal file
32
server/vendor/github.com/pion/webrtc/v3/icecandidatepair.go
generated
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package webrtc
|
||||
|
||||
import "fmt"
|
||||
|
||||
// ICECandidatePair represents an ICE Candidate pair
|
||||
type ICECandidatePair struct {
|
||||
statsID string
|
||||
Local *ICECandidate
|
||||
Remote *ICECandidate
|
||||
}
|
||||
|
||||
func newICECandidatePairStatsID(localID, remoteID string) string {
|
||||
return fmt.Sprintf("%s-%s", localID, remoteID)
|
||||
}
|
||||
|
||||
func (p *ICECandidatePair) String() string {
|
||||
return fmt.Sprintf("(local) %s <-> (remote) %s", p.Local, p.Remote)
|
||||
}
|
||||
|
||||
// NewICECandidatePair returns an initialized *ICECandidatePair
|
||||
// for the given pair of ICECandidate instances
|
||||
func NewICECandidatePair(local, remote *ICECandidate) *ICECandidatePair {
|
||||
statsID := newICECandidatePairStatsID(local.statsID, remote.statsID)
|
||||
return &ICECandidatePair{
|
||||
statsID: statsID,
|
||||
Local: local,
|
||||
Remote: remote,
|
||||
}
|
||||
}
|
||||
109
server/vendor/github.com/pion/webrtc/v3/icecandidatetype.go
generated
vendored
Normal file
109
server/vendor/github.com/pion/webrtc/v3/icecandidatetype.go
generated
vendored
Normal file
@@ -0,0 +1,109 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package webrtc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/pion/ice/v2"
|
||||
)
|
||||
|
||||
// ICECandidateType represents the type of the ICE candidate used.
|
||||
type ICECandidateType int
|
||||
|
||||
const (
|
||||
// ICECandidateTypeHost indicates that the candidate is of Host type as
|
||||
// described in https://tools.ietf.org/html/rfc8445#section-5.1.1.1. A
|
||||
// candidate obtained by binding to a specific port from an IP address on
|
||||
// the host. This includes IP addresses on physical interfaces and logical
|
||||
// ones, such as ones obtained through VPNs.
|
||||
ICECandidateTypeHost ICECandidateType = iota + 1
|
||||
|
||||
// ICECandidateTypeSrflx indicates the the candidate is of Server
|
||||
// Reflexive type as described
|
||||
// https://tools.ietf.org/html/rfc8445#section-5.1.1.2. A candidate type
|
||||
// whose IP address and port are a binding allocated by a NAT for an ICE
|
||||
// agent after it sends a packet through the NAT to a server, such as a
|
||||
// STUN server.
|
||||
ICECandidateTypeSrflx
|
||||
|
||||
// ICECandidateTypePrflx indicates that the candidate is of Peer
|
||||
// Reflexive type. A candidate type whose IP address and port are a binding
|
||||
// allocated by a NAT for an ICE agent after it sends a packet through the
|
||||
// NAT to its peer.
|
||||
ICECandidateTypePrflx
|
||||
|
||||
// ICECandidateTypeRelay indicates the the candidate is of Relay type as
|
||||
// described in https://tools.ietf.org/html/rfc8445#section-5.1.1.2. A
|
||||
// candidate type obtained from a relay server, such as a TURN server.
|
||||
ICECandidateTypeRelay
|
||||
)
|
||||
|
||||
// This is done this way because of a linter.
|
||||
const (
|
||||
iceCandidateTypeHostStr = "host"
|
||||
iceCandidateTypeSrflxStr = "srflx"
|
||||
iceCandidateTypePrflxStr = "prflx"
|
||||
iceCandidateTypeRelayStr = "relay"
|
||||
)
|
||||
|
||||
// NewICECandidateType takes a string and converts it into ICECandidateType
|
||||
func NewICECandidateType(raw string) (ICECandidateType, error) {
|
||||
switch raw {
|
||||
case iceCandidateTypeHostStr:
|
||||
return ICECandidateTypeHost, nil
|
||||
case iceCandidateTypeSrflxStr:
|
||||
return ICECandidateTypeSrflx, nil
|
||||
case iceCandidateTypePrflxStr:
|
||||
return ICECandidateTypePrflx, nil
|
||||
case iceCandidateTypeRelayStr:
|
||||
return ICECandidateTypeRelay, nil
|
||||
default:
|
||||
return ICECandidateType(Unknown), fmt.Errorf("%w: %s", errICECandidateTypeUnknown, raw)
|
||||
}
|
||||
}
|
||||
|
||||
func (t ICECandidateType) String() string {
|
||||
switch t {
|
||||
case ICECandidateTypeHost:
|
||||
return iceCandidateTypeHostStr
|
||||
case ICECandidateTypeSrflx:
|
||||
return iceCandidateTypeSrflxStr
|
||||
case ICECandidateTypePrflx:
|
||||
return iceCandidateTypePrflxStr
|
||||
case ICECandidateTypeRelay:
|
||||
return iceCandidateTypeRelayStr
|
||||
default:
|
||||
return ErrUnknownType.Error()
|
||||
}
|
||||
}
|
||||
|
||||
func getCandidateType(candidateType ice.CandidateType) (ICECandidateType, error) {
|
||||
switch candidateType {
|
||||
case ice.CandidateTypeHost:
|
||||
return ICECandidateTypeHost, nil
|
||||
case ice.CandidateTypeServerReflexive:
|
||||
return ICECandidateTypeSrflx, nil
|
||||
case ice.CandidateTypePeerReflexive:
|
||||
return ICECandidateTypePrflx, nil
|
||||
case ice.CandidateTypeRelay:
|
||||
return ICECandidateTypeRelay, nil
|
||||
default:
|
||||
// NOTE: this should never happen[tm]
|
||||
err := fmt.Errorf("%w: %s", errICEInvalidConvertCandidateType, candidateType.String())
|
||||
return ICECandidateType(Unknown), err
|
||||
}
|
||||
}
|
||||
|
||||
// MarshalText implements the encoding.TextMarshaler interface.
|
||||
func (t ICECandidateType) MarshalText() ([]byte, error) {
|
||||
return []byte(t.String()), nil
|
||||
}
|
||||
|
||||
// UnmarshalText implements the encoding.TextUnmarshaler interface.
|
||||
func (t *ICECandidateType) UnmarshalText(b []byte) error {
|
||||
var err error
|
||||
*t, err = NewICECandidateType(string(b))
|
||||
return err
|
||||
}
|
||||
50
server/vendor/github.com/pion/webrtc/v3/icecomponent.go
generated
vendored
Normal file
50
server/vendor/github.com/pion/webrtc/v3/icecomponent.go
generated
vendored
Normal file
@@ -0,0 +1,50 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package webrtc
|
||||
|
||||
// ICEComponent describes if the ice transport is used for RTP
|
||||
// (or RTCP multiplexing).
|
||||
type ICEComponent int
|
||||
|
||||
const (
|
||||
// ICEComponentRTP indicates that the ICE Transport is used for RTP (or
|
||||
// RTCP multiplexing), as defined in
|
||||
// https://tools.ietf.org/html/rfc5245#section-4.1.1.1. Protocols
|
||||
// multiplexed with RTP (e.g. data channel) share its component ID. This
|
||||
// represents the component-id value 1 when encoded in candidate-attribute.
|
||||
ICEComponentRTP ICEComponent = iota + 1
|
||||
|
||||
// ICEComponentRTCP indicates that the ICE Transport is used for RTCP as
|
||||
// defined by https://tools.ietf.org/html/rfc5245#section-4.1.1.1. This
|
||||
// represents the component-id value 2 when encoded in candidate-attribute.
|
||||
ICEComponentRTCP
|
||||
)
|
||||
|
||||
// This is done this way because of a linter.
|
||||
const (
|
||||
iceComponentRTPStr = "rtp"
|
||||
iceComponentRTCPStr = "rtcp"
|
||||
)
|
||||
|
||||
func newICEComponent(raw string) ICEComponent {
|
||||
switch raw {
|
||||
case iceComponentRTPStr:
|
||||
return ICEComponentRTP
|
||||
case iceComponentRTCPStr:
|
||||
return ICEComponentRTCP
|
||||
default:
|
||||
return ICEComponent(Unknown)
|
||||
}
|
||||
}
|
||||
|
||||
func (t ICEComponent) String() string {
|
||||
switch t {
|
||||
case ICEComponentRTP:
|
||||
return iceComponentRTPStr
|
||||
case ICEComponentRTCP:
|
||||
return iceComponentRTCPStr
|
||||
default:
|
||||
return ErrUnknownType.Error()
|
||||
}
|
||||
}
|
||||
97
server/vendor/github.com/pion/webrtc/v3/iceconnectionstate.go
generated
vendored
Normal file
97
server/vendor/github.com/pion/webrtc/v3/iceconnectionstate.go
generated
vendored
Normal file
@@ -0,0 +1,97 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package webrtc
|
||||
|
||||
// ICEConnectionState indicates signaling state of the ICE Connection.
|
||||
type ICEConnectionState int
|
||||
|
||||
const (
|
||||
// ICEConnectionStateNew indicates that any of the ICETransports are
|
||||
// in the "new" state and none of them are in the "checking", "disconnected"
|
||||
// or "failed" state, or all ICETransports are in the "closed" state, or
|
||||
// there are no transports.
|
||||
ICEConnectionStateNew ICEConnectionState = iota + 1
|
||||
|
||||
// ICEConnectionStateChecking indicates that any of the ICETransports
|
||||
// are in the "checking" state and none of them are in the "disconnected"
|
||||
// or "failed" state.
|
||||
ICEConnectionStateChecking
|
||||
|
||||
// ICEConnectionStateConnected indicates that all ICETransports are
|
||||
// in the "connected", "completed" or "closed" state and at least one of
|
||||
// them is in the "connected" state.
|
||||
ICEConnectionStateConnected
|
||||
|
||||
// ICEConnectionStateCompleted indicates that all ICETransports are
|
||||
// in the "completed" or "closed" state and at least one of them is in the
|
||||
// "completed" state.
|
||||
ICEConnectionStateCompleted
|
||||
|
||||
// ICEConnectionStateDisconnected indicates that any of the
|
||||
// ICETransports are in the "disconnected" state and none of them are
|
||||
// in the "failed" state.
|
||||
ICEConnectionStateDisconnected
|
||||
|
||||
// ICEConnectionStateFailed indicates that any of the ICETransports
|
||||
// are in the "failed" state.
|
||||
ICEConnectionStateFailed
|
||||
|
||||
// ICEConnectionStateClosed indicates that the PeerConnection's
|
||||
// isClosed is true.
|
||||
ICEConnectionStateClosed
|
||||
)
|
||||
|
||||
// This is done this way because of a linter.
|
||||
const (
|
||||
iceConnectionStateNewStr = "new"
|
||||
iceConnectionStateCheckingStr = "checking"
|
||||
iceConnectionStateConnectedStr = "connected"
|
||||
iceConnectionStateCompletedStr = "completed"
|
||||
iceConnectionStateDisconnectedStr = "disconnected"
|
||||
iceConnectionStateFailedStr = "failed"
|
||||
iceConnectionStateClosedStr = "closed"
|
||||
)
|
||||
|
||||
// NewICEConnectionState takes a string and converts it to ICEConnectionState
|
||||
func NewICEConnectionState(raw string) ICEConnectionState {
|
||||
switch raw {
|
||||
case iceConnectionStateNewStr:
|
||||
return ICEConnectionStateNew
|
||||
case iceConnectionStateCheckingStr:
|
||||
return ICEConnectionStateChecking
|
||||
case iceConnectionStateConnectedStr:
|
||||
return ICEConnectionStateConnected
|
||||
case iceConnectionStateCompletedStr:
|
||||
return ICEConnectionStateCompleted
|
||||
case iceConnectionStateDisconnectedStr:
|
||||
return ICEConnectionStateDisconnected
|
||||
case iceConnectionStateFailedStr:
|
||||
return ICEConnectionStateFailed
|
||||
case iceConnectionStateClosedStr:
|
||||
return ICEConnectionStateClosed
|
||||
default:
|
||||
return ICEConnectionState(Unknown)
|
||||
}
|
||||
}
|
||||
|
||||
func (c ICEConnectionState) String() string {
|
||||
switch c {
|
||||
case ICEConnectionStateNew:
|
||||
return iceConnectionStateNewStr
|
||||
case ICEConnectionStateChecking:
|
||||
return iceConnectionStateCheckingStr
|
||||
case ICEConnectionStateConnected:
|
||||
return iceConnectionStateConnectedStr
|
||||
case ICEConnectionStateCompleted:
|
||||
return iceConnectionStateCompletedStr
|
||||
case ICEConnectionStateDisconnected:
|
||||
return iceConnectionStateDisconnectedStr
|
||||
case ICEConnectionStateFailed:
|
||||
return iceConnectionStateFailedStr
|
||||
case ICEConnectionStateClosed:
|
||||
return iceConnectionStateClosedStr
|
||||
default:
|
||||
return ErrUnknownType.Error()
|
||||
}
|
||||
}
|
||||
72
server/vendor/github.com/pion/webrtc/v3/icecredentialtype.go
generated
vendored
Normal file
72
server/vendor/github.com/pion/webrtc/v3/icecredentialtype.go
generated
vendored
Normal file
@@ -0,0 +1,72 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package webrtc
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// ICECredentialType indicates the type of credentials used to connect to
|
||||
// an ICE server.
|
||||
type ICECredentialType int
|
||||
|
||||
const (
|
||||
// ICECredentialTypePassword describes username and password based
|
||||
// credentials as described in https://tools.ietf.org/html/rfc5389.
|
||||
ICECredentialTypePassword ICECredentialType = iota
|
||||
|
||||
// ICECredentialTypeOauth describes token based credential as described
|
||||
// in https://tools.ietf.org/html/rfc7635.
|
||||
ICECredentialTypeOauth
|
||||
)
|
||||
|
||||
// This is done this way because of a linter.
|
||||
const (
|
||||
iceCredentialTypePasswordStr = "password"
|
||||
iceCredentialTypeOauthStr = "oauth"
|
||||
)
|
||||
|
||||
func newICECredentialType(raw string) (ICECredentialType, error) {
|
||||
switch raw {
|
||||
case iceCredentialTypePasswordStr:
|
||||
return ICECredentialTypePassword, nil
|
||||
case iceCredentialTypeOauthStr:
|
||||
return ICECredentialTypeOauth, nil
|
||||
default:
|
||||
return ICECredentialTypePassword, errInvalidICECredentialTypeString
|
||||
}
|
||||
}
|
||||
|
||||
func (t ICECredentialType) String() string {
|
||||
switch t {
|
||||
case ICECredentialTypePassword:
|
||||
return iceCredentialTypePasswordStr
|
||||
case ICECredentialTypeOauth:
|
||||
return iceCredentialTypeOauthStr
|
||||
default:
|
||||
return ErrUnknownType.Error()
|
||||
}
|
||||
}
|
||||
|
||||
// UnmarshalJSON parses the JSON-encoded data and stores the result
|
||||
func (t *ICECredentialType) UnmarshalJSON(b []byte) error {
|
||||
var val string
|
||||
if err := json.Unmarshal(b, &val); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tmp, err := newICECredentialType(val)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: (%s)", err, val)
|
||||
}
|
||||
|
||||
*t = tmp
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalJSON returns the JSON encoding
|
||||
func (t ICECredentialType) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(t.String())
|
||||
}
|
||||
395
server/vendor/github.com/pion/webrtc/v3/icegatherer.go
generated
vendored
Normal file
395
server/vendor/github.com/pion/webrtc/v3/icegatherer.go
generated
vendored
Normal file
@@ -0,0 +1,395 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
//go:build !js
|
||||
// +build !js
|
||||
|
||||
package webrtc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/pion/ice/v2"
|
||||
"github.com/pion/logging"
|
||||
"github.com/pion/stun"
|
||||
)
|
||||
|
||||
// ICEGatherer gathers local host, server reflexive and relay
|
||||
// candidates, as well as enabling the retrieval of local Interactive
|
||||
// Connectivity Establishment (ICE) parameters which can be
|
||||
// exchanged in signaling.
|
||||
type ICEGatherer struct {
|
||||
lock sync.RWMutex
|
||||
log logging.LeveledLogger
|
||||
state ICEGathererState
|
||||
|
||||
validatedServers []*stun.URI
|
||||
gatherPolicy ICETransportPolicy
|
||||
|
||||
agent *ice.Agent
|
||||
|
||||
onLocalCandidateHandler atomic.Value // func(candidate *ICECandidate)
|
||||
onStateChangeHandler atomic.Value // func(state ICEGathererState)
|
||||
|
||||
// Used for GatheringCompletePromise
|
||||
onGatheringCompleteHandler atomic.Value // func()
|
||||
|
||||
api *API
|
||||
}
|
||||
|
||||
// NewICEGatherer creates a new NewICEGatherer.
|
||||
// This constructor is part of the ORTC API. It is not
|
||||
// meant to be used together with the basic WebRTC API.
|
||||
func (api *API) NewICEGatherer(opts ICEGatherOptions) (*ICEGatherer, error) {
|
||||
var validatedServers []*stun.URI
|
||||
if len(opts.ICEServers) > 0 {
|
||||
for _, server := range opts.ICEServers {
|
||||
url, err := server.urls()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
validatedServers = append(validatedServers, url...)
|
||||
}
|
||||
}
|
||||
|
||||
return &ICEGatherer{
|
||||
state: ICEGathererStateNew,
|
||||
gatherPolicy: opts.ICEGatherPolicy,
|
||||
validatedServers: validatedServers,
|
||||
api: api,
|
||||
log: api.settingEngine.LoggerFactory.NewLogger("ice"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (g *ICEGatherer) createAgent() error {
|
||||
g.lock.Lock()
|
||||
defer g.lock.Unlock()
|
||||
|
||||
if g.agent != nil || g.State() != ICEGathererStateNew {
|
||||
return nil
|
||||
}
|
||||
|
||||
candidateTypes := []ice.CandidateType{}
|
||||
if g.api.settingEngine.candidates.ICELite {
|
||||
candidateTypes = append(candidateTypes, ice.CandidateTypeHost)
|
||||
} else if g.gatherPolicy == ICETransportPolicyRelay {
|
||||
candidateTypes = append(candidateTypes, ice.CandidateTypeRelay)
|
||||
}
|
||||
|
||||
var nat1To1CandiTyp ice.CandidateType
|
||||
switch g.api.settingEngine.candidates.NAT1To1IPCandidateType {
|
||||
case ICECandidateTypeHost:
|
||||
nat1To1CandiTyp = ice.CandidateTypeHost
|
||||
case ICECandidateTypeSrflx:
|
||||
nat1To1CandiTyp = ice.CandidateTypeServerReflexive
|
||||
default:
|
||||
nat1To1CandiTyp = ice.CandidateTypeUnspecified
|
||||
}
|
||||
|
||||
mDNSMode := g.api.settingEngine.candidates.MulticastDNSMode
|
||||
if mDNSMode != ice.MulticastDNSModeDisabled && mDNSMode != ice.MulticastDNSModeQueryAndGather {
|
||||
// If enum is in state we don't recognized default to MulticastDNSModeQueryOnly
|
||||
mDNSMode = ice.MulticastDNSModeQueryOnly
|
||||
}
|
||||
|
||||
config := &ice.AgentConfig{
|
||||
Lite: g.api.settingEngine.candidates.ICELite,
|
||||
Urls: g.validatedServers,
|
||||
PortMin: g.api.settingEngine.ephemeralUDP.PortMin,
|
||||
PortMax: g.api.settingEngine.ephemeralUDP.PortMax,
|
||||
DisconnectedTimeout: g.api.settingEngine.timeout.ICEDisconnectedTimeout,
|
||||
FailedTimeout: g.api.settingEngine.timeout.ICEFailedTimeout,
|
||||
KeepaliveInterval: g.api.settingEngine.timeout.ICEKeepaliveInterval,
|
||||
LoggerFactory: g.api.settingEngine.LoggerFactory,
|
||||
CandidateTypes: candidateTypes,
|
||||
HostAcceptanceMinWait: g.api.settingEngine.timeout.ICEHostAcceptanceMinWait,
|
||||
SrflxAcceptanceMinWait: g.api.settingEngine.timeout.ICESrflxAcceptanceMinWait,
|
||||
PrflxAcceptanceMinWait: g.api.settingEngine.timeout.ICEPrflxAcceptanceMinWait,
|
||||
RelayAcceptanceMinWait: g.api.settingEngine.timeout.ICERelayAcceptanceMinWait,
|
||||
InterfaceFilter: g.api.settingEngine.candidates.InterfaceFilter,
|
||||
IPFilter: g.api.settingEngine.candidates.IPFilter,
|
||||
NAT1To1IPs: g.api.settingEngine.candidates.NAT1To1IPs,
|
||||
NAT1To1IPCandidateType: nat1To1CandiTyp,
|
||||
IncludeLoopback: g.api.settingEngine.candidates.IncludeLoopbackCandidate,
|
||||
Net: g.api.settingEngine.net,
|
||||
MulticastDNSMode: mDNSMode,
|
||||
MulticastDNSHostName: g.api.settingEngine.candidates.MulticastDNSHostName,
|
||||
LocalUfrag: g.api.settingEngine.candidates.UsernameFragment,
|
||||
LocalPwd: g.api.settingEngine.candidates.Password,
|
||||
TCPMux: g.api.settingEngine.iceTCPMux,
|
||||
UDPMux: g.api.settingEngine.iceUDPMux,
|
||||
ProxyDialer: g.api.settingEngine.iceProxyDialer,
|
||||
DisableActiveTCP: g.api.settingEngine.iceDisableActiveTCP,
|
||||
BindingRequestHandler: g.api.settingEngine.iceBindingRequestHandler,
|
||||
}
|
||||
|
||||
requestedNetworkTypes := g.api.settingEngine.candidates.ICENetworkTypes
|
||||
if len(requestedNetworkTypes) == 0 {
|
||||
requestedNetworkTypes = supportedNetworkTypes()
|
||||
}
|
||||
|
||||
for _, typ := range requestedNetworkTypes {
|
||||
config.NetworkTypes = append(config.NetworkTypes, ice.NetworkType(typ))
|
||||
}
|
||||
|
||||
agent, err := ice.NewAgent(config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
g.agent = agent
|
||||
return nil
|
||||
}
|
||||
|
||||
// Gather ICE candidates.
|
||||
func (g *ICEGatherer) Gather() error {
|
||||
if err := g.createAgent(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
agent := g.getAgent()
|
||||
// it is possible agent had just been closed
|
||||
if agent == nil {
|
||||
return fmt.Errorf("%w: unable to gather", errICEAgentNotExist)
|
||||
}
|
||||
|
||||
g.setState(ICEGathererStateGathering)
|
||||
if err := agent.OnCandidate(func(candidate ice.Candidate) {
|
||||
onLocalCandidateHandler := func(*ICECandidate) {}
|
||||
if handler, ok := g.onLocalCandidateHandler.Load().(func(candidate *ICECandidate)); ok && handler != nil {
|
||||
onLocalCandidateHandler = handler
|
||||
}
|
||||
|
||||
onGatheringCompleteHandler := func() {}
|
||||
if handler, ok := g.onGatheringCompleteHandler.Load().(func()); ok && handler != nil {
|
||||
onGatheringCompleteHandler = handler
|
||||
}
|
||||
|
||||
if candidate != nil {
|
||||
c, err := newICECandidateFromICE(candidate)
|
||||
if err != nil {
|
||||
g.log.Warnf("Failed to convert ice.Candidate: %s", err)
|
||||
return
|
||||
}
|
||||
onLocalCandidateHandler(&c)
|
||||
} else {
|
||||
g.setState(ICEGathererStateComplete)
|
||||
|
||||
onGatheringCompleteHandler()
|
||||
onLocalCandidateHandler(nil)
|
||||
}
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
return agent.GatherCandidates()
|
||||
}
|
||||
|
||||
// Close prunes all local candidates, and closes the ports.
|
||||
func (g *ICEGatherer) Close() error {
|
||||
return g.close(false /* shouldGracefullyClose */)
|
||||
}
|
||||
|
||||
// GracefulClose prunes all local candidates, and closes the ports. It also waits
|
||||
// for any goroutines it started to complete. This is only safe to call outside of
|
||||
// ICEGatherer callbacks or if in a callback, in its own goroutine.
|
||||
func (g *ICEGatherer) GracefulClose() error {
|
||||
return g.close(true /* shouldGracefullyClose */)
|
||||
}
|
||||
|
||||
func (g *ICEGatherer) close(shouldGracefullyClose bool) error {
|
||||
g.lock.Lock()
|
||||
defer g.lock.Unlock()
|
||||
|
||||
if g.agent == nil {
|
||||
return nil
|
||||
}
|
||||
if shouldGracefullyClose {
|
||||
if err := g.agent.GracefulClose(); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err := g.agent.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
g.agent = nil
|
||||
g.setState(ICEGathererStateClosed)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetLocalParameters returns the ICE parameters of the ICEGatherer.
|
||||
func (g *ICEGatherer) GetLocalParameters() (ICEParameters, error) {
|
||||
if err := g.createAgent(); err != nil {
|
||||
return ICEParameters{}, err
|
||||
}
|
||||
|
||||
agent := g.getAgent()
|
||||
// it is possible agent had just been closed
|
||||
if agent == nil {
|
||||
return ICEParameters{}, fmt.Errorf("%w: unable to get local parameters", errICEAgentNotExist)
|
||||
}
|
||||
|
||||
frag, pwd, err := agent.GetLocalUserCredentials()
|
||||
if err != nil {
|
||||
return ICEParameters{}, err
|
||||
}
|
||||
|
||||
return ICEParameters{
|
||||
UsernameFragment: frag,
|
||||
Password: pwd,
|
||||
ICELite: false,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetLocalCandidates returns the sequence of valid local candidates associated with the ICEGatherer.
|
||||
func (g *ICEGatherer) GetLocalCandidates() ([]ICECandidate, error) {
|
||||
if err := g.createAgent(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
agent := g.getAgent()
|
||||
// it is possible agent had just been closed
|
||||
if agent == nil {
|
||||
return nil, fmt.Errorf("%w: unable to get local candidates", errICEAgentNotExist)
|
||||
}
|
||||
|
||||
iceCandidates, err := agent.GetLocalCandidates()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return newICECandidatesFromICE(iceCandidates)
|
||||
}
|
||||
|
||||
// OnLocalCandidate sets an event handler which fires when a new local ICE candidate is available
|
||||
// Take note that the handler will be called with a nil pointer when gathering is finished.
|
||||
func (g *ICEGatherer) OnLocalCandidate(f func(*ICECandidate)) {
|
||||
g.onLocalCandidateHandler.Store(f)
|
||||
}
|
||||
|
||||
// OnStateChange fires any time the ICEGatherer changes
|
||||
func (g *ICEGatherer) OnStateChange(f func(ICEGathererState)) {
|
||||
g.onStateChangeHandler.Store(f)
|
||||
}
|
||||
|
||||
// State indicates the current state of the ICE gatherer.
|
||||
func (g *ICEGatherer) State() ICEGathererState {
|
||||
return atomicLoadICEGathererState(&g.state)
|
||||
}
|
||||
|
||||
func (g *ICEGatherer) setState(s ICEGathererState) {
|
||||
atomicStoreICEGathererState(&g.state, s)
|
||||
|
||||
if handler, ok := g.onStateChangeHandler.Load().(func(state ICEGathererState)); ok && handler != nil {
|
||||
handler(s)
|
||||
}
|
||||
}
|
||||
|
||||
func (g *ICEGatherer) getAgent() *ice.Agent {
|
||||
g.lock.RLock()
|
||||
defer g.lock.RUnlock()
|
||||
return g.agent
|
||||
}
|
||||
|
||||
func (g *ICEGatherer) collectStats(collector *statsReportCollector) {
|
||||
agent := g.getAgent()
|
||||
if agent == nil {
|
||||
return
|
||||
}
|
||||
|
||||
collector.Collecting()
|
||||
go func(collector *statsReportCollector, agent *ice.Agent) {
|
||||
for _, candidatePairStats := range agent.GetCandidatePairsStats() {
|
||||
collector.Collecting()
|
||||
|
||||
stats, err := toICECandidatePairStats(candidatePairStats)
|
||||
if err != nil {
|
||||
g.log.Error(err.Error())
|
||||
continue
|
||||
}
|
||||
|
||||
collector.Collect(stats.ID, stats)
|
||||
}
|
||||
|
||||
for _, candidateStats := range agent.GetLocalCandidatesStats() {
|
||||
collector.Collecting()
|
||||
|
||||
networkType, err := getNetworkType(candidateStats.NetworkType)
|
||||
if err != nil {
|
||||
g.log.Error(err.Error())
|
||||
}
|
||||
|
||||
candidateType, err := getCandidateType(candidateStats.CandidateType)
|
||||
if err != nil {
|
||||
g.log.Error(err.Error())
|
||||
}
|
||||
|
||||
stats := ICECandidateStats{
|
||||
Timestamp: statsTimestampFrom(candidateStats.Timestamp),
|
||||
ID: candidateStats.ID,
|
||||
Type: StatsTypeLocalCandidate,
|
||||
IP: candidateStats.IP,
|
||||
Port: int32(candidateStats.Port),
|
||||
Protocol: networkType.Protocol(),
|
||||
CandidateType: candidateType,
|
||||
Priority: int32(candidateStats.Priority),
|
||||
URL: candidateStats.URL,
|
||||
RelayProtocol: candidateStats.RelayProtocol,
|
||||
Deleted: candidateStats.Deleted,
|
||||
}
|
||||
collector.Collect(stats.ID, stats)
|
||||
}
|
||||
|
||||
for _, candidateStats := range agent.GetRemoteCandidatesStats() {
|
||||
collector.Collecting()
|
||||
networkType, err := getNetworkType(candidateStats.NetworkType)
|
||||
if err != nil {
|
||||
g.log.Error(err.Error())
|
||||
}
|
||||
|
||||
candidateType, err := getCandidateType(candidateStats.CandidateType)
|
||||
if err != nil {
|
||||
g.log.Error(err.Error())
|
||||
}
|
||||
|
||||
stats := ICECandidateStats{
|
||||
Timestamp: statsTimestampFrom(candidateStats.Timestamp),
|
||||
ID: candidateStats.ID,
|
||||
Type: StatsTypeRemoteCandidate,
|
||||
IP: candidateStats.IP,
|
||||
Port: int32(candidateStats.Port),
|
||||
Protocol: networkType.Protocol(),
|
||||
CandidateType: candidateType,
|
||||
Priority: int32(candidateStats.Priority),
|
||||
URL: candidateStats.URL,
|
||||
RelayProtocol: candidateStats.RelayProtocol,
|
||||
}
|
||||
collector.Collect(stats.ID, stats)
|
||||
}
|
||||
collector.Done()
|
||||
}(collector, agent)
|
||||
}
|
||||
|
||||
func (g *ICEGatherer) getSelectedCandidatePairStats() (ICECandidatePairStats, bool) {
|
||||
agent := g.getAgent()
|
||||
if agent == nil {
|
||||
return ICECandidatePairStats{}, false
|
||||
}
|
||||
|
||||
selectedCandidatePairStats, isAvailable := agent.GetSelectedCandidatePairStats()
|
||||
if !isAvailable {
|
||||
return ICECandidatePairStats{}, false
|
||||
}
|
||||
|
||||
stats, err := toICECandidatePairStats(selectedCandidatePairStats)
|
||||
if err != nil {
|
||||
g.log.Error(err.Error())
|
||||
return ICECandidatePairStats{}, false
|
||||
}
|
||||
|
||||
return stats, true
|
||||
}
|
||||
51
server/vendor/github.com/pion/webrtc/v3/icegathererstate.go
generated
vendored
Normal file
51
server/vendor/github.com/pion/webrtc/v3/icegathererstate.go
generated
vendored
Normal file
@@ -0,0 +1,51 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package webrtc
|
||||
|
||||
import (
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
// ICEGathererState represents the current state of the ICE gatherer.
|
||||
type ICEGathererState uint32
|
||||
|
||||
const (
|
||||
// ICEGathererStateNew indicates object has been created but
|
||||
// gather() has not been called.
|
||||
ICEGathererStateNew ICEGathererState = iota + 1
|
||||
|
||||
// ICEGathererStateGathering indicates gather() has been called,
|
||||
// and the ICEGatherer is in the process of gathering candidates.
|
||||
ICEGathererStateGathering
|
||||
|
||||
// ICEGathererStateComplete indicates the ICEGatherer has completed gathering.
|
||||
ICEGathererStateComplete
|
||||
|
||||
// ICEGathererStateClosed indicates the closed state can only be entered
|
||||
// when the ICEGatherer has been closed intentionally by calling close().
|
||||
ICEGathererStateClosed
|
||||
)
|
||||
|
||||
func (s ICEGathererState) String() string {
|
||||
switch s {
|
||||
case ICEGathererStateNew:
|
||||
return "new"
|
||||
case ICEGathererStateGathering:
|
||||
return "gathering"
|
||||
case ICEGathererStateComplete:
|
||||
return "complete"
|
||||
case ICEGathererStateClosed:
|
||||
return "closed"
|
||||
default:
|
||||
return unknownStr
|
||||
}
|
||||
}
|
||||
|
||||
func atomicStoreICEGathererState(state *ICEGathererState, newState ICEGathererState) {
|
||||
atomic.StoreUint32((*uint32)(state), uint32(newState))
|
||||
}
|
||||
|
||||
func atomicLoadICEGathererState(state *ICEGathererState) ICEGathererState {
|
||||
return ICEGathererState(atomic.LoadUint32((*uint32)(state)))
|
||||
}
|
||||
56
server/vendor/github.com/pion/webrtc/v3/icegatheringstate.go
generated
vendored
Normal file
56
server/vendor/github.com/pion/webrtc/v3/icegatheringstate.go
generated
vendored
Normal file
@@ -0,0 +1,56 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package webrtc
|
||||
|
||||
// ICEGatheringState describes the state of the candidate gathering process.
|
||||
type ICEGatheringState int
|
||||
|
||||
const (
|
||||
// ICEGatheringStateNew indicates that any of the ICETransports are
|
||||
// in the "new" gathering state and none of the transports are in the
|
||||
// "gathering" state, or there are no transports.
|
||||
ICEGatheringStateNew ICEGatheringState = iota + 1
|
||||
|
||||
// ICEGatheringStateGathering indicates that any of the ICETransports
|
||||
// are in the "gathering" state.
|
||||
ICEGatheringStateGathering
|
||||
|
||||
// ICEGatheringStateComplete indicates that at least one ICETransport
|
||||
// exists, and all ICETransports are in the "completed" gathering state.
|
||||
ICEGatheringStateComplete
|
||||
)
|
||||
|
||||
// This is done this way because of a linter.
|
||||
const (
|
||||
iceGatheringStateNewStr = "new"
|
||||
iceGatheringStateGatheringStr = "gathering"
|
||||
iceGatheringStateCompleteStr = "complete"
|
||||
)
|
||||
|
||||
// NewICEGatheringState takes a string and converts it to ICEGatheringState
|
||||
func NewICEGatheringState(raw string) ICEGatheringState {
|
||||
switch raw {
|
||||
case iceGatheringStateNewStr:
|
||||
return ICEGatheringStateNew
|
||||
case iceGatheringStateGatheringStr:
|
||||
return ICEGatheringStateGathering
|
||||
case iceGatheringStateCompleteStr:
|
||||
return ICEGatheringStateComplete
|
||||
default:
|
||||
return ICEGatheringState(Unknown)
|
||||
}
|
||||
}
|
||||
|
||||
func (t ICEGatheringState) String() string {
|
||||
switch t {
|
||||
case ICEGatheringStateNew:
|
||||
return iceGatheringStateNewStr
|
||||
case ICEGatheringStateGathering:
|
||||
return iceGatheringStateGatheringStr
|
||||
case ICEGatheringStateComplete:
|
||||
return iceGatheringStateCompleteStr
|
||||
default:
|
||||
return ErrUnknownType.Error()
|
||||
}
|
||||
}
|
||||
10
server/vendor/github.com/pion/webrtc/v3/icegatheroptions.go
generated
vendored
Normal file
10
server/vendor/github.com/pion/webrtc/v3/icegatheroptions.go
generated
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package webrtc
|
||||
|
||||
// ICEGatherOptions provides options relating to the gathering of ICE candidates.
|
||||
type ICEGatherOptions struct {
|
||||
ICEServers []ICEServer
|
||||
ICEGatherPolicy ICETransportPolicy
|
||||
}
|
||||
30
server/vendor/github.com/pion/webrtc/v3/icemux.go
generated
vendored
Normal file
30
server/vendor/github.com/pion/webrtc/v3/icemux.go
generated
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package webrtc
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"github.com/pion/ice/v2"
|
||||
"github.com/pion/logging"
|
||||
)
|
||||
|
||||
// NewICETCPMux creates a new instance of ice.TCPMuxDefault. It enables use of
|
||||
// passive ICE TCP candidates.
|
||||
func NewICETCPMux(logger logging.LeveledLogger, listener net.Listener, readBufferSize int) ice.TCPMux {
|
||||
return ice.NewTCPMuxDefault(ice.TCPMuxParams{
|
||||
Listener: listener,
|
||||
Logger: logger,
|
||||
ReadBufferSize: readBufferSize,
|
||||
})
|
||||
}
|
||||
|
||||
// NewICEUDPMux creates a new instance of ice.UDPMuxDefault. It allows many PeerConnections to be served
|
||||
// by a single UDP Port.
|
||||
func NewICEUDPMux(logger logging.LeveledLogger, udpConn net.PacketConn) ice.UDPMux {
|
||||
return ice.NewUDPMuxDefault(ice.UDPMuxParams{
|
||||
UDPConn: udpConn,
|
||||
Logger: logger,
|
||||
})
|
||||
}
|
||||
12
server/vendor/github.com/pion/webrtc/v3/iceparameters.go
generated
vendored
Normal file
12
server/vendor/github.com/pion/webrtc/v3/iceparameters.go
generated
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package webrtc
|
||||
|
||||
// ICEParameters includes the ICE username fragment
|
||||
// and password and other ICE-related parameters.
|
||||
type ICEParameters struct {
|
||||
UsernameFragment string `json:"usernameFragment"`
|
||||
Password string `json:"password"`
|
||||
ICELite bool `json:"iceLite"`
|
||||
}
|
||||
50
server/vendor/github.com/pion/webrtc/v3/iceprotocol.go
generated
vendored
Normal file
50
server/vendor/github.com/pion/webrtc/v3/iceprotocol.go
generated
vendored
Normal file
@@ -0,0 +1,50 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package webrtc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ICEProtocol indicates the transport protocol type that is used in the
|
||||
// ice.URL structure.
|
||||
type ICEProtocol int
|
||||
|
||||
const (
|
||||
// ICEProtocolUDP indicates the URL uses a UDP transport.
|
||||
ICEProtocolUDP ICEProtocol = iota + 1
|
||||
|
||||
// ICEProtocolTCP indicates the URL uses a TCP transport.
|
||||
ICEProtocolTCP
|
||||
)
|
||||
|
||||
// This is done this way because of a linter.
|
||||
const (
|
||||
iceProtocolUDPStr = "udp"
|
||||
iceProtocolTCPStr = "tcp"
|
||||
)
|
||||
|
||||
// NewICEProtocol takes a string and converts it to ICEProtocol
|
||||
func NewICEProtocol(raw string) (ICEProtocol, error) {
|
||||
switch {
|
||||
case strings.EqualFold(iceProtocolUDPStr, raw):
|
||||
return ICEProtocolUDP, nil
|
||||
case strings.EqualFold(iceProtocolTCPStr, raw):
|
||||
return ICEProtocolTCP, nil
|
||||
default:
|
||||
return ICEProtocol(Unknown), fmt.Errorf("%w: %s", errICEProtocolUnknown, raw)
|
||||
}
|
||||
}
|
||||
|
||||
func (t ICEProtocol) String() string {
|
||||
switch t {
|
||||
case ICEProtocolUDP:
|
||||
return iceProtocolUDPStr
|
||||
case ICEProtocolTCP:
|
||||
return iceProtocolTCPStr
|
||||
default:
|
||||
return ErrUnknownType.Error()
|
||||
}
|
||||
}
|
||||
59
server/vendor/github.com/pion/webrtc/v3/icerole.go
generated
vendored
Normal file
59
server/vendor/github.com/pion/webrtc/v3/icerole.go
generated
vendored
Normal file
@@ -0,0 +1,59 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package webrtc
|
||||
|
||||
// ICERole describes the role ice.Agent is playing in selecting the
|
||||
// preferred the candidate pair.
|
||||
type ICERole int
|
||||
|
||||
const (
|
||||
// ICERoleControlling indicates that the ICE agent that is responsible
|
||||
// for selecting the final choice of candidate pairs and signaling them
|
||||
// through STUN and an updated offer, if needed. In any session, one agent
|
||||
// is always controlling. The other is the controlled agent.
|
||||
ICERoleControlling ICERole = iota + 1
|
||||
|
||||
// ICERoleControlled indicates that an ICE agent that waits for the
|
||||
// controlling agent to select the final choice of candidate pairs.
|
||||
ICERoleControlled
|
||||
)
|
||||
|
||||
// This is done this way because of a linter.
|
||||
const (
|
||||
iceRoleControllingStr = "controlling"
|
||||
iceRoleControlledStr = "controlled"
|
||||
)
|
||||
|
||||
func newICERole(raw string) ICERole {
|
||||
switch raw {
|
||||
case iceRoleControllingStr:
|
||||
return ICERoleControlling
|
||||
case iceRoleControlledStr:
|
||||
return ICERoleControlled
|
||||
default:
|
||||
return ICERole(Unknown)
|
||||
}
|
||||
}
|
||||
|
||||
func (t ICERole) String() string {
|
||||
switch t {
|
||||
case ICERoleControlling:
|
||||
return iceRoleControllingStr
|
||||
case ICERoleControlled:
|
||||
return iceRoleControlledStr
|
||||
default:
|
||||
return ErrUnknownType.Error()
|
||||
}
|
||||
}
|
||||
|
||||
// MarshalText implements encoding.TextMarshaler
|
||||
func (t ICERole) MarshalText() ([]byte, error) {
|
||||
return []byte(t.String()), nil
|
||||
}
|
||||
|
||||
// UnmarshalText implements encoding.TextUnmarshaler
|
||||
func (t *ICERole) UnmarshalText(b []byte) error {
|
||||
*t = newICERole(string(b))
|
||||
return nil
|
||||
}
|
||||
182
server/vendor/github.com/pion/webrtc/v3/iceserver.go
generated
vendored
Normal file
182
server/vendor/github.com/pion/webrtc/v3/iceserver.go
generated
vendored
Normal file
@@ -0,0 +1,182 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
//go:build !js
|
||||
// +build !js
|
||||
|
||||
package webrtc
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/pion/stun"
|
||||
"github.com/pion/webrtc/v3/pkg/rtcerr"
|
||||
)
|
||||
|
||||
// ICEServer describes a single STUN and TURN server that can be used by
|
||||
// the ICEAgent to establish a connection with a peer.
|
||||
type ICEServer struct {
|
||||
URLs []string `json:"urls"`
|
||||
Username string `json:"username,omitempty"`
|
||||
Credential interface{} `json:"credential,omitempty"`
|
||||
CredentialType ICECredentialType `json:"credentialType,omitempty"`
|
||||
}
|
||||
|
||||
func (s ICEServer) parseURL(i int) (*stun.URI, error) {
|
||||
return stun.ParseURI(s.URLs[i])
|
||||
}
|
||||
|
||||
func (s ICEServer) validate() error {
|
||||
_, err := s.urls()
|
||||
return err
|
||||
}
|
||||
|
||||
func (s ICEServer) urls() ([]*stun.URI, error) {
|
||||
urls := []*stun.URI{}
|
||||
|
||||
for i := range s.URLs {
|
||||
url, err := s.parseURL(i)
|
||||
if err != nil {
|
||||
return nil, &rtcerr.InvalidAccessError{Err: err}
|
||||
}
|
||||
|
||||
if url.Scheme == stun.SchemeTypeTURN || url.Scheme == stun.SchemeTypeTURNS {
|
||||
// https://www.w3.org/TR/webrtc/#set-the-configuration (step #11.3.2)
|
||||
if s.Username == "" || s.Credential == nil {
|
||||
return nil, &rtcerr.InvalidAccessError{Err: ErrNoTurnCredentials}
|
||||
}
|
||||
url.Username = s.Username
|
||||
|
||||
switch s.CredentialType {
|
||||
case ICECredentialTypePassword:
|
||||
// https://www.w3.org/TR/webrtc/#set-the-configuration (step #11.3.3)
|
||||
password, ok := s.Credential.(string)
|
||||
if !ok {
|
||||
return nil, &rtcerr.InvalidAccessError{Err: ErrTurnCredentials}
|
||||
}
|
||||
url.Password = password
|
||||
|
||||
case ICECredentialTypeOauth:
|
||||
// https://www.w3.org/TR/webrtc/#set-the-configuration (step #11.3.4)
|
||||
if _, ok := s.Credential.(OAuthCredential); !ok {
|
||||
return nil, &rtcerr.InvalidAccessError{Err: ErrTurnCredentials}
|
||||
}
|
||||
|
||||
default:
|
||||
return nil, &rtcerr.InvalidAccessError{Err: ErrTurnCredentials}
|
||||
}
|
||||
}
|
||||
|
||||
urls = append(urls, url)
|
||||
}
|
||||
|
||||
return urls, nil
|
||||
}
|
||||
|
||||
func iceserverUnmarshalUrls(val interface{}) (*[]string, error) {
|
||||
s, ok := val.([]interface{})
|
||||
if !ok {
|
||||
return nil, errInvalidICEServer
|
||||
}
|
||||
out := make([]string, len(s))
|
||||
for idx, url := range s {
|
||||
out[idx], ok = url.(string)
|
||||
if !ok {
|
||||
return nil, errInvalidICEServer
|
||||
}
|
||||
}
|
||||
return &out, nil
|
||||
}
|
||||
|
||||
func iceserverUnmarshalOauth(val interface{}) (*OAuthCredential, error) {
|
||||
c, ok := val.(map[string]interface{})
|
||||
if !ok {
|
||||
return nil, errInvalidICEServer
|
||||
}
|
||||
MACKey, ok := c["MACKey"].(string)
|
||||
if !ok {
|
||||
return nil, errInvalidICEServer
|
||||
}
|
||||
AccessToken, ok := c["AccessToken"].(string)
|
||||
if !ok {
|
||||
return nil, errInvalidICEServer
|
||||
}
|
||||
return &OAuthCredential{
|
||||
MACKey: MACKey,
|
||||
AccessToken: AccessToken,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *ICEServer) iceserverUnmarshalFields(m map[string]interface{}) error {
|
||||
if val, ok := m["urls"]; ok {
|
||||
u, err := iceserverUnmarshalUrls(val)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.URLs = *u
|
||||
} else {
|
||||
s.URLs = []string{}
|
||||
}
|
||||
|
||||
if val, ok := m["username"]; ok {
|
||||
s.Username, ok = val.(string)
|
||||
if !ok {
|
||||
return errInvalidICEServer
|
||||
}
|
||||
}
|
||||
if val, ok := m["credentialType"]; ok {
|
||||
ct, ok := val.(string)
|
||||
if !ok {
|
||||
return errInvalidICEServer
|
||||
}
|
||||
tpe, err := newICECredentialType(ct)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.CredentialType = tpe
|
||||
} else {
|
||||
s.CredentialType = ICECredentialTypePassword
|
||||
}
|
||||
if val, ok := m["credential"]; ok {
|
||||
switch s.CredentialType {
|
||||
case ICECredentialTypePassword:
|
||||
s.Credential = val
|
||||
case ICECredentialTypeOauth:
|
||||
c, err := iceserverUnmarshalOauth(val)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.Credential = *c
|
||||
default:
|
||||
return errInvalidICECredentialTypeString
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON parses the JSON-encoded data and stores the result
|
||||
func (s *ICEServer) UnmarshalJSON(b []byte) error {
|
||||
var tmp interface{}
|
||||
err := json.Unmarshal(b, &tmp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if m, ok := tmp.(map[string]interface{}); ok {
|
||||
return s.iceserverUnmarshalFields(m)
|
||||
}
|
||||
return errInvalidICEServer
|
||||
}
|
||||
|
||||
// MarshalJSON returns the JSON encoding
|
||||
func (s ICEServer) MarshalJSON() ([]byte, error) {
|
||||
m := make(map[string]interface{})
|
||||
m["urls"] = s.URLs
|
||||
if s.Username != "" {
|
||||
m["username"] = s.Username
|
||||
}
|
||||
if s.Credential != nil {
|
||||
m["credential"] = s.Credential
|
||||
}
|
||||
m["credentialType"] = s.CredentialType
|
||||
return json.Marshal(m)
|
||||
}
|
||||
46
server/vendor/github.com/pion/webrtc/v3/iceserver_js.go
generated
vendored
Normal file
46
server/vendor/github.com/pion/webrtc/v3/iceserver_js.go
generated
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
//go:build js && wasm
|
||||
// +build js,wasm
|
||||
|
||||
package webrtc
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/pion/ice/v2"
|
||||
)
|
||||
|
||||
// ICEServer describes a single STUN and TURN server that can be used by
|
||||
// the ICEAgent to establish a connection with a peer.
|
||||
type ICEServer struct {
|
||||
URLs []string
|
||||
Username string
|
||||
// Note: TURN is not supported in the WASM bindings yet
|
||||
Credential interface{}
|
||||
CredentialType ICECredentialType
|
||||
}
|
||||
|
||||
func (s ICEServer) parseURL(i int) (*ice.URL, error) {
|
||||
return ice.ParseURL(s.URLs[i])
|
||||
}
|
||||
|
||||
func (s ICEServer) validate() ([]*ice.URL, error) {
|
||||
urls := []*ice.URL{}
|
||||
|
||||
for i := range s.URLs {
|
||||
url, err := s.parseURL(i)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if url.Scheme == ice.SchemeTypeTURN || url.Scheme == ice.SchemeTypeTURNS {
|
||||
return nil, errors.New("TURN is not currently supported in the JavaScript/Wasm bindings")
|
||||
}
|
||||
|
||||
urls = append(urls, url)
|
||||
}
|
||||
|
||||
return urls, nil
|
||||
}
|
||||
421
server/vendor/github.com/pion/webrtc/v3/icetransport.go
generated
vendored
Normal file
421
server/vendor/github.com/pion/webrtc/v3/icetransport.go
generated
vendored
Normal file
@@ -0,0 +1,421 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
//go:build !js
|
||||
// +build !js
|
||||
|
||||
package webrtc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/pion/ice/v2"
|
||||
"github.com/pion/logging"
|
||||
"github.com/pion/webrtc/v3/internal/mux"
|
||||
"github.com/pion/webrtc/v3/internal/util"
|
||||
)
|
||||
|
||||
// ICETransport allows an application access to information about the ICE
|
||||
// transport over which packets are sent and received.
|
||||
type ICETransport struct {
|
||||
lock sync.RWMutex
|
||||
|
||||
role ICERole
|
||||
|
||||
onConnectionStateChangeHandler atomic.Value // func(ICETransportState)
|
||||
internalOnConnectionStateChangeHandler atomic.Value // func(ICETransportState)
|
||||
onSelectedCandidatePairChangeHandler atomic.Value // func(*ICECandidatePair)
|
||||
|
||||
state atomic.Value // ICETransportState
|
||||
|
||||
gatherer *ICEGatherer
|
||||
conn *ice.Conn
|
||||
mux *mux.Mux
|
||||
|
||||
ctx context.Context
|
||||
ctxCancel func()
|
||||
|
||||
loggerFactory logging.LoggerFactory
|
||||
|
||||
log logging.LeveledLogger
|
||||
}
|
||||
|
||||
// GetSelectedCandidatePair returns the selected candidate pair on which packets are sent
|
||||
// if there is no selected pair nil is returned
|
||||
func (t *ICETransport) GetSelectedCandidatePair() (*ICECandidatePair, error) {
|
||||
agent := t.gatherer.getAgent()
|
||||
if agent == nil {
|
||||
return nil, nil //nolint:nilnil
|
||||
}
|
||||
|
||||
icePair, err := agent.GetSelectedCandidatePair()
|
||||
if icePair == nil || err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
local, err := newICECandidateFromICE(icePair.Local)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
remote, err := newICECandidateFromICE(icePair.Remote)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return NewICECandidatePair(&local, &remote), nil
|
||||
}
|
||||
|
||||
// GetSelectedCandidatePairStats returns the selected candidate pair stats on which packets are sent
|
||||
// if there is no selected pair empty stats, false is returned to indicate stats not available
|
||||
func (t *ICETransport) GetSelectedCandidatePairStats() (ICECandidatePairStats, bool) {
|
||||
return t.gatherer.getSelectedCandidatePairStats()
|
||||
}
|
||||
|
||||
// NewICETransport creates a new NewICETransport.
|
||||
func NewICETransport(gatherer *ICEGatherer, loggerFactory logging.LoggerFactory) *ICETransport {
|
||||
iceTransport := &ICETransport{
|
||||
gatherer: gatherer,
|
||||
loggerFactory: loggerFactory,
|
||||
log: loggerFactory.NewLogger("ortc"),
|
||||
}
|
||||
iceTransport.setState(ICETransportStateNew)
|
||||
return iceTransport
|
||||
}
|
||||
|
||||
// Start incoming connectivity checks based on its configured role.
|
||||
func (t *ICETransport) Start(gatherer *ICEGatherer, params ICEParameters, role *ICERole) error {
|
||||
t.lock.Lock()
|
||||
defer t.lock.Unlock()
|
||||
|
||||
if t.State() != ICETransportStateNew {
|
||||
return errICETransportNotInNew
|
||||
}
|
||||
|
||||
if gatherer != nil {
|
||||
t.gatherer = gatherer
|
||||
}
|
||||
|
||||
if err := t.ensureGatherer(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
agent := t.gatherer.getAgent()
|
||||
if agent == nil {
|
||||
return fmt.Errorf("%w: unable to start ICETransport", errICEAgentNotExist)
|
||||
}
|
||||
|
||||
if err := agent.OnConnectionStateChange(func(iceState ice.ConnectionState) {
|
||||
state := newICETransportStateFromICE(iceState)
|
||||
|
||||
t.setState(state)
|
||||
t.onConnectionStateChange(state)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := agent.OnSelectedCandidatePairChange(func(local, remote ice.Candidate) {
|
||||
candidates, err := newICECandidatesFromICE([]ice.Candidate{local, remote})
|
||||
if err != nil {
|
||||
t.log.Warnf("%w: %s", errICECandiatesCoversionFailed, err)
|
||||
return
|
||||
}
|
||||
t.onSelectedCandidatePairChange(NewICECandidatePair(&candidates[0], &candidates[1]))
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if role == nil {
|
||||
controlled := ICERoleControlled
|
||||
role = &controlled
|
||||
}
|
||||
t.role = *role
|
||||
|
||||
t.ctx, t.ctxCancel = context.WithCancel(context.Background())
|
||||
|
||||
// Drop the lock here to allow ICE candidates to be
|
||||
// added so that the agent can complete a connection
|
||||
t.lock.Unlock()
|
||||
|
||||
var iceConn *ice.Conn
|
||||
var err error
|
||||
switch *role {
|
||||
case ICERoleControlling:
|
||||
iceConn, err = agent.Dial(t.ctx,
|
||||
params.UsernameFragment,
|
||||
params.Password)
|
||||
|
||||
case ICERoleControlled:
|
||||
iceConn, err = agent.Accept(t.ctx,
|
||||
params.UsernameFragment,
|
||||
params.Password)
|
||||
|
||||
default:
|
||||
err = errICERoleUnknown
|
||||
}
|
||||
|
||||
// Reacquire the lock to set the connection/mux
|
||||
t.lock.Lock()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if t.State() == ICETransportStateClosed {
|
||||
return errICETransportClosed
|
||||
}
|
||||
|
||||
t.conn = iceConn
|
||||
|
||||
config := mux.Config{
|
||||
Conn: t.conn,
|
||||
BufferSize: int(t.gatherer.api.settingEngine.getReceiveMTU()),
|
||||
LoggerFactory: t.loggerFactory,
|
||||
}
|
||||
t.mux = mux.NewMux(config)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// restart is not exposed currently because ORTC has users create a whole new ICETransport
|
||||
// so for now lets keep it private so we don't cause ORTC users to depend on non-standard APIs
|
||||
func (t *ICETransport) restart() error {
|
||||
t.lock.Lock()
|
||||
defer t.lock.Unlock()
|
||||
|
||||
agent := t.gatherer.getAgent()
|
||||
if agent == nil {
|
||||
return fmt.Errorf("%w: unable to restart ICETransport", errICEAgentNotExist)
|
||||
}
|
||||
|
||||
if err := agent.Restart(t.gatherer.api.settingEngine.candidates.UsernameFragment, t.gatherer.api.settingEngine.candidates.Password); err != nil {
|
||||
return err
|
||||
}
|
||||
return t.gatherer.Gather()
|
||||
}
|
||||
|
||||
// Stop irreversibly stops the ICETransport.
|
||||
func (t *ICETransport) Stop() error {
|
||||
return t.stop(false /* shouldGracefullyClose */)
|
||||
}
|
||||
|
||||
// GracefulStop irreversibly stops the ICETransport. It also waits
|
||||
// for any goroutines it started to complete. This is only safe to call outside of
|
||||
// ICETransport callbacks or if in a callback, in its own goroutine.
|
||||
func (t *ICETransport) GracefulStop() error {
|
||||
return t.stop(true /* shouldGracefullyClose */)
|
||||
}
|
||||
|
||||
func (t *ICETransport) stop(shouldGracefullyClose bool) error {
|
||||
t.lock.Lock()
|
||||
t.setState(ICETransportStateClosed)
|
||||
|
||||
if t.ctxCancel != nil {
|
||||
t.ctxCancel()
|
||||
}
|
||||
|
||||
// mux and gatherer can only be set when ICETransport.State != Closed.
|
||||
mux := t.mux
|
||||
gatherer := t.gatherer
|
||||
t.lock.Unlock()
|
||||
|
||||
if t.mux != nil {
|
||||
var closeErrs []error
|
||||
if shouldGracefullyClose && gatherer != nil {
|
||||
// we can't access icegatherer/icetransport.Close via
|
||||
// mux's net.Conn Close so we call it earlier here.
|
||||
closeErrs = append(closeErrs, gatherer.GracefulClose())
|
||||
}
|
||||
closeErrs = append(closeErrs, mux.Close())
|
||||
return util.FlattenErrs(closeErrs)
|
||||
} else if gatherer != nil {
|
||||
if shouldGracefullyClose {
|
||||
return gatherer.GracefulClose()
|
||||
}
|
||||
return gatherer.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// OnSelectedCandidatePairChange sets a handler that is invoked when a new
|
||||
// ICE candidate pair is selected
|
||||
func (t *ICETransport) OnSelectedCandidatePairChange(f func(*ICECandidatePair)) {
|
||||
t.onSelectedCandidatePairChangeHandler.Store(f)
|
||||
}
|
||||
|
||||
func (t *ICETransport) onSelectedCandidatePairChange(pair *ICECandidatePair) {
|
||||
if handler, ok := t.onSelectedCandidatePairChangeHandler.Load().(func(*ICECandidatePair)); ok {
|
||||
handler(pair)
|
||||
}
|
||||
}
|
||||
|
||||
// OnConnectionStateChange sets a handler that is fired when the ICE
|
||||
// connection state changes.
|
||||
func (t *ICETransport) OnConnectionStateChange(f func(ICETransportState)) {
|
||||
t.onConnectionStateChangeHandler.Store(f)
|
||||
}
|
||||
|
||||
func (t *ICETransport) onConnectionStateChange(state ICETransportState) {
|
||||
if handler, ok := t.onConnectionStateChangeHandler.Load().(func(ICETransportState)); ok {
|
||||
handler(state)
|
||||
}
|
||||
if handler, ok := t.internalOnConnectionStateChangeHandler.Load().(func(ICETransportState)); ok {
|
||||
handler(state)
|
||||
}
|
||||
}
|
||||
|
||||
// Role indicates the current role of the ICE transport.
|
||||
func (t *ICETransport) Role() ICERole {
|
||||
t.lock.RLock()
|
||||
defer t.lock.RUnlock()
|
||||
|
||||
return t.role
|
||||
}
|
||||
|
||||
// SetRemoteCandidates sets the sequence of candidates associated with the remote ICETransport.
|
||||
func (t *ICETransport) SetRemoteCandidates(remoteCandidates []ICECandidate) error {
|
||||
t.lock.RLock()
|
||||
defer t.lock.RUnlock()
|
||||
|
||||
if err := t.ensureGatherer(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
agent := t.gatherer.getAgent()
|
||||
if agent == nil {
|
||||
return fmt.Errorf("%w: unable to set remote candidates", errICEAgentNotExist)
|
||||
}
|
||||
|
||||
for _, c := range remoteCandidates {
|
||||
i, err := c.toICE()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = agent.AddRemoteCandidate(i); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddRemoteCandidate adds a candidate associated with the remote ICETransport.
|
||||
func (t *ICETransport) AddRemoteCandidate(remoteCandidate *ICECandidate) error {
|
||||
t.lock.RLock()
|
||||
defer t.lock.RUnlock()
|
||||
|
||||
var (
|
||||
c ice.Candidate
|
||||
err error
|
||||
)
|
||||
|
||||
if err = t.ensureGatherer(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if remoteCandidate != nil {
|
||||
if c, err = remoteCandidate.toICE(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
agent := t.gatherer.getAgent()
|
||||
if agent == nil {
|
||||
return fmt.Errorf("%w: unable to add remote candidates", errICEAgentNotExist)
|
||||
}
|
||||
|
||||
return agent.AddRemoteCandidate(c)
|
||||
}
|
||||
|
||||
// State returns the current ice transport state.
|
||||
func (t *ICETransport) State() ICETransportState {
|
||||
if v, ok := t.state.Load().(ICETransportState); ok {
|
||||
return v
|
||||
}
|
||||
return ICETransportState(0)
|
||||
}
|
||||
|
||||
// GetLocalParameters returns an IceParameters object which provides information
|
||||
// uniquely identifying the local peer for the duration of the ICE session.
|
||||
func (t *ICETransport) GetLocalParameters() (ICEParameters, error) {
|
||||
if err := t.ensureGatherer(); err != nil {
|
||||
return ICEParameters{}, err
|
||||
}
|
||||
|
||||
return t.gatherer.GetLocalParameters()
|
||||
}
|
||||
|
||||
func (t *ICETransport) setState(i ICETransportState) {
|
||||
t.state.Store(i)
|
||||
}
|
||||
|
||||
func (t *ICETransport) newEndpoint(f mux.MatchFunc) *mux.Endpoint {
|
||||
t.lock.Lock()
|
||||
defer t.lock.Unlock()
|
||||
return t.mux.NewEndpoint(f)
|
||||
}
|
||||
|
||||
func (t *ICETransport) ensureGatherer() error {
|
||||
if t.gatherer == nil {
|
||||
return errICEGathererNotStarted
|
||||
} else if t.gatherer.getAgent() == nil {
|
||||
if err := t.gatherer.createAgent(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *ICETransport) collectStats(collector *statsReportCollector) {
|
||||
t.lock.Lock()
|
||||
conn := t.conn
|
||||
t.lock.Unlock()
|
||||
|
||||
collector.Collecting()
|
||||
|
||||
stats := TransportStats{
|
||||
Timestamp: statsTimestampFrom(time.Now()),
|
||||
Type: StatsTypeTransport,
|
||||
ID: "iceTransport",
|
||||
}
|
||||
|
||||
if conn != nil {
|
||||
stats.BytesSent = conn.BytesSent()
|
||||
stats.BytesReceived = conn.BytesReceived()
|
||||
}
|
||||
|
||||
collector.Collect(stats.ID, stats)
|
||||
}
|
||||
|
||||
func (t *ICETransport) haveRemoteCredentialsChange(newUfrag, newPwd string) bool {
|
||||
t.lock.Lock()
|
||||
defer t.lock.Unlock()
|
||||
|
||||
agent := t.gatherer.getAgent()
|
||||
if agent == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
uFrag, uPwd, err := agent.GetRemoteUserCredentials()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return uFrag != newUfrag || uPwd != newPwd
|
||||
}
|
||||
|
||||
func (t *ICETransport) setRemoteCredentials(newUfrag, newPwd string) error {
|
||||
t.lock.Lock()
|
||||
defer t.lock.Unlock()
|
||||
|
||||
agent := t.gatherer.getAgent()
|
||||
if agent == nil {
|
||||
return fmt.Errorf("%w: unable to SetRemoteCredentials", errICEAgentNotExist)
|
||||
}
|
||||
|
||||
return agent.SetRemoteCredentials(newUfrag, newPwd)
|
||||
}
|
||||
30
server/vendor/github.com/pion/webrtc/v3/icetransport_js.go
generated
vendored
Normal file
30
server/vendor/github.com/pion/webrtc/v3/icetransport_js.go
generated
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
//go:build js && wasm
|
||||
// +build js,wasm
|
||||
|
||||
package webrtc
|
||||
|
||||
import "syscall/js"
|
||||
|
||||
// ICETransport allows an application access to information about the ICE
|
||||
// transport over which packets are sent and received.
|
||||
type ICETransport struct {
|
||||
// Pointer to the underlying JavaScript ICETransport object.
|
||||
underlying js.Value
|
||||
}
|
||||
|
||||
// GetSelectedCandidatePair returns the selected candidate pair on which packets are sent
|
||||
// if there is no selected pair nil is returned
|
||||
func (t *ICETransport) GetSelectedCandidatePair() (*ICECandidatePair, error) {
|
||||
val := t.underlying.Call("getSelectedCandidatePair")
|
||||
if val.IsNull() || val.IsUndefined() {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return NewICECandidatePair(
|
||||
valueToICECandidate(val.Get("local")),
|
||||
valueToICECandidate(val.Get("remote")),
|
||||
), nil
|
||||
}
|
||||
68
server/vendor/github.com/pion/webrtc/v3/icetransportpolicy.go
generated
vendored
Normal file
68
server/vendor/github.com/pion/webrtc/v3/icetransportpolicy.go
generated
vendored
Normal file
@@ -0,0 +1,68 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package webrtc
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
// ICETransportPolicy defines the ICE candidate policy surface the
|
||||
// permitted candidates. Only these candidates are used for connectivity checks.
|
||||
type ICETransportPolicy int
|
||||
|
||||
// ICEGatherPolicy is the ORTC equivalent of ICETransportPolicy
|
||||
type ICEGatherPolicy = ICETransportPolicy
|
||||
|
||||
const (
|
||||
// ICETransportPolicyAll indicates any type of candidate is used.
|
||||
ICETransportPolicyAll ICETransportPolicy = iota
|
||||
|
||||
// ICETransportPolicyRelay indicates only media relay candidates such
|
||||
// as candidates passing through a TURN server are used.
|
||||
ICETransportPolicyRelay
|
||||
)
|
||||
|
||||
// This is done this way because of a linter.
|
||||
const (
|
||||
iceTransportPolicyRelayStr = "relay"
|
||||
iceTransportPolicyAllStr = "all"
|
||||
)
|
||||
|
||||
// NewICETransportPolicy takes a string and converts it to ICETransportPolicy
|
||||
func NewICETransportPolicy(raw string) ICETransportPolicy {
|
||||
switch raw {
|
||||
case iceTransportPolicyRelayStr:
|
||||
return ICETransportPolicyRelay
|
||||
case iceTransportPolicyAllStr:
|
||||
return ICETransportPolicyAll
|
||||
default:
|
||||
return ICETransportPolicy(Unknown)
|
||||
}
|
||||
}
|
||||
|
||||
func (t ICETransportPolicy) String() string {
|
||||
switch t {
|
||||
case ICETransportPolicyRelay:
|
||||
return iceTransportPolicyRelayStr
|
||||
case ICETransportPolicyAll:
|
||||
return iceTransportPolicyAllStr
|
||||
default:
|
||||
return ErrUnknownType.Error()
|
||||
}
|
||||
}
|
||||
|
||||
// UnmarshalJSON parses the JSON-encoded data and stores the result
|
||||
func (t *ICETransportPolicy) UnmarshalJSON(b []byte) error {
|
||||
var val string
|
||||
if err := json.Unmarshal(b, &val); err != nil {
|
||||
return err
|
||||
}
|
||||
*t = NewICETransportPolicy(val)
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalJSON returns the JSON encoding
|
||||
func (t ICETransportPolicy) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(t.String())
|
||||
}
|
||||
110
server/vendor/github.com/pion/webrtc/v3/icetransportstate.go
generated
vendored
Normal file
110
server/vendor/github.com/pion/webrtc/v3/icetransportstate.go
generated
vendored
Normal file
@@ -0,0 +1,110 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package webrtc
|
||||
|
||||
import "github.com/pion/ice/v2"
|
||||
|
||||
// ICETransportState represents the current state of the ICE transport.
|
||||
type ICETransportState int
|
||||
|
||||
const (
|
||||
// ICETransportStateNew indicates the ICETransport is waiting
|
||||
// for remote candidates to be supplied.
|
||||
ICETransportStateNew = iota + 1
|
||||
|
||||
// ICETransportStateChecking indicates the ICETransport has
|
||||
// received at least one remote candidate, and a local and remote
|
||||
// ICECandidateComplete dictionary was not added as the last candidate.
|
||||
ICETransportStateChecking
|
||||
|
||||
// ICETransportStateConnected indicates the ICETransport has
|
||||
// received a response to an outgoing connectivity check, or has
|
||||
// received incoming DTLS/media after a successful response to an
|
||||
// incoming connectivity check, but is still checking other candidate
|
||||
// pairs to see if there is a better connection.
|
||||
ICETransportStateConnected
|
||||
|
||||
// ICETransportStateCompleted indicates the ICETransport tested
|
||||
// all appropriate candidate pairs and at least one functioning
|
||||
// candidate pair has been found.
|
||||
ICETransportStateCompleted
|
||||
|
||||
// ICETransportStateFailed indicates the ICETransport the last
|
||||
// candidate was added and all appropriate candidate pairs have either
|
||||
// failed connectivity checks or have lost consent.
|
||||
ICETransportStateFailed
|
||||
|
||||
// ICETransportStateDisconnected indicates the ICETransport has received
|
||||
// at least one local and remote candidate, but the final candidate was
|
||||
// received yet and all appropriate candidate pairs thus far have been
|
||||
// tested and failed.
|
||||
ICETransportStateDisconnected
|
||||
|
||||
// ICETransportStateClosed indicates the ICETransport has shut down
|
||||
// and is no longer responding to STUN requests.
|
||||
ICETransportStateClosed
|
||||
)
|
||||
|
||||
func (c ICETransportState) String() string {
|
||||
switch c {
|
||||
case ICETransportStateNew:
|
||||
return "new"
|
||||
case ICETransportStateChecking:
|
||||
return "checking"
|
||||
case ICETransportStateConnected:
|
||||
return "connected"
|
||||
case ICETransportStateCompleted:
|
||||
return "completed"
|
||||
case ICETransportStateFailed:
|
||||
return "failed"
|
||||
case ICETransportStateDisconnected:
|
||||
return "disconnected"
|
||||
case ICETransportStateClosed:
|
||||
return "closed"
|
||||
default:
|
||||
return unknownStr
|
||||
}
|
||||
}
|
||||
|
||||
func newICETransportStateFromICE(i ice.ConnectionState) ICETransportState {
|
||||
switch i {
|
||||
case ice.ConnectionStateNew:
|
||||
return ICETransportStateNew
|
||||
case ice.ConnectionStateChecking:
|
||||
return ICETransportStateChecking
|
||||
case ice.ConnectionStateConnected:
|
||||
return ICETransportStateConnected
|
||||
case ice.ConnectionStateCompleted:
|
||||
return ICETransportStateCompleted
|
||||
case ice.ConnectionStateFailed:
|
||||
return ICETransportStateFailed
|
||||
case ice.ConnectionStateDisconnected:
|
||||
return ICETransportStateDisconnected
|
||||
case ice.ConnectionStateClosed:
|
||||
return ICETransportStateClosed
|
||||
default:
|
||||
return ICETransportState(Unknown)
|
||||
}
|
||||
}
|
||||
|
||||
func (c ICETransportState) toICE() ice.ConnectionState {
|
||||
switch c {
|
||||
case ICETransportStateNew:
|
||||
return ice.ConnectionStateNew
|
||||
case ICETransportStateChecking:
|
||||
return ice.ConnectionStateChecking
|
||||
case ICETransportStateConnected:
|
||||
return ice.ConnectionStateConnected
|
||||
case ICETransportStateCompleted:
|
||||
return ice.ConnectionStateCompleted
|
||||
case ICETransportStateFailed:
|
||||
return ice.ConnectionStateFailed
|
||||
case ICETransportStateDisconnected:
|
||||
return ice.ConnectionStateDisconnected
|
||||
case ICETransportStateClosed:
|
||||
return ice.ConnectionStateClosed
|
||||
default:
|
||||
return ice.ConnectionState(Unknown)
|
||||
}
|
||||
}
|
||||
167
server/vendor/github.com/pion/webrtc/v3/interceptor.go
generated
vendored
Normal file
167
server/vendor/github.com/pion/webrtc/v3/interceptor.go
generated
vendored
Normal file
@@ -0,0 +1,167 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
//go:build !js
|
||||
// +build !js
|
||||
|
||||
package webrtc
|
||||
|
||||
import (
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/pion/interceptor"
|
||||
"github.com/pion/interceptor/pkg/nack"
|
||||
"github.com/pion/interceptor/pkg/report"
|
||||
"github.com/pion/interceptor/pkg/twcc"
|
||||
"github.com/pion/rtp"
|
||||
"github.com/pion/sdp/v3"
|
||||
)
|
||||
|
||||
// RegisterDefaultInterceptors will register some useful interceptors.
|
||||
// If you want to customize which interceptors are loaded, you should copy the
|
||||
// code from this method and remove unwanted interceptors.
|
||||
func RegisterDefaultInterceptors(mediaEngine *MediaEngine, interceptorRegistry *interceptor.Registry) error {
|
||||
if err := ConfigureNack(mediaEngine, interceptorRegistry); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := ConfigureRTCPReports(interceptorRegistry); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return ConfigureTWCCSender(mediaEngine, interceptorRegistry)
|
||||
}
|
||||
|
||||
// ConfigureRTCPReports will setup everything necessary for generating Sender and Receiver Reports
|
||||
func ConfigureRTCPReports(interceptorRegistry *interceptor.Registry) error {
|
||||
reciver, err := report.NewReceiverInterceptor()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sender, err := report.NewSenderInterceptor()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
interceptorRegistry.Add(reciver)
|
||||
interceptorRegistry.Add(sender)
|
||||
return nil
|
||||
}
|
||||
|
||||
// ConfigureNack will setup everything necessary for handling generating/responding to nack messages.
|
||||
func ConfigureNack(mediaEngine *MediaEngine, interceptorRegistry *interceptor.Registry) error {
|
||||
generator, err := nack.NewGeneratorInterceptor()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
responder, err := nack.NewResponderInterceptor()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
mediaEngine.RegisterFeedback(RTCPFeedback{Type: "nack"}, RTPCodecTypeVideo)
|
||||
mediaEngine.RegisterFeedback(RTCPFeedback{Type: "nack", Parameter: "pli"}, RTPCodecTypeVideo)
|
||||
interceptorRegistry.Add(responder)
|
||||
interceptorRegistry.Add(generator)
|
||||
return nil
|
||||
}
|
||||
|
||||
// ConfigureTWCCHeaderExtensionSender will setup everything necessary for adding
|
||||
// a TWCC header extension to outgoing RTP packets. This will allow the remote peer to generate TWCC reports.
|
||||
func ConfigureTWCCHeaderExtensionSender(mediaEngine *MediaEngine, interceptorRegistry *interceptor.Registry) error {
|
||||
if err := mediaEngine.RegisterHeaderExtension(RTPHeaderExtensionCapability{URI: sdp.TransportCCURI}, RTPCodecTypeVideo); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := mediaEngine.RegisterHeaderExtension(RTPHeaderExtensionCapability{URI: sdp.TransportCCURI}, RTPCodecTypeAudio); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
i, err := twcc.NewHeaderExtensionInterceptor()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
interceptorRegistry.Add(i)
|
||||
return nil
|
||||
}
|
||||
|
||||
// ConfigureTWCCSender will setup everything necessary for generating TWCC reports.
|
||||
func ConfigureTWCCSender(mediaEngine *MediaEngine, interceptorRegistry *interceptor.Registry) error {
|
||||
mediaEngine.RegisterFeedback(RTCPFeedback{Type: TypeRTCPFBTransportCC}, RTPCodecTypeVideo)
|
||||
if err := mediaEngine.RegisterHeaderExtension(RTPHeaderExtensionCapability{URI: sdp.TransportCCURI}, RTPCodecTypeVideo); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
mediaEngine.RegisterFeedback(RTCPFeedback{Type: TypeRTCPFBTransportCC}, RTPCodecTypeAudio)
|
||||
if err := mediaEngine.RegisterHeaderExtension(RTPHeaderExtensionCapability{URI: sdp.TransportCCURI}, RTPCodecTypeAudio); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
generator, err := twcc.NewSenderInterceptor()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
interceptorRegistry.Add(generator)
|
||||
return nil
|
||||
}
|
||||
|
||||
// ConfigureSimulcastExtensionHeaders enables the RTP Extension Headers needed for Simulcast
|
||||
func ConfigureSimulcastExtensionHeaders(mediaEngine *MediaEngine) error {
|
||||
if err := mediaEngine.RegisterHeaderExtension(RTPHeaderExtensionCapability{URI: sdp.SDESMidURI}, RTPCodecTypeVideo); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := mediaEngine.RegisterHeaderExtension(RTPHeaderExtensionCapability{URI: sdp.SDESRTPStreamIDURI}, RTPCodecTypeVideo); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return mediaEngine.RegisterHeaderExtension(RTPHeaderExtensionCapability{URI: sdesRepairRTPStreamIDURI}, RTPCodecTypeVideo)
|
||||
}
|
||||
|
||||
type interceptorToTrackLocalWriter struct{ interceptor atomic.Value } // interceptor.RTPWriter }
|
||||
|
||||
func (i *interceptorToTrackLocalWriter) WriteRTP(header *rtp.Header, payload []byte) (int, error) {
|
||||
if writer, ok := i.interceptor.Load().(interceptor.RTPWriter); ok && writer != nil {
|
||||
return writer.Write(header, payload, interceptor.Attributes{})
|
||||
}
|
||||
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func (i *interceptorToTrackLocalWriter) Write(b []byte) (int, error) {
|
||||
packet := &rtp.Packet{}
|
||||
if err := packet.Unmarshal(b); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return i.WriteRTP(&packet.Header, packet.Payload)
|
||||
}
|
||||
|
||||
func createStreamInfo(id string, ssrc SSRC, payloadType PayloadType, codec RTPCodecCapability, webrtcHeaderExtensions []RTPHeaderExtensionParameter) *interceptor.StreamInfo {
|
||||
headerExtensions := make([]interceptor.RTPHeaderExtension, 0, len(webrtcHeaderExtensions))
|
||||
for _, h := range webrtcHeaderExtensions {
|
||||
headerExtensions = append(headerExtensions, interceptor.RTPHeaderExtension{ID: h.ID, URI: h.URI})
|
||||
}
|
||||
|
||||
feedbacks := make([]interceptor.RTCPFeedback, 0, len(codec.RTCPFeedback))
|
||||
for _, f := range codec.RTCPFeedback {
|
||||
feedbacks = append(feedbacks, interceptor.RTCPFeedback{Type: f.Type, Parameter: f.Parameter})
|
||||
}
|
||||
|
||||
return &interceptor.StreamInfo{
|
||||
ID: id,
|
||||
Attributes: interceptor.Attributes{},
|
||||
SSRC: uint32(ssrc),
|
||||
PayloadType: uint8(payloadType),
|
||||
RTPHeaderExtensions: headerExtensions,
|
||||
MimeType: codec.MimeType,
|
||||
ClockRate: codec.ClockRate,
|
||||
Channels: codec.Channels,
|
||||
SDPFmtpLine: codec.SDPFmtpLine,
|
||||
RTCPFeedback: feedbacks,
|
||||
}
|
||||
}
|
||||
41
server/vendor/github.com/pion/webrtc/v3/internal/fmtp/av1.go
generated
vendored
Normal file
41
server/vendor/github.com/pion/webrtc/v3/internal/fmtp/av1.go
generated
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package fmtp
|
||||
|
||||
type av1FMTP struct {
|
||||
parameters map[string]string
|
||||
}
|
||||
|
||||
func (h *av1FMTP) MimeType() string {
|
||||
return "video/av1"
|
||||
}
|
||||
|
||||
func (h *av1FMTP) Match(b FMTP) bool {
|
||||
c, ok := b.(*av1FMTP)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
// RTP Payload Format For AV1 (v1.0)
|
||||
// https://aomediacodec.github.io/av1-rtp-spec/
|
||||
// If the profile parameter is not present, it MUST be inferred to be 0 (“Main” profile).
|
||||
hProfile, ok := h.parameters["profile"]
|
||||
if !ok {
|
||||
hProfile = "0"
|
||||
}
|
||||
cProfile, ok := c.parameters["profile"]
|
||||
if !ok {
|
||||
cProfile = "0"
|
||||
}
|
||||
if hProfile != cProfile {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (h *av1FMTP) Parameter(key string) (string, bool) {
|
||||
v, ok := h.parameters[key]
|
||||
return v, ok
|
||||
}
|
||||
112
server/vendor/github.com/pion/webrtc/v3/internal/fmtp/fmtp.go
generated
vendored
Normal file
112
server/vendor/github.com/pion/webrtc/v3/internal/fmtp/fmtp.go
generated
vendored
Normal file
@@ -0,0 +1,112 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// Package fmtp implements per codec parsing of fmtp lines
|
||||
package fmtp
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
func parseParameters(line string) map[string]string {
|
||||
parameters := make(map[string]string)
|
||||
|
||||
for _, p := range strings.Split(line, ";") {
|
||||
pp := strings.SplitN(strings.TrimSpace(p), "=", 2)
|
||||
key := strings.ToLower(pp[0])
|
||||
var value string
|
||||
if len(pp) > 1 {
|
||||
value = pp[1]
|
||||
}
|
||||
parameters[key] = value
|
||||
}
|
||||
|
||||
return parameters
|
||||
}
|
||||
|
||||
// FMTP interface for implementing custom
|
||||
// FMTP parsers based on MimeType
|
||||
type FMTP interface {
|
||||
// MimeType returns the MimeType associated with
|
||||
// the fmtp
|
||||
MimeType() string
|
||||
// Match compares two fmtp descriptions for
|
||||
// compatibility based on the MimeType
|
||||
Match(f FMTP) bool
|
||||
// Parameter returns a value for the associated key
|
||||
// if contained in the parsed fmtp string
|
||||
Parameter(key string) (string, bool)
|
||||
}
|
||||
|
||||
// Parse parses an fmtp string based on the MimeType
|
||||
func Parse(mimeType, line string) FMTP {
|
||||
var f FMTP
|
||||
|
||||
parameters := parseParameters(line)
|
||||
|
||||
switch {
|
||||
case strings.EqualFold(mimeType, "video/h264"):
|
||||
f = &h264FMTP{
|
||||
parameters: parameters,
|
||||
}
|
||||
|
||||
case strings.EqualFold(mimeType, "video/vp9"):
|
||||
f = &vp9FMTP{
|
||||
parameters: parameters,
|
||||
}
|
||||
|
||||
case strings.EqualFold(mimeType, "video/av1"):
|
||||
f = &av1FMTP{
|
||||
parameters: parameters,
|
||||
}
|
||||
|
||||
default:
|
||||
f = &genericFMTP{
|
||||
mimeType: mimeType,
|
||||
parameters: parameters,
|
||||
}
|
||||
}
|
||||
|
||||
return f
|
||||
}
|
||||
|
||||
type genericFMTP struct {
|
||||
mimeType string
|
||||
parameters map[string]string
|
||||
}
|
||||
|
||||
func (g *genericFMTP) MimeType() string {
|
||||
return g.mimeType
|
||||
}
|
||||
|
||||
// Match returns true if g and b are compatible fmtp descriptions
|
||||
// The generic implementation is used for MimeTypes that are not defined
|
||||
func (g *genericFMTP) Match(b FMTP) bool {
|
||||
c, ok := b.(*genericFMTP)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
if !strings.EqualFold(g.mimeType, c.MimeType()) {
|
||||
return false
|
||||
}
|
||||
|
||||
for k, v := range g.parameters {
|
||||
if vb, ok := c.parameters[k]; ok && !strings.EqualFold(vb, v) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
for k, v := range c.parameters {
|
||||
if va, ok := g.parameters[k]; ok && !strings.EqualFold(va, v) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (g *genericFMTP) Parameter(key string) (string, bool) {
|
||||
v, ok := g.parameters[key]
|
||||
return v, ok
|
||||
}
|
||||
84
server/vendor/github.com/pion/webrtc/v3/internal/fmtp/h264.go
generated
vendored
Normal file
84
server/vendor/github.com/pion/webrtc/v3/internal/fmtp/h264.go
generated
vendored
Normal file
@@ -0,0 +1,84 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package fmtp
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
)
|
||||
|
||||
func profileLevelIDMatches(a, b string) bool {
|
||||
aa, err := hex.DecodeString(a)
|
||||
if err != nil || len(aa) < 2 {
|
||||
return false
|
||||
}
|
||||
bb, err := hex.DecodeString(b)
|
||||
if err != nil || len(bb) < 2 {
|
||||
return false
|
||||
}
|
||||
return aa[0] == bb[0] && aa[1] == bb[1]
|
||||
}
|
||||
|
||||
type h264FMTP struct {
|
||||
parameters map[string]string
|
||||
}
|
||||
|
||||
func (h *h264FMTP) MimeType() string {
|
||||
return "video/h264"
|
||||
}
|
||||
|
||||
// Match returns true if h and b are compatible fmtp descriptions
|
||||
// Based on RFC6184 Section 8.2.2:
|
||||
//
|
||||
// The parameters identifying a media format configuration for H.264
|
||||
// are profile-level-id and packetization-mode. These media format
|
||||
// configuration parameters (except for the level part of profile-
|
||||
// level-id) MUST be used symmetrically; that is, the answerer MUST
|
||||
// either maintain all configuration parameters or remove the media
|
||||
// format (payload type) completely if one or more of the parameter
|
||||
// values are not supported.
|
||||
// Informative note: The requirement for symmetric use does not
|
||||
// apply for the level part of profile-level-id and does not apply
|
||||
// for the other stream properties and capability parameters.
|
||||
func (h *h264FMTP) Match(b FMTP) bool {
|
||||
c, ok := b.(*h264FMTP)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
// test packetization-mode
|
||||
hpmode, hok := h.parameters["packetization-mode"]
|
||||
if !hok {
|
||||
return false
|
||||
}
|
||||
cpmode, cok := c.parameters["packetization-mode"]
|
||||
if !cok {
|
||||
return false
|
||||
}
|
||||
|
||||
if hpmode != cpmode {
|
||||
return false
|
||||
}
|
||||
|
||||
// test profile-level-id
|
||||
hplid, hok := h.parameters["profile-level-id"]
|
||||
if !hok {
|
||||
return false
|
||||
}
|
||||
|
||||
cplid, cok := c.parameters["profile-level-id"]
|
||||
if !cok {
|
||||
return false
|
||||
}
|
||||
|
||||
if !profileLevelIDMatches(hplid, cplid) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (h *h264FMTP) Parameter(key string) (string, bool) {
|
||||
v, ok := h.parameters[key]
|
||||
return v, ok
|
||||
}
|
||||
41
server/vendor/github.com/pion/webrtc/v3/internal/fmtp/vp9.go
generated
vendored
Normal file
41
server/vendor/github.com/pion/webrtc/v3/internal/fmtp/vp9.go
generated
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package fmtp
|
||||
|
||||
type vp9FMTP struct {
|
||||
parameters map[string]string
|
||||
}
|
||||
|
||||
func (h *vp9FMTP) MimeType() string {
|
||||
return "video/vp9"
|
||||
}
|
||||
|
||||
func (h *vp9FMTP) Match(b FMTP) bool {
|
||||
c, ok := b.(*vp9FMTP)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
// RTP Payload Format for VP9 Video - draft-ietf-payload-vp9-16
|
||||
// https://datatracker.ietf.org/doc/html/draft-ietf-payload-vp9-16
|
||||
// If no profile-id is present, Profile 0 MUST be inferred
|
||||
hProfileID, ok := h.parameters["profile-id"]
|
||||
if !ok {
|
||||
hProfileID = "0"
|
||||
}
|
||||
cProfileID, ok := c.parameters["profile-id"]
|
||||
if !ok {
|
||||
cProfileID = "0"
|
||||
}
|
||||
if hProfileID != cProfileID {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (h *vp9FMTP) Parameter(key string) (string, bool) {
|
||||
v, ok := h.parameters[key]
|
||||
return v, ok
|
||||
}
|
||||
78
server/vendor/github.com/pion/webrtc/v3/internal/mux/endpoint.go
generated
vendored
Normal file
78
server/vendor/github.com/pion/webrtc/v3/internal/mux/endpoint.go
generated
vendored
Normal file
@@ -0,0 +1,78 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package mux
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/pion/ice/v2"
|
||||
"github.com/pion/transport/v2/packetio"
|
||||
)
|
||||
|
||||
// Endpoint implements net.Conn. It is used to read muxed packets.
|
||||
type Endpoint struct {
|
||||
mux *Mux
|
||||
buffer *packetio.Buffer
|
||||
}
|
||||
|
||||
// Close unregisters the endpoint from the Mux
|
||||
func (e *Endpoint) Close() (err error) {
|
||||
err = e.close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
e.mux.RemoveEndpoint(e)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *Endpoint) close() error {
|
||||
return e.buffer.Close()
|
||||
}
|
||||
|
||||
// Read reads a packet of len(p) bytes from the underlying conn
|
||||
// that are matched by the associated MuxFunc
|
||||
func (e *Endpoint) Read(p []byte) (int, error) {
|
||||
return e.buffer.Read(p)
|
||||
}
|
||||
|
||||
// Write writes len(p) bytes to the underlying conn
|
||||
func (e *Endpoint) Write(p []byte) (int, error) {
|
||||
n, err := e.mux.nextConn.Write(p)
|
||||
if errors.Is(err, ice.ErrNoCandidatePairs) {
|
||||
return 0, nil
|
||||
} else if errors.Is(err, ice.ErrClosed) {
|
||||
return 0, io.ErrClosedPipe
|
||||
}
|
||||
|
||||
return n, err
|
||||
}
|
||||
|
||||
// LocalAddr is a stub
|
||||
func (e *Endpoint) LocalAddr() net.Addr {
|
||||
return e.mux.nextConn.LocalAddr()
|
||||
}
|
||||
|
||||
// RemoteAddr is a stub
|
||||
func (e *Endpoint) RemoteAddr() net.Addr {
|
||||
return e.mux.nextConn.RemoteAddr()
|
||||
}
|
||||
|
||||
// SetDeadline is a stub
|
||||
func (e *Endpoint) SetDeadline(time.Time) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetReadDeadline is a stub
|
||||
func (e *Endpoint) SetReadDeadline(time.Time) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetWriteDeadline is a stub
|
||||
func (e *Endpoint) SetWriteDeadline(time.Time) error {
|
||||
return nil
|
||||
}
|
||||
163
server/vendor/github.com/pion/webrtc/v3/internal/mux/mux.go
generated
vendored
Normal file
163
server/vendor/github.com/pion/webrtc/v3/internal/mux/mux.go
generated
vendored
Normal file
@@ -0,0 +1,163 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// Package mux multiplexes packets on a single socket (RFC7983)
|
||||
package mux
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
"sync"
|
||||
|
||||
"github.com/pion/ice/v2"
|
||||
"github.com/pion/logging"
|
||||
"github.com/pion/transport/v2/packetio"
|
||||
)
|
||||
|
||||
// The maximum amount of data that can be buffered before returning errors.
|
||||
const maxBufferSize = 1000 * 1000 // 1MB
|
||||
|
||||
// Config collects the arguments to mux.Mux construction into
|
||||
// a single structure
|
||||
type Config struct {
|
||||
Conn net.Conn
|
||||
BufferSize int
|
||||
LoggerFactory logging.LoggerFactory
|
||||
}
|
||||
|
||||
// Mux allows multiplexing
|
||||
type Mux struct {
|
||||
lock sync.RWMutex
|
||||
nextConn net.Conn
|
||||
endpoints map[*Endpoint]MatchFunc
|
||||
bufferSize int
|
||||
closedCh chan struct{}
|
||||
|
||||
log logging.LeveledLogger
|
||||
}
|
||||
|
||||
// NewMux creates a new Mux
|
||||
func NewMux(config Config) *Mux {
|
||||
m := &Mux{
|
||||
nextConn: config.Conn,
|
||||
endpoints: make(map[*Endpoint]MatchFunc),
|
||||
bufferSize: config.BufferSize,
|
||||
closedCh: make(chan struct{}),
|
||||
log: config.LoggerFactory.NewLogger("mux"),
|
||||
}
|
||||
|
||||
go m.readLoop()
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
// NewEndpoint creates a new Endpoint
|
||||
func (m *Mux) NewEndpoint(f MatchFunc) *Endpoint {
|
||||
e := &Endpoint{
|
||||
mux: m,
|
||||
buffer: packetio.NewBuffer(),
|
||||
}
|
||||
|
||||
// Set a maximum size of the buffer in bytes.
|
||||
e.buffer.SetLimitSize(maxBufferSize)
|
||||
|
||||
m.lock.Lock()
|
||||
m.endpoints[e] = f
|
||||
m.lock.Unlock()
|
||||
|
||||
return e
|
||||
}
|
||||
|
||||
// RemoveEndpoint removes an endpoint from the Mux
|
||||
func (m *Mux) RemoveEndpoint(e *Endpoint) {
|
||||
m.lock.Lock()
|
||||
defer m.lock.Unlock()
|
||||
delete(m.endpoints, e)
|
||||
}
|
||||
|
||||
// Close closes the Mux and all associated Endpoints.
|
||||
func (m *Mux) Close() error {
|
||||
m.lock.Lock()
|
||||
for e := range m.endpoints {
|
||||
if err := e.close(); err != nil {
|
||||
m.lock.Unlock()
|
||||
return err
|
||||
}
|
||||
|
||||
delete(m.endpoints, e)
|
||||
}
|
||||
m.lock.Unlock()
|
||||
|
||||
err := m.nextConn.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Wait for readLoop to end
|
||||
<-m.closedCh
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Mux) readLoop() {
|
||||
defer func() {
|
||||
close(m.closedCh)
|
||||
}()
|
||||
|
||||
buf := make([]byte, m.bufferSize)
|
||||
for {
|
||||
n, err := m.nextConn.Read(buf)
|
||||
switch {
|
||||
case errors.Is(err, io.EOF), errors.Is(err, ice.ErrClosed):
|
||||
return
|
||||
case errors.Is(err, io.ErrShortBuffer), errors.Is(err, packetio.ErrTimeout):
|
||||
m.log.Errorf("mux: failed to read from packetio.Buffer %s", err.Error())
|
||||
continue
|
||||
case err != nil:
|
||||
m.log.Errorf("mux: ending readLoop packetio.Buffer error %s", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if err = m.dispatch(buf[:n]); err != nil {
|
||||
if errors.Is(err, io.ErrClosedPipe) {
|
||||
// if the buffer was closed, that's not an error we care to report
|
||||
return
|
||||
}
|
||||
m.log.Errorf("mux: ending readLoop dispatch error %s", err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Mux) dispatch(buf []byte) error {
|
||||
var endpoint *Endpoint
|
||||
|
||||
m.lock.Lock()
|
||||
for e, f := range m.endpoints {
|
||||
if f(buf) {
|
||||
endpoint = e
|
||||
break
|
||||
}
|
||||
}
|
||||
m.lock.Unlock()
|
||||
|
||||
if endpoint == nil {
|
||||
if len(buf) > 0 {
|
||||
m.log.Warnf("Warning: mux: no endpoint for packet starting with %d", buf[0])
|
||||
} else {
|
||||
m.log.Warnf("Warning: mux: no endpoint for zero length packet")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
_, err := endpoint.buffer.Write(buf)
|
||||
|
||||
// Expected when bytes are received faster than the endpoint can process them (#2152, #2180)
|
||||
if errors.Is(err, packetio.ErrFull) {
|
||||
m.log.Infof("mux: endpoint buffer is full, dropping packet")
|
||||
return nil
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
65
server/vendor/github.com/pion/webrtc/v3/internal/mux/muxfunc.go
generated
vendored
Normal file
65
server/vendor/github.com/pion/webrtc/v3/internal/mux/muxfunc.go
generated
vendored
Normal file
@@ -0,0 +1,65 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package mux
|
||||
|
||||
// MatchFunc allows custom logic for mapping packets to an Endpoint
|
||||
type MatchFunc func([]byte) bool
|
||||
|
||||
// MatchAll always returns true
|
||||
func MatchAll([]byte) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// MatchRange returns true if the first byte of buf is in [lower..upper]
|
||||
func MatchRange(lower, upper byte, buf []byte) bool {
|
||||
if len(buf) < 1 {
|
||||
return false
|
||||
}
|
||||
b := buf[0]
|
||||
return b >= lower && b <= upper
|
||||
}
|
||||
|
||||
// MatchFuncs as described in RFC7983
|
||||
// https://tools.ietf.org/html/rfc7983
|
||||
// +----------------+
|
||||
// | [0..3] -+--> forward to STUN
|
||||
// | |
|
||||
// | [16..19] -+--> forward to ZRTP
|
||||
// | |
|
||||
// packet --> | [20..63] -+--> forward to DTLS
|
||||
// | |
|
||||
// | [64..79] -+--> forward to TURN Channel
|
||||
// | |
|
||||
// | [128..191] -+--> forward to RTP/RTCP
|
||||
// +----------------+
|
||||
|
||||
// MatchDTLS is a MatchFunc that accepts packets with the first byte in [20..63]
|
||||
// as defied in RFC7983
|
||||
func MatchDTLS(b []byte) bool {
|
||||
return MatchRange(20, 63, b)
|
||||
}
|
||||
|
||||
// MatchSRTPOrSRTCP is a MatchFunc that accepts packets with the first byte in [128..191]
|
||||
// as defied in RFC7983
|
||||
func MatchSRTPOrSRTCP(b []byte) bool {
|
||||
return MatchRange(128, 191, b)
|
||||
}
|
||||
|
||||
func isRTCP(buf []byte) bool {
|
||||
// Not long enough to determine RTP/RTCP
|
||||
if len(buf) < 4 {
|
||||
return false
|
||||
}
|
||||
return buf[1] >= 192 && buf[1] <= 223
|
||||
}
|
||||
|
||||
// MatchSRTP is a MatchFunc that only matches SRTP and not SRTCP
|
||||
func MatchSRTP(buf []byte) bool {
|
||||
return MatchSRTPOrSRTCP(buf) && !isRTCP(buf)
|
||||
}
|
||||
|
||||
// MatchSRTCP is a MatchFunc that only matches SRTCP and not SRTP
|
||||
func MatchSRTCP(buf []byte) bool {
|
||||
return MatchSRTPOrSRTCP(buf) && isRTCP(buf)
|
||||
}
|
||||
75
server/vendor/github.com/pion/webrtc/v3/internal/util/util.go
generated
vendored
Normal file
75
server/vendor/github.com/pion/webrtc/v3/internal/util/util.go
generated
vendored
Normal file
@@ -0,0 +1,75 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// Package util provides auxiliary functions internally used in webrtc package
|
||||
package util
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"github.com/pion/randutil"
|
||||
)
|
||||
|
||||
const (
|
||||
runesAlpha = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
)
|
||||
|
||||
// Use global random generator to properly seed by crypto grade random.
|
||||
var globalMathRandomGenerator = randutil.NewMathRandomGenerator() // nolint:gochecknoglobals
|
||||
|
||||
// MathRandAlpha generates a mathmatical random alphabet sequence of the requested length.
|
||||
func MathRandAlpha(n int) string {
|
||||
return globalMathRandomGenerator.GenerateString(n, runesAlpha)
|
||||
}
|
||||
|
||||
// RandUint32 generates a mathmatical random uint32.
|
||||
func RandUint32() uint32 {
|
||||
return globalMathRandomGenerator.Uint32()
|
||||
}
|
||||
|
||||
// FlattenErrs flattens multiple errors into one
|
||||
func FlattenErrs(errs []error) error {
|
||||
errs2 := []error{}
|
||||
for _, e := range errs {
|
||||
if e != nil {
|
||||
errs2 = append(errs2, e)
|
||||
}
|
||||
}
|
||||
if len(errs2) == 0 {
|
||||
return nil
|
||||
}
|
||||
return multiError(errs2)
|
||||
}
|
||||
|
||||
type multiError []error //nolint:errname
|
||||
|
||||
func (me multiError) Error() string {
|
||||
var errstrings []string
|
||||
|
||||
for _, err := range me {
|
||||
if err != nil {
|
||||
errstrings = append(errstrings, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
if len(errstrings) == 0 {
|
||||
return "multiError must contain multiple error but is empty"
|
||||
}
|
||||
|
||||
return strings.Join(errstrings, "\n")
|
||||
}
|
||||
|
||||
func (me multiError) Is(err error) bool {
|
||||
for _, e := range me {
|
||||
if errors.Is(e, err) {
|
||||
return true
|
||||
}
|
||||
if me2, ok := e.(multiError); ok { //nolint:errorlint
|
||||
if me2.Is(err) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
172
server/vendor/github.com/pion/webrtc/v3/js_utils.go
generated
vendored
Normal file
172
server/vendor/github.com/pion/webrtc/v3/js_utils.go
generated
vendored
Normal file
@@ -0,0 +1,172 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
//go:build js && wasm
|
||||
// +build js,wasm
|
||||
|
||||
package webrtc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"syscall/js"
|
||||
)
|
||||
|
||||
// awaitPromise accepts a js.Value representing a Promise. If the promise
|
||||
// resolves, it returns (result, nil). If the promise rejects, it returns
|
||||
// (js.Undefined, error). awaitPromise has a synchronous-like API but does not
|
||||
// block the JavaScript event loop.
|
||||
func awaitPromise(promise js.Value) (js.Value, error) {
|
||||
resultsChan := make(chan js.Value)
|
||||
errChan := make(chan js.Error)
|
||||
|
||||
thenFunc := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
|
||||
go func() {
|
||||
resultsChan <- args[0]
|
||||
}()
|
||||
return js.Undefined()
|
||||
})
|
||||
defer thenFunc.Release()
|
||||
|
||||
catchFunc := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
|
||||
go func() {
|
||||
errChan <- js.Error{args[0]}
|
||||
}()
|
||||
return js.Undefined()
|
||||
})
|
||||
defer catchFunc.Release()
|
||||
|
||||
promise.Call("then", thenFunc).Call("catch", catchFunc)
|
||||
|
||||
select {
|
||||
case result := <-resultsChan:
|
||||
return result, nil
|
||||
case err := <-errChan:
|
||||
return js.Undefined(), err
|
||||
}
|
||||
}
|
||||
|
||||
func valueToUint16Pointer(val js.Value) *uint16 {
|
||||
if val.IsNull() || val.IsUndefined() {
|
||||
return nil
|
||||
}
|
||||
convertedVal := uint16(val.Int())
|
||||
return &convertedVal
|
||||
}
|
||||
|
||||
func valueToStringPointer(val js.Value) *string {
|
||||
if val.IsNull() || val.IsUndefined() {
|
||||
return nil
|
||||
}
|
||||
stringVal := val.String()
|
||||
return &stringVal
|
||||
}
|
||||
|
||||
func stringToValueOrUndefined(val string) js.Value {
|
||||
if val == "" {
|
||||
return js.Undefined()
|
||||
}
|
||||
return js.ValueOf(val)
|
||||
}
|
||||
|
||||
func uint8ToValueOrUndefined(val uint8) js.Value {
|
||||
if val == 0 {
|
||||
return js.Undefined()
|
||||
}
|
||||
return js.ValueOf(val)
|
||||
}
|
||||
|
||||
func interfaceToValueOrUndefined(val interface{}) js.Value {
|
||||
if val == nil {
|
||||
return js.Undefined()
|
||||
}
|
||||
return js.ValueOf(val)
|
||||
}
|
||||
|
||||
func valueToStringOrZero(val js.Value) string {
|
||||
if val.IsUndefined() || val.IsNull() {
|
||||
return ""
|
||||
}
|
||||
return val.String()
|
||||
}
|
||||
|
||||
func valueToUint8OrZero(val js.Value) uint8 {
|
||||
if val.IsUndefined() || val.IsNull() {
|
||||
return 0
|
||||
}
|
||||
return uint8(val.Int())
|
||||
}
|
||||
|
||||
func valueToUint16OrZero(val js.Value) uint16 {
|
||||
if val.IsNull() || val.IsUndefined() {
|
||||
return 0
|
||||
}
|
||||
return uint16(val.Int())
|
||||
}
|
||||
|
||||
func valueToUint32OrZero(val js.Value) uint32 {
|
||||
if val.IsNull() || val.IsUndefined() {
|
||||
return 0
|
||||
}
|
||||
return uint32(val.Int())
|
||||
}
|
||||
|
||||
func valueToStrings(val js.Value) []string {
|
||||
result := make([]string, val.Length())
|
||||
for i := 0; i < val.Length(); i++ {
|
||||
result[i] = val.Index(i).String()
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func stringPointerToValue(val *string) js.Value {
|
||||
if val == nil {
|
||||
return js.Undefined()
|
||||
}
|
||||
return js.ValueOf(*val)
|
||||
}
|
||||
|
||||
func uint16PointerToValue(val *uint16) js.Value {
|
||||
if val == nil {
|
||||
return js.Undefined()
|
||||
}
|
||||
return js.ValueOf(*val)
|
||||
}
|
||||
|
||||
func boolPointerToValue(val *bool) js.Value {
|
||||
if val == nil {
|
||||
return js.Undefined()
|
||||
}
|
||||
return js.ValueOf(*val)
|
||||
}
|
||||
|
||||
func stringsToValue(strings []string) js.Value {
|
||||
val := make([]interface{}, len(strings))
|
||||
for i, s := range strings {
|
||||
val[i] = s
|
||||
}
|
||||
return js.ValueOf(val)
|
||||
}
|
||||
|
||||
func stringEnumToValueOrUndefined(s string) js.Value {
|
||||
if s == "unknown" {
|
||||
return js.Undefined()
|
||||
}
|
||||
return js.ValueOf(s)
|
||||
}
|
||||
|
||||
// Converts the return value of recover() to an error.
|
||||
func recoveryToError(e interface{}) error {
|
||||
switch e := e.(type) {
|
||||
case error:
|
||||
return e
|
||||
default:
|
||||
return fmt.Errorf("recovered with non-error value: (%T) %s", e, e)
|
||||
}
|
||||
}
|
||||
|
||||
func uint8ArrayValueToBytes(val js.Value) []byte {
|
||||
result := make([]byte, val.Length())
|
||||
js.CopyBytesToGo(result, val)
|
||||
|
||||
return result
|
||||
}
|
||||
707
server/vendor/github.com/pion/webrtc/v3/mediaengine.go
generated
vendored
Normal file
707
server/vendor/github.com/pion/webrtc/v3/mediaengine.go
generated
vendored
Normal file
@@ -0,0 +1,707 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
//go:build !js
|
||||
// +build !js
|
||||
|
||||
package webrtc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/pion/rtp"
|
||||
"github.com/pion/rtp/codecs"
|
||||
"github.com/pion/sdp/v3"
|
||||
"github.com/pion/webrtc/v3/internal/fmtp"
|
||||
)
|
||||
|
||||
const (
|
||||
// MimeTypeH264 H264 MIME type.
|
||||
// Note: Matching should be case insensitive.
|
||||
MimeTypeH264 = "video/H264"
|
||||
// MimeTypeH265 H265 MIME type
|
||||
// Note: Matching should be case insensitive.
|
||||
MimeTypeH265 = "video/H265"
|
||||
// MimeTypeOpus Opus MIME type
|
||||
// Note: Matching should be case insensitive.
|
||||
MimeTypeOpus = "audio/opus"
|
||||
// MimeTypeVP8 VP8 MIME type
|
||||
// Note: Matching should be case insensitive.
|
||||
MimeTypeVP8 = "video/VP8"
|
||||
// MimeTypeVP9 VP9 MIME type
|
||||
// Note: Matching should be case insensitive.
|
||||
MimeTypeVP9 = "video/VP9"
|
||||
// MimeTypeAV1 AV1 MIME type
|
||||
// Note: Matching should be case insensitive.
|
||||
MimeTypeAV1 = "video/AV1"
|
||||
// MimeTypeG722 G722 MIME type
|
||||
// Note: Matching should be case insensitive.
|
||||
MimeTypeG722 = "audio/G722"
|
||||
// MimeTypePCMU PCMU MIME type
|
||||
// Note: Matching should be case insensitive.
|
||||
MimeTypePCMU = "audio/PCMU"
|
||||
// MimeTypePCMA PCMA MIME type
|
||||
// Note: Matching should be case insensitive.
|
||||
MimeTypePCMA = "audio/PCMA"
|
||||
)
|
||||
|
||||
type mediaEngineHeaderExtension struct {
|
||||
uri string
|
||||
isAudio, isVideo bool
|
||||
|
||||
// If set only Transceivers of this direction are allowed
|
||||
allowedDirections []RTPTransceiverDirection
|
||||
}
|
||||
|
||||
// A MediaEngine defines the codecs supported by a PeerConnection, and the
|
||||
// configuration of those codecs.
|
||||
type MediaEngine struct {
|
||||
// If we have attempted to negotiate a codec type yet.
|
||||
negotiatedVideo, negotiatedAudio bool
|
||||
|
||||
videoCodecs, audioCodecs []RTPCodecParameters
|
||||
negotiatedVideoCodecs, negotiatedAudioCodecs []RTPCodecParameters
|
||||
|
||||
headerExtensions []mediaEngineHeaderExtension
|
||||
negotiatedHeaderExtensions map[int]mediaEngineHeaderExtension
|
||||
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
// RegisterDefaultCodecs registers the default codecs supported by Pion WebRTC.
|
||||
// RegisterDefaultCodecs is not safe for concurrent use.
|
||||
func (m *MediaEngine) RegisterDefaultCodecs() error {
|
||||
// Default Pion Audio Codecs
|
||||
for _, codec := range []RTPCodecParameters{
|
||||
{
|
||||
RTPCodecCapability: RTPCodecCapability{MimeTypeOpus, 48000, 2, "minptime=10;useinbandfec=1", nil},
|
||||
PayloadType: 111,
|
||||
},
|
||||
{
|
||||
RTPCodecCapability: RTPCodecCapability{MimeTypeG722, 8000, 0, "", nil},
|
||||
PayloadType: 9,
|
||||
},
|
||||
{
|
||||
RTPCodecCapability: RTPCodecCapability{MimeTypePCMU, 8000, 0, "", nil},
|
||||
PayloadType: 0,
|
||||
},
|
||||
{
|
||||
RTPCodecCapability: RTPCodecCapability{MimeTypePCMA, 8000, 0, "", nil},
|
||||
PayloadType: 8,
|
||||
},
|
||||
} {
|
||||
if err := m.RegisterCodec(codec, RTPCodecTypeAudio); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
videoRTCPFeedback := []RTCPFeedback{{"goog-remb", ""}, {"ccm", "fir"}, {"nack", ""}, {"nack", "pli"}}
|
||||
for _, codec := range []RTPCodecParameters{
|
||||
{
|
||||
RTPCodecCapability: RTPCodecCapability{MimeTypeVP8, 90000, 0, "", videoRTCPFeedback},
|
||||
PayloadType: 96,
|
||||
},
|
||||
{
|
||||
RTPCodecCapability: RTPCodecCapability{"video/rtx", 90000, 0, "apt=96", nil},
|
||||
PayloadType: 97,
|
||||
},
|
||||
|
||||
{
|
||||
RTPCodecCapability: RTPCodecCapability{MimeTypeH264, 90000, 0, "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f", videoRTCPFeedback},
|
||||
PayloadType: 102,
|
||||
},
|
||||
{
|
||||
RTPCodecCapability: RTPCodecCapability{"video/rtx", 90000, 0, "apt=102", nil},
|
||||
PayloadType: 103,
|
||||
},
|
||||
|
||||
{
|
||||
RTPCodecCapability: RTPCodecCapability{MimeTypeH264, 90000, 0, "level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42001f", videoRTCPFeedback},
|
||||
PayloadType: 104,
|
||||
},
|
||||
{
|
||||
RTPCodecCapability: RTPCodecCapability{"video/rtx", 90000, 0, "apt=104", nil},
|
||||
PayloadType: 105,
|
||||
},
|
||||
|
||||
{
|
||||
RTPCodecCapability: RTPCodecCapability{MimeTypeH264, 90000, 0, "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f", videoRTCPFeedback},
|
||||
PayloadType: 106,
|
||||
},
|
||||
{
|
||||
RTPCodecCapability: RTPCodecCapability{"video/rtx", 90000, 0, "apt=106", nil},
|
||||
PayloadType: 107,
|
||||
},
|
||||
|
||||
{
|
||||
RTPCodecCapability: RTPCodecCapability{MimeTypeH264, 90000, 0, "level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42e01f", videoRTCPFeedback},
|
||||
PayloadType: 108,
|
||||
},
|
||||
{
|
||||
RTPCodecCapability: RTPCodecCapability{"video/rtx", 90000, 0, "apt=108", nil},
|
||||
PayloadType: 109,
|
||||
},
|
||||
|
||||
{
|
||||
RTPCodecCapability: RTPCodecCapability{MimeTypeH264, 90000, 0, "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=4d001f", videoRTCPFeedback},
|
||||
PayloadType: 127,
|
||||
},
|
||||
{
|
||||
RTPCodecCapability: RTPCodecCapability{"video/rtx", 90000, 0, "apt=127", nil},
|
||||
PayloadType: 125,
|
||||
},
|
||||
|
||||
{
|
||||
RTPCodecCapability: RTPCodecCapability{MimeTypeH264, 90000, 0, "level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=4d001f", videoRTCPFeedback},
|
||||
PayloadType: 39,
|
||||
},
|
||||
{
|
||||
RTPCodecCapability: RTPCodecCapability{"video/rtx", 90000, 0, "apt=39", nil},
|
||||
PayloadType: 40,
|
||||
},
|
||||
|
||||
{
|
||||
RTPCodecCapability: RTPCodecCapability{MimeTypeAV1, 90000, 0, "", videoRTCPFeedback},
|
||||
PayloadType: 45,
|
||||
},
|
||||
{
|
||||
RTPCodecCapability: RTPCodecCapability{"video/rtx", 90000, 0, "apt=45", nil},
|
||||
PayloadType: 46,
|
||||
},
|
||||
|
||||
{
|
||||
RTPCodecCapability: RTPCodecCapability{MimeTypeVP9, 90000, 0, "profile-id=0", videoRTCPFeedback},
|
||||
PayloadType: 98,
|
||||
},
|
||||
{
|
||||
RTPCodecCapability: RTPCodecCapability{"video/rtx", 90000, 0, "apt=98", nil},
|
||||
PayloadType: 99,
|
||||
},
|
||||
|
||||
{
|
||||
RTPCodecCapability: RTPCodecCapability{MimeTypeVP9, 90000, 0, "profile-id=2", videoRTCPFeedback},
|
||||
PayloadType: 100,
|
||||
},
|
||||
{
|
||||
RTPCodecCapability: RTPCodecCapability{"video/rtx", 90000, 0, "apt=100", nil},
|
||||
PayloadType: 101,
|
||||
},
|
||||
|
||||
{
|
||||
RTPCodecCapability: RTPCodecCapability{MimeTypeH264, 90000, 0, "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=64001f", videoRTCPFeedback},
|
||||
PayloadType: 112,
|
||||
},
|
||||
{
|
||||
RTPCodecCapability: RTPCodecCapability{"video/rtx", 90000, 0, "apt=112", nil},
|
||||
PayloadType: 113,
|
||||
},
|
||||
} {
|
||||
if err := m.RegisterCodec(codec, RTPCodecTypeVideo); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// addCodec will append codec if it not exists
|
||||
func (m *MediaEngine) addCodec(codecs []RTPCodecParameters, codec RTPCodecParameters) []RTPCodecParameters {
|
||||
for _, c := range codecs {
|
||||
if c.MimeType == codec.MimeType && c.PayloadType == codec.PayloadType {
|
||||
return codecs
|
||||
}
|
||||
}
|
||||
return append(codecs, codec)
|
||||
}
|
||||
|
||||
// RegisterCodec adds codec to the MediaEngine
|
||||
// These are the list of codecs supported by this PeerConnection.
|
||||
// RegisterCodec is not safe for concurrent use.
|
||||
func (m *MediaEngine) RegisterCodec(codec RTPCodecParameters, typ RTPCodecType) error {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
codec.statsID = fmt.Sprintf("RTPCodec-%d", time.Now().UnixNano())
|
||||
switch typ {
|
||||
case RTPCodecTypeAudio:
|
||||
m.audioCodecs = m.addCodec(m.audioCodecs, codec)
|
||||
case RTPCodecTypeVideo:
|
||||
m.videoCodecs = m.addCodec(m.videoCodecs, codec)
|
||||
default:
|
||||
return ErrUnknownType
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RegisterHeaderExtension adds a header extension to the MediaEngine
|
||||
// To determine the negotiated value use `GetHeaderExtensionID` after signaling is complete
|
||||
func (m *MediaEngine) RegisterHeaderExtension(extension RTPHeaderExtensionCapability, typ RTPCodecType, allowedDirections ...RTPTransceiverDirection) error {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
if m.negotiatedHeaderExtensions == nil {
|
||||
m.negotiatedHeaderExtensions = map[int]mediaEngineHeaderExtension{}
|
||||
}
|
||||
|
||||
if len(allowedDirections) == 0 {
|
||||
allowedDirections = []RTPTransceiverDirection{RTPTransceiverDirectionRecvonly, RTPTransceiverDirectionSendonly}
|
||||
}
|
||||
|
||||
for _, direction := range allowedDirections {
|
||||
if direction != RTPTransceiverDirectionRecvonly && direction != RTPTransceiverDirectionSendonly {
|
||||
return ErrRegisterHeaderExtensionInvalidDirection
|
||||
}
|
||||
}
|
||||
|
||||
extensionIndex := -1
|
||||
for i := range m.headerExtensions {
|
||||
if extension.URI == m.headerExtensions[i].uri {
|
||||
extensionIndex = i
|
||||
}
|
||||
}
|
||||
|
||||
if extensionIndex == -1 {
|
||||
m.headerExtensions = append(m.headerExtensions, mediaEngineHeaderExtension{})
|
||||
extensionIndex = len(m.headerExtensions) - 1
|
||||
}
|
||||
|
||||
if typ == RTPCodecTypeAudio {
|
||||
m.headerExtensions[extensionIndex].isAudio = true
|
||||
} else if typ == RTPCodecTypeVideo {
|
||||
m.headerExtensions[extensionIndex].isVideo = true
|
||||
}
|
||||
|
||||
m.headerExtensions[extensionIndex].uri = extension.URI
|
||||
m.headerExtensions[extensionIndex].allowedDirections = allowedDirections
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RegisterFeedback adds feedback mechanism to already registered codecs.
|
||||
func (m *MediaEngine) RegisterFeedback(feedback RTCPFeedback, typ RTPCodecType) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
switch typ {
|
||||
case RTPCodecTypeVideo:
|
||||
for i, v := range m.videoCodecs {
|
||||
v.RTCPFeedback = append(v.RTCPFeedback, feedback)
|
||||
m.videoCodecs[i] = v
|
||||
}
|
||||
case RTPCodecTypeAudio:
|
||||
for i, v := range m.audioCodecs {
|
||||
v.RTCPFeedback = append(v.RTCPFeedback, feedback)
|
||||
m.audioCodecs[i] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// getHeaderExtensionID returns the negotiated ID for a header extension.
|
||||
// If the Header Extension isn't enabled ok will be false
|
||||
func (m *MediaEngine) getHeaderExtensionID(extension RTPHeaderExtensionCapability) (val int, audioNegotiated, videoNegotiated bool) {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
|
||||
if m.negotiatedHeaderExtensions == nil {
|
||||
return 0, false, false
|
||||
}
|
||||
|
||||
for id, h := range m.negotiatedHeaderExtensions {
|
||||
if extension.URI == h.uri {
|
||||
return id, h.isAudio, h.isVideo
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// copy copies any user modifiable state of the MediaEngine
|
||||
// all internal state is reset
|
||||
func (m *MediaEngine) copy() *MediaEngine {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
cloned := &MediaEngine{
|
||||
videoCodecs: append([]RTPCodecParameters{}, m.videoCodecs...),
|
||||
audioCodecs: append([]RTPCodecParameters{}, m.audioCodecs...),
|
||||
headerExtensions: append([]mediaEngineHeaderExtension{}, m.headerExtensions...),
|
||||
}
|
||||
if len(m.headerExtensions) > 0 {
|
||||
cloned.negotiatedHeaderExtensions = map[int]mediaEngineHeaderExtension{}
|
||||
}
|
||||
return cloned
|
||||
}
|
||||
|
||||
func findCodecByPayload(codecs []RTPCodecParameters, payloadType PayloadType) *RTPCodecParameters {
|
||||
for _, codec := range codecs {
|
||||
if codec.PayloadType == payloadType {
|
||||
return &codec
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MediaEngine) getCodecByPayload(payloadType PayloadType) (RTPCodecParameters, RTPCodecType, error) {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
|
||||
// if we've negotiated audio or video, check the negotiated types before our
|
||||
// built-in payload types, to ensure we pick the codec the other side wants.
|
||||
if m.negotiatedVideo {
|
||||
if codec := findCodecByPayload(m.negotiatedVideoCodecs, payloadType); codec != nil {
|
||||
return *codec, RTPCodecTypeVideo, nil
|
||||
}
|
||||
}
|
||||
if m.negotiatedAudio {
|
||||
if codec := findCodecByPayload(m.negotiatedAudioCodecs, payloadType); codec != nil {
|
||||
return *codec, RTPCodecTypeAudio, nil
|
||||
}
|
||||
}
|
||||
if !m.negotiatedVideo {
|
||||
if codec := findCodecByPayload(m.videoCodecs, payloadType); codec != nil {
|
||||
return *codec, RTPCodecTypeVideo, nil
|
||||
}
|
||||
}
|
||||
if !m.negotiatedAudio {
|
||||
if codec := findCodecByPayload(m.audioCodecs, payloadType); codec != nil {
|
||||
return *codec, RTPCodecTypeAudio, nil
|
||||
}
|
||||
}
|
||||
|
||||
return RTPCodecParameters{}, 0, ErrCodecNotFound
|
||||
}
|
||||
|
||||
func (m *MediaEngine) collectStats(collector *statsReportCollector) {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
|
||||
statsLoop := func(codecs []RTPCodecParameters) {
|
||||
for _, codec := range codecs {
|
||||
collector.Collecting()
|
||||
stats := CodecStats{
|
||||
Timestamp: statsTimestampFrom(time.Now()),
|
||||
Type: StatsTypeCodec,
|
||||
ID: codec.statsID,
|
||||
PayloadType: codec.PayloadType,
|
||||
MimeType: codec.MimeType,
|
||||
ClockRate: codec.ClockRate,
|
||||
Channels: uint8(codec.Channels),
|
||||
SDPFmtpLine: codec.SDPFmtpLine,
|
||||
}
|
||||
|
||||
collector.Collect(stats.ID, stats)
|
||||
}
|
||||
}
|
||||
|
||||
statsLoop(m.videoCodecs)
|
||||
statsLoop(m.audioCodecs)
|
||||
}
|
||||
|
||||
// Look up a codec and enable if it exists
|
||||
func (m *MediaEngine) matchRemoteCodec(remoteCodec RTPCodecParameters, typ RTPCodecType, exactMatches, partialMatches []RTPCodecParameters) (RTPCodecParameters, codecMatchType, error) {
|
||||
codecs := m.videoCodecs
|
||||
if typ == RTPCodecTypeAudio {
|
||||
codecs = m.audioCodecs
|
||||
}
|
||||
|
||||
remoteFmtp := fmtp.Parse(remoteCodec.RTPCodecCapability.MimeType, remoteCodec.RTPCodecCapability.SDPFmtpLine)
|
||||
if apt, hasApt := remoteFmtp.Parameter("apt"); hasApt {
|
||||
payloadType, err := strconv.ParseUint(apt, 10, 8)
|
||||
if err != nil {
|
||||
return RTPCodecParameters{}, codecMatchNone, err
|
||||
}
|
||||
|
||||
aptMatch := codecMatchNone
|
||||
var aptCodec RTPCodecParameters
|
||||
for _, codec := range exactMatches {
|
||||
if codec.PayloadType == PayloadType(payloadType) {
|
||||
aptMatch = codecMatchExact
|
||||
aptCodec = codec
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if aptMatch == codecMatchNone {
|
||||
for _, codec := range partialMatches {
|
||||
if codec.PayloadType == PayloadType(payloadType) {
|
||||
aptMatch = codecMatchPartial
|
||||
aptCodec = codec
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if aptMatch == codecMatchNone {
|
||||
return RTPCodecParameters{}, codecMatchNone, nil // not an error, we just ignore this codec we don't support
|
||||
}
|
||||
|
||||
// replace the apt value with the original codec's payload type
|
||||
toMatchCodec := remoteCodec
|
||||
if aptMatched, mt := codecParametersFuzzySearch(aptCodec, codecs); mt == aptMatch {
|
||||
toMatchCodec.SDPFmtpLine = strings.Replace(toMatchCodec.SDPFmtpLine, fmt.Sprintf("apt=%d", payloadType), fmt.Sprintf("apt=%d", aptMatched.PayloadType), 1)
|
||||
}
|
||||
|
||||
// if apt's media codec is partial match, then apt codec must be partial match too
|
||||
localCodec, matchType := codecParametersFuzzySearch(toMatchCodec, codecs)
|
||||
if matchType == codecMatchExact && aptMatch == codecMatchPartial {
|
||||
matchType = codecMatchPartial
|
||||
}
|
||||
return localCodec, matchType, nil
|
||||
}
|
||||
|
||||
localCodec, matchType := codecParametersFuzzySearch(remoteCodec, codecs)
|
||||
return localCodec, matchType, nil
|
||||
}
|
||||
|
||||
// Update header extensions from a remote media section
|
||||
func (m *MediaEngine) updateHeaderExtensionFromMediaSection(media *sdp.MediaDescription) error {
|
||||
var typ RTPCodecType
|
||||
switch {
|
||||
case strings.EqualFold(media.MediaName.Media, "audio"):
|
||||
typ = RTPCodecTypeAudio
|
||||
case strings.EqualFold(media.MediaName.Media, "video"):
|
||||
typ = RTPCodecTypeVideo
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
extensions, err := rtpExtensionsFromMediaDescription(media)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for extension, id := range extensions {
|
||||
if err = m.updateHeaderExtension(id, extension, typ); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Look up a header extension and enable if it exists
|
||||
func (m *MediaEngine) updateHeaderExtension(id int, extension string, typ RTPCodecType) error {
|
||||
if m.negotiatedHeaderExtensions == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, localExtension := range m.headerExtensions {
|
||||
if localExtension.uri == extension {
|
||||
h := mediaEngineHeaderExtension{uri: extension, allowedDirections: localExtension.allowedDirections}
|
||||
if existingValue, ok := m.negotiatedHeaderExtensions[id]; ok {
|
||||
h = existingValue
|
||||
}
|
||||
|
||||
switch {
|
||||
case localExtension.isAudio && typ == RTPCodecTypeAudio:
|
||||
h.isAudio = true
|
||||
case localExtension.isVideo && typ == RTPCodecTypeVideo:
|
||||
h.isVideo = true
|
||||
}
|
||||
|
||||
m.negotiatedHeaderExtensions[id] = h
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MediaEngine) pushCodecs(codecs []RTPCodecParameters, typ RTPCodecType) {
|
||||
for _, codec := range codecs {
|
||||
if typ == RTPCodecTypeAudio {
|
||||
m.negotiatedAudioCodecs = m.addCodec(m.negotiatedAudioCodecs, codec)
|
||||
} else if typ == RTPCodecTypeVideo {
|
||||
m.negotiatedVideoCodecs = m.addCodec(m.negotiatedVideoCodecs, codec)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update the MediaEngine from a remote description
|
||||
func (m *MediaEngine) updateFromRemoteDescription(desc sdp.SessionDescription) error {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
for _, media := range desc.MediaDescriptions {
|
||||
var typ RTPCodecType
|
||||
|
||||
switch {
|
||||
case strings.EqualFold(media.MediaName.Media, "audio"):
|
||||
typ = RTPCodecTypeAudio
|
||||
case strings.EqualFold(media.MediaName.Media, "video"):
|
||||
typ = RTPCodecTypeVideo
|
||||
}
|
||||
|
||||
switch {
|
||||
case !m.negotiatedAudio && typ == RTPCodecTypeAudio:
|
||||
m.negotiatedAudio = true
|
||||
case !m.negotiatedVideo && typ == RTPCodecTypeVideo:
|
||||
m.negotiatedVideo = true
|
||||
default:
|
||||
// update header extesions from remote sdp if codec is negotiated, Firefox
|
||||
// would send updated header extension in renegotiation.
|
||||
// e.g. publish first track without simucalst ->negotiated-> publish second track with simucalst
|
||||
// then the two media secontions have different rtp header extensions in offer
|
||||
if err := m.updateHeaderExtensionFromMediaSection(media); err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
codecs, err := codecsFromMediaDescription(media)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
exactMatches := make([]RTPCodecParameters, 0, len(codecs))
|
||||
partialMatches := make([]RTPCodecParameters, 0, len(codecs))
|
||||
|
||||
for _, remoteCodec := range codecs {
|
||||
localCodec, matchType, mErr := m.matchRemoteCodec(remoteCodec, typ, exactMatches, partialMatches)
|
||||
if mErr != nil {
|
||||
return mErr
|
||||
}
|
||||
|
||||
remoteCodec.RTCPFeedback = rtcpFeedbackIntersection(localCodec.RTCPFeedback, remoteCodec.RTCPFeedback)
|
||||
|
||||
if matchType == codecMatchExact {
|
||||
exactMatches = append(exactMatches, remoteCodec)
|
||||
} else if matchType == codecMatchPartial {
|
||||
partialMatches = append(partialMatches, remoteCodec)
|
||||
}
|
||||
}
|
||||
|
||||
// use exact matches when they exist, otherwise fall back to partial
|
||||
switch {
|
||||
case len(exactMatches) > 0:
|
||||
m.pushCodecs(exactMatches, typ)
|
||||
case len(partialMatches) > 0:
|
||||
m.pushCodecs(partialMatches, typ)
|
||||
default:
|
||||
// no match, not negotiated
|
||||
continue
|
||||
}
|
||||
|
||||
if err := m.updateHeaderExtensionFromMediaSection(media); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MediaEngine) getCodecsByKind(typ RTPCodecType) []RTPCodecParameters {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
|
||||
if typ == RTPCodecTypeVideo {
|
||||
if m.negotiatedVideo {
|
||||
return m.negotiatedVideoCodecs
|
||||
}
|
||||
|
||||
return m.videoCodecs
|
||||
} else if typ == RTPCodecTypeAudio {
|
||||
if m.negotiatedAudio {
|
||||
return m.negotiatedAudioCodecs
|
||||
}
|
||||
|
||||
return m.audioCodecs
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MediaEngine) getRTPParametersByKind(typ RTPCodecType, directions []RTPTransceiverDirection) RTPParameters { //nolint:gocognit
|
||||
headerExtensions := make([]RTPHeaderExtensionParameter, 0)
|
||||
|
||||
// perform before locking to prevent recursive RLocks
|
||||
foundCodecs := m.getCodecsByKind(typ)
|
||||
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
if m.negotiatedVideo && typ == RTPCodecTypeVideo ||
|
||||
m.negotiatedAudio && typ == RTPCodecTypeAudio {
|
||||
for id, e := range m.negotiatedHeaderExtensions {
|
||||
if haveRTPTransceiverDirectionIntersection(e.allowedDirections, directions) && (e.isAudio && typ == RTPCodecTypeAudio || e.isVideo && typ == RTPCodecTypeVideo) {
|
||||
headerExtensions = append(headerExtensions, RTPHeaderExtensionParameter{ID: id, URI: e.uri})
|
||||
}
|
||||
}
|
||||
} else {
|
||||
mediaHeaderExtensions := make(map[int]mediaEngineHeaderExtension)
|
||||
for _, e := range m.headerExtensions {
|
||||
usingNegotiatedID := false
|
||||
for id := range m.negotiatedHeaderExtensions {
|
||||
if m.negotiatedHeaderExtensions[id].uri == e.uri {
|
||||
usingNegotiatedID = true
|
||||
mediaHeaderExtensions[id] = e
|
||||
break
|
||||
}
|
||||
}
|
||||
if !usingNegotiatedID {
|
||||
for id := 1; id < 15; id++ {
|
||||
idAvailable := true
|
||||
if _, ok := mediaHeaderExtensions[id]; ok {
|
||||
idAvailable = false
|
||||
}
|
||||
if _, taken := m.negotiatedHeaderExtensions[id]; idAvailable && !taken {
|
||||
mediaHeaderExtensions[id] = e
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for id, e := range mediaHeaderExtensions {
|
||||
if haveRTPTransceiverDirectionIntersection(e.allowedDirections, directions) && (e.isAudio && typ == RTPCodecTypeAudio || e.isVideo && typ == RTPCodecTypeVideo) {
|
||||
headerExtensions = append(headerExtensions, RTPHeaderExtensionParameter{ID: id, URI: e.uri})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return RTPParameters{
|
||||
HeaderExtensions: headerExtensions,
|
||||
Codecs: foundCodecs,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *MediaEngine) getRTPParametersByPayloadType(payloadType PayloadType) (RTPParameters, error) {
|
||||
codec, typ, err := m.getCodecByPayload(payloadType)
|
||||
if err != nil {
|
||||
return RTPParameters{}, err
|
||||
}
|
||||
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
headerExtensions := make([]RTPHeaderExtensionParameter, 0)
|
||||
for id, e := range m.negotiatedHeaderExtensions {
|
||||
if e.isAudio && typ == RTPCodecTypeAudio || e.isVideo && typ == RTPCodecTypeVideo {
|
||||
headerExtensions = append(headerExtensions, RTPHeaderExtensionParameter{ID: id, URI: e.uri})
|
||||
}
|
||||
}
|
||||
|
||||
return RTPParameters{
|
||||
HeaderExtensions: headerExtensions,
|
||||
Codecs: []RTPCodecParameters{codec},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func payloaderForCodec(codec RTPCodecCapability) (rtp.Payloader, error) {
|
||||
switch strings.ToLower(codec.MimeType) {
|
||||
case strings.ToLower(MimeTypeH264):
|
||||
return &codecs.H264Payloader{}, nil
|
||||
case strings.ToLower(MimeTypeOpus):
|
||||
return &codecs.OpusPayloader{}, nil
|
||||
case strings.ToLower(MimeTypeVP8):
|
||||
return &codecs.VP8Payloader{
|
||||
EnablePictureID: true,
|
||||
}, nil
|
||||
case strings.ToLower(MimeTypeVP9):
|
||||
return &codecs.VP9Payloader{}, nil
|
||||
case strings.ToLower(MimeTypeAV1):
|
||||
return &codecs.AV1Payloader{}, nil
|
||||
case strings.ToLower(MimeTypeG722):
|
||||
return &codecs.G722Payloader{}, nil
|
||||
case strings.ToLower(MimeTypePCMU), strings.ToLower(MimeTypePCMA):
|
||||
return &codecs.G711Payloader{}, nil
|
||||
default:
|
||||
return nil, ErrNoPayloaderForCodec
|
||||
}
|
||||
}
|
||||
107
server/vendor/github.com/pion/webrtc/v3/networktype.go
generated
vendored
Normal file
107
server/vendor/github.com/pion/webrtc/v3/networktype.go
generated
vendored
Normal file
@@ -0,0 +1,107 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package webrtc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/pion/ice/v2"
|
||||
)
|
||||
|
||||
func supportedNetworkTypes() []NetworkType {
|
||||
return []NetworkType{
|
||||
NetworkTypeUDP4,
|
||||
NetworkTypeUDP6,
|
||||
// NetworkTypeTCP4, // Not supported yet
|
||||
// NetworkTypeTCP6, // Not supported yet
|
||||
}
|
||||
}
|
||||
|
||||
// NetworkType represents the type of network
|
||||
type NetworkType int
|
||||
|
||||
const (
|
||||
// NetworkTypeUDP4 indicates UDP over IPv4.
|
||||
NetworkTypeUDP4 NetworkType = iota + 1
|
||||
|
||||
// NetworkTypeUDP6 indicates UDP over IPv6.
|
||||
NetworkTypeUDP6
|
||||
|
||||
// NetworkTypeTCP4 indicates TCP over IPv4.
|
||||
NetworkTypeTCP4
|
||||
|
||||
// NetworkTypeTCP6 indicates TCP over IPv6.
|
||||
NetworkTypeTCP6
|
||||
)
|
||||
|
||||
// This is done this way because of a linter.
|
||||
const (
|
||||
networkTypeUDP4Str = "udp4"
|
||||
networkTypeUDP6Str = "udp6"
|
||||
networkTypeTCP4Str = "tcp4"
|
||||
networkTypeTCP6Str = "tcp6"
|
||||
)
|
||||
|
||||
func (t NetworkType) String() string {
|
||||
switch t {
|
||||
case NetworkTypeUDP4:
|
||||
return networkTypeUDP4Str
|
||||
case NetworkTypeUDP6:
|
||||
return networkTypeUDP6Str
|
||||
case NetworkTypeTCP4:
|
||||
return networkTypeTCP4Str
|
||||
case NetworkTypeTCP6:
|
||||
return networkTypeTCP6Str
|
||||
default:
|
||||
return ErrUnknownType.Error()
|
||||
}
|
||||
}
|
||||
|
||||
// Protocol returns udp or tcp
|
||||
func (t NetworkType) Protocol() string {
|
||||
switch t {
|
||||
case NetworkTypeUDP4:
|
||||
return "udp"
|
||||
case NetworkTypeUDP6:
|
||||
return "udp"
|
||||
case NetworkTypeTCP4:
|
||||
return "tcp"
|
||||
case NetworkTypeTCP6:
|
||||
return "tcp"
|
||||
default:
|
||||
return ErrUnknownType.Error()
|
||||
}
|
||||
}
|
||||
|
||||
// NewNetworkType allows create network type from string
|
||||
// It will be useful for getting custom network types from external config.
|
||||
func NewNetworkType(raw string) (NetworkType, error) {
|
||||
switch raw {
|
||||
case networkTypeUDP4Str:
|
||||
return NetworkTypeUDP4, nil
|
||||
case networkTypeUDP6Str:
|
||||
return NetworkTypeUDP6, nil
|
||||
case networkTypeTCP4Str:
|
||||
return NetworkTypeTCP4, nil
|
||||
case networkTypeTCP6Str:
|
||||
return NetworkTypeTCP6, nil
|
||||
default:
|
||||
return NetworkType(Unknown), fmt.Errorf("%w: %s", errNetworkTypeUnknown, raw)
|
||||
}
|
||||
}
|
||||
|
||||
func getNetworkType(iceNetworkType ice.NetworkType) (NetworkType, error) {
|
||||
switch iceNetworkType {
|
||||
case ice.NetworkTypeUDP4:
|
||||
return NetworkTypeUDP4, nil
|
||||
case ice.NetworkTypeUDP6:
|
||||
return NetworkTypeUDP6, nil
|
||||
case ice.NetworkTypeTCP4:
|
||||
return NetworkTypeTCP4, nil
|
||||
case ice.NetworkTypeTCP6:
|
||||
return NetworkTypeTCP6, nil
|
||||
default:
|
||||
return NetworkType(Unknown), fmt.Errorf("%w: %s", errNetworkTypeUnknown, iceNetworkType.String())
|
||||
}
|
||||
}
|
||||
18
server/vendor/github.com/pion/webrtc/v3/oauthcredential.go
generated
vendored
Normal file
18
server/vendor/github.com/pion/webrtc/v3/oauthcredential.go
generated
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package webrtc
|
||||
|
||||
// OAuthCredential represents OAuth credential information which is used by
|
||||
// the STUN/TURN client to connect to an ICE server as defined in
|
||||
// https://tools.ietf.org/html/rfc7635. Note that the kid parameter is not
|
||||
// located in OAuthCredential, but in ICEServer's username member.
|
||||
type OAuthCredential struct {
|
||||
// MACKey is a base64-url encoded format. It is used in STUN message
|
||||
// integrity hash calculation.
|
||||
MACKey string
|
||||
|
||||
// AccessToken is a base64-encoded format. This is an encrypted
|
||||
// self-contained token that is opaque to the application.
|
||||
AccessToken string
|
||||
}
|
||||
29
server/vendor/github.com/pion/webrtc/v3/offeransweroptions.go
generated
vendored
Normal file
29
server/vendor/github.com/pion/webrtc/v3/offeransweroptions.go
generated
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package webrtc
|
||||
|
||||
// OfferAnswerOptions is a base structure which describes the options that
|
||||
// can be used to control the offer/answer creation process.
|
||||
type OfferAnswerOptions struct {
|
||||
// VoiceActivityDetection allows the application to provide information
|
||||
// about whether it wishes voice detection feature to be enabled or disabled.
|
||||
VoiceActivityDetection bool
|
||||
}
|
||||
|
||||
// AnswerOptions structure describes the options used to control the answer
|
||||
// creation process.
|
||||
type AnswerOptions struct {
|
||||
OfferAnswerOptions
|
||||
}
|
||||
|
||||
// OfferOptions structure describes the options used to control the offer
|
||||
// creation process
|
||||
type OfferOptions struct {
|
||||
OfferAnswerOptions
|
||||
|
||||
// ICERestart forces the underlying ice gathering process to be restarted.
|
||||
// When this value is true, the generated description will have ICE
|
||||
// credentials that are different from the current credentials
|
||||
ICERestart bool
|
||||
}
|
||||
154
server/vendor/github.com/pion/webrtc/v3/operations.go
generated
vendored
Normal file
154
server/vendor/github.com/pion/webrtc/v3/operations.go
generated
vendored
Normal file
@@ -0,0 +1,154 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package webrtc
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Operation is a function
|
||||
type operation func()
|
||||
|
||||
// Operations is a task executor.
|
||||
type operations struct {
|
||||
mu sync.Mutex
|
||||
busyCh chan struct{}
|
||||
ops *list.List
|
||||
|
||||
updateNegotiationNeededFlagOnEmptyChain *atomicBool
|
||||
onNegotiationNeeded func()
|
||||
isClosed bool
|
||||
}
|
||||
|
||||
func newOperations(
|
||||
updateNegotiationNeededFlagOnEmptyChain *atomicBool,
|
||||
onNegotiationNeeded func(),
|
||||
) *operations {
|
||||
return &operations{
|
||||
ops: list.New(),
|
||||
updateNegotiationNeededFlagOnEmptyChain: updateNegotiationNeededFlagOnEmptyChain,
|
||||
onNegotiationNeeded: onNegotiationNeeded,
|
||||
}
|
||||
}
|
||||
|
||||
// Enqueue adds a new action to be executed. If there are no actions scheduled,
|
||||
// the execution will start immediately in a new goroutine. If the queue has been
|
||||
// closed, the operation will be dropped. The queue is only deliberately closed
|
||||
// by a user.
|
||||
func (o *operations) Enqueue(op operation) {
|
||||
o.mu.Lock()
|
||||
defer o.mu.Unlock()
|
||||
_ = o.tryEnqueue(op)
|
||||
}
|
||||
|
||||
// tryEnqueue attempts to enqueue the given operation. It returns false
|
||||
// if the op is invalid or the queue is closed. mu must be locked by
|
||||
// tryEnqueue's caller.
|
||||
func (o *operations) tryEnqueue(op operation) bool {
|
||||
if op == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if o.isClosed {
|
||||
return false
|
||||
}
|
||||
o.ops.PushBack(op)
|
||||
|
||||
if o.busyCh == nil {
|
||||
o.busyCh = make(chan struct{})
|
||||
go o.start()
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// IsEmpty checks if there are tasks in the queue
|
||||
func (o *operations) IsEmpty() bool {
|
||||
o.mu.Lock()
|
||||
defer o.mu.Unlock()
|
||||
return o.ops.Len() == 0
|
||||
}
|
||||
|
||||
// Done blocks until all currently enqueued operations are finished executing.
|
||||
// For more complex synchronization, use Enqueue directly.
|
||||
func (o *operations) Done() {
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
o.mu.Lock()
|
||||
enqueued := o.tryEnqueue(func() {
|
||||
wg.Done()
|
||||
})
|
||||
o.mu.Unlock()
|
||||
if !enqueued {
|
||||
return
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
// GracefulClose waits for the operations queue to be cleared and forbids
|
||||
// new operations from being enqueued.
|
||||
func (o *operations) GracefulClose() {
|
||||
o.mu.Lock()
|
||||
if o.isClosed {
|
||||
o.mu.Unlock()
|
||||
return
|
||||
}
|
||||
// do not enqueue anymore ops from here on
|
||||
// o.isClosed=true will also not allow a new busyCh
|
||||
// to be created.
|
||||
o.isClosed = true
|
||||
|
||||
busyCh := o.busyCh
|
||||
o.mu.Unlock()
|
||||
if busyCh == nil {
|
||||
return
|
||||
}
|
||||
<-busyCh
|
||||
}
|
||||
|
||||
func (o *operations) pop() func() {
|
||||
o.mu.Lock()
|
||||
defer o.mu.Unlock()
|
||||
if o.ops.Len() == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
e := o.ops.Front()
|
||||
o.ops.Remove(e)
|
||||
if op, ok := e.Value.(operation); ok {
|
||||
return op
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *operations) start() {
|
||||
defer func() {
|
||||
o.mu.Lock()
|
||||
defer o.mu.Unlock()
|
||||
// this wil lbe the most recent busy chan
|
||||
close(o.busyCh)
|
||||
|
||||
if o.ops.Len() == 0 || o.isClosed {
|
||||
o.busyCh = nil
|
||||
return
|
||||
}
|
||||
|
||||
// either a new operation was enqueued while we
|
||||
// were busy, or an operation panicked
|
||||
o.busyCh = make(chan struct{})
|
||||
go o.start()
|
||||
}()
|
||||
|
||||
fn := o.pop()
|
||||
for fn != nil {
|
||||
fn()
|
||||
fn = o.pop()
|
||||
}
|
||||
if !o.updateNegotiationNeededFlagOnEmptyChain.get() {
|
||||
return
|
||||
}
|
||||
o.updateNegotiationNeededFlagOnEmptyChain.set(false)
|
||||
o.onNegotiationNeeded()
|
||||
}
|
||||
11
server/vendor/github.com/pion/webrtc/v3/package.json
generated
vendored
Normal file
11
server/vendor/github.com/pion/webrtc/v3/package.json
generated
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"name": "webrtc",
|
||||
"repository": "git@github.com:pion/webrtc.git",
|
||||
"private": true,
|
||||
"devDependencies": {
|
||||
"wrtc": "0.4.7"
|
||||
},
|
||||
"dependencies": {
|
||||
"request": "2.88.2"
|
||||
}
|
||||
}
|
||||
2641
server/vendor/github.com/pion/webrtc/v3/peerconnection.go
generated
vendored
Normal file
2641
server/vendor/github.com/pion/webrtc/v3/peerconnection.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
774
server/vendor/github.com/pion/webrtc/v3/peerconnection_js.go
generated
vendored
Normal file
774
server/vendor/github.com/pion/webrtc/v3/peerconnection_js.go
generated
vendored
Normal file
@@ -0,0 +1,774 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
//go:build js && wasm
|
||||
// +build js,wasm
|
||||
|
||||
// Package webrtc implements the WebRTC 1.0 as defined in W3C WebRTC specification document.
|
||||
package webrtc
|
||||
|
||||
import (
|
||||
"syscall/js"
|
||||
|
||||
"github.com/pion/ice/v2"
|
||||
"github.com/pion/webrtc/v3/pkg/rtcerr"
|
||||
)
|
||||
|
||||
// PeerConnection represents a WebRTC connection that establishes a
|
||||
// peer-to-peer communications with another PeerConnection instance in a
|
||||
// browser, or to another endpoint implementing the required protocols.
|
||||
type PeerConnection struct {
|
||||
// Pointer to the underlying JavaScript RTCPeerConnection object.
|
||||
underlying js.Value
|
||||
|
||||
// Keep track of handlers/callbacks so we can call Release as required by the
|
||||
// syscall/js API. Initially nil.
|
||||
onSignalingStateChangeHandler *js.Func
|
||||
onDataChannelHandler *js.Func
|
||||
onNegotiationNeededHandler *js.Func
|
||||
onConnectionStateChangeHandler *js.Func
|
||||
onICEConnectionStateChangeHandler *js.Func
|
||||
onICECandidateHandler *js.Func
|
||||
onICEGatheringStateChangeHandler *js.Func
|
||||
|
||||
// Used by GatheringCompletePromise
|
||||
onGatherCompleteHandler func()
|
||||
|
||||
// A reference to the associated API state used by this connection
|
||||
api *API
|
||||
}
|
||||
|
||||
// NewPeerConnection creates a peerconnection.
|
||||
func NewPeerConnection(configuration Configuration) (*PeerConnection, error) {
|
||||
api := NewAPI()
|
||||
return api.NewPeerConnection(configuration)
|
||||
}
|
||||
|
||||
// NewPeerConnection creates a new PeerConnection with the provided configuration against the received API object
|
||||
func (api *API) NewPeerConnection(configuration Configuration) (_ *PeerConnection, err error) {
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
err = recoveryToError(e)
|
||||
}
|
||||
}()
|
||||
configMap := configurationToValue(configuration)
|
||||
underlying := js.Global().Get("window").Get("RTCPeerConnection").New(configMap)
|
||||
return &PeerConnection{
|
||||
underlying: underlying,
|
||||
api: api,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// JSValue returns the underlying PeerConnection
|
||||
func (pc *PeerConnection) JSValue() js.Value {
|
||||
return pc.underlying
|
||||
}
|
||||
|
||||
// OnSignalingStateChange sets an event handler which is invoked when the
|
||||
// peer connection's signaling state changes
|
||||
func (pc *PeerConnection) OnSignalingStateChange(f func(SignalingState)) {
|
||||
if pc.onSignalingStateChangeHandler != nil {
|
||||
oldHandler := pc.onSignalingStateChangeHandler
|
||||
defer oldHandler.Release()
|
||||
}
|
||||
onSignalingStateChangeHandler := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
|
||||
state := newSignalingState(args[0].String())
|
||||
go f(state)
|
||||
return js.Undefined()
|
||||
})
|
||||
pc.onSignalingStateChangeHandler = &onSignalingStateChangeHandler
|
||||
pc.underlying.Set("onsignalingstatechange", onSignalingStateChangeHandler)
|
||||
}
|
||||
|
||||
// OnDataChannel sets an event handler which is invoked when a data
|
||||
// channel message arrives from a remote peer.
|
||||
func (pc *PeerConnection) OnDataChannel(f func(*DataChannel)) {
|
||||
if pc.onDataChannelHandler != nil {
|
||||
oldHandler := pc.onDataChannelHandler
|
||||
defer oldHandler.Release()
|
||||
}
|
||||
onDataChannelHandler := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
|
||||
// pion/webrtc/projects/15
|
||||
// This reference to the underlying DataChannel doesn't know
|
||||
// about any other references to the same DataChannel. This might result in
|
||||
// memory leaks where we don't clean up handler functions. Could possibly fix
|
||||
// by keeping a mutex-protected list of all DataChannel references as a
|
||||
// property of this PeerConnection, but at the cost of additional overhead.
|
||||
dataChannel := &DataChannel{
|
||||
underlying: args[0].Get("channel"),
|
||||
api: pc.api,
|
||||
}
|
||||
go f(dataChannel)
|
||||
return js.Undefined()
|
||||
})
|
||||
pc.onDataChannelHandler = &onDataChannelHandler
|
||||
pc.underlying.Set("ondatachannel", onDataChannelHandler)
|
||||
}
|
||||
|
||||
// OnNegotiationNeeded sets an event handler which is invoked when
|
||||
// a change has occurred which requires session negotiation
|
||||
func (pc *PeerConnection) OnNegotiationNeeded(f func()) {
|
||||
if pc.onNegotiationNeededHandler != nil {
|
||||
oldHandler := pc.onNegotiationNeededHandler
|
||||
defer oldHandler.Release()
|
||||
}
|
||||
onNegotiationNeededHandler := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
|
||||
go f()
|
||||
return js.Undefined()
|
||||
})
|
||||
pc.onNegotiationNeededHandler = &onNegotiationNeededHandler
|
||||
pc.underlying.Set("onnegotiationneeded", onNegotiationNeededHandler)
|
||||
}
|
||||
|
||||
// OnICEConnectionStateChange sets an event handler which is called
|
||||
// when an ICE connection state is changed.
|
||||
func (pc *PeerConnection) OnICEConnectionStateChange(f func(ICEConnectionState)) {
|
||||
if pc.onICEConnectionStateChangeHandler != nil {
|
||||
oldHandler := pc.onICEConnectionStateChangeHandler
|
||||
defer oldHandler.Release()
|
||||
}
|
||||
onICEConnectionStateChangeHandler := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
|
||||
connectionState := NewICEConnectionState(pc.underlying.Get("iceConnectionState").String())
|
||||
go f(connectionState)
|
||||
return js.Undefined()
|
||||
})
|
||||
pc.onICEConnectionStateChangeHandler = &onICEConnectionStateChangeHandler
|
||||
pc.underlying.Set("oniceconnectionstatechange", onICEConnectionStateChangeHandler)
|
||||
}
|
||||
|
||||
// OnConnectionStateChange sets an event handler which is called
|
||||
// when an PeerConnectionState is changed.
|
||||
func (pc *PeerConnection) OnConnectionStateChange(f func(PeerConnectionState)) {
|
||||
if pc.onConnectionStateChangeHandler != nil {
|
||||
oldHandler := pc.onConnectionStateChangeHandler
|
||||
defer oldHandler.Release()
|
||||
}
|
||||
onConnectionStateChangeHandler := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
|
||||
connectionState := newPeerConnectionState(pc.underlying.Get("connectionState").String())
|
||||
go f(connectionState)
|
||||
return js.Undefined()
|
||||
})
|
||||
pc.onConnectionStateChangeHandler = &onConnectionStateChangeHandler
|
||||
pc.underlying.Set("onconnectionstatechange", onConnectionStateChangeHandler)
|
||||
}
|
||||
|
||||
func (pc *PeerConnection) checkConfiguration(configuration Configuration) error {
|
||||
// https://www.w3.org/TR/webrtc/#dom-rtcpeerconnection-setconfiguration (step #2)
|
||||
if pc.ConnectionState() == PeerConnectionStateClosed {
|
||||
return &rtcerr.InvalidStateError{Err: ErrConnectionClosed}
|
||||
}
|
||||
|
||||
existingConfig := pc.GetConfiguration()
|
||||
// https://www.w3.org/TR/webrtc/#set-the-configuration (step #3)
|
||||
if configuration.PeerIdentity != "" {
|
||||
if configuration.PeerIdentity != existingConfig.PeerIdentity {
|
||||
return &rtcerr.InvalidModificationError{Err: ErrModifyingPeerIdentity}
|
||||
}
|
||||
}
|
||||
|
||||
// https://github.com/pion/webrtc/issues/513
|
||||
// https://www.w3.org/TR/webrtc/#set-the-configuration (step #4)
|
||||
// if len(configuration.Certificates) > 0 {
|
||||
// if len(configuration.Certificates) != len(existingConfiguration.Certificates) {
|
||||
// return &rtcerr.InvalidModificationError{Err: ErrModifyingCertificates}
|
||||
// }
|
||||
|
||||
// for i, certificate := range configuration.Certificates {
|
||||
// if !pc.configuration.Certificates[i].Equals(certificate) {
|
||||
// return &rtcerr.InvalidModificationError{Err: ErrModifyingCertificates}
|
||||
// }
|
||||
// }
|
||||
// pc.configuration.Certificates = configuration.Certificates
|
||||
// }
|
||||
|
||||
// https://www.w3.org/TR/webrtc/#set-the-configuration (step #5)
|
||||
if configuration.BundlePolicy != BundlePolicy(Unknown) {
|
||||
if configuration.BundlePolicy != existingConfig.BundlePolicy {
|
||||
return &rtcerr.InvalidModificationError{Err: ErrModifyingBundlePolicy}
|
||||
}
|
||||
}
|
||||
|
||||
// https://www.w3.org/TR/webrtc/#set-the-configuration (step #6)
|
||||
if configuration.RTCPMuxPolicy != RTCPMuxPolicy(Unknown) {
|
||||
if configuration.RTCPMuxPolicy != existingConfig.RTCPMuxPolicy {
|
||||
return &rtcerr.InvalidModificationError{Err: ErrModifyingRTCPMuxPolicy}
|
||||
}
|
||||
}
|
||||
|
||||
// https://www.w3.org/TR/webrtc/#set-the-configuration (step #7)
|
||||
if configuration.ICECandidatePoolSize != 0 {
|
||||
if configuration.ICECandidatePoolSize != existingConfig.ICECandidatePoolSize &&
|
||||
pc.LocalDescription() != nil {
|
||||
return &rtcerr.InvalidModificationError{Err: ErrModifyingICECandidatePoolSize}
|
||||
}
|
||||
}
|
||||
|
||||
// https://www.w3.org/TR/webrtc/#set-the-configuration (step #11)
|
||||
if len(configuration.ICEServers) > 0 {
|
||||
// https://www.w3.org/TR/webrtc/#set-the-configuration (step #11.3)
|
||||
for _, server := range configuration.ICEServers {
|
||||
if _, err := server.validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetConfiguration updates the configuration of this PeerConnection object.
|
||||
func (pc *PeerConnection) SetConfiguration(configuration Configuration) (err error) {
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
err = recoveryToError(e)
|
||||
}
|
||||
}()
|
||||
if err := pc.checkConfiguration(configuration); err != nil {
|
||||
return err
|
||||
}
|
||||
configMap := configurationToValue(configuration)
|
||||
pc.underlying.Call("setConfiguration", configMap)
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetConfiguration returns a Configuration object representing the current
|
||||
// configuration of this PeerConnection object. The returned object is a
|
||||
// copy and direct mutation on it will not take affect until SetConfiguration
|
||||
// has been called with Configuration passed as its only argument.
|
||||
// https://www.w3.org/TR/webrtc/#dom-rtcpeerconnection-getconfiguration
|
||||
func (pc *PeerConnection) GetConfiguration() Configuration {
|
||||
return valueToConfiguration(pc.underlying.Call("getConfiguration"))
|
||||
}
|
||||
|
||||
// CreateOffer starts the PeerConnection and generates the localDescription
|
||||
func (pc *PeerConnection) CreateOffer(options *OfferOptions) (_ SessionDescription, err error) {
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
err = recoveryToError(e)
|
||||
}
|
||||
}()
|
||||
promise := pc.underlying.Call("createOffer", offerOptionsToValue(options))
|
||||
desc, err := awaitPromise(promise)
|
||||
if err != nil {
|
||||
return SessionDescription{}, err
|
||||
}
|
||||
return *valueToSessionDescription(desc), nil
|
||||
}
|
||||
|
||||
// CreateAnswer starts the PeerConnection and generates the localDescription
|
||||
func (pc *PeerConnection) CreateAnswer(options *AnswerOptions) (_ SessionDescription, err error) {
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
err = recoveryToError(e)
|
||||
}
|
||||
}()
|
||||
promise := pc.underlying.Call("createAnswer", answerOptionsToValue(options))
|
||||
desc, err := awaitPromise(promise)
|
||||
if err != nil {
|
||||
return SessionDescription{}, err
|
||||
}
|
||||
return *valueToSessionDescription(desc), nil
|
||||
}
|
||||
|
||||
// SetLocalDescription sets the SessionDescription of the local peer
|
||||
func (pc *PeerConnection) SetLocalDescription(desc SessionDescription) (err error) {
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
err = recoveryToError(e)
|
||||
}
|
||||
}()
|
||||
promise := pc.underlying.Call("setLocalDescription", sessionDescriptionToValue(&desc))
|
||||
_, err = awaitPromise(promise)
|
||||
return err
|
||||
}
|
||||
|
||||
// LocalDescription returns PendingLocalDescription if it is not null and
|
||||
// otherwise it returns CurrentLocalDescription. This property is used to
|
||||
// determine if setLocalDescription has already been called.
|
||||
// https://www.w3.org/TR/webrtc/#dom-rtcpeerconnection-localdescription
|
||||
func (pc *PeerConnection) LocalDescription() *SessionDescription {
|
||||
return valueToSessionDescription(pc.underlying.Get("localDescription"))
|
||||
}
|
||||
|
||||
// SetRemoteDescription sets the SessionDescription of the remote peer
|
||||
func (pc *PeerConnection) SetRemoteDescription(desc SessionDescription) (err error) {
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
err = recoveryToError(e)
|
||||
}
|
||||
}()
|
||||
promise := pc.underlying.Call("setRemoteDescription", sessionDescriptionToValue(&desc))
|
||||
_, err = awaitPromise(promise)
|
||||
return err
|
||||
}
|
||||
|
||||
// RemoteDescription returns PendingRemoteDescription if it is not null and
|
||||
// otherwise it returns CurrentRemoteDescription. This property is used to
|
||||
// determine if setRemoteDescription has already been called.
|
||||
// https://www.w3.org/TR/webrtc/#dom-rtcpeerconnection-remotedescription
|
||||
func (pc *PeerConnection) RemoteDescription() *SessionDescription {
|
||||
return valueToSessionDescription(pc.underlying.Get("remoteDescription"))
|
||||
}
|
||||
|
||||
// AddICECandidate accepts an ICE candidate string and adds it
|
||||
// to the existing set of candidates
|
||||
func (pc *PeerConnection) AddICECandidate(candidate ICECandidateInit) (err error) {
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
err = recoveryToError(e)
|
||||
}
|
||||
}()
|
||||
promise := pc.underlying.Call("addIceCandidate", iceCandidateInitToValue(candidate))
|
||||
_, err = awaitPromise(promise)
|
||||
return err
|
||||
}
|
||||
|
||||
// ICEConnectionState returns the ICE connection state of the
|
||||
// PeerConnection instance.
|
||||
func (pc *PeerConnection) ICEConnectionState() ICEConnectionState {
|
||||
return NewICEConnectionState(pc.underlying.Get("iceConnectionState").String())
|
||||
}
|
||||
|
||||
// OnICECandidate sets an event handler which is invoked when a new ICE
|
||||
// candidate is found.
|
||||
func (pc *PeerConnection) OnICECandidate(f func(candidate *ICECandidate)) {
|
||||
if pc.onICECandidateHandler != nil {
|
||||
oldHandler := pc.onICECandidateHandler
|
||||
defer oldHandler.Release()
|
||||
}
|
||||
onICECandidateHandler := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
|
||||
candidate := valueToICECandidate(args[0].Get("candidate"))
|
||||
if candidate == nil && pc.onGatherCompleteHandler != nil {
|
||||
go pc.onGatherCompleteHandler()
|
||||
}
|
||||
|
||||
go f(candidate)
|
||||
return js.Undefined()
|
||||
})
|
||||
pc.onICECandidateHandler = &onICECandidateHandler
|
||||
pc.underlying.Set("onicecandidate", onICECandidateHandler)
|
||||
}
|
||||
|
||||
// OnICEGatheringStateChange sets an event handler which is invoked when the
|
||||
// ICE candidate gathering state has changed.
|
||||
func (pc *PeerConnection) OnICEGatheringStateChange(f func()) {
|
||||
if pc.onICEGatheringStateChangeHandler != nil {
|
||||
oldHandler := pc.onICEGatheringStateChangeHandler
|
||||
defer oldHandler.Release()
|
||||
}
|
||||
onICEGatheringStateChangeHandler := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
|
||||
go f()
|
||||
return js.Undefined()
|
||||
})
|
||||
pc.onICEGatheringStateChangeHandler = &onICEGatheringStateChangeHandler
|
||||
pc.underlying.Set("onicegatheringstatechange", onICEGatheringStateChangeHandler)
|
||||
}
|
||||
|
||||
// CreateDataChannel creates a new DataChannel object with the given label
|
||||
// and optional DataChannelInit used to configure properties of the
|
||||
// underlying channel such as data reliability.
|
||||
func (pc *PeerConnection) CreateDataChannel(label string, options *DataChannelInit) (_ *DataChannel, err error) {
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
err = recoveryToError(e)
|
||||
}
|
||||
}()
|
||||
channel := pc.underlying.Call("createDataChannel", label, dataChannelInitToValue(options))
|
||||
return &DataChannel{
|
||||
underlying: channel,
|
||||
api: pc.api,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// SetIdentityProvider is used to configure an identity provider to generate identity assertions
|
||||
func (pc *PeerConnection) SetIdentityProvider(provider string) (err error) {
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
err = recoveryToError(e)
|
||||
}
|
||||
}()
|
||||
pc.underlying.Call("setIdentityProvider", provider)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close ends the PeerConnection
|
||||
func (pc *PeerConnection) Close() (err error) {
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
err = recoveryToError(e)
|
||||
}
|
||||
}()
|
||||
|
||||
pc.underlying.Call("close")
|
||||
|
||||
// Release any handlers as required by the syscall/js API.
|
||||
if pc.onSignalingStateChangeHandler != nil {
|
||||
pc.onSignalingStateChangeHandler.Release()
|
||||
}
|
||||
if pc.onDataChannelHandler != nil {
|
||||
pc.onDataChannelHandler.Release()
|
||||
}
|
||||
if pc.onNegotiationNeededHandler != nil {
|
||||
pc.onNegotiationNeededHandler.Release()
|
||||
}
|
||||
if pc.onConnectionStateChangeHandler != nil {
|
||||
pc.onConnectionStateChangeHandler.Release()
|
||||
}
|
||||
if pc.onICEConnectionStateChangeHandler != nil {
|
||||
pc.onICEConnectionStateChangeHandler.Release()
|
||||
}
|
||||
if pc.onICECandidateHandler != nil {
|
||||
pc.onICECandidateHandler.Release()
|
||||
}
|
||||
if pc.onICEGatheringStateChangeHandler != nil {
|
||||
pc.onICEGatheringStateChangeHandler.Release()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CurrentLocalDescription represents the local description that was
|
||||
// successfully negotiated the last time the PeerConnection transitioned
|
||||
// into the stable state plus any local candidates that have been generated
|
||||
// by the ICEAgent since the offer or answer was created.
|
||||
func (pc *PeerConnection) CurrentLocalDescription() *SessionDescription {
|
||||
desc := pc.underlying.Get("currentLocalDescription")
|
||||
return valueToSessionDescription(desc)
|
||||
}
|
||||
|
||||
// PendingLocalDescription represents a local description that is in the
|
||||
// process of being negotiated plus any local candidates that have been
|
||||
// generated by the ICEAgent since the offer or answer was created. If the
|
||||
// PeerConnection is in the stable state, the value is null.
|
||||
func (pc *PeerConnection) PendingLocalDescription() *SessionDescription {
|
||||
desc := pc.underlying.Get("pendingLocalDescription")
|
||||
return valueToSessionDescription(desc)
|
||||
}
|
||||
|
||||
// CurrentRemoteDescription represents the last remote description that was
|
||||
// successfully negotiated the last time the PeerConnection transitioned
|
||||
// into the stable state plus any remote candidates that have been supplied
|
||||
// via AddICECandidate() since the offer or answer was created.
|
||||
func (pc *PeerConnection) CurrentRemoteDescription() *SessionDescription {
|
||||
desc := pc.underlying.Get("currentRemoteDescription")
|
||||
return valueToSessionDescription(desc)
|
||||
}
|
||||
|
||||
// PendingRemoteDescription represents a remote description that is in the
|
||||
// process of being negotiated, complete with any remote candidates that
|
||||
// have been supplied via AddICECandidate() since the offer or answer was
|
||||
// created. If the PeerConnection is in the stable state, the value is
|
||||
// null.
|
||||
func (pc *PeerConnection) PendingRemoteDescription() *SessionDescription {
|
||||
desc := pc.underlying.Get("pendingRemoteDescription")
|
||||
return valueToSessionDescription(desc)
|
||||
}
|
||||
|
||||
// SignalingState returns the signaling state of the PeerConnection instance.
|
||||
func (pc *PeerConnection) SignalingState() SignalingState {
|
||||
rawState := pc.underlying.Get("signalingState").String()
|
||||
return newSignalingState(rawState)
|
||||
}
|
||||
|
||||
// ICEGatheringState attribute the ICE gathering state of the PeerConnection
|
||||
// instance.
|
||||
func (pc *PeerConnection) ICEGatheringState() ICEGatheringState {
|
||||
rawState := pc.underlying.Get("iceGatheringState").String()
|
||||
return NewICEGatheringState(rawState)
|
||||
}
|
||||
|
||||
// ConnectionState attribute the connection state of the PeerConnection
|
||||
// instance.
|
||||
func (pc *PeerConnection) ConnectionState() PeerConnectionState {
|
||||
rawState := pc.underlying.Get("connectionState").String()
|
||||
return newPeerConnectionState(rawState)
|
||||
}
|
||||
|
||||
func (pc *PeerConnection) setGatherCompleteHandler(handler func()) {
|
||||
pc.onGatherCompleteHandler = handler
|
||||
|
||||
// If no onIceCandidate handler has been set provide an empty one
|
||||
// otherwise our onGatherCompleteHandler will not be executed
|
||||
if pc.onICECandidateHandler == nil {
|
||||
pc.OnICECandidate(func(i *ICECandidate) {})
|
||||
}
|
||||
}
|
||||
|
||||
// AddTransceiverFromKind Create a new RtpTransceiver and adds it to the set of transceivers.
|
||||
func (pc *PeerConnection) AddTransceiverFromKind(kind RTPCodecType, init ...RTPTransceiverInit) (transceiver *RTPTransceiver, err error) {
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
err = recoveryToError(e)
|
||||
}
|
||||
}()
|
||||
|
||||
if len(init) == 1 {
|
||||
return &RTPTransceiver{
|
||||
underlying: pc.underlying.Call("addTransceiver", kind.String(), rtpTransceiverInitInitToValue(init[0])),
|
||||
}, err
|
||||
}
|
||||
|
||||
return &RTPTransceiver{
|
||||
underlying: pc.underlying.Call("addTransceiver", kind.String()),
|
||||
}, err
|
||||
}
|
||||
|
||||
// GetTransceivers returns the RtpTransceiver that are currently attached to this PeerConnection
|
||||
func (pc *PeerConnection) GetTransceivers() (transceivers []*RTPTransceiver) {
|
||||
rawTransceivers := pc.underlying.Call("getTransceivers")
|
||||
transceivers = make([]*RTPTransceiver, rawTransceivers.Length())
|
||||
|
||||
for i := 0; i < rawTransceivers.Length(); i++ {
|
||||
transceivers[i] = &RTPTransceiver{
|
||||
underlying: rawTransceivers.Index(i),
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// SCTP returns the SCTPTransport for this PeerConnection
|
||||
//
|
||||
// The SCTP transport over which SCTP data is sent and received. If SCTP has not been negotiated, the value is nil.
|
||||
// https://www.w3.org/TR/webrtc/#attributes-15
|
||||
func (pc *PeerConnection) SCTP() *SCTPTransport {
|
||||
underlying := pc.underlying.Get("sctp")
|
||||
if underlying.IsNull() || underlying.IsUndefined() {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &SCTPTransport{
|
||||
underlying: underlying,
|
||||
}
|
||||
}
|
||||
|
||||
// Converts a Configuration to js.Value so it can be passed
|
||||
// through to the JavaScript WebRTC API. Any zero values are converted to
|
||||
// js.Undefined(), which will result in the default value being used.
|
||||
func configurationToValue(configuration Configuration) js.Value {
|
||||
return js.ValueOf(map[string]interface{}{
|
||||
"iceServers": iceServersToValue(configuration.ICEServers),
|
||||
"iceTransportPolicy": stringEnumToValueOrUndefined(configuration.ICETransportPolicy.String()),
|
||||
"bundlePolicy": stringEnumToValueOrUndefined(configuration.BundlePolicy.String()),
|
||||
"rtcpMuxPolicy": stringEnumToValueOrUndefined(configuration.RTCPMuxPolicy.String()),
|
||||
"peerIdentity": stringToValueOrUndefined(configuration.PeerIdentity),
|
||||
"iceCandidatePoolSize": uint8ToValueOrUndefined(configuration.ICECandidatePoolSize),
|
||||
|
||||
// Note: Certificates are not currently supported.
|
||||
// "certificates": configuration.Certificates,
|
||||
})
|
||||
}
|
||||
|
||||
func iceServersToValue(iceServers []ICEServer) js.Value {
|
||||
if len(iceServers) == 0 {
|
||||
return js.Undefined()
|
||||
}
|
||||
maps := make([]interface{}, len(iceServers))
|
||||
for i, server := range iceServers {
|
||||
maps[i] = iceServerToValue(server)
|
||||
}
|
||||
return js.ValueOf(maps)
|
||||
}
|
||||
|
||||
func oauthCredentialToValue(o OAuthCredential) js.Value {
|
||||
out := map[string]interface{}{
|
||||
"MACKey": o.MACKey,
|
||||
"AccessToken": o.AccessToken,
|
||||
}
|
||||
return js.ValueOf(out)
|
||||
}
|
||||
|
||||
func iceServerToValue(server ICEServer) js.Value {
|
||||
out := map[string]interface{}{
|
||||
"urls": stringsToValue(server.URLs), // required
|
||||
}
|
||||
if server.Username != "" {
|
||||
out["username"] = stringToValueOrUndefined(server.Username)
|
||||
}
|
||||
if server.Credential != nil {
|
||||
switch t := server.Credential.(type) {
|
||||
case string:
|
||||
out["credential"] = stringToValueOrUndefined(t)
|
||||
case OAuthCredential:
|
||||
out["credential"] = oauthCredentialToValue(t)
|
||||
}
|
||||
}
|
||||
out["credentialType"] = stringEnumToValueOrUndefined(server.CredentialType.String())
|
||||
return js.ValueOf(out)
|
||||
}
|
||||
|
||||
func valueToConfiguration(configValue js.Value) Configuration {
|
||||
if configValue.IsNull() || configValue.IsUndefined() {
|
||||
return Configuration{}
|
||||
}
|
||||
return Configuration{
|
||||
ICEServers: valueToICEServers(configValue.Get("iceServers")),
|
||||
ICETransportPolicy: NewICETransportPolicy(valueToStringOrZero(configValue.Get("iceTransportPolicy"))),
|
||||
BundlePolicy: newBundlePolicy(valueToStringOrZero(configValue.Get("bundlePolicy"))),
|
||||
RTCPMuxPolicy: newRTCPMuxPolicy(valueToStringOrZero(configValue.Get("rtcpMuxPolicy"))),
|
||||
PeerIdentity: valueToStringOrZero(configValue.Get("peerIdentity")),
|
||||
ICECandidatePoolSize: valueToUint8OrZero(configValue.Get("iceCandidatePoolSize")),
|
||||
|
||||
// Note: Certificates are not supported.
|
||||
// Certificates []Certificate
|
||||
}
|
||||
}
|
||||
|
||||
func valueToICEServers(iceServersValue js.Value) []ICEServer {
|
||||
if iceServersValue.IsNull() || iceServersValue.IsUndefined() {
|
||||
return nil
|
||||
}
|
||||
iceServers := make([]ICEServer, iceServersValue.Length())
|
||||
for i := 0; i < iceServersValue.Length(); i++ {
|
||||
iceServers[i] = valueToICEServer(iceServersValue.Index(i))
|
||||
}
|
||||
return iceServers
|
||||
}
|
||||
|
||||
func valueToICECredential(iceCredentialValue js.Value) interface{} {
|
||||
if iceCredentialValue.IsNull() || iceCredentialValue.IsUndefined() {
|
||||
return nil
|
||||
}
|
||||
if iceCredentialValue.Type() == js.TypeString {
|
||||
return iceCredentialValue.String()
|
||||
}
|
||||
if iceCredentialValue.Type() == js.TypeObject {
|
||||
return OAuthCredential{
|
||||
MACKey: iceCredentialValue.Get("MACKey").String(),
|
||||
AccessToken: iceCredentialValue.Get("AccessToken").String(),
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func valueToICEServer(iceServerValue js.Value) ICEServer {
|
||||
tpe, err := newICECredentialType(valueToStringOrZero(iceServerValue.Get("credentialType")))
|
||||
if err != nil {
|
||||
tpe = ICECredentialTypePassword
|
||||
}
|
||||
s := ICEServer{
|
||||
URLs: valueToStrings(iceServerValue.Get("urls")), // required
|
||||
Username: valueToStringOrZero(iceServerValue.Get("username")),
|
||||
// Note: Credential and CredentialType are not currently supported.
|
||||
Credential: valueToICECredential(iceServerValue.Get("credential")),
|
||||
CredentialType: tpe,
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
func valueToICECandidate(val js.Value) *ICECandidate {
|
||||
if val.IsNull() || val.IsUndefined() {
|
||||
return nil
|
||||
}
|
||||
if val.Get("protocol").IsUndefined() && !val.Get("candidate").IsUndefined() {
|
||||
// Missing some fields, assume it's Firefox and parse SDP candidate.
|
||||
c, err := ice.UnmarshalCandidate(val.Get("candidate").String())
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
iceCandidate, err := newICECandidateFromICE(c)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &iceCandidate
|
||||
}
|
||||
protocol, _ := NewICEProtocol(val.Get("protocol").String())
|
||||
candidateType, _ := NewICECandidateType(val.Get("type").String())
|
||||
return &ICECandidate{
|
||||
Foundation: val.Get("foundation").String(),
|
||||
Priority: valueToUint32OrZero(val.Get("priority")),
|
||||
Address: val.Get("address").String(),
|
||||
Protocol: protocol,
|
||||
Port: valueToUint16OrZero(val.Get("port")),
|
||||
Typ: candidateType,
|
||||
Component: stringToComponentIDOrZero(val.Get("component").String()),
|
||||
RelatedAddress: val.Get("relatedAddress").String(),
|
||||
RelatedPort: valueToUint16OrZero(val.Get("relatedPort")),
|
||||
}
|
||||
}
|
||||
|
||||
func stringToComponentIDOrZero(val string) uint16 {
|
||||
// See: https://developer.mozilla.org/en-US/docs/Web/API/RTCIceComponent
|
||||
switch val {
|
||||
case "rtp":
|
||||
return 1
|
||||
case "rtcp":
|
||||
return 2
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func sessionDescriptionToValue(desc *SessionDescription) js.Value {
|
||||
if desc == nil {
|
||||
return js.Undefined()
|
||||
}
|
||||
return js.ValueOf(map[string]interface{}{
|
||||
"type": desc.Type.String(),
|
||||
"sdp": desc.SDP,
|
||||
})
|
||||
}
|
||||
|
||||
func valueToSessionDescription(descValue js.Value) *SessionDescription {
|
||||
if descValue.IsNull() || descValue.IsUndefined() {
|
||||
return nil
|
||||
}
|
||||
return &SessionDescription{
|
||||
Type: NewSDPType(descValue.Get("type").String()),
|
||||
SDP: descValue.Get("sdp").String(),
|
||||
}
|
||||
}
|
||||
|
||||
func offerOptionsToValue(offerOptions *OfferOptions) js.Value {
|
||||
if offerOptions == nil {
|
||||
return js.Undefined()
|
||||
}
|
||||
return js.ValueOf(map[string]interface{}{
|
||||
"iceRestart": offerOptions.ICERestart,
|
||||
"voiceActivityDetection": offerOptions.VoiceActivityDetection,
|
||||
})
|
||||
}
|
||||
|
||||
func answerOptionsToValue(answerOptions *AnswerOptions) js.Value {
|
||||
if answerOptions == nil {
|
||||
return js.Undefined()
|
||||
}
|
||||
return js.ValueOf(map[string]interface{}{
|
||||
"voiceActivityDetection": answerOptions.VoiceActivityDetection,
|
||||
})
|
||||
}
|
||||
|
||||
func iceCandidateInitToValue(candidate ICECandidateInit) js.Value {
|
||||
return js.ValueOf(map[string]interface{}{
|
||||
"candidate": candidate.Candidate,
|
||||
"sdpMid": stringPointerToValue(candidate.SDPMid),
|
||||
"sdpMLineIndex": uint16PointerToValue(candidate.SDPMLineIndex),
|
||||
"usernameFragment": stringPointerToValue(candidate.UsernameFragment),
|
||||
})
|
||||
}
|
||||
|
||||
func dataChannelInitToValue(options *DataChannelInit) js.Value {
|
||||
if options == nil {
|
||||
return js.Undefined()
|
||||
}
|
||||
|
||||
maxPacketLifeTime := uint16PointerToValue(options.MaxPacketLifeTime)
|
||||
return js.ValueOf(map[string]interface{}{
|
||||
"ordered": boolPointerToValue(options.Ordered),
|
||||
"maxPacketLifeTime": maxPacketLifeTime,
|
||||
// See https://bugs.chromium.org/p/chromium/issues/detail?id=696681
|
||||
// Chrome calls this "maxRetransmitTime"
|
||||
"maxRetransmitTime": maxPacketLifeTime,
|
||||
"maxRetransmits": uint16PointerToValue(options.MaxRetransmits),
|
||||
"protocol": stringPointerToValue(options.Protocol),
|
||||
"negotiated": boolPointerToValue(options.Negotiated),
|
||||
"id": uint16PointerToValue(options.ID),
|
||||
})
|
||||
}
|
||||
|
||||
func rtpTransceiverInitInitToValue(init RTPTransceiverInit) js.Value {
|
||||
return js.ValueOf(map[string]interface{}{
|
||||
"direction": init.Direction.String(),
|
||||
})
|
||||
}
|
||||
86
server/vendor/github.com/pion/webrtc/v3/peerconnectionstate.go
generated
vendored
Normal file
86
server/vendor/github.com/pion/webrtc/v3/peerconnectionstate.go
generated
vendored
Normal file
@@ -0,0 +1,86 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package webrtc
|
||||
|
||||
// PeerConnectionState indicates the state of the PeerConnection.
|
||||
type PeerConnectionState int
|
||||
|
||||
const (
|
||||
// PeerConnectionStateNew indicates that any of the ICETransports or
|
||||
// DTLSTransports are in the "new" state and none of the transports are
|
||||
// in the "connecting", "checking", "failed" or "disconnected" state, or
|
||||
// all transports are in the "closed" state, or there are no transports.
|
||||
PeerConnectionStateNew PeerConnectionState = iota + 1
|
||||
|
||||
// PeerConnectionStateConnecting indicates that any of the
|
||||
// ICETransports or DTLSTransports are in the "connecting" or
|
||||
// "checking" state and none of them is in the "failed" state.
|
||||
PeerConnectionStateConnecting
|
||||
|
||||
// PeerConnectionStateConnected indicates that all ICETransports and
|
||||
// DTLSTransports are in the "connected", "completed" or "closed" state
|
||||
// and at least one of them is in the "connected" or "completed" state.
|
||||
PeerConnectionStateConnected
|
||||
|
||||
// PeerConnectionStateDisconnected indicates that any of the
|
||||
// ICETransports or DTLSTransports are in the "disconnected" state
|
||||
// and none of them are in the "failed" or "connecting" or "checking" state.
|
||||
PeerConnectionStateDisconnected
|
||||
|
||||
// PeerConnectionStateFailed indicates that any of the ICETransports
|
||||
// or DTLSTransports are in a "failed" state.
|
||||
PeerConnectionStateFailed
|
||||
|
||||
// PeerConnectionStateClosed indicates the peer connection is closed
|
||||
// and the isClosed member variable of PeerConnection is true.
|
||||
PeerConnectionStateClosed
|
||||
)
|
||||
|
||||
// This is done this way because of a linter.
|
||||
const (
|
||||
peerConnectionStateNewStr = "new"
|
||||
peerConnectionStateConnectingStr = "connecting"
|
||||
peerConnectionStateConnectedStr = "connected"
|
||||
peerConnectionStateDisconnectedStr = "disconnected"
|
||||
peerConnectionStateFailedStr = "failed"
|
||||
peerConnectionStateClosedStr = "closed"
|
||||
)
|
||||
|
||||
func newPeerConnectionState(raw string) PeerConnectionState {
|
||||
switch raw {
|
||||
case peerConnectionStateNewStr:
|
||||
return PeerConnectionStateNew
|
||||
case peerConnectionStateConnectingStr:
|
||||
return PeerConnectionStateConnecting
|
||||
case peerConnectionStateConnectedStr:
|
||||
return PeerConnectionStateConnected
|
||||
case peerConnectionStateDisconnectedStr:
|
||||
return PeerConnectionStateDisconnected
|
||||
case peerConnectionStateFailedStr:
|
||||
return PeerConnectionStateFailed
|
||||
case peerConnectionStateClosedStr:
|
||||
return PeerConnectionStateClosed
|
||||
default:
|
||||
return PeerConnectionState(Unknown)
|
||||
}
|
||||
}
|
||||
|
||||
func (t PeerConnectionState) String() string {
|
||||
switch t {
|
||||
case PeerConnectionStateNew:
|
||||
return peerConnectionStateNewStr
|
||||
case PeerConnectionStateConnecting:
|
||||
return peerConnectionStateConnectingStr
|
||||
case PeerConnectionStateConnected:
|
||||
return peerConnectionStateConnectedStr
|
||||
case PeerConnectionStateDisconnected:
|
||||
return peerConnectionStateDisconnectedStr
|
||||
case PeerConnectionStateFailed:
|
||||
return peerConnectionStateFailedStr
|
||||
case PeerConnectionStateClosed:
|
||||
return peerConnectionStateClosedStr
|
||||
default:
|
||||
return ErrUnknownType.Error()
|
||||
}
|
||||
}
|
||||
31
server/vendor/github.com/pion/webrtc/v3/pkg/media/media.go
generated
vendored
Normal file
31
server/vendor/github.com/pion/webrtc/v3/pkg/media/media.go
generated
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// Package media provides media writer and filters
|
||||
package media
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/pion/rtp"
|
||||
)
|
||||
|
||||
// A Sample contains encoded media and timing information
|
||||
type Sample struct {
|
||||
Data []byte
|
||||
Timestamp time.Time
|
||||
Duration time.Duration
|
||||
PacketTimestamp uint32
|
||||
PrevDroppedPackets uint16
|
||||
Metadata interface{}
|
||||
}
|
||||
|
||||
// Writer defines an interface to handle
|
||||
// the creation of media files
|
||||
type Writer interface {
|
||||
// Add the content of an RTP packet to the media
|
||||
WriteRTP(packet *rtp.Packet) error
|
||||
// Close the media
|
||||
// Note: Close implementation must be idempotent
|
||||
Close() error
|
||||
}
|
||||
163
server/vendor/github.com/pion/webrtc/v3/pkg/rtcerr/errors.go
generated
vendored
Normal file
163
server/vendor/github.com/pion/webrtc/v3/pkg/rtcerr/errors.go
generated
vendored
Normal file
@@ -0,0 +1,163 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// Package rtcerr implements the error wrappers defined throughout the
|
||||
// WebRTC 1.0 specifications.
|
||||
package rtcerr
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// UnknownError indicates the operation failed for an unknown transient reason.
|
||||
type UnknownError struct {
|
||||
Err error
|
||||
}
|
||||
|
||||
func (e *UnknownError) Error() string {
|
||||
return fmt.Sprintf("UnknownError: %v", e.Err)
|
||||
}
|
||||
|
||||
// Unwrap returns the result of calling the Unwrap method on err, if err's type contains
|
||||
// an Unwrap method returning error. Otherwise, Unwrap returns nil.
|
||||
func (e *UnknownError) Unwrap() error {
|
||||
return e.Err
|
||||
}
|
||||
|
||||
// InvalidStateError indicates the object is in an invalid state.
|
||||
type InvalidStateError struct {
|
||||
Err error
|
||||
}
|
||||
|
||||
func (e *InvalidStateError) Error() string {
|
||||
return fmt.Sprintf("InvalidStateError: %v", e.Err)
|
||||
}
|
||||
|
||||
// Unwrap returns the result of calling the Unwrap method on err, if err's type contains
|
||||
// an Unwrap method returning error. Otherwise, Unwrap returns nil.
|
||||
func (e *InvalidStateError) Unwrap() error {
|
||||
return e.Err
|
||||
}
|
||||
|
||||
// InvalidAccessError indicates the object does not support the operation or
|
||||
// argument.
|
||||
type InvalidAccessError struct {
|
||||
Err error
|
||||
}
|
||||
|
||||
func (e *InvalidAccessError) Error() string {
|
||||
return fmt.Sprintf("InvalidAccessError: %v", e.Err)
|
||||
}
|
||||
|
||||
// Unwrap returns the result of calling the Unwrap method on err, if err's type contains
|
||||
// an Unwrap method returning error. Otherwise, Unwrap returns nil.
|
||||
func (e *InvalidAccessError) Unwrap() error {
|
||||
return e.Err
|
||||
}
|
||||
|
||||
// NotSupportedError indicates the operation is not supported.
|
||||
type NotSupportedError struct {
|
||||
Err error
|
||||
}
|
||||
|
||||
func (e *NotSupportedError) Error() string {
|
||||
return fmt.Sprintf("NotSupportedError: %v", e.Err)
|
||||
}
|
||||
|
||||
// Unwrap returns the result of calling the Unwrap method on err, if err's type contains
|
||||
// an Unwrap method returning error. Otherwise, Unwrap returns nil.
|
||||
func (e *NotSupportedError) Unwrap() error {
|
||||
return e.Err
|
||||
}
|
||||
|
||||
// InvalidModificationError indicates the object cannot be modified in this way.
|
||||
type InvalidModificationError struct {
|
||||
Err error
|
||||
}
|
||||
|
||||
func (e *InvalidModificationError) Error() string {
|
||||
return fmt.Sprintf("InvalidModificationError: %v", e.Err)
|
||||
}
|
||||
|
||||
// Unwrap returns the result of calling the Unwrap method on err, if err's type contains
|
||||
// an Unwrap method returning error. Otherwise, Unwrap returns nil.
|
||||
func (e *InvalidModificationError) Unwrap() error {
|
||||
return e.Err
|
||||
}
|
||||
|
||||
// SyntaxError indicates the string did not match the expected pattern.
|
||||
type SyntaxError struct {
|
||||
Err error
|
||||
}
|
||||
|
||||
func (e *SyntaxError) Error() string {
|
||||
return fmt.Sprintf("SyntaxError: %v", e.Err)
|
||||
}
|
||||
|
||||
// Unwrap returns the result of calling the Unwrap method on err, if err's type contains
|
||||
// an Unwrap method returning error. Otherwise, Unwrap returns nil.
|
||||
func (e *SyntaxError) Unwrap() error {
|
||||
return e.Err
|
||||
}
|
||||
|
||||
// TypeError indicates an error when a value is not of the expected type.
|
||||
type TypeError struct {
|
||||
Err error
|
||||
}
|
||||
|
||||
func (e *TypeError) Error() string {
|
||||
return fmt.Sprintf("TypeError: %v", e.Err)
|
||||
}
|
||||
|
||||
// Unwrap returns the result of calling the Unwrap method on err, if err's type contains
|
||||
// an Unwrap method returning error. Otherwise, Unwrap returns nil.
|
||||
func (e *TypeError) Unwrap() error {
|
||||
return e.Err
|
||||
}
|
||||
|
||||
// OperationError indicates the operation failed for an operation-specific
|
||||
// reason.
|
||||
type OperationError struct {
|
||||
Err error
|
||||
}
|
||||
|
||||
func (e *OperationError) Error() string {
|
||||
return fmt.Sprintf("OperationError: %v", e.Err)
|
||||
}
|
||||
|
||||
// Unwrap returns the result of calling the Unwrap method on err, if err's type contains
|
||||
// an Unwrap method returning error. Otherwise, Unwrap returns nil.
|
||||
func (e *OperationError) Unwrap() error {
|
||||
return e.Err
|
||||
}
|
||||
|
||||
// NotReadableError indicates the input/output read operation failed.
|
||||
type NotReadableError struct {
|
||||
Err error
|
||||
}
|
||||
|
||||
func (e *NotReadableError) Error() string {
|
||||
return fmt.Sprintf("NotReadableError: %v", e.Err)
|
||||
}
|
||||
|
||||
// Unwrap returns the result of calling the Unwrap method on err, if err's type contains
|
||||
// an Unwrap method returning error. Otherwise, Unwrap returns nil.
|
||||
func (e *NotReadableError) Unwrap() error {
|
||||
return e.Err
|
||||
}
|
||||
|
||||
// RangeError indicates an error when a value is not in the set or range
|
||||
// of allowed values.
|
||||
type RangeError struct {
|
||||
Err error
|
||||
}
|
||||
|
||||
func (e *RangeError) Error() string {
|
||||
return fmt.Sprintf("RangeError: %v", e.Err)
|
||||
}
|
||||
|
||||
// Unwrap returns the result of calling the Unwrap method on err, if err's type contains
|
||||
// an Unwrap method returning error. Otherwise, Unwrap returns nil.
|
||||
func (e *RangeError) Unwrap() error {
|
||||
return e.Err
|
||||
}
|
||||
6
server/vendor/github.com/pion/webrtc/v3/renovate.json
generated
vendored
Normal file
6
server/vendor/github.com/pion/webrtc/v3/renovate.json
generated
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||
"extends": [
|
||||
"github>pion/renovate-config"
|
||||
]
|
||||
}
|
||||
34
server/vendor/github.com/pion/webrtc/v3/rtcpfeedback.go
generated
vendored
Normal file
34
server/vendor/github.com/pion/webrtc/v3/rtcpfeedback.go
generated
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package webrtc
|
||||
|
||||
const (
|
||||
// TypeRTCPFBTransportCC ..
|
||||
TypeRTCPFBTransportCC = "transport-cc"
|
||||
|
||||
// TypeRTCPFBGoogREMB ..
|
||||
TypeRTCPFBGoogREMB = "goog-remb"
|
||||
|
||||
// TypeRTCPFBACK ..
|
||||
TypeRTCPFBACK = "ack"
|
||||
|
||||
// TypeRTCPFBCCM ..
|
||||
TypeRTCPFBCCM = "ccm"
|
||||
|
||||
// TypeRTCPFBNACK ..
|
||||
TypeRTCPFBNACK = "nack"
|
||||
)
|
||||
|
||||
// RTCPFeedback signals the connection to use additional RTCP packet types.
|
||||
// https://draft.ortc.org/#dom-rtcrtcpfeedback
|
||||
type RTCPFeedback struct {
|
||||
// Type is the type of feedback.
|
||||
// see: https://draft.ortc.org/#dom-rtcrtcpfeedback
|
||||
// valid: ack, ccm, nack, goog-remb, transport-cc
|
||||
Type string
|
||||
|
||||
// The parameter value depends on the type.
|
||||
// For example, type="nack" parameter="pli" will send Picture Loss Indicator packets.
|
||||
Parameter string
|
||||
}
|
||||
69
server/vendor/github.com/pion/webrtc/v3/rtcpmuxpolicy.go
generated
vendored
Normal file
69
server/vendor/github.com/pion/webrtc/v3/rtcpmuxpolicy.go
generated
vendored
Normal file
@@ -0,0 +1,69 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package webrtc
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
// RTCPMuxPolicy affects what ICE candidates are gathered to support
|
||||
// non-multiplexed RTCP.
|
||||
type RTCPMuxPolicy int
|
||||
|
||||
const (
|
||||
// RTCPMuxPolicyNegotiate indicates to gather ICE candidates for both
|
||||
// RTP and RTCP candidates. If the remote-endpoint is capable of
|
||||
// multiplexing RTCP, multiplex RTCP on the RTP candidates. If it is not,
|
||||
// use both the RTP and RTCP candidates separately.
|
||||
RTCPMuxPolicyNegotiate RTCPMuxPolicy = iota + 1
|
||||
|
||||
// RTCPMuxPolicyRequire indicates to gather ICE candidates only for
|
||||
// RTP and multiplex RTCP on the RTP candidates. If the remote endpoint is
|
||||
// not capable of rtcp-mux, session negotiation will fail.
|
||||
RTCPMuxPolicyRequire
|
||||
)
|
||||
|
||||
// This is done this way because of a linter.
|
||||
const (
|
||||
rtcpMuxPolicyNegotiateStr = "negotiate"
|
||||
rtcpMuxPolicyRequireStr = "require"
|
||||
)
|
||||
|
||||
func newRTCPMuxPolicy(raw string) RTCPMuxPolicy {
|
||||
switch raw {
|
||||
case rtcpMuxPolicyNegotiateStr:
|
||||
return RTCPMuxPolicyNegotiate
|
||||
case rtcpMuxPolicyRequireStr:
|
||||
return RTCPMuxPolicyRequire
|
||||
default:
|
||||
return RTCPMuxPolicy(Unknown)
|
||||
}
|
||||
}
|
||||
|
||||
func (t RTCPMuxPolicy) String() string {
|
||||
switch t {
|
||||
case RTCPMuxPolicyNegotiate:
|
||||
return rtcpMuxPolicyNegotiateStr
|
||||
case RTCPMuxPolicyRequire:
|
||||
return rtcpMuxPolicyRequireStr
|
||||
default:
|
||||
return ErrUnknownType.Error()
|
||||
}
|
||||
}
|
||||
|
||||
// UnmarshalJSON parses the JSON-encoded data and stores the result
|
||||
func (t *RTCPMuxPolicy) UnmarshalJSON(b []byte) error {
|
||||
var val string
|
||||
if err := json.Unmarshal(b, &val); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*t = newRTCPMuxPolicy(val)
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalJSON returns the JSON encoding
|
||||
func (t RTCPMuxPolicy) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(t.String())
|
||||
}
|
||||
12
server/vendor/github.com/pion/webrtc/v3/rtpcapabilities.go
generated
vendored
Normal file
12
server/vendor/github.com/pion/webrtc/v3/rtpcapabilities.go
generated
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package webrtc
|
||||
|
||||
// RTPCapabilities represents the capabilities of a transceiver
|
||||
//
|
||||
// https://w3c.github.io/webrtc-pc/#rtcrtpcapabilities
|
||||
type RTPCapabilities struct {
|
||||
Codecs []RTPCodecCapability
|
||||
HeaderExtensions []RTPHeaderExtensionCapability
|
||||
}
|
||||
136
server/vendor/github.com/pion/webrtc/v3/rtpcodec.go
generated
vendored
Normal file
136
server/vendor/github.com/pion/webrtc/v3/rtpcodec.go
generated
vendored
Normal file
@@ -0,0 +1,136 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package webrtc
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/pion/webrtc/v3/internal/fmtp"
|
||||
)
|
||||
|
||||
// RTPCodecType determines the type of a codec
|
||||
type RTPCodecType int
|
||||
|
||||
const (
|
||||
|
||||
// RTPCodecTypeAudio indicates this is an audio codec
|
||||
RTPCodecTypeAudio RTPCodecType = iota + 1
|
||||
|
||||
// RTPCodecTypeVideo indicates this is a video codec
|
||||
RTPCodecTypeVideo
|
||||
)
|
||||
|
||||
func (t RTPCodecType) String() string {
|
||||
switch t {
|
||||
case RTPCodecTypeAudio:
|
||||
return "audio" //nolint: goconst
|
||||
case RTPCodecTypeVideo:
|
||||
return "video" //nolint: goconst
|
||||
default:
|
||||
return ErrUnknownType.Error()
|
||||
}
|
||||
}
|
||||
|
||||
// NewRTPCodecType creates a RTPCodecType from a string
|
||||
func NewRTPCodecType(r string) RTPCodecType {
|
||||
switch {
|
||||
case strings.EqualFold(r, RTPCodecTypeAudio.String()):
|
||||
return RTPCodecTypeAudio
|
||||
case strings.EqualFold(r, RTPCodecTypeVideo.String()):
|
||||
return RTPCodecTypeVideo
|
||||
default:
|
||||
return RTPCodecType(0)
|
||||
}
|
||||
}
|
||||
|
||||
// RTPCodecCapability provides information about codec capabilities.
|
||||
//
|
||||
// https://w3c.github.io/webrtc-pc/#dictionary-rtcrtpcodeccapability-members
|
||||
type RTPCodecCapability struct {
|
||||
MimeType string
|
||||
ClockRate uint32
|
||||
Channels uint16
|
||||
SDPFmtpLine string
|
||||
RTCPFeedback []RTCPFeedback
|
||||
}
|
||||
|
||||
// RTPHeaderExtensionCapability is used to define a RFC5285 RTP header extension supported by the codec.
|
||||
//
|
||||
// https://w3c.github.io/webrtc-pc/#dom-rtcrtpcapabilities-headerextensions
|
||||
type RTPHeaderExtensionCapability struct {
|
||||
URI string
|
||||
}
|
||||
|
||||
// RTPHeaderExtensionParameter represents a negotiated RFC5285 RTP header extension.
|
||||
//
|
||||
// https://w3c.github.io/webrtc-pc/#dictionary-rtcrtpheaderextensionparameters-members
|
||||
type RTPHeaderExtensionParameter struct {
|
||||
URI string
|
||||
ID int
|
||||
}
|
||||
|
||||
// RTPCodecParameters is a sequence containing the media codecs that an RtpSender
|
||||
// will choose from, as well as entries for RTX, RED and FEC mechanisms. This also
|
||||
// includes the PayloadType that has been negotiated
|
||||
//
|
||||
// https://w3c.github.io/webrtc-pc/#rtcrtpcodecparameters
|
||||
type RTPCodecParameters struct {
|
||||
RTPCodecCapability
|
||||
PayloadType PayloadType
|
||||
|
||||
statsID string
|
||||
}
|
||||
|
||||
// RTPParameters is a list of negotiated codecs and header extensions
|
||||
//
|
||||
// https://w3c.github.io/webrtc-pc/#dictionary-rtcrtpparameters-members
|
||||
type RTPParameters struct {
|
||||
HeaderExtensions []RTPHeaderExtensionParameter
|
||||
Codecs []RTPCodecParameters
|
||||
}
|
||||
|
||||
type codecMatchType int
|
||||
|
||||
const (
|
||||
codecMatchNone codecMatchType = 0
|
||||
codecMatchPartial codecMatchType = 1
|
||||
codecMatchExact codecMatchType = 2
|
||||
)
|
||||
|
||||
// Do a fuzzy find for a codec in the list of codecs
|
||||
// Used for lookup up a codec in an existing list to find a match
|
||||
// Returns codecMatchExact, codecMatchPartial, or codecMatchNone
|
||||
func codecParametersFuzzySearch(needle RTPCodecParameters, haystack []RTPCodecParameters) (RTPCodecParameters, codecMatchType) {
|
||||
needleFmtp := fmtp.Parse(needle.RTPCodecCapability.MimeType, needle.RTPCodecCapability.SDPFmtpLine)
|
||||
|
||||
// First attempt to match on MimeType + SDPFmtpLine
|
||||
for _, c := range haystack {
|
||||
cfmtp := fmtp.Parse(c.RTPCodecCapability.MimeType, c.RTPCodecCapability.SDPFmtpLine)
|
||||
if needleFmtp.Match(cfmtp) {
|
||||
return c, codecMatchExact
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to just MimeType
|
||||
for _, c := range haystack {
|
||||
if strings.EqualFold(c.RTPCodecCapability.MimeType, needle.RTPCodecCapability.MimeType) {
|
||||
return c, codecMatchPartial
|
||||
}
|
||||
}
|
||||
|
||||
return RTPCodecParameters{}, codecMatchNone
|
||||
}
|
||||
func rtcpFeedbackIntersection(a, b []RTCPFeedback) (out []RTCPFeedback) {
|
||||
for _, aFeedback := range a {
|
||||
for _, bFeeback := range b {
|
||||
if aFeedback.Type == bFeeback.Type && aFeedback.Parameter == bFeeback.Parameter {
|
||||
out = append(out, aFeedback)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
20
server/vendor/github.com/pion/webrtc/v3/rtpcodingparameters.go
generated
vendored
Normal file
20
server/vendor/github.com/pion/webrtc/v3/rtpcodingparameters.go
generated
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package webrtc
|
||||
|
||||
// RTPRtxParameters dictionary contains information relating to retransmission (RTX) settings.
|
||||
// https://draft.ortc.org/#dom-rtcrtprtxparameters
|
||||
type RTPRtxParameters struct {
|
||||
SSRC SSRC `json:"ssrc"`
|
||||
}
|
||||
|
||||
// RTPCodingParameters provides information relating to both encoding and decoding.
|
||||
// This is a subset of the RFC since Pion WebRTC doesn't implement encoding/decoding itself
|
||||
// http://draft.ortc.org/#dom-rtcrtpcodingparameters
|
||||
type RTPCodingParameters struct {
|
||||
RID string `json:"rid"`
|
||||
SSRC SSRC `json:"ssrc"`
|
||||
PayloadType PayloadType `json:"payloadType"`
|
||||
RTX RTPRtxParameters `json:"rtx"`
|
||||
}
|
||||
11
server/vendor/github.com/pion/webrtc/v3/rtpdecodingparameters.go
generated
vendored
Normal file
11
server/vendor/github.com/pion/webrtc/v3/rtpdecodingparameters.go
generated
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package webrtc
|
||||
|
||||
// RTPDecodingParameters provides information relating to both encoding and decoding.
|
||||
// This is a subset of the RFC since Pion WebRTC doesn't implement decoding itself
|
||||
// http://draft.ortc.org/#dom-rtcrtpdecodingparameters
|
||||
type RTPDecodingParameters struct {
|
||||
RTPCodingParameters
|
||||
}
|
||||
11
server/vendor/github.com/pion/webrtc/v3/rtpencodingparameters.go
generated
vendored
Normal file
11
server/vendor/github.com/pion/webrtc/v3/rtpencodingparameters.go
generated
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package webrtc
|
||||
|
||||
// RTPEncodingParameters provides information relating to both encoding and decoding.
|
||||
// This is a subset of the RFC since Pion WebRTC doesn't implement encoding itself
|
||||
// http://draft.ortc.org/#dom-rtcrtpencodingparameters
|
||||
type RTPEncodingParameters struct {
|
||||
RTPCodingParameters
|
||||
}
|
||||
9
server/vendor/github.com/pion/webrtc/v3/rtpreceiveparameters.go
generated
vendored
Normal file
9
server/vendor/github.com/pion/webrtc/v3/rtpreceiveparameters.go
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package webrtc
|
||||
|
||||
// RTPReceiveParameters contains the RTP stack settings used by receivers
|
||||
type RTPReceiveParameters struct {
|
||||
Encodings []RTPDecodingParameters
|
||||
}
|
||||
538
server/vendor/github.com/pion/webrtc/v3/rtpreceiver.go
generated
vendored
Normal file
538
server/vendor/github.com/pion/webrtc/v3/rtpreceiver.go
generated
vendored
Normal file
@@ -0,0 +1,538 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
//go:build !js
|
||||
// +build !js
|
||||
|
||||
package webrtc
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/pion/interceptor"
|
||||
"github.com/pion/rtcp"
|
||||
"github.com/pion/srtp/v2"
|
||||
"github.com/pion/webrtc/v3/internal/util"
|
||||
)
|
||||
|
||||
// trackStreams maintains a mapping of RTP/RTCP streams to a specific track
|
||||
// a RTPReceiver may contain multiple streams if we are dealing with Simulcast
|
||||
type trackStreams struct {
|
||||
track *TrackRemote
|
||||
|
||||
streamInfo, repairStreamInfo *interceptor.StreamInfo
|
||||
|
||||
rtpReadStream *srtp.ReadStreamSRTP
|
||||
rtpInterceptor interceptor.RTPReader
|
||||
|
||||
rtcpReadStream *srtp.ReadStreamSRTCP
|
||||
rtcpInterceptor interceptor.RTCPReader
|
||||
|
||||
repairReadStream *srtp.ReadStreamSRTP
|
||||
repairInterceptor interceptor.RTPReader
|
||||
repairStreamChannel chan rtxPacketWithAttributes
|
||||
|
||||
repairRtcpReadStream *srtp.ReadStreamSRTCP
|
||||
repairRtcpInterceptor interceptor.RTCPReader
|
||||
}
|
||||
|
||||
type rtxPacketWithAttributes struct {
|
||||
pkt []byte
|
||||
attributes interceptor.Attributes
|
||||
pool *sync.Pool
|
||||
}
|
||||
|
||||
func (p *rtxPacketWithAttributes) release() {
|
||||
if p.pkt != nil {
|
||||
b := p.pkt[:cap(p.pkt)]
|
||||
p.pool.Put(b) // nolint:staticcheck
|
||||
p.pkt = nil
|
||||
}
|
||||
}
|
||||
|
||||
// RTPReceiver allows an application to inspect the receipt of a TrackRemote
|
||||
type RTPReceiver struct {
|
||||
kind RTPCodecType
|
||||
transport *DTLSTransport
|
||||
|
||||
tracks []trackStreams
|
||||
|
||||
closed, received chan interface{}
|
||||
mu sync.RWMutex
|
||||
|
||||
tr *RTPTransceiver
|
||||
|
||||
// A reference to the associated api object
|
||||
api *API
|
||||
|
||||
rtxPool sync.Pool
|
||||
}
|
||||
|
||||
// NewRTPReceiver constructs a new RTPReceiver
|
||||
func (api *API) NewRTPReceiver(kind RTPCodecType, transport *DTLSTransport) (*RTPReceiver, error) {
|
||||
if transport == nil {
|
||||
return nil, errRTPReceiverDTLSTransportNil
|
||||
}
|
||||
|
||||
r := &RTPReceiver{
|
||||
kind: kind,
|
||||
transport: transport,
|
||||
api: api,
|
||||
closed: make(chan interface{}),
|
||||
received: make(chan interface{}),
|
||||
tracks: []trackStreams{},
|
||||
rtxPool: sync.Pool{New: func() interface{} {
|
||||
return make([]byte, api.settingEngine.getReceiveMTU())
|
||||
}},
|
||||
}
|
||||
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func (r *RTPReceiver) setRTPTransceiver(tr *RTPTransceiver) {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
r.tr = tr
|
||||
}
|
||||
|
||||
// Transport returns the currently-configured *DTLSTransport or nil
|
||||
// if one has not yet been configured
|
||||
func (r *RTPReceiver) Transport() *DTLSTransport {
|
||||
r.mu.RLock()
|
||||
defer r.mu.RUnlock()
|
||||
return r.transport
|
||||
}
|
||||
|
||||
func (r *RTPReceiver) getParameters() RTPParameters {
|
||||
parameters := r.api.mediaEngine.getRTPParametersByKind(r.kind, []RTPTransceiverDirection{RTPTransceiverDirectionRecvonly})
|
||||
if r.tr != nil {
|
||||
parameters.Codecs = r.tr.getCodecs()
|
||||
}
|
||||
return parameters
|
||||
}
|
||||
|
||||
// GetParameters describes the current configuration for the encoding and
|
||||
// transmission of media on the receiver's track.
|
||||
func (r *RTPReceiver) GetParameters() RTPParameters {
|
||||
r.mu.RLock()
|
||||
defer r.mu.RUnlock()
|
||||
return r.getParameters()
|
||||
}
|
||||
|
||||
// Track returns the RtpTransceiver TrackRemote
|
||||
func (r *RTPReceiver) Track() *TrackRemote {
|
||||
r.mu.RLock()
|
||||
defer r.mu.RUnlock()
|
||||
|
||||
if len(r.tracks) != 1 {
|
||||
return nil
|
||||
}
|
||||
return r.tracks[0].track
|
||||
}
|
||||
|
||||
// Tracks returns the RtpTransceiver tracks
|
||||
// A RTPReceiver to support Simulcast may now have multiple tracks
|
||||
func (r *RTPReceiver) Tracks() []*TrackRemote {
|
||||
r.mu.RLock()
|
||||
defer r.mu.RUnlock()
|
||||
|
||||
var tracks []*TrackRemote
|
||||
for i := range r.tracks {
|
||||
tracks = append(tracks, r.tracks[i].track)
|
||||
}
|
||||
return tracks
|
||||
}
|
||||
|
||||
// RTPTransceiver returns the RTPTransceiver this
|
||||
// RTPReceiver belongs too, or nil if none
|
||||
func (r *RTPReceiver) RTPTransceiver() *RTPTransceiver {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
|
||||
return r.tr
|
||||
}
|
||||
|
||||
// configureReceive initialize the track
|
||||
func (r *RTPReceiver) configureReceive(parameters RTPReceiveParameters) {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
|
||||
for i := range parameters.Encodings {
|
||||
t := trackStreams{
|
||||
track: newTrackRemote(
|
||||
r.kind,
|
||||
parameters.Encodings[i].SSRC,
|
||||
parameters.Encodings[i].RTX.SSRC,
|
||||
parameters.Encodings[i].RID,
|
||||
r,
|
||||
),
|
||||
}
|
||||
|
||||
r.tracks = append(r.tracks, t)
|
||||
}
|
||||
}
|
||||
|
||||
// startReceive starts all the transports
|
||||
func (r *RTPReceiver) startReceive(parameters RTPReceiveParameters) error {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
select {
|
||||
case <-r.received:
|
||||
return errRTPReceiverReceiveAlreadyCalled
|
||||
default:
|
||||
}
|
||||
defer close(r.received)
|
||||
|
||||
globalParams := r.getParameters()
|
||||
codec := RTPCodecCapability{}
|
||||
if len(globalParams.Codecs) != 0 {
|
||||
codec = globalParams.Codecs[0].RTPCodecCapability
|
||||
}
|
||||
|
||||
for i := range parameters.Encodings {
|
||||
if parameters.Encodings[i].RID != "" {
|
||||
// RID based tracks will be set up in receiveForRid
|
||||
continue
|
||||
}
|
||||
|
||||
var t *trackStreams
|
||||
for idx, ts := range r.tracks {
|
||||
if ts.track != nil && ts.track.SSRC() == parameters.Encodings[i].SSRC {
|
||||
t = &r.tracks[idx]
|
||||
break
|
||||
}
|
||||
}
|
||||
if t == nil {
|
||||
return fmt.Errorf("%w: %d", errRTPReceiverWithSSRCTrackStreamNotFound, parameters.Encodings[i].SSRC)
|
||||
}
|
||||
|
||||
t.streamInfo = createStreamInfo("", parameters.Encodings[i].SSRC, 0, codec, globalParams.HeaderExtensions)
|
||||
var err error
|
||||
if t.rtpReadStream, t.rtpInterceptor, t.rtcpReadStream, t.rtcpInterceptor, err = r.transport.streamsForSSRC(parameters.Encodings[i].SSRC, *t.streamInfo); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if rtxSsrc := parameters.Encodings[i].RTX.SSRC; rtxSsrc != 0 {
|
||||
streamInfo := createStreamInfo("", rtxSsrc, 0, codec, globalParams.HeaderExtensions)
|
||||
rtpReadStream, rtpInterceptor, rtcpReadStream, rtcpInterceptor, err := r.transport.streamsForSSRC(rtxSsrc, *streamInfo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = r.receiveForRtx(rtxSsrc, "", streamInfo, rtpReadStream, rtpInterceptor, rtcpReadStream, rtcpInterceptor); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Receive initialize the track and starts all the transports
|
||||
func (r *RTPReceiver) Receive(parameters RTPReceiveParameters) error {
|
||||
r.configureReceive(parameters)
|
||||
return r.startReceive(parameters)
|
||||
}
|
||||
|
||||
// Read reads incoming RTCP for this RTPReceiver
|
||||
func (r *RTPReceiver) Read(b []byte) (n int, a interceptor.Attributes, err error) {
|
||||
select {
|
||||
case <-r.received:
|
||||
return r.tracks[0].rtcpInterceptor.Read(b, a)
|
||||
case <-r.closed:
|
||||
return 0, nil, io.ErrClosedPipe
|
||||
}
|
||||
}
|
||||
|
||||
// ReadSimulcast reads incoming RTCP for this RTPReceiver for given rid
|
||||
func (r *RTPReceiver) ReadSimulcast(b []byte, rid string) (n int, a interceptor.Attributes, err error) {
|
||||
select {
|
||||
case <-r.received:
|
||||
for _, t := range r.tracks {
|
||||
if t.track != nil && t.track.rid == rid {
|
||||
return t.rtcpInterceptor.Read(b, a)
|
||||
}
|
||||
}
|
||||
return 0, nil, fmt.Errorf("%w: %s", errRTPReceiverForRIDTrackStreamNotFound, rid)
|
||||
case <-r.closed:
|
||||
return 0, nil, io.ErrClosedPipe
|
||||
}
|
||||
}
|
||||
|
||||
// ReadRTCP is a convenience method that wraps Read and unmarshal for you.
|
||||
// It also runs any configured interceptors.
|
||||
func (r *RTPReceiver) ReadRTCP() ([]rtcp.Packet, interceptor.Attributes, error) {
|
||||
b := make([]byte, r.api.settingEngine.getReceiveMTU())
|
||||
i, attributes, err := r.Read(b)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
pkts, err := rtcp.Unmarshal(b[:i])
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return pkts, attributes, nil
|
||||
}
|
||||
|
||||
// ReadSimulcastRTCP is a convenience method that wraps ReadSimulcast and unmarshal for you
|
||||
func (r *RTPReceiver) ReadSimulcastRTCP(rid string) ([]rtcp.Packet, interceptor.Attributes, error) {
|
||||
b := make([]byte, r.api.settingEngine.getReceiveMTU())
|
||||
i, attributes, err := r.ReadSimulcast(b, rid)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
pkts, err := rtcp.Unmarshal(b[:i])
|
||||
return pkts, attributes, err
|
||||
}
|
||||
|
||||
func (r *RTPReceiver) haveReceived() bool {
|
||||
select {
|
||||
case <-r.received:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Stop irreversibly stops the RTPReceiver
|
||||
func (r *RTPReceiver) Stop() error {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
var err error
|
||||
|
||||
select {
|
||||
case <-r.closed:
|
||||
return err
|
||||
default:
|
||||
}
|
||||
|
||||
select {
|
||||
case <-r.received:
|
||||
for i := range r.tracks {
|
||||
errs := []error{}
|
||||
|
||||
if r.tracks[i].rtcpReadStream != nil {
|
||||
errs = append(errs, r.tracks[i].rtcpReadStream.Close())
|
||||
}
|
||||
|
||||
if r.tracks[i].rtpReadStream != nil {
|
||||
errs = append(errs, r.tracks[i].rtpReadStream.Close())
|
||||
}
|
||||
|
||||
if r.tracks[i].repairReadStream != nil {
|
||||
errs = append(errs, r.tracks[i].repairReadStream.Close())
|
||||
}
|
||||
|
||||
if r.tracks[i].repairRtcpReadStream != nil {
|
||||
errs = append(errs, r.tracks[i].repairRtcpReadStream.Close())
|
||||
}
|
||||
|
||||
if r.tracks[i].streamInfo != nil {
|
||||
r.api.interceptor.UnbindRemoteStream(r.tracks[i].streamInfo)
|
||||
}
|
||||
|
||||
if r.tracks[i].repairStreamInfo != nil {
|
||||
r.api.interceptor.UnbindRemoteStream(r.tracks[i].repairStreamInfo)
|
||||
}
|
||||
|
||||
err = util.FlattenErrs(errs)
|
||||
}
|
||||
default:
|
||||
}
|
||||
|
||||
close(r.closed)
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *RTPReceiver) streamsForTrack(t *TrackRemote) *trackStreams {
|
||||
for i := range r.tracks {
|
||||
if r.tracks[i].track == t {
|
||||
return &r.tracks[i]
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// readRTP should only be called by a track, this only exists so we can keep state in one place
|
||||
func (r *RTPReceiver) readRTP(b []byte, reader *TrackRemote) (n int, a interceptor.Attributes, err error) {
|
||||
<-r.received
|
||||
if t := r.streamsForTrack(reader); t != nil {
|
||||
return t.rtpInterceptor.Read(b, a)
|
||||
}
|
||||
|
||||
return 0, nil, fmt.Errorf("%w: %d", errRTPReceiverWithSSRCTrackStreamNotFound, reader.SSRC())
|
||||
}
|
||||
|
||||
// receiveForRid is the sibling of Receive expect for RIDs instead of SSRCs
|
||||
// It populates all the internal state for the given RID
|
||||
func (r *RTPReceiver) receiveForRid(rid string, params RTPParameters, streamInfo *interceptor.StreamInfo, rtpReadStream *srtp.ReadStreamSRTP, rtpInterceptor interceptor.RTPReader, rtcpReadStream *srtp.ReadStreamSRTCP, rtcpInterceptor interceptor.RTCPReader) (*TrackRemote, error) {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
|
||||
for i := range r.tracks {
|
||||
if r.tracks[i].track.RID() == rid {
|
||||
r.tracks[i].track.mu.Lock()
|
||||
r.tracks[i].track.kind = r.kind
|
||||
r.tracks[i].track.codec = params.Codecs[0]
|
||||
r.tracks[i].track.params = params
|
||||
r.tracks[i].track.ssrc = SSRC(streamInfo.SSRC)
|
||||
r.tracks[i].track.mu.Unlock()
|
||||
|
||||
r.tracks[i].streamInfo = streamInfo
|
||||
r.tracks[i].rtpReadStream = rtpReadStream
|
||||
r.tracks[i].rtpInterceptor = rtpInterceptor
|
||||
r.tracks[i].rtcpReadStream = rtcpReadStream
|
||||
r.tracks[i].rtcpInterceptor = rtcpInterceptor
|
||||
|
||||
return r.tracks[i].track, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("%w: %s", errRTPReceiverForRIDTrackStreamNotFound, rid)
|
||||
}
|
||||
|
||||
// receiveForRtx starts a routine that processes the repair stream
|
||||
func (r *RTPReceiver) receiveForRtx(ssrc SSRC, rsid string, streamInfo *interceptor.StreamInfo, rtpReadStream *srtp.ReadStreamSRTP, rtpInterceptor interceptor.RTPReader, rtcpReadStream *srtp.ReadStreamSRTCP, rtcpInterceptor interceptor.RTCPReader) error {
|
||||
var track *trackStreams
|
||||
if ssrc != 0 && len(r.tracks) == 1 {
|
||||
track = &r.tracks[0]
|
||||
} else {
|
||||
for i := range r.tracks {
|
||||
if r.tracks[i].track.RID() == rsid {
|
||||
track = &r.tracks[i]
|
||||
if track.track.RtxSSRC() == 0 {
|
||||
track.track.setRtxSSRC(SSRC(streamInfo.SSRC))
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if track == nil {
|
||||
return fmt.Errorf("%w: ssrc(%d) rsid(%s)", errRTPReceiverForRIDTrackStreamNotFound, ssrc, rsid)
|
||||
}
|
||||
|
||||
track.repairStreamInfo = streamInfo
|
||||
track.repairReadStream = rtpReadStream
|
||||
track.repairInterceptor = rtpInterceptor
|
||||
track.repairRtcpReadStream = rtcpReadStream
|
||||
track.repairRtcpInterceptor = rtcpInterceptor
|
||||
track.repairStreamChannel = make(chan rtxPacketWithAttributes, 50)
|
||||
|
||||
go func() {
|
||||
for {
|
||||
b := r.rtxPool.Get().([]byte) // nolint:forcetypeassert
|
||||
i, attributes, err := track.repairInterceptor.Read(b, nil)
|
||||
if err != nil {
|
||||
r.rtxPool.Put(b) // nolint:staticcheck
|
||||
return
|
||||
}
|
||||
|
||||
// RTX packets have a different payload format. Move the OSN in the payload to the RTP header and rewrite the
|
||||
// payload type and SSRC, so that we can return RTX packets to the caller 'transparently' i.e. in the same format
|
||||
// as non-RTX RTP packets
|
||||
hasExtension := b[0]&0b10000 > 0
|
||||
hasPadding := b[0]&0b100000 > 0
|
||||
csrcCount := b[0] & 0b1111
|
||||
headerLength := uint16(12 + (4 * csrcCount))
|
||||
paddingLength := 0
|
||||
if hasExtension {
|
||||
headerLength += 4 * (1 + binary.BigEndian.Uint16(b[headerLength+2:headerLength+4]))
|
||||
}
|
||||
if hasPadding {
|
||||
paddingLength = int(b[i-1])
|
||||
}
|
||||
|
||||
if i-int(headerLength)-paddingLength < 2 {
|
||||
// BWE probe packet, ignore
|
||||
r.rtxPool.Put(b) // nolint:staticcheck
|
||||
continue
|
||||
}
|
||||
|
||||
if attributes == nil {
|
||||
attributes = make(interceptor.Attributes)
|
||||
}
|
||||
attributes.Set(AttributeRtxPayloadType, b[1]&0x7F)
|
||||
attributes.Set(AttributeRtxSequenceNumber, binary.BigEndian.Uint16(b[2:4]))
|
||||
attributes.Set(AttributeRtxSsrc, binary.BigEndian.Uint32(b[8:12]))
|
||||
|
||||
b[1] = (b[1] & 0x80) | uint8(track.track.PayloadType())
|
||||
b[2] = b[headerLength]
|
||||
b[3] = b[headerLength+1]
|
||||
binary.BigEndian.PutUint32(b[8:12], uint32(track.track.SSRC()))
|
||||
copy(b[headerLength:i-2], b[headerLength+2:i])
|
||||
|
||||
select {
|
||||
case <-r.closed:
|
||||
r.rtxPool.Put(b) // nolint:staticcheck
|
||||
return
|
||||
case track.repairStreamChannel <- rtxPacketWithAttributes{pkt: b[:i-2], attributes: attributes, pool: &r.rtxPool}:
|
||||
default:
|
||||
// skip the RTX packet if the repair stream channel is full, could be blocked in the application's read loop
|
||||
}
|
||||
}
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetReadDeadline sets the max amount of time the RTCP stream will block before returning. 0 is forever.
|
||||
func (r *RTPReceiver) SetReadDeadline(t time.Time) error {
|
||||
r.mu.RLock()
|
||||
defer r.mu.RUnlock()
|
||||
|
||||
return r.tracks[0].rtcpReadStream.SetReadDeadline(t)
|
||||
}
|
||||
|
||||
// SetReadDeadlineSimulcast sets the max amount of time the RTCP stream for a given rid will block before returning. 0 is forever.
|
||||
func (r *RTPReceiver) SetReadDeadlineSimulcast(deadline time.Time, rid string) error {
|
||||
r.mu.RLock()
|
||||
defer r.mu.RUnlock()
|
||||
|
||||
for _, t := range r.tracks {
|
||||
if t.track != nil && t.track.rid == rid {
|
||||
return t.rtcpReadStream.SetReadDeadline(deadline)
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("%w: %s", errRTPReceiverForRIDTrackStreamNotFound, rid)
|
||||
}
|
||||
|
||||
// setRTPReadDeadline sets the max amount of time the RTP stream will block before returning. 0 is forever.
|
||||
// This should be fired by calling SetReadDeadline on the TrackRemote
|
||||
func (r *RTPReceiver) setRTPReadDeadline(deadline time.Time, reader *TrackRemote) error {
|
||||
r.mu.RLock()
|
||||
defer r.mu.RUnlock()
|
||||
|
||||
if t := r.streamsForTrack(reader); t != nil {
|
||||
return t.rtpReadStream.SetReadDeadline(deadline)
|
||||
}
|
||||
return fmt.Errorf("%w: %d", errRTPReceiverWithSSRCTrackStreamNotFound, reader.SSRC())
|
||||
}
|
||||
|
||||
// readRTX returns an RTX packet if one is available on the RTX track, otherwise returns nil
|
||||
func (r *RTPReceiver) readRTX(reader *TrackRemote) *rtxPacketWithAttributes {
|
||||
if !reader.HasRTX() {
|
||||
return nil
|
||||
}
|
||||
|
||||
select {
|
||||
case <-r.received:
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
|
||||
if t := r.streamsForTrack(reader); t != nil {
|
||||
select {
|
||||
case rtxPacketReceived := <-t.repairStreamChannel:
|
||||
return &rtxPacketReceived
|
||||
default:
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
36
server/vendor/github.com/pion/webrtc/v3/rtpreceiver_go.go
generated
vendored
Normal file
36
server/vendor/github.com/pion/webrtc/v3/rtpreceiver_go.go
generated
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
//go:build !js
|
||||
// +build !js
|
||||
|
||||
package webrtc
|
||||
|
||||
import "github.com/pion/interceptor"
|
||||
|
||||
// SetRTPParameters applies provided RTPParameters the RTPReceiver's tracks.
|
||||
//
|
||||
// This method is part of the ORTC API. It is not
|
||||
// meant to be used together with the basic WebRTC API.
|
||||
//
|
||||
// The amount of provided codecs must match the number of tracks on the receiver.
|
||||
func (r *RTPReceiver) SetRTPParameters(params RTPParameters) {
|
||||
headerExtensions := make([]interceptor.RTPHeaderExtension, 0, len(params.HeaderExtensions))
|
||||
for _, h := range params.HeaderExtensions {
|
||||
headerExtensions = append(headerExtensions, interceptor.RTPHeaderExtension{ID: h.ID, URI: h.URI})
|
||||
}
|
||||
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
|
||||
for ndx, codec := range params.Codecs {
|
||||
currentTrack := r.tracks[ndx].track
|
||||
|
||||
r.tracks[ndx].streamInfo.RTPHeaderExtensions = headerExtensions
|
||||
|
||||
currentTrack.mu.Lock()
|
||||
currentTrack.codec = codec
|
||||
currentTrack.params = params
|
||||
currentTrack.mu.Unlock()
|
||||
}
|
||||
}
|
||||
15
server/vendor/github.com/pion/webrtc/v3/rtpreceiver_js.go
generated
vendored
Normal file
15
server/vendor/github.com/pion/webrtc/v3/rtpreceiver_js.go
generated
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
//go:build js && wasm
|
||||
// +build js,wasm
|
||||
|
||||
package webrtc
|
||||
|
||||
import "syscall/js"
|
||||
|
||||
// RTPReceiver allows an application to inspect the receipt of a TrackRemote
|
||||
type RTPReceiver struct {
|
||||
// Pointer to the underlying JavaScript RTCRTPReceiver object.
|
||||
underlying js.Value
|
||||
}
|
||||
460
server/vendor/github.com/pion/webrtc/v3/rtpsender.go
generated
vendored
Normal file
460
server/vendor/github.com/pion/webrtc/v3/rtpsender.go
generated
vendored
Normal file
@@ -0,0 +1,460 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
//go:build !js
|
||||
// +build !js
|
||||
|
||||
package webrtc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/pion/interceptor"
|
||||
"github.com/pion/randutil"
|
||||
"github.com/pion/rtcp"
|
||||
"github.com/pion/rtp"
|
||||
"github.com/pion/webrtc/v3/internal/util"
|
||||
)
|
||||
|
||||
type trackEncoding struct {
|
||||
track TrackLocal
|
||||
|
||||
srtpStream *srtpWriterFuture
|
||||
|
||||
rtcpInterceptor interceptor.RTCPReader
|
||||
streamInfo interceptor.StreamInfo
|
||||
|
||||
context *baseTrackLocalContext
|
||||
|
||||
ssrc SSRC
|
||||
}
|
||||
|
||||
// RTPSender allows an application to control how a given Track is encoded and transmitted to a remote peer
|
||||
type RTPSender struct {
|
||||
trackEncodings []*trackEncoding
|
||||
|
||||
transport *DTLSTransport
|
||||
|
||||
payloadType PayloadType
|
||||
kind RTPCodecType
|
||||
|
||||
// nolint:godox
|
||||
// TODO(sgotti) remove this when in future we'll avoid replacing
|
||||
// a transceiver sender since we can just check the
|
||||
// transceiver negotiation status
|
||||
negotiated bool
|
||||
|
||||
// A reference to the associated api object
|
||||
api *API
|
||||
id string
|
||||
|
||||
rtpTransceiver *RTPTransceiver
|
||||
|
||||
mu sync.RWMutex
|
||||
sendCalled, stopCalled chan struct{}
|
||||
}
|
||||
|
||||
// NewRTPSender constructs a new RTPSender
|
||||
func (api *API) NewRTPSender(track TrackLocal, transport *DTLSTransport) (*RTPSender, error) {
|
||||
if track == nil {
|
||||
return nil, errRTPSenderTrackNil
|
||||
} else if transport == nil {
|
||||
return nil, errRTPSenderDTLSTransportNil
|
||||
}
|
||||
|
||||
id, err := randutil.GenerateCryptoRandomString(32, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
r := &RTPSender{
|
||||
transport: transport,
|
||||
api: api,
|
||||
sendCalled: make(chan struct{}),
|
||||
stopCalled: make(chan struct{}),
|
||||
id: id,
|
||||
kind: track.Kind(),
|
||||
}
|
||||
|
||||
r.addEncoding(track)
|
||||
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func (r *RTPSender) isNegotiated() bool {
|
||||
r.mu.RLock()
|
||||
defer r.mu.RUnlock()
|
||||
return r.negotiated
|
||||
}
|
||||
|
||||
func (r *RTPSender) setNegotiated() {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
r.negotiated = true
|
||||
}
|
||||
|
||||
func (r *RTPSender) setRTPTransceiver(rtpTransceiver *RTPTransceiver) {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
r.rtpTransceiver = rtpTransceiver
|
||||
}
|
||||
|
||||
// Transport returns the currently-configured *DTLSTransport or nil
|
||||
// if one has not yet been configured
|
||||
func (r *RTPSender) Transport() *DTLSTransport {
|
||||
r.mu.RLock()
|
||||
defer r.mu.RUnlock()
|
||||
return r.transport
|
||||
}
|
||||
|
||||
func (r *RTPSender) getParameters() RTPSendParameters {
|
||||
var encodings []RTPEncodingParameters
|
||||
for _, trackEncoding := range r.trackEncodings {
|
||||
var rid string
|
||||
if trackEncoding.track != nil {
|
||||
rid = trackEncoding.track.RID()
|
||||
}
|
||||
encodings = append(encodings, RTPEncodingParameters{
|
||||
RTPCodingParameters: RTPCodingParameters{
|
||||
RID: rid,
|
||||
SSRC: trackEncoding.ssrc,
|
||||
PayloadType: r.payloadType,
|
||||
},
|
||||
})
|
||||
}
|
||||
sendParameters := RTPSendParameters{
|
||||
RTPParameters: r.api.mediaEngine.getRTPParametersByKind(
|
||||
r.kind,
|
||||
[]RTPTransceiverDirection{RTPTransceiverDirectionSendonly},
|
||||
),
|
||||
Encodings: encodings,
|
||||
}
|
||||
if r.rtpTransceiver != nil {
|
||||
sendParameters.Codecs = r.rtpTransceiver.getCodecs()
|
||||
} else {
|
||||
sendParameters.Codecs = r.api.mediaEngine.getCodecsByKind(r.kind)
|
||||
}
|
||||
return sendParameters
|
||||
}
|
||||
|
||||
// GetParameters describes the current configuration for the encoding and
|
||||
// transmission of media on the sender's track.
|
||||
func (r *RTPSender) GetParameters() RTPSendParameters {
|
||||
r.mu.RLock()
|
||||
defer r.mu.RUnlock()
|
||||
return r.getParameters()
|
||||
}
|
||||
|
||||
// AddEncoding adds an encoding to RTPSender. Used by simulcast senders.
|
||||
func (r *RTPSender) AddEncoding(track TrackLocal) error {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
|
||||
if track == nil {
|
||||
return errRTPSenderTrackNil
|
||||
}
|
||||
|
||||
if track.RID() == "" {
|
||||
return errRTPSenderRidNil
|
||||
}
|
||||
|
||||
if r.hasStopped() {
|
||||
return errRTPSenderStopped
|
||||
}
|
||||
|
||||
if r.hasSent() {
|
||||
return errRTPSenderSendAlreadyCalled
|
||||
}
|
||||
|
||||
var refTrack TrackLocal
|
||||
if len(r.trackEncodings) != 0 {
|
||||
refTrack = r.trackEncodings[0].track
|
||||
}
|
||||
if refTrack == nil || refTrack.RID() == "" {
|
||||
return errRTPSenderNoBaseEncoding
|
||||
}
|
||||
|
||||
if refTrack.ID() != track.ID() || refTrack.StreamID() != track.StreamID() || refTrack.Kind() != track.Kind() {
|
||||
return errRTPSenderBaseEncodingMismatch
|
||||
}
|
||||
|
||||
for _, encoding := range r.trackEncodings {
|
||||
if encoding.track == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if encoding.track.RID() == track.RID() {
|
||||
return errRTPSenderRIDCollision
|
||||
}
|
||||
}
|
||||
|
||||
r.addEncoding(track)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *RTPSender) addEncoding(track TrackLocal) {
|
||||
ssrc := SSRC(randutil.NewMathRandomGenerator().Uint32())
|
||||
trackEncoding := &trackEncoding{
|
||||
track: track,
|
||||
srtpStream: &srtpWriterFuture{ssrc: ssrc},
|
||||
ssrc: ssrc,
|
||||
}
|
||||
trackEncoding.srtpStream.rtpSender = r
|
||||
trackEncoding.rtcpInterceptor = r.api.interceptor.BindRTCPReader(
|
||||
interceptor.RTCPReaderFunc(func(in []byte, a interceptor.Attributes) (n int, attributes interceptor.Attributes, err error) {
|
||||
n, err = trackEncoding.srtpStream.Read(in)
|
||||
return n, a, err
|
||||
}),
|
||||
)
|
||||
|
||||
r.trackEncodings = append(r.trackEncodings, trackEncoding)
|
||||
}
|
||||
|
||||
// Track returns the RTCRtpTransceiver track, or nil
|
||||
func (r *RTPSender) Track() TrackLocal {
|
||||
r.mu.RLock()
|
||||
defer r.mu.RUnlock()
|
||||
|
||||
if len(r.trackEncodings) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return r.trackEncodings[0].track
|
||||
}
|
||||
|
||||
// ReplaceTrack replaces the track currently being used as the sender's source with a new TrackLocal.
|
||||
// The new track must be of the same media kind (audio, video, etc) and switching the track should not
|
||||
// require negotiation.
|
||||
func (r *RTPSender) ReplaceTrack(track TrackLocal) error {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
|
||||
if track != nil && r.kind != track.Kind() {
|
||||
return ErrRTPSenderNewTrackHasIncorrectKind
|
||||
}
|
||||
|
||||
// cannot replace simulcast envelope
|
||||
if track != nil && len(r.trackEncodings) > 1 {
|
||||
return ErrRTPSenderNewTrackHasIncorrectEnvelope
|
||||
}
|
||||
|
||||
var replacedTrack TrackLocal
|
||||
var context *baseTrackLocalContext
|
||||
for _, e := range r.trackEncodings {
|
||||
replacedTrack = e.track
|
||||
context = e.context
|
||||
|
||||
if r.hasSent() && replacedTrack != nil {
|
||||
if err := replacedTrack.Unbind(context); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if !r.hasSent() || track == nil {
|
||||
e.track = track
|
||||
}
|
||||
}
|
||||
|
||||
if !r.hasSent() || track == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// If we reach this point in the routine, there is only 1 track encoding
|
||||
codec, err := track.Bind(&baseTrackLocalContext{
|
||||
id: context.ID(),
|
||||
params: r.api.mediaEngine.getRTPParametersByKind(track.Kind(), []RTPTransceiverDirection{RTPTransceiverDirectionSendonly}),
|
||||
ssrc: context.SSRC(),
|
||||
writeStream: context.WriteStream(),
|
||||
rtcpInterceptor: context.RTCPReader(),
|
||||
})
|
||||
if err != nil {
|
||||
// Re-bind the original track
|
||||
if _, reBindErr := replacedTrack.Bind(context); reBindErr != nil {
|
||||
return reBindErr
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// Codec has changed
|
||||
if r.payloadType != codec.PayloadType {
|
||||
context.params.Codecs = []RTPCodecParameters{codec}
|
||||
}
|
||||
|
||||
r.trackEncodings[0].track = track
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Send Attempts to set the parameters controlling the sending of media.
|
||||
func (r *RTPSender) Send(parameters RTPSendParameters) error {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
|
||||
switch {
|
||||
case r.hasSent():
|
||||
return errRTPSenderSendAlreadyCalled
|
||||
case r.trackEncodings[0].track == nil:
|
||||
return errRTPSenderTrackRemoved
|
||||
}
|
||||
|
||||
for idx, trackEncoding := range r.trackEncodings {
|
||||
writeStream := &interceptorToTrackLocalWriter{}
|
||||
trackEncoding.context = &baseTrackLocalContext{
|
||||
id: r.id,
|
||||
params: r.api.mediaEngine.getRTPParametersByKind(trackEncoding.track.Kind(), []RTPTransceiverDirection{RTPTransceiverDirectionSendonly}),
|
||||
ssrc: parameters.Encodings[idx].SSRC,
|
||||
writeStream: writeStream,
|
||||
rtcpInterceptor: trackEncoding.rtcpInterceptor,
|
||||
}
|
||||
|
||||
codec, err := trackEncoding.track.Bind(trackEncoding.context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
trackEncoding.context.params.Codecs = []RTPCodecParameters{codec}
|
||||
|
||||
trackEncoding.streamInfo = *createStreamInfo(
|
||||
r.id,
|
||||
parameters.Encodings[idx].SSRC,
|
||||
codec.PayloadType,
|
||||
codec.RTPCodecCapability,
|
||||
parameters.HeaderExtensions,
|
||||
)
|
||||
srtpStream := trackEncoding.srtpStream
|
||||
rtpInterceptor := r.api.interceptor.BindLocalStream(
|
||||
&trackEncoding.streamInfo,
|
||||
interceptor.RTPWriterFunc(func(header *rtp.Header, payload []byte, attributes interceptor.Attributes) (int, error) {
|
||||
return srtpStream.WriteRTP(header, payload)
|
||||
}),
|
||||
)
|
||||
writeStream.interceptor.Store(rtpInterceptor)
|
||||
}
|
||||
|
||||
close(r.sendCalled)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stop irreversibly stops the RTPSender
|
||||
func (r *RTPSender) Stop() error {
|
||||
r.mu.Lock()
|
||||
|
||||
if stopped := r.hasStopped(); stopped {
|
||||
r.mu.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
close(r.stopCalled)
|
||||
r.mu.Unlock()
|
||||
|
||||
if !r.hasSent() {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := r.ReplaceTrack(nil); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
errs := []error{}
|
||||
for _, trackEncoding := range r.trackEncodings {
|
||||
r.api.interceptor.UnbindLocalStream(&trackEncoding.streamInfo)
|
||||
errs = append(errs, trackEncoding.srtpStream.Close())
|
||||
}
|
||||
|
||||
return util.FlattenErrs(errs)
|
||||
}
|
||||
|
||||
// Read reads incoming RTCP for this RTPSender
|
||||
func (r *RTPSender) Read(b []byte) (n int, a interceptor.Attributes, err error) {
|
||||
select {
|
||||
case <-r.sendCalled:
|
||||
return r.trackEncodings[0].rtcpInterceptor.Read(b, a)
|
||||
case <-r.stopCalled:
|
||||
return 0, nil, io.ErrClosedPipe
|
||||
}
|
||||
}
|
||||
|
||||
// ReadRTCP is a convenience method that wraps Read and unmarshals for you.
|
||||
func (r *RTPSender) ReadRTCP() ([]rtcp.Packet, interceptor.Attributes, error) {
|
||||
b := make([]byte, r.api.settingEngine.getReceiveMTU())
|
||||
i, attributes, err := r.Read(b)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
pkts, err := rtcp.Unmarshal(b[:i])
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return pkts, attributes, nil
|
||||
}
|
||||
|
||||
// ReadSimulcast reads incoming RTCP for this RTPSender for given rid
|
||||
func (r *RTPSender) ReadSimulcast(b []byte, rid string) (n int, a interceptor.Attributes, err error) {
|
||||
select {
|
||||
case <-r.sendCalled:
|
||||
for _, t := range r.trackEncodings {
|
||||
if t.track != nil && t.track.RID() == rid {
|
||||
return t.rtcpInterceptor.Read(b, a)
|
||||
}
|
||||
}
|
||||
return 0, nil, fmt.Errorf("%w: %s", errRTPSenderNoTrackForRID, rid)
|
||||
case <-r.stopCalled:
|
||||
return 0, nil, io.ErrClosedPipe
|
||||
}
|
||||
}
|
||||
|
||||
// ReadSimulcastRTCP is a convenience method that wraps ReadSimulcast and unmarshal for you
|
||||
func (r *RTPSender) ReadSimulcastRTCP(rid string) ([]rtcp.Packet, interceptor.Attributes, error) {
|
||||
b := make([]byte, r.api.settingEngine.getReceiveMTU())
|
||||
i, attributes, err := r.ReadSimulcast(b, rid)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
pkts, err := rtcp.Unmarshal(b[:i])
|
||||
return pkts, attributes, err
|
||||
}
|
||||
|
||||
// SetReadDeadline sets the deadline for the Read operation.
|
||||
// Setting to zero means no deadline.
|
||||
func (r *RTPSender) SetReadDeadline(t time.Time) error {
|
||||
return r.trackEncodings[0].srtpStream.SetReadDeadline(t)
|
||||
}
|
||||
|
||||
// SetReadDeadlineSimulcast sets the max amount of time the RTCP stream for a given rid will block before returning. 0 is forever.
|
||||
func (r *RTPSender) SetReadDeadlineSimulcast(deadline time.Time, rid string) error {
|
||||
r.mu.RLock()
|
||||
defer r.mu.RUnlock()
|
||||
|
||||
for _, t := range r.trackEncodings {
|
||||
if t.track != nil && t.track.RID() == rid {
|
||||
return t.srtpStream.SetReadDeadline(deadline)
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("%w: %s", errRTPSenderNoTrackForRID, rid)
|
||||
}
|
||||
|
||||
// hasSent tells if data has been ever sent for this instance
|
||||
func (r *RTPSender) hasSent() bool {
|
||||
select {
|
||||
case <-r.sendCalled:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// hasStopped tells if stop has been called
|
||||
func (r *RTPSender) hasStopped() bool {
|
||||
select {
|
||||
case <-r.stopCalled:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
15
server/vendor/github.com/pion/webrtc/v3/rtpsender_js.go
generated
vendored
Normal file
15
server/vendor/github.com/pion/webrtc/v3/rtpsender_js.go
generated
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
//go:build js && wasm
|
||||
// +build js,wasm
|
||||
|
||||
package webrtc
|
||||
|
||||
import "syscall/js"
|
||||
|
||||
// RTPSender allows an application to control how a given Track is encoded and transmitted to a remote peer
|
||||
type RTPSender struct {
|
||||
// Pointer to the underlying JavaScript RTCRTPSender object.
|
||||
underlying js.Value
|
||||
}
|
||||
10
server/vendor/github.com/pion/webrtc/v3/rtpsendparameters.go
generated
vendored
Normal file
10
server/vendor/github.com/pion/webrtc/v3/rtpsendparameters.go
generated
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package webrtc
|
||||
|
||||
// RTPSendParameters contains the RTP stack settings used by receivers
|
||||
type RTPSendParameters struct {
|
||||
RTPParameters
|
||||
Encodings []RTPEncodingParameters
|
||||
}
|
||||
298
server/vendor/github.com/pion/webrtc/v3/rtptransceiver.go
generated
vendored
Normal file
298
server/vendor/github.com/pion/webrtc/v3/rtptransceiver.go
generated
vendored
Normal file
@@ -0,0 +1,298 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
//go:build !js
|
||||
// +build !js
|
||||
|
||||
package webrtc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/pion/rtp"
|
||||
)
|
||||
|
||||
// RTPTransceiver represents a combination of an RTPSender and an RTPReceiver that share a common mid.
|
||||
type RTPTransceiver struct {
|
||||
mid atomic.Value // string
|
||||
sender atomic.Value // *RTPSender
|
||||
receiver atomic.Value // *RTPReceiver
|
||||
direction atomic.Value // RTPTransceiverDirection
|
||||
currentDirection atomic.Value // RTPTransceiverDirection
|
||||
|
||||
codecs []RTPCodecParameters // User provided codecs via SetCodecPreferences
|
||||
|
||||
stopped bool
|
||||
kind RTPCodecType
|
||||
|
||||
api *API
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
func newRTPTransceiver(
|
||||
receiver *RTPReceiver,
|
||||
sender *RTPSender,
|
||||
direction RTPTransceiverDirection,
|
||||
kind RTPCodecType,
|
||||
api *API,
|
||||
) *RTPTransceiver {
|
||||
t := &RTPTransceiver{kind: kind, api: api}
|
||||
t.setReceiver(receiver)
|
||||
t.setSender(sender)
|
||||
t.setDirection(direction)
|
||||
t.setCurrentDirection(RTPTransceiverDirection(Unknown))
|
||||
return t
|
||||
}
|
||||
|
||||
// SetCodecPreferences sets preferred list of supported codecs
|
||||
// if codecs is empty or nil we reset to default from MediaEngine
|
||||
func (t *RTPTransceiver) SetCodecPreferences(codecs []RTPCodecParameters) error {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
|
||||
for _, codec := range codecs {
|
||||
if _, matchType := codecParametersFuzzySearch(codec, t.api.mediaEngine.getCodecsByKind(t.kind)); matchType == codecMatchNone {
|
||||
return fmt.Errorf("%w %s", errRTPTransceiverCodecUnsupported, codec.MimeType)
|
||||
}
|
||||
}
|
||||
|
||||
t.codecs = codecs
|
||||
return nil
|
||||
}
|
||||
|
||||
// Codecs returns list of supported codecs
|
||||
func (t *RTPTransceiver) getCodecs() []RTPCodecParameters {
|
||||
t.mu.RLock()
|
||||
defer t.mu.RUnlock()
|
||||
|
||||
mediaEngineCodecs := t.api.mediaEngine.getCodecsByKind(t.kind)
|
||||
if len(t.codecs) == 0 {
|
||||
return mediaEngineCodecs
|
||||
}
|
||||
|
||||
filteredCodecs := []RTPCodecParameters{}
|
||||
for _, codec := range t.codecs {
|
||||
if c, matchType := codecParametersFuzzySearch(codec, mediaEngineCodecs); matchType != codecMatchNone {
|
||||
if codec.PayloadType == 0 {
|
||||
codec.PayloadType = c.PayloadType
|
||||
}
|
||||
codec.RTCPFeedback = rtcpFeedbackIntersection(codec.RTCPFeedback, c.RTCPFeedback)
|
||||
filteredCodecs = append(filteredCodecs, codec)
|
||||
}
|
||||
}
|
||||
|
||||
return filteredCodecs
|
||||
}
|
||||
|
||||
// Sender returns the RTPTransceiver's RTPSender if it has one
|
||||
func (t *RTPTransceiver) Sender() *RTPSender {
|
||||
if v, ok := t.sender.Load().(*RTPSender); ok {
|
||||
return v
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetSender sets the RTPSender and Track to current transceiver
|
||||
func (t *RTPTransceiver) SetSender(s *RTPSender, track TrackLocal) error {
|
||||
t.setSender(s)
|
||||
return t.setSendingTrack(track)
|
||||
}
|
||||
|
||||
func (t *RTPTransceiver) setSender(s *RTPSender) {
|
||||
if s != nil {
|
||||
s.setRTPTransceiver(t)
|
||||
}
|
||||
|
||||
if prevSender := t.Sender(); prevSender != nil {
|
||||
prevSender.setRTPTransceiver(nil)
|
||||
}
|
||||
|
||||
t.sender.Store(s)
|
||||
}
|
||||
|
||||
// Receiver returns the RTPTransceiver's RTPReceiver if it has one
|
||||
func (t *RTPTransceiver) Receiver() *RTPReceiver {
|
||||
if v, ok := t.receiver.Load().(*RTPReceiver); ok {
|
||||
return v
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetMid sets the RTPTransceiver's mid. If it was already set, will return an error.
|
||||
func (t *RTPTransceiver) SetMid(mid string) error {
|
||||
if currentMid := t.Mid(); currentMid != "" {
|
||||
return fmt.Errorf("%w: %s to %s", errRTPTransceiverCannotChangeMid, currentMid, mid)
|
||||
}
|
||||
t.mid.Store(mid)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Mid gets the Transceiver's mid value. When not already set, this value will be set in CreateOffer or CreateAnswer.
|
||||
func (t *RTPTransceiver) Mid() string {
|
||||
if v, ok := t.mid.Load().(string); ok {
|
||||
return v
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Kind returns RTPTransceiver's kind.
|
||||
func (t *RTPTransceiver) Kind() RTPCodecType {
|
||||
return t.kind
|
||||
}
|
||||
|
||||
// Direction returns the RTPTransceiver's current direction
|
||||
func (t *RTPTransceiver) Direction() RTPTransceiverDirection {
|
||||
if direction, ok := t.direction.Load().(RTPTransceiverDirection); ok {
|
||||
return direction
|
||||
}
|
||||
return RTPTransceiverDirection(0)
|
||||
}
|
||||
|
||||
// Stop irreversibly stops the RTPTransceiver
|
||||
func (t *RTPTransceiver) Stop() error {
|
||||
if sender := t.Sender(); sender != nil {
|
||||
if err := sender.Stop(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if receiver := t.Receiver(); receiver != nil {
|
||||
if err := receiver.Stop(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
t.setDirection(RTPTransceiverDirectionInactive)
|
||||
t.setCurrentDirection(RTPTransceiverDirectionInactive)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *RTPTransceiver) setReceiver(r *RTPReceiver) {
|
||||
if r != nil {
|
||||
r.setRTPTransceiver(t)
|
||||
}
|
||||
|
||||
if prevReceiver := t.Receiver(); prevReceiver != nil {
|
||||
prevReceiver.setRTPTransceiver(nil)
|
||||
}
|
||||
|
||||
t.receiver.Store(r)
|
||||
}
|
||||
|
||||
func (t *RTPTransceiver) setDirection(d RTPTransceiverDirection) {
|
||||
t.direction.Store(d)
|
||||
}
|
||||
|
||||
func (t *RTPTransceiver) setCurrentDirection(d RTPTransceiverDirection) {
|
||||
t.currentDirection.Store(d)
|
||||
}
|
||||
|
||||
func (t *RTPTransceiver) getCurrentDirection() RTPTransceiverDirection {
|
||||
if v, ok := t.currentDirection.Load().(RTPTransceiverDirection); ok {
|
||||
return v
|
||||
}
|
||||
return RTPTransceiverDirection(Unknown)
|
||||
}
|
||||
|
||||
func (t *RTPTransceiver) setSendingTrack(track TrackLocal) error {
|
||||
if err := t.Sender().ReplaceTrack(track); err != nil {
|
||||
return err
|
||||
}
|
||||
if track == nil {
|
||||
t.setSender(nil)
|
||||
}
|
||||
|
||||
switch {
|
||||
case track != nil && t.Direction() == RTPTransceiverDirectionRecvonly:
|
||||
t.setDirection(RTPTransceiverDirectionSendrecv)
|
||||
case track != nil && t.Direction() == RTPTransceiverDirectionInactive:
|
||||
t.setDirection(RTPTransceiverDirectionSendonly)
|
||||
case track == nil && t.Direction() == RTPTransceiverDirectionSendrecv:
|
||||
t.setDirection(RTPTransceiverDirectionRecvonly)
|
||||
case track != nil && t.Direction() == RTPTransceiverDirectionSendonly:
|
||||
// Handle the case where a sendonly transceiver was added by a negotiation
|
||||
// initiated by remote peer. For example a remote peer added a transceiver
|
||||
// with direction recvonly.
|
||||
case track != nil && t.Direction() == RTPTransceiverDirectionSendrecv:
|
||||
// Similar to above, but for sendrecv transceiver.
|
||||
case track == nil && t.Direction() == RTPTransceiverDirectionSendonly:
|
||||
t.setDirection(RTPTransceiverDirectionInactive)
|
||||
default:
|
||||
return errRTPTransceiverSetSendingInvalidState
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func findByMid(mid string, localTransceivers []*RTPTransceiver) (*RTPTransceiver, []*RTPTransceiver) {
|
||||
for i, t := range localTransceivers {
|
||||
if t.Mid() == mid {
|
||||
return t, append(localTransceivers[:i], localTransceivers[i+1:]...)
|
||||
}
|
||||
}
|
||||
|
||||
return nil, localTransceivers
|
||||
}
|
||||
|
||||
// Given a direction+type pluck a transceiver from the passed list
|
||||
// if no entry satisfies the requested type+direction return a inactive Transceiver
|
||||
func satisfyTypeAndDirection(remoteKind RTPCodecType, remoteDirection RTPTransceiverDirection, localTransceivers []*RTPTransceiver) (*RTPTransceiver, []*RTPTransceiver) {
|
||||
// Get direction order from most preferred to least
|
||||
getPreferredDirections := func() []RTPTransceiverDirection {
|
||||
switch remoteDirection {
|
||||
case RTPTransceiverDirectionSendrecv:
|
||||
return []RTPTransceiverDirection{RTPTransceiverDirectionRecvonly, RTPTransceiverDirectionSendrecv, RTPTransceiverDirectionSendonly}
|
||||
case RTPTransceiverDirectionSendonly:
|
||||
return []RTPTransceiverDirection{RTPTransceiverDirectionRecvonly, RTPTransceiverDirectionSendrecv}
|
||||
case RTPTransceiverDirectionRecvonly:
|
||||
return []RTPTransceiverDirection{RTPTransceiverDirectionSendonly, RTPTransceiverDirectionSendrecv}
|
||||
default:
|
||||
return []RTPTransceiverDirection{}
|
||||
}
|
||||
}
|
||||
|
||||
for _, possibleDirection := range getPreferredDirections() {
|
||||
for i := range localTransceivers {
|
||||
t := localTransceivers[i]
|
||||
if t.Mid() == "" && t.kind == remoteKind && possibleDirection == t.Direction() {
|
||||
return t, append(localTransceivers[:i], localTransceivers[i+1:]...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil, localTransceivers
|
||||
}
|
||||
|
||||
// handleUnknownRTPPacket consumes a single RTP Packet and returns information that is helpful
|
||||
// for demuxing and handling an unknown SSRC (usually for Simulcast)
|
||||
func handleUnknownRTPPacket(buf []byte, midExtensionID, streamIDExtensionID, repairStreamIDExtensionID uint8, mid, rid, rsid *string) (payloadType PayloadType, paddingOnly bool, err error) {
|
||||
rp := &rtp.Packet{}
|
||||
if err = rp.Unmarshal(buf); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if rp.Padding && len(rp.Payload) == 0 {
|
||||
paddingOnly = true
|
||||
}
|
||||
|
||||
if !rp.Header.Extension {
|
||||
return
|
||||
}
|
||||
|
||||
payloadType = PayloadType(rp.PayloadType)
|
||||
if payload := rp.GetExtension(midExtensionID); payload != nil {
|
||||
*mid = string(payload)
|
||||
}
|
||||
|
||||
if payload := rp.GetExtension(streamIDExtensionID); payload != nil {
|
||||
*rid = string(payload)
|
||||
}
|
||||
|
||||
if payload := rp.GetExtension(repairStreamIDExtensionID); payload != nil {
|
||||
*rsid = string(payload)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
42
server/vendor/github.com/pion/webrtc/v3/rtptransceiver_js.go
generated
vendored
Normal file
42
server/vendor/github.com/pion/webrtc/v3/rtptransceiver_js.go
generated
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
//go:build js && wasm
|
||||
// +build js,wasm
|
||||
|
||||
package webrtc
|
||||
|
||||
import (
|
||||
"syscall/js"
|
||||
)
|
||||
|
||||
// RTPTransceiver represents a combination of an RTPSender and an RTPReceiver that share a common mid.
|
||||
type RTPTransceiver struct {
|
||||
// Pointer to the underlying JavaScript RTCRTPTransceiver object.
|
||||
underlying js.Value
|
||||
}
|
||||
|
||||
// Direction returns the RTPTransceiver's current direction
|
||||
func (r *RTPTransceiver) Direction() RTPTransceiverDirection {
|
||||
return NewRTPTransceiverDirection(r.underlying.Get("direction").String())
|
||||
}
|
||||
|
||||
// Sender returns the RTPTransceiver's RTPSender if it has one
|
||||
func (r *RTPTransceiver) Sender() *RTPSender {
|
||||
underlying := r.underlying.Get("sender")
|
||||
if underlying.IsNull() {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &RTPSender{underlying: underlying}
|
||||
}
|
||||
|
||||
// Receiver returns the RTPTransceiver's RTPReceiver if it has one
|
||||
func (r *RTPTransceiver) Receiver() *RTPReceiver {
|
||||
underlying := r.underlying.Get("receiver")
|
||||
if underlying.IsNull() {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &RTPReceiver{underlying: underlying}
|
||||
}
|
||||
88
server/vendor/github.com/pion/webrtc/v3/rtptransceiverdirection.go
generated
vendored
Normal file
88
server/vendor/github.com/pion/webrtc/v3/rtptransceiverdirection.go
generated
vendored
Normal file
@@ -0,0 +1,88 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package webrtc
|
||||
|
||||
// RTPTransceiverDirection indicates the direction of the RTPTransceiver.
|
||||
type RTPTransceiverDirection int
|
||||
|
||||
const (
|
||||
// RTPTransceiverDirectionSendrecv indicates the RTPSender will offer
|
||||
// to send RTP and the RTPReceiver will offer to receive RTP.
|
||||
RTPTransceiverDirectionSendrecv RTPTransceiverDirection = iota + 1
|
||||
|
||||
// RTPTransceiverDirectionSendonly indicates the RTPSender will offer
|
||||
// to send RTP.
|
||||
RTPTransceiverDirectionSendonly
|
||||
|
||||
// RTPTransceiverDirectionRecvonly indicates the RTPReceiver will
|
||||
// offer to receive RTP.
|
||||
RTPTransceiverDirectionRecvonly
|
||||
|
||||
// RTPTransceiverDirectionInactive indicates the RTPSender won't offer
|
||||
// to send RTP and the RTPReceiver won't offer to receive RTP.
|
||||
RTPTransceiverDirectionInactive
|
||||
)
|
||||
|
||||
// This is done this way because of a linter.
|
||||
const (
|
||||
rtpTransceiverDirectionSendrecvStr = "sendrecv"
|
||||
rtpTransceiverDirectionSendonlyStr = "sendonly"
|
||||
rtpTransceiverDirectionRecvonlyStr = "recvonly"
|
||||
rtpTransceiverDirectionInactiveStr = "inactive"
|
||||
)
|
||||
|
||||
// NewRTPTransceiverDirection defines a procedure for creating a new
|
||||
// RTPTransceiverDirection from a raw string naming the transceiver direction.
|
||||
func NewRTPTransceiverDirection(raw string) RTPTransceiverDirection {
|
||||
switch raw {
|
||||
case rtpTransceiverDirectionSendrecvStr:
|
||||
return RTPTransceiverDirectionSendrecv
|
||||
case rtpTransceiverDirectionSendonlyStr:
|
||||
return RTPTransceiverDirectionSendonly
|
||||
case rtpTransceiverDirectionRecvonlyStr:
|
||||
return RTPTransceiverDirectionRecvonly
|
||||
case rtpTransceiverDirectionInactiveStr:
|
||||
return RTPTransceiverDirectionInactive
|
||||
default:
|
||||
return RTPTransceiverDirection(Unknown)
|
||||
}
|
||||
}
|
||||
|
||||
func (t RTPTransceiverDirection) String() string {
|
||||
switch t {
|
||||
case RTPTransceiverDirectionSendrecv:
|
||||
return rtpTransceiverDirectionSendrecvStr
|
||||
case RTPTransceiverDirectionSendonly:
|
||||
return rtpTransceiverDirectionSendonlyStr
|
||||
case RTPTransceiverDirectionRecvonly:
|
||||
return rtpTransceiverDirectionRecvonlyStr
|
||||
case RTPTransceiverDirectionInactive:
|
||||
return rtpTransceiverDirectionInactiveStr
|
||||
default:
|
||||
return ErrUnknownType.Error()
|
||||
}
|
||||
}
|
||||
|
||||
// Revers indicate the opposite direction
|
||||
func (t RTPTransceiverDirection) Revers() RTPTransceiverDirection {
|
||||
switch t {
|
||||
case RTPTransceiverDirectionSendonly:
|
||||
return RTPTransceiverDirectionRecvonly
|
||||
case RTPTransceiverDirectionRecvonly:
|
||||
return RTPTransceiverDirectionSendonly
|
||||
default:
|
||||
return t
|
||||
}
|
||||
}
|
||||
|
||||
func haveRTPTransceiverDirectionIntersection(haystack []RTPTransceiverDirection, needle []RTPTransceiverDirection) bool {
|
||||
for _, n := range needle {
|
||||
for _, h := range haystack {
|
||||
if n == h {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
15
server/vendor/github.com/pion/webrtc/v3/rtptransceiverinit.go
generated
vendored
Normal file
15
server/vendor/github.com/pion/webrtc/v3/rtptransceiverinit.go
generated
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package webrtc
|
||||
|
||||
// RTPTransceiverInit dictionary is used when calling the WebRTC function addTransceiver() to provide configuration options for the new transceiver.
|
||||
type RTPTransceiverInit struct {
|
||||
Direction RTPTransceiverDirection
|
||||
SendEncodings []RTPEncodingParameters
|
||||
// Streams []*Track
|
||||
}
|
||||
|
||||
// RtpTransceiverInit is a temporary mapping while we fix case sensitivity
|
||||
// Deprecated: Use RTPTransceiverInit instead
|
||||
type RtpTransceiverInit = RTPTransceiverInit //nolint: stylecheck,golint
|
||||
9
server/vendor/github.com/pion/webrtc/v3/sctpcapabilities.go
generated
vendored
Normal file
9
server/vendor/github.com/pion/webrtc/v3/sctpcapabilities.go
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package webrtc
|
||||
|
||||
// SCTPCapabilities indicates the capabilities of the SCTPTransport.
|
||||
type SCTPCapabilities struct {
|
||||
MaxMessageSize uint32 `json:"maxMessageSize"`
|
||||
}
|
||||
451
server/vendor/github.com/pion/webrtc/v3/sctptransport.go
generated
vendored
Normal file
451
server/vendor/github.com/pion/webrtc/v3/sctptransport.go
generated
vendored
Normal file
@@ -0,0 +1,451 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
//go:build !js
|
||||
// +build !js
|
||||
|
||||
package webrtc
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"math"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/pion/datachannel"
|
||||
"github.com/pion/logging"
|
||||
"github.com/pion/sctp"
|
||||
"github.com/pion/webrtc/v3/pkg/rtcerr"
|
||||
)
|
||||
|
||||
const sctpMaxChannels = uint16(65535)
|
||||
|
||||
// SCTPTransport provides details about the SCTP transport.
|
||||
type SCTPTransport struct {
|
||||
lock sync.RWMutex
|
||||
|
||||
dtlsTransport *DTLSTransport
|
||||
|
||||
// State represents the current state of the SCTP transport.
|
||||
state SCTPTransportState
|
||||
|
||||
// SCTPTransportState doesn't have an enum to distinguish between New/Connecting
|
||||
// so we need a dedicated field
|
||||
isStarted bool
|
||||
|
||||
// MaxMessageSize represents the maximum size of data that can be passed to
|
||||
// DataChannel's send() method.
|
||||
maxMessageSize float64
|
||||
|
||||
// MaxChannels represents the maximum amount of DataChannel's that can
|
||||
// be used simultaneously.
|
||||
maxChannels *uint16
|
||||
|
||||
// OnStateChange func()
|
||||
|
||||
onErrorHandler func(error)
|
||||
onCloseHandler func(error)
|
||||
|
||||
sctpAssociation *sctp.Association
|
||||
onDataChannelHandler func(*DataChannel)
|
||||
onDataChannelOpenedHandler func(*DataChannel)
|
||||
|
||||
// DataChannels
|
||||
dataChannels []*DataChannel
|
||||
dataChannelsOpened uint32
|
||||
dataChannelsRequested uint32
|
||||
dataChannelsAccepted uint32
|
||||
|
||||
api *API
|
||||
log logging.LeveledLogger
|
||||
}
|
||||
|
||||
// NewSCTPTransport creates a new SCTPTransport.
|
||||
// This constructor is part of the ORTC API. It is not
|
||||
// meant to be used together with the basic WebRTC API.
|
||||
func (api *API) NewSCTPTransport(dtls *DTLSTransport) *SCTPTransport {
|
||||
res := &SCTPTransport{
|
||||
dtlsTransport: dtls,
|
||||
state: SCTPTransportStateConnecting,
|
||||
api: api,
|
||||
log: api.settingEngine.LoggerFactory.NewLogger("ortc"),
|
||||
}
|
||||
|
||||
res.updateMessageSize()
|
||||
res.updateMaxChannels()
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
// Transport returns the DTLSTransport instance the SCTPTransport is sending over.
|
||||
func (r *SCTPTransport) Transport() *DTLSTransport {
|
||||
r.lock.RLock()
|
||||
defer r.lock.RUnlock()
|
||||
|
||||
return r.dtlsTransport
|
||||
}
|
||||
|
||||
// GetCapabilities returns the SCTPCapabilities of the SCTPTransport.
|
||||
func (r *SCTPTransport) GetCapabilities() SCTPCapabilities {
|
||||
return SCTPCapabilities{
|
||||
MaxMessageSize: 0,
|
||||
}
|
||||
}
|
||||
|
||||
// Start the SCTPTransport. Since both local and remote parties must mutually
|
||||
// create an SCTPTransport, SCTP SO (Simultaneous Open) is used to establish
|
||||
// a connection over SCTP.
|
||||
func (r *SCTPTransport) Start(SCTPCapabilities) error {
|
||||
if r.isStarted {
|
||||
return nil
|
||||
}
|
||||
r.isStarted = true
|
||||
|
||||
dtlsTransport := r.Transport()
|
||||
if dtlsTransport == nil || dtlsTransport.conn == nil {
|
||||
return errSCTPTransportDTLS
|
||||
}
|
||||
|
||||
sctpAssociation, err := sctp.Client(sctp.Config{
|
||||
NetConn: dtlsTransport.conn,
|
||||
MaxReceiveBufferSize: r.api.settingEngine.sctp.maxReceiveBufferSize,
|
||||
EnableZeroChecksum: r.api.settingEngine.sctp.enableZeroChecksum,
|
||||
LoggerFactory: r.api.settingEngine.LoggerFactory,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
r.lock.Lock()
|
||||
r.sctpAssociation = sctpAssociation
|
||||
r.state = SCTPTransportStateConnected
|
||||
dataChannels := append([]*DataChannel{}, r.dataChannels...)
|
||||
r.lock.Unlock()
|
||||
|
||||
var openedDCCount uint32
|
||||
for _, d := range dataChannels {
|
||||
if d.ReadyState() == DataChannelStateConnecting {
|
||||
err := d.open(r)
|
||||
if err != nil {
|
||||
r.log.Warnf("failed to open data channel: %s", err)
|
||||
continue
|
||||
}
|
||||
openedDCCount++
|
||||
}
|
||||
}
|
||||
|
||||
r.lock.Lock()
|
||||
r.dataChannelsOpened += openedDCCount
|
||||
r.lock.Unlock()
|
||||
|
||||
go r.acceptDataChannels(sctpAssociation)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stop stops the SCTPTransport
|
||||
func (r *SCTPTransport) Stop() error {
|
||||
r.lock.Lock()
|
||||
defer r.lock.Unlock()
|
||||
if r.sctpAssociation == nil {
|
||||
return nil
|
||||
}
|
||||
err := r.sctpAssociation.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
r.sctpAssociation = nil
|
||||
r.state = SCTPTransportStateClosed
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *SCTPTransport) acceptDataChannels(a *sctp.Association) {
|
||||
r.lock.RLock()
|
||||
dataChannels := make([]*datachannel.DataChannel, 0, len(r.dataChannels))
|
||||
for _, dc := range r.dataChannels {
|
||||
dc.mu.Lock()
|
||||
isNil := dc.dataChannel == nil
|
||||
dc.mu.Unlock()
|
||||
if isNil {
|
||||
continue
|
||||
}
|
||||
dataChannels = append(dataChannels, dc.dataChannel)
|
||||
}
|
||||
r.lock.RUnlock()
|
||||
|
||||
ACCEPT:
|
||||
for {
|
||||
dc, err := datachannel.Accept(a, &datachannel.Config{
|
||||
LoggerFactory: r.api.settingEngine.LoggerFactory,
|
||||
}, dataChannels...)
|
||||
if err != nil {
|
||||
if !errors.Is(err, io.EOF) {
|
||||
r.log.Errorf("Failed to accept data channel: %v", err)
|
||||
r.onError(err)
|
||||
r.onClose(err)
|
||||
} else {
|
||||
r.onClose(nil)
|
||||
}
|
||||
return
|
||||
}
|
||||
for _, ch := range dataChannels {
|
||||
if ch.StreamIdentifier() == dc.StreamIdentifier() {
|
||||
continue ACCEPT
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
maxRetransmits *uint16
|
||||
maxPacketLifeTime *uint16
|
||||
)
|
||||
val := uint16(dc.Config.ReliabilityParameter)
|
||||
ordered := true
|
||||
|
||||
switch dc.Config.ChannelType {
|
||||
case datachannel.ChannelTypeReliable:
|
||||
ordered = true
|
||||
case datachannel.ChannelTypeReliableUnordered:
|
||||
ordered = false
|
||||
case datachannel.ChannelTypePartialReliableRexmit:
|
||||
ordered = true
|
||||
maxRetransmits = &val
|
||||
case datachannel.ChannelTypePartialReliableRexmitUnordered:
|
||||
ordered = false
|
||||
maxRetransmits = &val
|
||||
case datachannel.ChannelTypePartialReliableTimed:
|
||||
ordered = true
|
||||
maxPacketLifeTime = &val
|
||||
case datachannel.ChannelTypePartialReliableTimedUnordered:
|
||||
ordered = false
|
||||
maxPacketLifeTime = &val
|
||||
default:
|
||||
}
|
||||
|
||||
sid := dc.StreamIdentifier()
|
||||
rtcDC, err := r.api.newDataChannel(&DataChannelParameters{
|
||||
ID: &sid,
|
||||
Label: dc.Config.Label,
|
||||
Protocol: dc.Config.Protocol,
|
||||
Negotiated: dc.Config.Negotiated,
|
||||
Ordered: ordered,
|
||||
MaxPacketLifeTime: maxPacketLifeTime,
|
||||
MaxRetransmits: maxRetransmits,
|
||||
}, r, r.api.settingEngine.LoggerFactory.NewLogger("ortc"))
|
||||
if err != nil {
|
||||
// This data channel is invalid. Close it and log an error.
|
||||
if err1 := dc.Close(); err1 != nil {
|
||||
r.log.Errorf("Failed to close invalid data channel: %v", err1)
|
||||
}
|
||||
r.log.Errorf("Failed to accept data channel: %v", err)
|
||||
r.onError(err)
|
||||
// We've received a datachannel with invalid configuration. We can still receive other datachannels.
|
||||
continue ACCEPT
|
||||
}
|
||||
|
||||
<-r.onDataChannel(rtcDC)
|
||||
rtcDC.handleOpen(dc, true, dc.Config.Negotiated)
|
||||
|
||||
r.lock.Lock()
|
||||
r.dataChannelsOpened++
|
||||
handler := r.onDataChannelOpenedHandler
|
||||
r.lock.Unlock()
|
||||
|
||||
if handler != nil {
|
||||
handler(rtcDC)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// OnError sets an event handler which is invoked when the SCTP Association errors.
|
||||
func (r *SCTPTransport) OnError(f func(err error)) {
|
||||
r.lock.Lock()
|
||||
defer r.lock.Unlock()
|
||||
r.onErrorHandler = f
|
||||
}
|
||||
|
||||
func (r *SCTPTransport) onError(err error) {
|
||||
r.lock.RLock()
|
||||
handler := r.onErrorHandler
|
||||
r.lock.RUnlock()
|
||||
|
||||
if handler != nil {
|
||||
go handler(err)
|
||||
}
|
||||
}
|
||||
|
||||
// OnClose sets an event handler which is invoked when the SCTP Association closes.
|
||||
func (r *SCTPTransport) OnClose(f func(err error)) {
|
||||
r.lock.Lock()
|
||||
defer r.lock.Unlock()
|
||||
r.onCloseHandler = f
|
||||
}
|
||||
|
||||
func (r *SCTPTransport) onClose(err error) {
|
||||
r.lock.RLock()
|
||||
handler := r.onCloseHandler
|
||||
r.lock.RUnlock()
|
||||
|
||||
if handler != nil {
|
||||
go handler(err)
|
||||
}
|
||||
}
|
||||
|
||||
// OnDataChannel sets an event handler which is invoked when a data
|
||||
// channel message arrives from a remote peer.
|
||||
func (r *SCTPTransport) OnDataChannel(f func(*DataChannel)) {
|
||||
r.lock.Lock()
|
||||
defer r.lock.Unlock()
|
||||
r.onDataChannelHandler = f
|
||||
}
|
||||
|
||||
// OnDataChannelOpened sets an event handler which is invoked when a data
|
||||
// channel is opened
|
||||
func (r *SCTPTransport) OnDataChannelOpened(f func(*DataChannel)) {
|
||||
r.lock.Lock()
|
||||
defer r.lock.Unlock()
|
||||
r.onDataChannelOpenedHandler = f
|
||||
}
|
||||
|
||||
func (r *SCTPTransport) onDataChannel(dc *DataChannel) (done chan struct{}) {
|
||||
r.lock.Lock()
|
||||
r.dataChannels = append(r.dataChannels, dc)
|
||||
r.dataChannelsAccepted++
|
||||
handler := r.onDataChannelHandler
|
||||
r.lock.Unlock()
|
||||
|
||||
done = make(chan struct{})
|
||||
if handler == nil || dc == nil {
|
||||
close(done)
|
||||
return
|
||||
}
|
||||
|
||||
// Run this synchronously to allow setup done in onDataChannelFn()
|
||||
// to complete before datachannel event handlers might be called.
|
||||
go func() {
|
||||
handler(dc)
|
||||
close(done)
|
||||
}()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (r *SCTPTransport) updateMessageSize() {
|
||||
r.lock.Lock()
|
||||
defer r.lock.Unlock()
|
||||
|
||||
var remoteMaxMessageSize float64 = 65536 // pion/webrtc#758
|
||||
var canSendSize float64 = 65536 // pion/webrtc#758
|
||||
|
||||
r.maxMessageSize = r.calcMessageSize(remoteMaxMessageSize, canSendSize)
|
||||
}
|
||||
|
||||
func (r *SCTPTransport) calcMessageSize(remoteMaxMessageSize, canSendSize float64) float64 {
|
||||
switch {
|
||||
case remoteMaxMessageSize == 0 &&
|
||||
canSendSize == 0:
|
||||
return math.Inf(1)
|
||||
|
||||
case remoteMaxMessageSize == 0:
|
||||
return canSendSize
|
||||
|
||||
case canSendSize == 0:
|
||||
return remoteMaxMessageSize
|
||||
|
||||
case canSendSize > remoteMaxMessageSize:
|
||||
return remoteMaxMessageSize
|
||||
|
||||
default:
|
||||
return canSendSize
|
||||
}
|
||||
}
|
||||
|
||||
func (r *SCTPTransport) updateMaxChannels() {
|
||||
val := sctpMaxChannels
|
||||
r.maxChannels = &val
|
||||
}
|
||||
|
||||
// MaxChannels is the maximum number of RTCDataChannels that can be open simultaneously.
|
||||
func (r *SCTPTransport) MaxChannels() uint16 {
|
||||
r.lock.Lock()
|
||||
defer r.lock.Unlock()
|
||||
|
||||
if r.maxChannels == nil {
|
||||
return sctpMaxChannels
|
||||
}
|
||||
|
||||
return *r.maxChannels
|
||||
}
|
||||
|
||||
// State returns the current state of the SCTPTransport
|
||||
func (r *SCTPTransport) State() SCTPTransportState {
|
||||
r.lock.RLock()
|
||||
defer r.lock.RUnlock()
|
||||
return r.state
|
||||
}
|
||||
|
||||
func (r *SCTPTransport) collectStats(collector *statsReportCollector) {
|
||||
collector.Collecting()
|
||||
|
||||
stats := SCTPTransportStats{
|
||||
Timestamp: statsTimestampFrom(time.Now()),
|
||||
Type: StatsTypeSCTPTransport,
|
||||
ID: "sctpTransport",
|
||||
}
|
||||
|
||||
association := r.association()
|
||||
if association != nil {
|
||||
stats.BytesSent = association.BytesSent()
|
||||
stats.BytesReceived = association.BytesReceived()
|
||||
stats.SmoothedRoundTripTime = association.SRTT() * 0.001 // convert milliseconds to seconds
|
||||
stats.CongestionWindow = association.CWND()
|
||||
stats.ReceiverWindow = association.RWND()
|
||||
stats.MTU = association.MTU()
|
||||
}
|
||||
|
||||
collector.Collect(stats.ID, stats)
|
||||
}
|
||||
|
||||
func (r *SCTPTransport) generateAndSetDataChannelID(dtlsRole DTLSRole, idOut **uint16) error {
|
||||
var id uint16
|
||||
if dtlsRole != DTLSRoleClient {
|
||||
id++
|
||||
}
|
||||
|
||||
max := r.MaxChannels()
|
||||
|
||||
r.lock.Lock()
|
||||
defer r.lock.Unlock()
|
||||
|
||||
// Create map of ids so we can compare without double-looping each time.
|
||||
idsMap := make(map[uint16]struct{}, len(r.dataChannels))
|
||||
for _, dc := range r.dataChannels {
|
||||
if dc.ID() == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
idsMap[*dc.ID()] = struct{}{}
|
||||
}
|
||||
|
||||
for ; id < max-1; id += 2 {
|
||||
if _, ok := idsMap[id]; ok {
|
||||
continue
|
||||
}
|
||||
*idOut = &id
|
||||
return nil
|
||||
}
|
||||
|
||||
return &rtcerr.OperationError{Err: ErrMaxDataChannelID}
|
||||
}
|
||||
|
||||
func (r *SCTPTransport) association() *sctp.Association {
|
||||
if r == nil {
|
||||
return nil
|
||||
}
|
||||
r.lock.RLock()
|
||||
association := r.sctpAssociation
|
||||
r.lock.RUnlock()
|
||||
return association
|
||||
}
|
||||
27
server/vendor/github.com/pion/webrtc/v3/sctptransport_js.go
generated
vendored
Normal file
27
server/vendor/github.com/pion/webrtc/v3/sctptransport_js.go
generated
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
//go:build js && wasm
|
||||
// +build js,wasm
|
||||
|
||||
package webrtc
|
||||
|
||||
import "syscall/js"
|
||||
|
||||
// SCTPTransport provides details about the SCTP transport.
|
||||
type SCTPTransport struct {
|
||||
// Pointer to the underlying JavaScript SCTPTransport object.
|
||||
underlying js.Value
|
||||
}
|
||||
|
||||
// Transport returns the DTLSTransport instance the SCTPTransport is sending over.
|
||||
func (r *SCTPTransport) Transport() *DTLSTransport {
|
||||
underlying := r.underlying.Get("transport")
|
||||
if underlying.IsNull() || underlying.IsUndefined() {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &DTLSTransport{
|
||||
underlying: underlying,
|
||||
}
|
||||
}
|
||||
57
server/vendor/github.com/pion/webrtc/v3/sctptransportstate.go
generated
vendored
Normal file
57
server/vendor/github.com/pion/webrtc/v3/sctptransportstate.go
generated
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package webrtc
|
||||
|
||||
// SCTPTransportState indicates the state of the SCTP transport.
|
||||
type SCTPTransportState int
|
||||
|
||||
const (
|
||||
// SCTPTransportStateConnecting indicates the SCTPTransport is in the
|
||||
// process of negotiating an association. This is the initial state of the
|
||||
// SCTPTransportState when an SCTPTransport is created.
|
||||
SCTPTransportStateConnecting SCTPTransportState = iota + 1
|
||||
|
||||
// SCTPTransportStateConnected indicates the negotiation of an
|
||||
// association is completed.
|
||||
SCTPTransportStateConnected
|
||||
|
||||
// SCTPTransportStateClosed indicates a SHUTDOWN or ABORT chunk is
|
||||
// received or when the SCTP association has been closed intentionally,
|
||||
// such as by closing the peer connection or applying a remote description
|
||||
// that rejects data or changes the SCTP port.
|
||||
SCTPTransportStateClosed
|
||||
)
|
||||
|
||||
// This is done this way because of a linter.
|
||||
const (
|
||||
sctpTransportStateConnectingStr = "connecting"
|
||||
sctpTransportStateConnectedStr = "connected"
|
||||
sctpTransportStateClosedStr = "closed"
|
||||
)
|
||||
|
||||
func newSCTPTransportState(raw string) SCTPTransportState {
|
||||
switch raw {
|
||||
case sctpTransportStateConnectingStr:
|
||||
return SCTPTransportStateConnecting
|
||||
case sctpTransportStateConnectedStr:
|
||||
return SCTPTransportStateConnected
|
||||
case sctpTransportStateClosedStr:
|
||||
return SCTPTransportStateClosed
|
||||
default:
|
||||
return SCTPTransportState(Unknown)
|
||||
}
|
||||
}
|
||||
|
||||
func (s SCTPTransportState) String() string {
|
||||
switch s {
|
||||
case SCTPTransportStateConnecting:
|
||||
return sctpTransportStateConnectingStr
|
||||
case SCTPTransportStateConnected:
|
||||
return sctpTransportStateConnectedStr
|
||||
case SCTPTransportStateClosed:
|
||||
return sctpTransportStateClosedStr
|
||||
default:
|
||||
return ErrUnknownType.Error()
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user