1
0
Fork 0
mirror of https://github.com/ossrs/srs.git synced 2025-03-09 15:49:59 +00:00

TEST: Upgrade pion to v3.2.9. (#3567)

------

Co-authored-by: chundonglinlin <chundonglinlin@163.com>
This commit is contained in:
Winlin 2023-06-05 11:25:04 +08:00 committed by GitHub
parent 104cf14d68
commit df854339ea
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
1383 changed files with 118469 additions and 41421 deletions

View file

@ -1,3 +1,6 @@
---
# SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
# SPDX-License-Identifier: MIT
exclude_paths:
- examples/examples.json

View file

@ -1,3 +1,6 @@
# SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
# SPDX-License-Identifier: MIT
### JetBrains IDE ###
#####################
.idea/
@ -22,3 +25,4 @@ cover.out
*.wasm
examples/sfu-ws/cert.pem
examples/sfu-ws/key.pem
wasm_exec.js

View file

@ -1,3 +1,6 @@
# SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
# SPDX-License-Identifier: MIT
linters-settings:
govet:
check-shadowing: true
@ -10,19 +13,34 @@ linters-settings:
modules:
- github.com/pkg/errors:
recommendations:
- errors
- 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
- deadcode # Finds unused code
- 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
@ -35,40 +53,59 @@ linters:
- 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
- golint # Golint differs from gofmt. Gofmt reformats Go source code, whereas golint prints out style mistakes
- 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
- scopelint # Scopelint checks for unpinned variables in go programs
- 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
- structcheck # Finds unused struct fields
- 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
- varcheck # Finds unused global variables and constants
- 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:
@ -78,12 +115,23 @@ issues:
- 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

View file

@ -0,0 +1,5 @@
# SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
# SPDX-License-Identifier: MIT
builds:
- skip: true

View file

@ -0,0 +1,214 @@
# 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 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 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>
donotanswer <viktor.ferter@doclerholding.com>
earle <aguilar@dm.ai>
Egon Elbre <egonelbre@gmail.com>
Eric Daniels <eric@erdaniels.com>
Eric Fontaine <ericfontainejazz@gmail.com>
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>
Hanjun Kim <hallazzang@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 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>
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>
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 DuBois <duboisea@justin.tv>
Sean DuBois <duboisea@twitch.tv>
Sean DuBois <seaduboi@amazon.com>
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 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>
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

View file

@ -1,21 +1,9 @@
MIT License
Copyright (c) 2018
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:
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 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.
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.

View file

@ -6,31 +6,20 @@
</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://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>
<a href="https://travis-ci.org/pion/webrtc"><img src="https://travis-ci.org/pion/webrtc.svg?branch=master" alt="Build Status"></a>
<a href="https://pkg.go.dev/github.com/pion/webrtc/v3"><img src="https://pkg.go.dev/badge/github.com/pion/webrtc/v3" alt="PkgGoDev"></a>
<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"><img src="https://goreportcard.com/badge/github.com/pion/webrtc" alt="Go Report Card"></a>
<a href="https://www.codacy.com/app/Sean-Der/webrtc"><img src="https://api.codacy.com/project/badge/Grade/18f4aec384894e6aac0b94effe51961d" alt="Codacy Badge"></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>
### New Release
Pion WebRTC v3.0.0 has been released! See the [release notes](https://github.com/pion/webrtc/wiki/Release-WebRTC@v3.0.0) to learn about new features and breaking changes.
If you aren't able to upgrade yet check the [tags](https://github.com/pion/webrtc/tags) for the latest `v2` release.
We would love your feedback! Please create GitHub issues or join [the Slack channel](https://pion.ly/slack) to follow development and speak with the maintainers.
----
### 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.
@ -54,6 +43,9 @@ Now go build something awesome! Here are some **ideas** to get your creative jui
* 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.
@ -93,8 +85,9 @@ This book is vendor agnostic and will not have any Pion specific information.
* [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)
* Full loss recovery and congestion control is not complete, see [pion/interceptor](https://github.com/pion/interceptor) for progress
* See [ion](https://github.com/pion/ion-sfu/tree/master/pkg/buffer) for how an implementor can do it today
* [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
@ -113,12 +106,14 @@ This book is vendor agnostic and will not have any Pion specific information.
* **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).
@ -128,143 +123,7 @@ We are always looking to support **your projects**. Please reach out if you have
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:
* [John Bradley](https://github.com/kc5nra) - *Original Author*
* [Michael Melvin Santry](https://github.com/santrym) - *Mascot*
* [Raphael Randschau](https://github.com/nicolai86) - *STUN*
* [Sean DuBois](https://github.com/Sean-Der) - *Original Author*
* [Michiel De Backker](https://github.com/backkem) - *SDP, Public API, Project Management*
* [Brendan Rius](https://github.com/brendanrius) - *Cleanup*
* [Konstantin Itskov](https://github.com/trivigy) - *SDP Parsing*
* [chenkaiC4](https://github.com/chenkaiC4) - *Fix GolangCI Linter*
* [Ronan J](https://github.com/ronanj) - *Fix STCP PPID*
* [wattanakorn495](https://github.com/wattanakorn495)
* [Max Hawkins](https://github.com/maxhawkins) - *RTCP*
* [Justin Okamoto](https://github.com/justinokamoto) - *Fix Docs*
* [leeoxiang](https://github.com/notedit) - *Implement Janus examples*
* [Denis](https://github.com/Hixon10) - *Adding docker-compose to pion-to-pion example*
* [earle](https://github.com/aguilEA) - *Generate DTLS fingerprint in Go*
* [Jake B](https://github.com/silbinarywolf) - *Fix Windows installation instructions*
* [Michael MacDonald](https://github.com/mjmac) - *Plan B compatibility, Remote TURN/Trickle-ICE, Logging framework*
* [Oleg Kovalov](https://github.com/cristaloleg) *Use wildcards instead of hardcoding travis-ci config*
* [Woodrow Douglass](https://github.com/wdouglass) *RTCP, RTP improvements, G.722 support, Bugfixes*
* [Tobias Fridén](https://github.com/tobiasfriden) *SRTP authentication verification*
* [Yutaka Takeda](https://github.com/enobufs) *Fix ICE connection timeout*
* [Hugo Arregui](https://github.com/hugoArregui) *Fix connection timeout*
* [Rob Deutsch](https://github.com/rob-deutsch) *RTPReceiver graceful shutdown*
* [Jin Lei](https://github.com/jinleileiking) - *SFU example use http*
* [Will Watson](https://github.com/wwatson) - *Enable gocritic*
* [Luke Curley](https://github.com/kixelated)
* [Antoine Baché](https://github.com/Antonito) - *OGG Opus export*
* [frank](https://github.com/feixiao) - *Building examples on OSX*
* [mxmCherry](https://github.com/mxmCherry)
* [Alex Browne](https://github.com/albrow) - *JavaScript/Wasm bindings*
* [adwpc](https://github.com/adwpc) - *SFU example with websocket*
* [imalic3](https://github.com/imalic3) - *SFU websocket example with datachannel broadcast*
* [Žiga Željko](https://github.com/zigazeljko)
* [Simonacca Fotokite](https://github.com/simonacca-fotokite)
* [Marouane](https://github.com/nindolabs) *Fix Offer bundle generation*
* [Christopher Fry](https://github.com/christopherfry)
* [Adam Kiss](https://github.com/masterada)
* [xsbchen](https://github.com/xsbchen)
* [Alex Harford](https://github.com/alexjh)
* [Aleksandr Razumov](https://github.com/ernado)
* [mchlrhw](https://github.com/mchlrhw)
* [AlexWoo(武杰)](https://github.com/AlexWoo) *Fix RemoteDescription parsing for certificate fingerprint*
* [Cecylia Bocovich](https://github.com/cohosh)
* [Slugalisk](https://github.com/slugalisk)
* [Agugua Kenechukwu](https://github.com/spaceCh1mp)
* [Ato Araki](https://github.com/atotto)
* [Rafael Viscarra](https://github.com/rviscarra)
* [Mike Coleman](https://github.com/fivebats)
* [Suhas Gaddam](https://github.com/suhasgaddam)
* [Atsushi Watanabe](https://github.com/at-wat)
* [Robert Eperjesi](https://github.com/epes)
* [Aaron France](https://github.com/AeroNotix)
* [Gareth Hayes](https://github.com/gazhayes)
* [Sebastian Waisbrot](https://github.com/seppo0010)
* [Masataka Hisasue](https://github.com/sylba2050) - *Fix Docs*
* [Hongchao Ma(马洪超)](https://github.com/hcm007)
* [Aaron France](https://github.com/AeroNotix)
* [Chris Hiszpanski](https://github.com/thinkski) - *Fix Answer bundle generation*
* [Vicken Simonian](https://github.com/vsimon)
* [Guilherme Souza](https://github.com/gqgs)
* [Andrew N. Shalaev](https://github.com/isqad)
* [David Hamilton](https://github.com/dihamilton)
* [Ilya Mayorov](https://github.com/faroyam)
* [Patrick Lange](https://github.com/langep)
* [cyannuk](https://github.com/cyannuk)
* [Lukas Herman](https://github.com/lherman-cs)
* [Konstantin Chugalinskiy](https://github.com/kchugalinskiy)
* [Bao Nguyen](https://github.com/sysbot)
* [Luke S](https://github.com/encounter)
* [Hendrik Hofstadt](https://github.com/hendrikhofstadt)
* [Clayton McCray](https://github.com/ClaytonMcCray)
* [lawl](https://github.com/lawl)
* [Jorropo](https://github.com/Jorropo)
* [Akil](https://github.com/akilude)
* [Quentin Renard](https://github.com/asticode)
* [opennota](https://github.com/opennota)
* [Simon Eisenmann](https://github.com/longsleep)
* [Ben Weitzman](https://github.com/benweitzman)
* [Masahiro Nakamura](https://github.com/tsuu32)
* [Tarrence van As](https://github.com/tarrencev)
* [Yuki Igarashi](https://github.com/bonprosoft)
* [Egon Elbre](https://github.com/egonelbre)
* [Jerko Steiner](https://github.com/jeremija)
* [Roman Romanenko](https://github.com/r-novel)
* [YongXin SHI](https://github.com/a-wing)
* [Magnus Wahlstrand](https://github.com/kyeett)
* [Chad Retz](https://github.com/cretz)
* [Simone Gotti](https://github.com/sgotti)
* [Cedric Fung](https://github.com/cedricfung)
* [Norman Rasmussen](https://github.com/normanr) - *Fix Empty DataChannel messages*
* [salmān aljammāz](https://github.com/saljam)
* [cnderrauber](https://github.com/cnderrauber)
* [Juliusz Chroboczek](https://github.com/jech)
* [John Berthels](https://github.com/jbert)
* [Somers Matthews](https://github.com/somersbmatthews)
* [Vitaliy F](https://github.com/funvit)
* [Ivan Egorov](https://github.com/vany-egorov)
* [Nick Mykins](https://github.com/nmyk)
* [Jason Brady](https://github.com/jbrady42)
* [krishna chiatanya](https://github.com/kittuov)
* [JacobZwang](https://github.com/JacobZwang)
* [박종훈](https://github.com/JonghunBok)
* [Sam Lancia](https://github.com/nerd2)
* [Henry](https://github.com/cryptix)
* [Jeff Tchang](https://github.com/tachang)
* [JooYoung Lim](https://github.com/DevRockstarZ)
* [Sidney San Martín](https://github.com/s4y)
* [soolaugust](https://github.com/soolaugust)
* [Kuzmin Vladimir](https://github.com/tekig)
* [Alessandro Ros](https://github.com/aler9)
* [Thomas Miller](https://github.com/tmiv)
* [yoko(q191201771)](https://github.com/q191201771)
* [Joshua Obasaju](https://github.com/obasajujoshua31)
* [Mission Liao](https://github.com/mission-liao)
* [Hanjun Kim](https://github.com/hallazzang)
* [ZHENK](https://github.com/scorpionknifes)
* [Rahul Nakre](https://github.com/rahulnakre)
* [OrlandoCo](https://github.com/OrlandoCo)
* [Assad Obaid](https://github.com/assadobaid)
* [Jamie Good](https://github.com/jamiegood) - *Bug fix in jsfiddle example*
* [Artur Shellunts](https://github.com/ashellunts)
* [Sean Knight](https://github.com/SeanKnight)
* [o0olele](https://github.com/o0olele)
* [Bo Shi](https://github.com/bshimc)
* [Suzuki Takeo](https://github.com/BambooTuna)
* [baiyufei](https://github.com/baiyufei)
* [pascal-ace](https://github.com/pascal-ace)
* [Threadnaught](https://github.com/Threadnaught)
* [Dean Eigenmann](https://github.com/decanus)
* [Cameron Elliott](https://github.com/cameronelliott)
* [Pascal Benoit](https://github.com/pascal-ace)
* [Mats](https://github.com/Mindgamesnl)
* [donotanswer](https://github.com/f-viktor)
* [Reese](https://github.com/figadore)
* [David Zhao](https://github.com/davidzhao)
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

View file

@ -1,3 +1,7 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
//go:build !js
// +build !js
package webrtc
@ -7,50 +11,47 @@ import (
"github.com/pion/logging"
)
// API bundles the global functions of the WebRTC and ORTC API.
// Some of these functions are also exported globally using the
// defaultAPI object. Note that the global version of the API
// may be phased out in the future.
// 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
interceptor interceptor.Interceptor
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{}
a := &API{
interceptor: &interceptor.NoOp{},
settingEngine: &SettingEngine{},
mediaEngine: &MediaEngine{},
interceptorRegistry: &interceptor.Registry{},
}
for _, o := range options {
o(a)
}
if a.settingEngine == nil {
a.settingEngine = &SettingEngine{}
}
if a.settingEngine.LoggerFactory == nil {
a.settingEngine.LoggerFactory = logging.NewDefaultLoggerFactory()
}
if a.mediaEngine == nil {
a.mediaEngine = &MediaEngine{}
}
if a.interceptor == nil {
a.interceptor = &interceptor.NoOp{}
}
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) {
if m != nil {
a.mediaEngine = m
} else {
a.mediaEngine = m
if a.mediaEngine == nil {
a.mediaEngine = &MediaEngine{}
}
}
@ -66,8 +67,11 @@ func WithSettingEngine(s SettingEngine) func(a *API) {
// WithInterceptorRegistry allows providing Interceptors to the API.
// Settings should not be changed after passing the registry to an API.
func WithInterceptorRegistry(interceptorRegistry *interceptor.Registry) func(a *API) {
func WithInterceptorRegistry(ir *interceptor.Registry) func(a *API) {
return func(a *API) {
a.interceptor = interceptorRegistry.Build()
a.interceptorRegistry = ir
if a.interceptorRegistry == nil {
a.interceptorRegistry = &interceptor.Registry{}
}
}
}

View file

@ -1,3 +1,7 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
//go:build js && wasm
// +build js,wasm
package webrtc

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package webrtc
import "sync/atomic"
@ -18,3 +21,11 @@ func (b *atomicBool) set(value bool) { // nolint: unparam
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
}

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package webrtc
import (

View file

@ -1,3 +1,7 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
//go:build !js
// +build !js
package webrtc
@ -10,9 +14,10 @@ import (
"crypto/x509"
"crypto/x509/pkix"
"encoding/base64"
"encoding/hex"
"encoding/pem"
"fmt"
"math/big"
"strings"
"time"
"github.com/pion/dtls/v2/pkg/crypto/fingerprint"
@ -103,10 +108,12 @@ func (c Certificate) GetFingerprints() ([]DTLSFingerprint, error) {
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{
@ -121,12 +128,6 @@ func (c Certificate) GetFingerprints() ([]DTLSFingerprint, error) {
// GenerateCertificate causes the creation of an X.509 certificate and
// corresponding private key.
func GenerateCertificate(secretKey crypto.PrivateKey) (*Certificate, error) {
origin := make([]byte, 16)
/* #nosec */
if _, err := rand.Read(origin); err != nil {
return nil, &rtcerr.UnknownError{Err: err}
}
// Max random value, a 130-bits integer, i.e 2^130 - 1
maxBigInt := new(big.Int)
/* #nosec */
@ -138,18 +139,12 @@ func GenerateCertificate(secretKey crypto.PrivateKey) (*Certificate, error) {
}
return NewCertificate(secretKey, x509.Certificate{
ExtKeyUsage: []x509.ExtKeyUsage{
x509.ExtKeyUsageClientAuth,
x509.ExtKeyUsageServerAuth,
},
BasicConstraintsValid: true,
NotBefore: time.Now(),
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
NotAfter: time.Now().AddDate(0, 1, 0),
SerialNumber: serialNumber,
Version: 2,
Subject: pkix.Name{CommonName: hex.EncodeToString(origin)},
IsCA: true,
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},
})
}
@ -183,3 +178,57 @@ func (c Certificate) collectStats(report *statsReportCollector) error {
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
}

View file

@ -3,6 +3,8 @@
#
# 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:

View file

@ -1,3 +1,7 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
//go:build !js
// +build !js
package webrtc

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package webrtc
import "strings"

View file

@ -1,3 +1,7 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
//go:build js && wasm
// +build js,wasm
package webrtc

View file

@ -1,11 +1,15 @@
// 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"
ssrcStr = "ssrc"
// Equal to UDP MTU
receiveMTU = 1460
@ -21,5 +25,19 @@ const (
mediaSectionApplication = "application"
sdpAttributeRid = "rid"
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"
)
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}
}

View file

@ -1,3 +1,7 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
//go:build !js
// +build !js
package webrtc
@ -48,6 +52,8 @@ type DataChannel struct {
onMessageHandler func(DataChannelMessage)
openHandlerOnce sync.Once
onOpenHandler func()
dialHandlerOnce sync.Once
onDialHandler func()
onCloseHandler func()
onBufferedAmountLow func()
onErrorHandler func(error)
@ -64,7 +70,7 @@ type DataChannel struct {
// 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, api.settingEngine.LoggerFactory.NewLogger("ortc"))
d, err := api.newDataChannel(params, nil, api.settingEngine.LoggerFactory.NewLogger("ortc"))
if err != nil {
return nil, err
}
@ -79,13 +85,14 @@ func (api *API) NewDataChannel(transport *SCTPTransport, params *DataChannelPara
// 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, log logging.LeveledLogger) (*DataChannel, error) {
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,
@ -104,19 +111,17 @@ func (api *API) newDataChannel(params *DataChannelParameters, log logging.Levele
// 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
if d.sctpTransport != nil { // already open
d.mu.Unlock()
return nil
}
d.sctpTransport = sctpTransport
if err := d.ensureSCTP(); err != nil {
d.mu.Unlock()
return err
}
var channelType datachannel.ChannelType
var reliabilityParameter uint32
@ -155,13 +160,17 @@ func (d *DataChannel) open(sctpTransport *SCTPTransport) error {
}
if d.id == nil {
err := d.sctpTransport.generateAndSetDataChannelID(d.sctpTransport.dtlsTransport.role(), &d.id)
// 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(d.sctpTransport.association, *d.id, cfg)
dc, err := datachannel.Dial(association, *d.id, cfg)
if err != nil {
d.mu.Unlock()
return err
@ -172,20 +181,8 @@ func (d *DataChannel) open(sctpTransport *SCTPTransport) error {
dc.OnBufferedAmountLow(d.onBufferedAmountLow)
d.mu.Unlock()
d.handleOpen(dc)
return nil
}
func (d *DataChannel) ensureSCTP() error {
if d.sctpTransport == nil {
return errSCTPNotEstablished
}
d.sctpTransport.lock.RLock()
defer d.sctpTransport.lock.RUnlock()
if d.sctpTransport.association == nil {
return errSCTPNotEstablished
}
d.onDial()
d.handleOpen(dc, false, d.negotiated)
return nil
}
@ -238,6 +235,30 @@ func (d *DataChannel) onOpen() {
}
}
// 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
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.
func (d *DataChannel) OnClose(f func()) {
@ -279,13 +300,26 @@ func (d *DataChannel) onMessage(msg DataChannelMessage) {
handler(msg)
}
func (d *DataChannel) handleOpen(dc *datachannel.DataChannel) {
func (d *DataChannel) handleOpen(dc *datachannel.DataChannel, isRemote, isAlreadyNegotiated bool) {
d.mu.Lock()
d.dataChannel = dc
d.mu.Unlock()
d.setReadyState(DataChannelStateOpen)
d.onOpen()
// 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(d.bufferedAmountLowThreshold)
d.dataChannel.OnBufferedAmountLow(d.onBufferedAmountLow)
d.onOpen()
} else {
dc.OnOpen(func() {
d.onOpen()
})
}
d.mu.Lock()
defer d.mu.Unlock()
@ -321,12 +355,12 @@ var rlBufPool = sync.Pool{New: func() interface{} {
func (d *DataChannel) readLoop() {
for {
buffer := rlBufPool.Get().([]byte)
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 err != io.EOF {
if !errors.Is(err, io.EOF) {
d.onError(err)
}
d.onClose()
@ -428,7 +462,7 @@ func (d *DataChannel) Label() string {
return d.label
}
// Ordered represents if the DataChannel is ordered, and false if
// 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()
@ -488,8 +522,8 @@ func (d *DataChannel) ID() *uint16 {
// ReadyState represents the state of the DataChannel object.
func (d *DataChannel) ReadyState() DataChannelState {
if v := d.readyState.Load(); v != nil {
return v.(DataChannelState)
if v, ok := d.readyState.Load().(DataChannelState); ok {
return v
}
return DataChannelState(0)
}

View file

@ -1,3 +1,7 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
//go:build js && wasm
// +build js,wasm
package webrtc
@ -162,7 +166,7 @@ func (d *DataChannel) Label() string {
// out-of-order delivery is allowed.
func (d *DataChannel) Ordered() bool {
ordered := d.underlying.Get("ordered")
if jsValueIsUndefined(ordered) {
if ordered.IsUndefined() {
return true // default is true
}
return ordered.Bool()
@ -171,13 +175,13 @@ func (d *DataChannel) 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 !jsValueIsUndefined(d.underlying.Get("maxPacketLifeTime")) {
if !d.underlying.Get("maxPacketLifeTime").IsUndefined() {
return valueToUint16Pointer(d.underlying.Get("maxPacketLifeTime"))
} else {
// See https://bugs.chromium.org/p/chromium/issues/detail?id=696681
// Chrome calls this "maxRetransmitTime"
return valueToUint16Pointer(d.underlying.Get("maxRetransmitTime"))
}
// 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

View file

@ -1,3 +1,7 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
//go:build js && wasm
// +build js,wasm
package webrtc

View file

@ -1,3 +1,6 @@
// 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

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package webrtc
// DataChannelMessage represents a message received from the

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package webrtc
// DataChannelParameters describes the configuration of the DataChannel.

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package webrtc
// DataChannelState indicates the state of a data channel.

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package webrtc
// DTLSFingerprint specifies the hash function algorithm and certificate

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package webrtc
// DTLSParameters holds information relating to DTLS configuration.

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package webrtc
import (

View file

@ -1,3 +1,7 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
//go:build !js
// +build !js
package webrtc
@ -17,6 +21,9 @@ import (
"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"
@ -49,6 +56,7 @@ type DTLSTransport struct {
dtlsMatcher mux.MatchFunc
api *API
log logging.LeveledLogger
}
// NewDTLSTransport creates a new DTLSTransport.
@ -61,6 +69,7 @@ func (api *API) NewDTLSTransport(transport *ICETransport, certificates []Certifi
state: DTLSTransportStateNew,
dtlsMatcher: mux.MatchDTLS,
srtpReady: make(chan struct{}),
log: api.settingEngine.LoggerFactory.NewLogger("DTLSTransport"),
}
if len(certificates) > 0 {
@ -118,6 +127,28 @@ func (t *DTLSTransport) State() DTLSTransportState {
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{}
@ -182,16 +213,19 @@ func (t *DTLSTransport) startSRTP() error {
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)
}
@ -202,16 +236,16 @@ func (t *DTLSTransport) startSRTP() error {
}
func (t *DTLSTransport) getSRTPSession() (*srtp.SessionSRTP, error) {
if value := t.srtpSession.Load(); value != nil {
return value.(*srtp.SessionSRTP), nil
if value, ok := t.srtpSession.Load().(*srtp.SessionSRTP); ok {
return value, nil
}
return nil, errDtlsTransportNotStarted
}
func (t *DTLSTransport) getSRTCPSession() (*srtp.SessionSRTCP, error) {
if value := t.srtcpSession.Load(); value != nil {
return value.(*srtp.SessionSRTCP), nil
if value, ok := t.srtcpSession.Load().(*srtp.SessionSRTCP); ok {
return value, nil
}
return nil, errDtlsTransportNotStarted
@ -259,8 +293,8 @@ func (t *DTLSTransport) Start(remoteParameters DTLSParameters) error {
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.srtpEndpoint = t.iceTransport.newEndpoint(mux.MatchSRTP)
t.srtcpEndpoint = t.iceTransport.newEndpoint(mux.MatchSRTCP)
t.remoteParameters = remoteParameters
cert := t.certificates[0]
@ -273,15 +307,21 @@ func (t *DTLSTransport) Start(remoteParameters DTLSParameters) error {
PrivateKey: cert.privateKey,
},
},
SRTPProtectionProfiles: []dtls.SRTPProtectionProfile{dtls.SRTP_AEAD_AES_128_GCM, dtls.SRTP_AES128_CM_HMAC_SHA1_80},
ClientAuth: dtls.RequireAnyClientCert,
LoggerFactory: t.api.settingEngine.LoggerFactory,
InsecureSkipVerify: true,
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)
dtlsEndpoint := t.iceTransport.newEndpoint(mux.MatchDTLS)
role, dtlsConfig, err := prepareTransport()
if err != nil {
return err
@ -291,6 +331,18 @@ func (t *DTLSTransport) Start(remoteParameters DTLSParameters) error {
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
// Connect as DTLS Client/Server, function is blocking and we
// must not hold the DTLSTransport lock
if role == DTLSRoleClient {
@ -317,6 +369,8 @@ func (t *DTLSTransport) Start(remoteParameters DTLSParameters) error {
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
default:
@ -324,31 +378,37 @@ func (t *DTLSTransport) Start(remoteParameters DTLSParameters) error {
return ErrNoSRTPProtectionProfile
}
t.conn = dtlsConn
t.onStateChange(DTLSTransportStateConnected)
if t.api.settingEngine.disableCertificateFingerprintVerification {
return nil
}
// Check the fingerprint if a certificate was exchanged
remoteCerts := t.conn.ConnectionState().PeerCertificates
remoteCerts := dtlsConn.ConnectionState().PeerCertificates
if len(remoteCerts) == 0 {
t.onStateChange(DTLSTransportStateFailed)
return errNoRemoteCertificate
}
t.remoteCertificate = remoteCerts[0]
parsedRemoteCert, err := x509.ParseCertificate(t.remoteCertificate)
if err != nil {
t.onStateChange(DTLSTransportStateFailed)
return err
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
}
}
if err = t.validateFingerPrint(parsedRemoteCert); err != nil {
t.onStateChange(DTLSTransportStateFailed)
return err
}
t.conn = dtlsConn
t.onStateChange(DTLSTransportStateConnected)
return t.startSRTP()
}
@ -361,12 +421,12 @@ func (t *DTLSTransport) Stop() error {
// Try closing everything and collect the errors
var closeErrs []error
if srtpSessionValue := t.srtpSession.Load(); srtpSessionValue != nil {
closeErrs = append(closeErrs, srtpSessionValue.(*srtp.SessionSRTP).Close())
if srtpSession, err := t.getSRTPSession(); err == nil && srtpSession != nil {
closeErrs = append(closeErrs, srtpSession.Close())
}
if srtcpSessionValue := t.srtcpSession.Load(); srtcpSessionValue != nil {
closeErrs = append(closeErrs, srtcpSessionValue.(*srtp.SessionSRTCP).Close())
if srtcpSession, err := t.getSRTCPSession(); err == nil && srtcpSession != nil {
closeErrs = append(closeErrs, srtcpSession.Close())
}
for i := range t.simulcastStreams {
@ -404,7 +464,7 @@ func (t *DTLSTransport) validateFingerPrint(remoteCert *x509.Certificate) error
}
func (t *DTLSTransport) ensureICEConn() error {
if t.iceTransport == nil || t.iceTransport.State() == ICETransportStateNew {
if t.iceTransport == nil {
return errICEConnectionNotStarted
}
@ -417,3 +477,37 @@ func (t *DTLSTransport) storeSimulcastStream(s *srtp.ReadStreamSRTP) {
t.simulcastStreams = append(t.simulcastStreams, s)
}
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.RTPReaderFunc(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
}

View 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,
}
}

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package webrtc
// DTLSTransportState indicates the DTLS transport establishment state.

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package webrtc
import (
@ -82,7 +85,7 @@ var (
// ErrIncorrectSDPSemantics indicates that the PeerConnection was configured to
// generate SDP Answers with different SDP Semantics than the received Offer
ErrIncorrectSDPSemantics = errors.New("offer SDP semantics does not match configuration")
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")
@ -135,6 +138,16 @@ var (
// 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")
@ -182,8 +195,8 @@ var (
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")
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")
@ -193,15 +206,22 @@ var (
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")
errRTPReceiverForSSRCTrackStreamNotFound = errors.New("no trackStreams found for 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")
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")
@ -216,5 +236,14 @@ var (
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")
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")
)

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package webrtc
import (

View file

@ -1,23 +0,0 @@
module github.com/pion/webrtc/v3
go 1.12
require (
github.com/onsi/ginkgo v1.14.2 // indirect
github.com/onsi/gomega v1.10.3 // indirect
github.com/pion/datachannel v1.4.21
github.com/pion/dtls/v2 v2.0.8
github.com/pion/ice/v2 v2.0.15
github.com/pion/interceptor v0.0.10
github.com/pion/logging v0.2.2
github.com/pion/randutil v0.1.0
github.com/pion/rtcp v1.2.6
github.com/pion/rtp v1.6.2
github.com/pion/sctp v1.7.11
github.com/pion/sdp/v3 v3.0.4
github.com/pion/srtp/v2 v2.0.1
github.com/pion/transport v0.12.2
github.com/sclevine/agouti v3.0.0+incompatible
github.com/stretchr/testify v1.7.0
golang.org/x/net v0.0.0-20210119194325-5f4716e94777
)

View file

@ -1,149 +0,0 @@
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/uuid v1.1.5 h1:kxhtnfFVi+rYdOALN0B3k9UT86zVJKfBimRaciULW4I=
github.com/google/uuid v1.1.5/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
github.com/onsi/ginkgo v1.14.2 h1:8mVmC9kjFFmA8H4pKMUhcblgifdkOIXPvbhN1T36q1M=
github.com/onsi/ginkgo v1.14.2/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/onsi/gomega v1.10.3 h1:gph6h/qe9GSUw1NhH1gp+qb+h8rXD8Cy60Z32Qw3ELA=
github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc=
github.com/pion/datachannel v1.4.21 h1:3ZvhNyfmxsAqltQrApLPQMhSFNA+aT87RqyCq4OXmf0=
github.com/pion/datachannel v1.4.21/go.mod h1:oiNyP4gHx2DIwRzX/MFyH0Rz/Gz05OgBlayAI2hAWjg=
github.com/pion/dtls/v2 v2.0.4 h1:WuUcqi6oYMu/noNTz92QrF1DaFj4eXbhQ6dzaaAwOiI=
github.com/pion/dtls/v2 v2.0.4/go.mod h1:qAkFscX0ZHoI1E07RfYPoRw3manThveu+mlTDdOxoGI=
github.com/pion/dtls/v2 v2.0.8 h1:reGe8rNIMfO/UAeFLqO61tl64t154Qfkr4U3Gzu1tsg=
github.com/pion/dtls/v2 v2.0.8/go.mod h1:QuDII+8FVvk9Dp5t5vYIMTo7hh7uBkra+8QIm7QGm10=
github.com/pion/ice/v2 v2.0.15 h1:KZrwa2ciL9od8+TUVJiYTNsCW9J5lktBjGwW1MacEnQ=
github.com/pion/ice/v2 v2.0.15/go.mod h1:ZIiVGevpgAxF/cXiIVmuIUtCb3Xs4gCzCbXB6+nFkSI=
github.com/pion/interceptor v0.0.10 h1:dXFyFWRJFwmzQqyn0U8dUAbOJu+JJnMVAqxmvTu30B4=
github.com/pion/interceptor v0.0.10/go.mod h1:qzeuWuD/ZXvPqOnxNcnhWfkCZ2e1kwwslicyyPnhoK4=
github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY=
github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms=
github.com/pion/mdns v0.0.4 h1:O4vvVqr4DGX63vzmO6Fw9vpy3lfztVWHGCQfyw0ZLSY=
github.com/pion/mdns v0.0.4/go.mod h1:R1sL0p50l42S5lJs91oNdUL58nm0QHrhxnSegr++qC0=
github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA=
github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8=
github.com/pion/rtcp v1.2.6 h1:1zvwBbyd0TeEuuWftrd/4d++m+/kZSeiguxU61LFWpo=
github.com/pion/rtcp v1.2.6/go.mod h1:52rMNPWFsjr39z9B9MhnkqhPLoeHTv1aN63o/42bWE0=
github.com/pion/rtp v1.6.2 h1:iGBerLX6JiDjB9NXuaPzHyxHFG9JsIEdgwTC0lp5n/U=
github.com/pion/rtp v1.6.2/go.mod h1:bDb5n+BFZxXx0Ea7E5qe+klMuqiBrP+w8XSjiWtCUko=
github.com/pion/sctp v1.7.10/go.mod h1:EhpTUQu1/lcK3xI+eriS6/96fWetHGCvBi9MSsnaBN0=
github.com/pion/sctp v1.7.11 h1:UCnj7MsobLKLuP/Hh+JMiI/6W5Bs/VF45lWKgHFjSIE=
github.com/pion/sctp v1.7.11/go.mod h1:EhpTUQu1/lcK3xI+eriS6/96fWetHGCvBi9MSsnaBN0=
github.com/pion/sdp/v3 v3.0.4 h1:2Kf+dgrzJflNCSw3TV5v2VLeI0s/qkzy2r5jlR0wzf8=
github.com/pion/sdp/v3 v3.0.4/go.mod h1:bNiSknmJE0HYBprTHXKPQ3+JjacTv5uap92ueJZKsRk=
github.com/pion/srtp/v2 v2.0.1 h1:kgfh65ob3EcnFYA4kUBvU/menCp9u7qaJLXwWgpobzs=
github.com/pion/srtp/v2 v2.0.1/go.mod h1:c8NWHhhkFf/drmHTAblkdu8++lsISEBBdAuiyxgqIsE=
github.com/pion/stun v0.3.5 h1:uLUCBCkQby4S1cf6CGuR9QrVOKcvUwFeemaC865QHDg=
github.com/pion/stun v0.3.5/go.mod h1:gDMim+47EeEtfWogA37n6qXZS88L5V6LqFcf+DZA2UA=
github.com/pion/transport v0.8.10/go.mod h1:tBmha/UCjpum5hqTWhfAEs3CO4/tHSg0MYRhSzR+CZ8=
github.com/pion/transport v0.10.0/go.mod h1:BnHnUipd0rZQyTVB2SBGojFHT9CBt5C5TcsJSQGkvSE=
github.com/pion/transport v0.10.1/go.mod h1:PBis1stIILMiis0PewDw91WJeLJkyIMcEk+DwKOzf4A=
github.com/pion/transport v0.12.1/go.mod h1:N3+vZQD9HlDP5GWkZ85LohxNsDcNgofQmyL6ojX5d8Q=
github.com/pion/transport v0.12.2 h1:WYEjhloRHt1R86LhUKjC5y+P52Y11/QqEUalvtzVoys=
github.com/pion/transport v0.12.2/go.mod h1:N3+vZQD9HlDP5GWkZ85LohxNsDcNgofQmyL6ojX5d8Q=
github.com/pion/turn/v2 v2.0.5 h1:iwMHqDfPEDEOFzwWKT56eFmh6DYC6o/+xnLAEzgISbA=
github.com/pion/turn/v2 v2.0.5/go.mod h1:APg43CFyt/14Uy7heYUOGWdkem/Wu4PhCO/bjyrTqMw=
github.com/pion/udp v0.1.0 h1:uGxQsNyrqG3GLINv36Ff60covYmfrLoxzwnCsIYspXI=
github.com/pion/udp v0.1.0/go.mod h1:BPELIjbwE9PRbd/zxI/KYBnbo7B6+oA6YuEaNE8lths=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/sclevine/agouti v3.0.0+incompatible h1:8IBJS6PWz3uTlMP3YBIR5f+KAldcGuOeFkFbUWfBgK4=
github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897 h1:pLI5jrR7OSLijeIDcmRxNmw2api+jEfxLoykJVice/E=
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad h1:DN0cp81fZ3njFcrLCytUHRSUkqBjfTo4Tx9RJTWs0EY=
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20191126235420-ef20fe5d7933/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201201195509-5d6afe98e0b7 h1:3uJsdck53FDIpWwLeAXlia9p4C8j0BO2xZrqzKpL0D8=
golang.org/x/net v0.0.0-20201201195509-5d6afe98e0b7/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210119194325-5f4716e94777 h1:003p0dJM77cxMSyCPFphvZf/Y5/NXf5fzg6ufd1/Oew=
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f h1:+Nyd8tzPX9R7BWHguqsrbFdRx3WQ/1ib8I44HXV5yTA=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View file

@ -1,3 +1,7 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
//go:build !js
// +build !js
package webrtc

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package webrtc
import (

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package webrtc
// ICECandidateInit is used to serialize ice candidates

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package webrtc
import "fmt"

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package webrtc
import (

View file

@ -1,3 +1,6 @@
// 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

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package webrtc
// ICEConnectionState indicates signaling state of the ICE Connection.

View file

@ -1,5 +1,13 @@
// 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
@ -20,14 +28,14 @@ const (
iceCredentialTypeOauthStr = "oauth"
)
func newICECredentialType(raw string) ICECredentialType {
func newICECredentialType(raw string) (ICECredentialType, error) {
switch raw {
case iceCredentialTypePasswordStr:
return ICECredentialTypePassword
return ICECredentialTypePassword, nil
case iceCredentialTypeOauthStr:
return ICECredentialTypeOauth
return ICECredentialTypeOauth, nil
default:
return ICECredentialType(Unknown)
return ICECredentialTypePassword, errInvalidICECredentialTypeString
}
}
@ -41,3 +49,24 @@ func (t ICECredentialType) String() string {
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())
}

View file

@ -1,13 +1,19 @@
// 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
@ -19,7 +25,7 @@ type ICEGatherer struct {
log logging.LeveledLogger
state ICEGathererState
validatedServers []*ice.URL
validatedServers []*stun.URI
gatherPolicy ICETransportPolicy
agent *ice.Agent
@ -37,7 +43,7 @@ type ICEGatherer struct {
// 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 []*ice.URL
var validatedServers []*stun.URI
if len(opts.ICEServers) > 0 {
for _, server := range opts.ICEServers {
url, err := server.urls()
@ -103,14 +109,17 @@ func (g *ICEGatherer) createAgent() error {
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,
Net: g.api.settingEngine.vnet,
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,
}
@ -138,9 +147,11 @@ func (g *ICEGatherer) Gather() error {
return err
}
g.lock.Lock()
agent := g.agent
g.lock.Unlock()
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) {
@ -196,7 +207,13 @@ func (g *ICEGatherer) GetLocalParameters() (ICEParameters, error) {
return ICEParameters{}, err
}
frag, pwd, err := g.agent.GetLocalUserCredentials()
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
}
@ -213,7 +230,14 @@ func (g *ICEGatherer) GetLocalCandidates() ([]ICECandidate, error) {
if err := g.createAgent(); err != nil {
return nil, err
}
iceCandidates, err := g.agent.GetLocalCandidates()
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
}
@ -222,7 +246,7 @@ func (g *ICEGatherer) GetLocalCandidates() ([]ICECandidate, error) {
}
// OnLocalCandidate sets an event handler which fires when a new local ICE candidate is available
// Take note that the handler is gonna be called with a nil pointer when gathering is finished.
// 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)
}

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package webrtc
import (

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package webrtc
// ICEGatheringState describes the state of the candidate gathering process.

View file

@ -1,3 +1,6 @@
// 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.

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package webrtc
import (
@ -16,3 +19,12 @@ func NewICETCPMux(logger logging.LeveledLogger, listener net.Listener, readBuffe
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,
})
}

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package webrtc
// ICEParameters includes the ICE username fragment

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package webrtc
import (

View file

@ -1,3 +1,6 @@
// 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

View file

@ -1,9 +1,15 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
//go:build !js
// +build !js
package webrtc
import (
"github.com/pion/ice/v2"
"encoding/json"
"github.com/pion/stun"
"github.com/pion/webrtc/v3/pkg/rtcerr"
)
@ -16,8 +22,8 @@ type ICEServer struct {
CredentialType ICECredentialType `json:"credentialType,omitempty"`
}
func (s ICEServer) parseURL(i int) (*ice.URL, error) {
return ice.ParseURL(s.URLs[i])
func (s ICEServer) parseURL(i int) (*stun.URI, error) {
return stun.ParseURI(s.URLs[i])
}
func (s ICEServer) validate() error {
@ -25,8 +31,8 @@ func (s ICEServer) validate() error {
return err
}
func (s ICEServer) urls() ([]*ice.URL, error) {
urls := []*ice.URL{}
func (s ICEServer) urls() ([]*stun.URI, error) {
urls := []*stun.URI{}
for i := range s.URLs {
url, err := s.parseURL(i)
@ -34,7 +40,7 @@ func (s ICEServer) urls() ([]*ice.URL, error) {
return nil, &rtcerr.InvalidAccessError{Err: err}
}
if url.Scheme == ice.SchemeTypeTURN || url.Scheme == ice.SchemeTypeTURNS {
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}
@ -66,3 +72,111 @@ func (s ICEServer) urls() ([]*ice.URL, error) {
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)
}

View file

@ -1,3 +1,7 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
//go:build js && wasm
// +build js,wasm
package webrtc

View file

@ -1,3 +1,7 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
//go:build !js
// +build !js
package webrtc
@ -20,12 +24,10 @@ type ICETransport struct {
lock sync.RWMutex
role ICERole
// Component ICEComponent
// State ICETransportState
// gatheringState ICEGathererState
onConnectionStateChangeHandler atomic.Value // func(ICETransportState)
onSelectedCandidatePairChangeHandler atomic.Value // func(*ICECandidatePair)
onConnectionStateChangeHandler atomic.Value // func(ICETransportState)
internalOnConnectionStateChangeHandler atomic.Value // func(ICETransportState)
onSelectedCandidatePairChangeHandler atomic.Value // func(*ICECandidatePair)
state atomic.Value // ICETransportState
@ -41,25 +43,31 @@ type ICETransport struct {
log logging.LeveledLogger
}
// func (t *ICETransport) GetLocalCandidates() []ICECandidate {
//
// }
//
// func (t *ICETransport) GetRemoteCandidates() []ICECandidate {
//
// }
//
// func (t *ICETransport) GetSelectedCandidatePair() ICECandidatePair {
//
// }
//
// func (t *ICETransport) GetLocalParameters() ICEParameters {
//
// }
//
// func (t *ICETransport) GetRemoteParameters() ICEParameters {
//
// }
// 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 &ICECandidatePair{Local: &local, Remote: &remote}, nil
}
// NewICETransport creates a new NewICETransport.
func NewICETransport(gatherer *ICEGatherer, loggerFactory logging.LoggerFactory) *ICETransport {
@ -152,7 +160,7 @@ func (t *ICETransport) Start(gatherer *ICEGatherer, params ICEParameters, role *
config := mux.Config{
Conn: t.conn,
BufferSize: receiveMTU,
BufferSize: int(t.gatherer.api.settingEngine.getReceiveMTU()),
LoggerFactory: t.loggerFactory,
}
t.mux = mux.NewMux(config)
@ -203,9 +211,8 @@ func (t *ICETransport) OnSelectedCandidatePairChange(f func(*ICECandidatePair))
}
func (t *ICETransport) onSelectedCandidatePairChange(pair *ICECandidatePair) {
handler := t.onSelectedCandidatePairChangeHandler.Load()
if handler != nil {
handler.(func(*ICECandidatePair))(pair)
if handler, ok := t.onSelectedCandidatePairChangeHandler.Load().(func(*ICECandidatePair)); ok {
handler(pair)
}
}
@ -216,9 +223,11 @@ func (t *ICETransport) OnConnectionStateChange(f func(ICETransportState)) {
}
func (t *ICETransport) onConnectionStateChange(state ICETransportState) {
handler := t.onConnectionStateChangeHandler.Load()
if handler != nil {
handler.(func(ICETransportState))(state)
if handler, ok := t.onConnectionStateChangeHandler.Load().(func(ICETransportState)); ok {
handler(state)
}
if handler, ok := t.internalOnConnectionStateChangeHandler.Load().(func(ICETransportState)); ok {
handler(state)
}
}
@ -288,8 +297,8 @@ func (t *ICETransport) AddRemoteCandidate(remoteCandidate *ICECandidate) error {
// State returns the current ice transport state.
func (t *ICETransport) State() ICETransportState {
if v := t.state.Load(); v != nil {
return v.(ICETransportState)
if v, ok := t.state.Load().(ICETransportState); ok {
return v
}
return ICETransportState(0)
}
@ -298,8 +307,7 @@ func (t *ICETransport) setState(i ICETransportState) {
t.state.Store(i)
}
// NewEndpoint registers a new endpoint on the underlying mux.
func (t *ICETransport) NewEndpoint(f mux.MatchFunc) *mux.Endpoint {
func (t *ICETransport) newEndpoint(f mux.MatchFunc) *mux.Endpoint {
t.lock.Lock()
defer t.lock.Unlock()
return t.mux.NewEndpoint(f)

View 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
}

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package webrtc
import (

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package webrtc
import "github.com/pion/ice/v2"

View file

@ -1,3 +1,7 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
//go:build !js
// +build !js
package webrtc
@ -8,7 +12,9 @@ import (
"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.
@ -23,7 +29,7 @@ func RegisterDefaultInterceptors(mediaEngine *MediaEngine, interceptorRegistry *
return err
}
return nil
return ConfigureTWCCSender(mediaEngine, interceptorRegistry)
}
// ConfigureRTCPReports will setup everything necessary for generating Sender and Receiver Reports
@ -62,6 +68,47 @@ func ConfigureNack(mediaEngine *MediaEngine, interceptorRegistry *interceptor.Re
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
}
type interceptorToTrackLocalWriter struct{ interceptor atomic.Value } // interceptor.RTPWriter }
func (i *interceptorToTrackLocalWriter) WriteRTP(header *rtp.Header, payload []byte) (int, error) {
@ -81,7 +128,7 @@ func (i *interceptorToTrackLocalWriter) Write(b []byte) (int, error) {
return i.WriteRTP(&packet.Header, packet.Payload)
}
func createStreamInfo(id string, ssrc SSRC, payloadType PayloadType, codec RTPCodecCapability, webrtcHeaderExtensions []RTPHeaderExtensionParameter) interceptor.StreamInfo {
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})
@ -92,7 +139,7 @@ func createStreamInfo(id string, ssrc SSRC, payloadType PayloadType, codec RTPCo
feedbacks = append(feedbacks, interceptor.RTCPFeedback{Type: f.Type, Parameter: f.Parameter})
}
return interceptor.StreamInfo{
return &interceptor.StreamInfo{
ID: id,
Attributes: interceptor.Attributes{},
SSRC: uint32(ssrc),

View file

@ -0,0 +1,95 @@
// 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"
)
// 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 := 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
}
switch {
case strings.EqualFold(mimetype, "video/h264"):
f = &h264FMTP{
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
}

View 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
}

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package mux
import (
@ -7,7 +10,7 @@ import (
"time"
"github.com/pion/ice/v2"
"github.com/pion/transport/packetio"
"github.com/pion/transport/v2/packetio"
)
// Endpoint implements net.Conn. It is used to read muxed packets.
@ -60,16 +63,16 @@ func (e *Endpoint) RemoteAddr() net.Addr {
}
// SetDeadline is a stub
func (e *Endpoint) SetDeadline(t time.Time) error {
func (e *Endpoint) SetDeadline(time.Time) error {
return nil
}
// SetReadDeadline is a stub
func (e *Endpoint) SetReadDeadline(t time.Time) error {
func (e *Endpoint) SetReadDeadline(time.Time) error {
return nil
}
// SetWriteDeadline is a stub
func (e *Endpoint) SetWriteDeadline(t time.Time) error {
func (e *Endpoint) SetWriteDeadline(time.Time) error {
return nil
}

View file

@ -1,12 +1,18 @@
// 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/packetio"
"github.com/pion/transport/v2/packetio"
)
// The maximum amount of data that can be buffered before returning errors.
@ -54,8 +60,6 @@ func (m *Mux) NewEndpoint(f MatchFunc) *Endpoint {
}
// Set a maximum size of the buffer in bytes.
// NOTE: We actually won't get anywhere close to this limit.
// SRTP will constantly read from the endpoint and drop packets if it's full.
e.buffer.SetLimitSize(maxBufferSize)
m.lock.Lock()
@ -76,8 +80,8 @@ func (m *Mux) RemoveEndpoint(e *Endpoint) {
func (m *Mux) Close() error {
m.lock.Lock()
for e := range m.endpoints {
err := e.close()
if err != nil {
if err := e.close(); err != nil {
m.lock.Unlock()
return err
}
@ -104,12 +108,19 @@ func (m *Mux) readLoop() {
buf := make([]byte, m.bufferSize)
for {
n, err := m.nextConn.Read(buf)
if err != nil {
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
}
err = m.dispatch(buf[:n])
if err != nil {
if err = m.dispatch(buf[:n]); err != nil {
m.log.Errorf("mux: ending readLoop dispatch error %s", err.Error())
return
}
}
@ -129,7 +140,7 @@ func (m *Mux) dispatch(buf []byte) error {
if endpoint == nil {
if len(buf) > 0 {
m.log.Warnf("Warning: mux: no endpoint for packet starting with %d\n", 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")
}
@ -137,9 +148,12 @@ func (m *Mux) dispatch(buf []byte) error {
}
_, err := endpoint.buffer.Write(buf)
if err != nil {
return err
// 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 nil
return err
}

View file

@ -1,32 +1,23 @@
package mux
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
import (
"bytes"
"encoding/binary"
)
package mux
// MatchFunc allows custom logic for mapping packets to an Endpoint
type MatchFunc func([]byte) bool
// MatchAll always returns true
func MatchAll(b []byte) bool {
func MatchAll([]byte) bool {
return true
}
// MatchNone always returns false
func MatchNone(b []byte) bool {
return false
}
// MatchRange is a MatchFunc that accepts packets with the first byte in [lower..upper]
func MatchRange(lower, upper byte) MatchFunc {
return func(buf []byte) bool {
if len(buf) < 1 {
return false
}
b := buf[0]
return b >= lower && b <= upper
// 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
@ -43,34 +34,16 @@ func MatchRange(lower, upper byte) MatchFunc {
// | [128..191] -+--> forward to RTP/RTCP
// +----------------+
// MatchSTUN is a MatchFunc that accepts packets with the first byte in [0..3]
// as defied in RFC7983
func MatchSTUN(b []byte) bool {
return MatchRange(0, 3)(b)
}
// MatchZRTP is a MatchFunc that accepts packets with the first byte in [16..19]
// as defied in RFC7983
func MatchZRTP(b []byte) bool {
return MatchRange(16, 19)(b)
}
// 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)
}
// MatchTURN is a MatchFunc that accepts packets with the first byte in [64..79]
// as defied in RFC7983
func MatchTURN(b []byte) bool {
return MatchRange(64, 79)(b)
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)
return MatchRange(128, 191, b)
}
func isRTCP(buf []byte) bool {
@ -78,16 +51,7 @@ func isRTCP(buf []byte) bool {
if len(buf) < 4 {
return false
}
var rtcpPacketType uint8
r := bytes.NewReader([]byte{buf[1]})
if err := binary.Read(r, binary.BigEndian, &rtcpPacketType); err != nil {
return false
} else if rtcpPacketType >= 192 && rtcpPacketType <= 223 {
return true
}
return false
return buf[1] >= 192 && buf[1] <= 223
}
// MatchSRTP is a MatchFunc that only matches SRTP and not SRTCP

View file

@ -1,3 +1,6 @@
// 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
@ -39,7 +42,7 @@ func FlattenErrs(errs []error) error {
return multiError(errs2)
}
type multiError []error
type multiError []error //nolint:errname
func (me multiError) Error() string {
var errstrings []string
@ -62,7 +65,7 @@ func (me multiError) Is(err error) bool {
if errors.Is(e, err) {
return true
}
if me2, ok := e.(multiError); ok {
if me2, ok := e.(multiError); ok { //nolint:errorlint
if me2.Is(err) {
return true
}

View file

@ -1,13 +0,0 @@
// +build js,go1.14
package webrtc
import "syscall/js"
func jsValueIsUndefined(v js.Value) bool {
return v.IsUndefined()
}
func jsValueIsNull(v js.Value) bool {
return v.IsNull()
}

View file

@ -1,13 +0,0 @@
// +build js,!go1.14
package webrtc
import "syscall/js"
func jsValueIsUndefined(v js.Value) bool {
return v == js.Undefined()
}
func jsValueIsNull(v js.Value) bool {
return v == js.Null()
}

View file

@ -1,3 +1,7 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
//go:build js && wasm
// +build js,wasm
package webrtc
@ -22,7 +26,6 @@ func awaitPromise(promise js.Value) (js.Value, error) {
return js.Undefined()
})
defer thenFunc.Release()
promise.Call("then", thenFunc)
catchFunc := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
go func() {
@ -31,7 +34,8 @@ func awaitPromise(promise js.Value) (js.Value, error) {
return js.Undefined()
})
defer catchFunc.Release()
promise.Call("catch", catchFunc)
promise.Call("then", thenFunc).Call("catch", catchFunc)
select {
case result := <-resultsChan:
@ -42,7 +46,7 @@ func awaitPromise(promise js.Value) (js.Value, error) {
}
func valueToUint16Pointer(val js.Value) *uint16 {
if jsValueIsNull(val) || jsValueIsUndefined(val) {
if val.IsNull() || val.IsUndefined() {
return nil
}
convertedVal := uint16(val.Int())
@ -50,7 +54,7 @@ func valueToUint16Pointer(val js.Value) *uint16 {
}
func valueToStringPointer(val js.Value) *string {
if jsValueIsNull(val) || jsValueIsUndefined(val) {
if val.IsNull() || val.IsUndefined() {
return nil
}
stringVal := val.String()
@ -79,28 +83,28 @@ func interfaceToValueOrUndefined(val interface{}) js.Value {
}
func valueToStringOrZero(val js.Value) string {
if jsValueIsUndefined(val) || jsValueIsNull(val) {
if val.IsUndefined() || val.IsNull() {
return ""
}
return val.String()
}
func valueToUint8OrZero(val js.Value) uint8 {
if jsValueIsUndefined(val) || jsValueIsNull(val) {
if val.IsUndefined() || val.IsNull() {
return 0
}
return uint8(val.Int())
}
func valueToUint16OrZero(val js.Value) uint16 {
if jsValueIsNull(val) || jsValueIsUndefined(val) {
if val.IsNull() || val.IsUndefined() {
return 0
}
return uint16(val.Int())
}
func valueToUint32OrZero(val js.Value) uint32 {
if jsValueIsNull(val) || jsValueIsUndefined(val) {
if val.IsNull() || val.IsUndefined() {
return 0
}
return uint32(val.Int())

View file

@ -1,3 +1,7 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
//go:build !js
// +build !js
package webrtc
@ -6,17 +10,22 @@ 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"
@ -26,6 +35,9 @@ const (
// 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"
@ -46,8 +58,7 @@ type mediaEngineHeaderExtension struct {
}
// A MediaEngine defines the codecs supported by a PeerConnection, and the
// configuration of those codecs. A MediaEngine must not be shared between
// PeerConnections.
// configuration of those codecs.
type MediaEngine struct {
// If we have attempted to negotiate a codec type yet.
negotiatedVideo, negotiatedAudio bool
@ -57,6 +68,8 @@ type MediaEngine struct {
headerExtensions []mediaEngineHeaderExtension
negotiatedHeaderExtensions map[int]mediaEngineHeaderExtension
mu sync.RWMutex
}
// RegisterDefaultCodecs registers the default codecs supported by Pion WebRTC.
@ -196,6 +209,9 @@ func (m *MediaEngine) addCodec(codecs []RTPCodecParameters, codec RTPCodecParame
// 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:
@ -211,6 +227,9 @@ func (m *MediaEngine) RegisterCodec(codec RTPCodecParameters, typ RTPCodecType)
// 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{}
}
@ -251,6 +270,9 @@ func (m *MediaEngine) RegisterHeaderExtension(extension RTPHeaderExtensionCapabi
// 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 {
@ -268,6 +290,9 @@ func (m *MediaEngine) RegisterFeedback(feedback RTCPFeedback, typ RTPCodecType)
// 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
}
@ -284,6 +309,8 @@ func (m *MediaEngine) getHeaderExtensionID(extension RTPHeaderExtensionCapabilit
// 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...),
@ -295,15 +322,39 @@ func (m *MediaEngine) copy() *MediaEngine {
return cloned
}
func (m *MediaEngine) getCodecByPayload(payloadType PayloadType) (RTPCodecParameters, RTPCodecType, error) {
for _, codec := range m.negotiatedVideoCodecs {
func findCodecByPayload(codecs []RTPCodecParameters, payloadType PayloadType) *RTPCodecParameters {
for _, codec := range codecs {
if codec.PayloadType == payloadType {
return codec, RTPCodecTypeVideo, nil
return &codec
}
}
for _, codec := range m.negotiatedAudioCodecs {
if codec.PayloadType == payloadType {
return codec, RTPCodecTypeAudio, nil
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
}
}
@ -311,6 +362,9 @@ func (m *MediaEngine) getCodecByPayload(payloadType PayloadType) (RTPCodecParame
}
func (m *MediaEngine) collectStats(collector *statsReportCollector) {
m.mu.RLock()
defer m.mu.RUnlock()
statsLoop := func(codecs []RTPCodecParameters) {
for _, codec := range codecs {
collector.Collecting()
@ -334,21 +388,46 @@ func (m *MediaEngine) collectStats(collector *statsReportCollector) {
}
// Look up a codec and enable if it exists
func (m *MediaEngine) matchRemoteCodec(remoteCodec RTPCodecParameters, typ RTPCodecType) (codecMatchType, error) {
func (m *MediaEngine) matchRemoteCodec(remoteCodec RTPCodecParameters, typ RTPCodecType, exactMatches, partialMatches []RTPCodecParameters) (codecMatchType, error) {
codecs := m.videoCodecs
if typ == RTPCodecTypeAudio {
codecs = m.audioCodecs
}
if strings.HasPrefix(remoteCodec.RTPCodecCapability.SDPFmtpLine, "apt=") {
payloadType, err := strconv.Atoi(strings.TrimPrefix(remoteCodec.RTPCodecCapability.SDPFmtpLine, "apt="))
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 codecMatchNone, err
}
if _, _, err = m.getCodecByPayload(PayloadType(payloadType)); err != nil {
aptMatch := codecMatchNone
for _, codec := range exactMatches {
if codec.PayloadType == PayloadType(payloadType) {
aptMatch = codecMatchExact
break
}
}
if aptMatch == codecMatchNone {
for _, codec := range partialMatches {
if codec.PayloadType == PayloadType(payloadType) {
aptMatch = codecMatchPartial
break
}
}
}
if aptMatch == codecMatchNone {
return codecMatchNone, nil // not an error, we just ignore this codec we don't support
}
// if apt's media codec is partial match, then apt codec must be partial match too
_, matchType := codecParametersFuzzySearch(remoteCodec, codecs)
if matchType == codecMatchExact && aptMatch == codecMatchPartial {
matchType = codecMatchPartial
}
return matchType, nil
}
_, matchType := codecParametersFuzzySearch(remoteCodec, codecs)
@ -393,6 +472,9 @@ func (m *MediaEngine) pushCodecs(codecs []RTPCodecParameters, typ RTPCodecType)
// 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 {
@ -415,7 +497,7 @@ func (m *MediaEngine) updateFromRemoteDescription(desc sdp.SessionDescription) e
partialMatches := make([]RTPCodecParameters, 0, len(codecs))
for _, codec := range codecs {
matchType, mErr := m.matchRemoteCodec(codec, typ)
matchType, mErr := m.matchRemoteCodec(codec, typ, exactMatches, partialMatches)
if mErr != nil {
return mErr
}
@ -453,6 +535,9 @@ func (m *MediaEngine) updateFromRemoteDescription(desc sdp.SessionDescription) e
}
func (m *MediaEngine) getCodecsByKind(typ RTPCodecType) []RTPCodecParameters {
m.mu.RLock()
defer m.mu.RUnlock()
if typ == RTPCodecTypeVideo {
if m.negotiatedVideo {
return m.negotiatedVideoCodecs
@ -470,9 +555,14 @@ func (m *MediaEngine) getCodecsByKind(typ RTPCodecType) []RTPCodecParameters {
return nil
}
func (m *MediaEngine) getRTPParametersByKind(typ RTPCodecType, directions []RTPTransceiverDirection) RTPParameters {
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 {
@ -481,16 +571,40 @@ func (m *MediaEngine) getRTPParametersByKind(typ RTPCodecType, directions []RTPT
}
}
} else {
for id, e := range m.headerExtensions {
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 + 1, URI: e.uri})
headerExtensions = append(headerExtensions, RTPHeaderExtensionParameter{ID: id, URI: e.uri})
}
}
}
return RTPParameters{
HeaderExtensions: headerExtensions,
Codecs: m.getCodecsByKind(typ),
Codecs: foundCodecs,
}
}
@ -500,6 +614,8 @@ func (m *MediaEngine) getRTPParametersByPayloadType(payloadType PayloadType) (RT
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 {
@ -520,9 +636,13 @@ func payloaderForCodec(codec RTPCodecCapability) (rtp.Payloader, error) {
case strings.ToLower(MimeTypeOpus):
return &codecs.OpusPayloader{}, nil
case strings.ToLower(MimeTypeVP8):
return &codecs.VP8Payloader{}, nil
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):

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package webrtc
import (

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package webrtc
// OAuthCredential represents OAuth credential information which is used by

View file

@ -1,3 +1,6 @@
// 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

View file

@ -1,6 +1,10 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package webrtc
import (
"container/list"
"sync"
)
@ -11,11 +15,13 @@ type operation func()
type operations struct {
mu sync.Mutex
busy bool
ops []operation
ops *list.List
}
func newOperations() *operations {
return &operations{}
return &operations{
ops: list.New(),
}
}
// Enqueue adds a new action to be executed. If there are no actions scheduled,
@ -27,7 +33,7 @@ func (o *operations) Enqueue(op operation) {
o.mu.Lock()
running := o.busy
o.ops = append(o.ops, op)
o.ops.PushBack(op)
o.busy = true
o.mu.Unlock()
@ -40,7 +46,7 @@ func (o *operations) Enqueue(op operation) {
func (o *operations) IsEmpty() bool {
o.mu.Lock()
defer o.mu.Unlock()
return len(o.ops) == 0
return o.ops.Len() == 0
}
// Done blocks until all currently enqueued operations are finished executing.
@ -57,20 +63,23 @@ func (o *operations) Done() {
func (o *operations) pop() func() {
o.mu.Lock()
defer o.mu.Unlock()
if len(o.ops) == 0 {
if o.ops.Len() == 0 {
return nil
}
fn := o.ops[0]
o.ops = o.ops[1:]
return fn
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()
if len(o.ops) == 0 {
if o.ops.Len() == 0 {
o.busy = false
return
}

File diff suppressed because it is too large Load diff

View file

@ -1,3 +1,7 @@
// 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.
@ -55,6 +59,7 @@ func (api *API) NewPeerConnection(configuration Configuration) (_ *PeerConnectio
}, nil
}
// JSValue returns the underlying PeerConnection
func (pc *PeerConnection) JSValue() js.Value {
return pc.underlying
}
@ -488,6 +493,54 @@ func (pc *PeerConnection) setGatherCompleteHandler(handler func()) {
}
}
// 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.
@ -516,18 +569,35 @@ func iceServersToValue(iceServers []ICEServer) js.Value {
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 {
return js.ValueOf(map[string]interface{}{
"urls": stringsToValue(server.URLs), // required
"username": stringToValueOrUndefined(server.Username),
// Note: credential and credentialType are not currently supported.
// "credential": interfaceToValueOrUndefined(server.Credential),
// "credentialType": stringEnumToValueOrUndefined(server.CredentialType.String()),
})
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 jsValueIsNull(configValue) || jsValueIsUndefined(configValue) {
if configValue.IsNull() || configValue.IsUndefined() {
return Configuration{}
}
return Configuration{
@ -544,7 +614,7 @@ func valueToConfiguration(configValue js.Value) Configuration {
}
func valueToICEServers(iceServersValue js.Value) []ICEServer {
if jsValueIsNull(iceServersValue) || jsValueIsUndefined(iceServersValue) {
if iceServersValue.IsNull() || iceServersValue.IsUndefined() {
return nil
}
iceServers := make([]ICEServer, iceServersValue.Length())
@ -554,21 +624,43 @@ func valueToICEServers(iceServersValue js.Value) []ICEServer {
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 {
return 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: iceServerValue.Get("credential"),
// CredentialType: newICECredentialType(valueToStringOrZero(iceServerValue.Get("credentialType"))),
Credential: valueToICECredential(iceServerValue.Get("credential")),
CredentialType: tpe,
}
return s
}
func valueToICECandidate(val js.Value) *ICECandidate {
if jsValueIsNull(val) || jsValueIsUndefined(val) {
if val.IsNull() || val.IsUndefined() {
return nil
}
if jsValueIsUndefined(val.Get("protocol")) && !jsValueIsUndefined(val.Get("candidate")) {
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 {
@ -619,7 +711,7 @@ func sessionDescriptionToValue(desc *SessionDescription) js.Value {
}
func valueToSessionDescription(descValue js.Value) *SessionDescription {
if jsValueIsNull(descValue) || jsValueIsUndefined(descValue) {
if descValue.IsNull() || descValue.IsUndefined() {
return nil
}
return &SessionDescription{
@ -674,3 +766,9 @@ func dataChannelInitToValue(options *DataChannelInit) js.Value {
"id": uint16PointerToValue(options.ID),
})
}
func rtpTransceiverInitInitToValue(init RTPTransceiverInit) js.Value {
return js.ValueOf(map[string]interface{}{
"direction": init.Direction.String(),
})
}

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package webrtc
// PeerConnectionState indicates the state of the PeerConnection.

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
// Package h264reader implements a H264 Annex-B Reader
package h264reader
@ -14,6 +17,7 @@ type H264Reader struct {
countOfConsecutiveZeroBytes int
nalPrefixParsed bool
readBuffer []byte
tmpReadBuf []byte
}
var (
@ -32,6 +36,7 @@ func NewReader(in io.Reader) (*H264Reader, error) {
nalBuffer: make([]byte, 0),
nalPrefixParsed: false,
readBuffer: make([]byte, 0),
tmpReadBuf: make([]byte, 4096),
}
return reader, nil
@ -49,15 +54,16 @@ type NAL struct {
Data []byte // header byte + rbsp
}
func (reader *H264Reader) read(numToRead int) (data []byte) {
func (reader *H264Reader) read(numToRead int) (data []byte, e error) {
for len(reader.readBuffer) < numToRead {
buf := make([]byte, 4096)
n, err := reader.stream.Read(buf)
if n == 0 || err != nil {
n, err := reader.stream.Read(reader.tmpReadBuf)
if err != nil {
return nil, err
}
if n == 0 {
break
}
buf = buf[0:n]
reader.readBuffer = append(reader.readBuffer, buf...)
reader.readBuffer = append(reader.readBuffer, reader.tmpReadBuf[0:n]...)
}
var numShouldRead int
if numToRead <= len(reader.readBuffer) {
@ -67,14 +73,17 @@ func (reader *H264Reader) read(numToRead int) (data []byte) {
}
data = reader.readBuffer[0:numShouldRead]
reader.readBuffer = reader.readBuffer[numShouldRead:]
return data
return data, nil
}
func (reader *H264Reader) bitStreamStartsWithH264Prefix() (prefixLength int, e error) {
nalPrefix3Bytes := []byte{0, 0, 1}
nalPrefix4Bytes := []byte{0, 0, 0, 1}
prefixBuffer := reader.read(4)
prefixBuffer, e := reader.read(4)
if e != nil {
return
}
n := len(prefixBuffer)
@ -121,7 +130,11 @@ func (reader *H264Reader) NextNAL() (*NAL, error) {
}
for {
buffer := reader.read(1)
buffer, err := reader.read(1)
if err != nil {
break
}
n := len(buffer)
if n != 1 {
@ -135,9 +148,8 @@ func (reader *H264Reader) NextNAL() (*NAL, error) {
if nal.UnitType == NalUnitTypeSEI {
reader.nalBuffer = nil
continue
} else {
break
}
break
}
reader.nalBuffer = append(reader.nalBuffer, readByte)
@ -166,12 +178,14 @@ func (reader *H264Reader) processByte(readByte byte) (nalFound bool) {
if reader.countOfConsecutiveZeroBytes > 2 {
countOfConsecutiveZeroBytesInPrefix = 3
}
nalUnitLength := len(reader.nalBuffer) - countOfConsecutiveZeroBytesInPrefix
reader.nalBuffer = reader.nalBuffer[0:nalUnitLength]
nalFound = true
} else {
reader.countOfConsecutiveZeroBytes = 0
if nalUnitLength := len(reader.nalBuffer) - countOfConsecutiveZeroBytesInPrefix; nalUnitLength > 0 {
reader.nalBuffer = reader.nalBuffer[0:nalUnitLength]
nalFound = true
}
}
reader.countOfConsecutiveZeroBytes = 0
default:
reader.countOfConsecutiveZeroBytes = 0
}

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package h264reader
import "strconv"

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
// Package h264writer implements H264 media container writer
package h264writer
@ -18,14 +21,15 @@ type (
// Therefore, only 1-23, 24 (STAP-A), 28 (FU-A) NAL types are allowed.
// https://tools.ietf.org/html/rfc6184#section-5.2
H264Writer struct {
writer io.Writer
hasKeyFrame bool
writer io.Writer
hasKeyFrame bool
cachedPacket *codecs.H264Packet
}
)
// New builds a new H264 writer
func New(filename string) (*H264Writer, error) {
f, err := os.Create(filename)
f, err := os.Create(filename) //nolint:gosec
if err != nil {
return nil, err
}
@ -53,7 +57,11 @@ func (h *H264Writer) WriteRTP(packet *rtp.Packet) error {
}
}
data, err := (&codecs.H264Packet{}).Unmarshal(packet.Payload)
if h.cachedPacket == nil {
h.cachedPacket = &codecs.H264Packet{}
}
data, err := h.cachedPacket.Unmarshal(packet.Payload)
if err != nil {
return err
}
@ -65,6 +73,7 @@ func (h *H264Writer) WriteRTP(packet *rtp.Packet) error {
// Close closes the underlying writer
func (h *H264Writer) Close() error {
h.cachedPacket = nil
if h.writer != nil {
if closer, ok := h.writer.(io.Closer); ok {
return closer.Close()
@ -75,16 +84,25 @@ func (h *H264Writer) Close() error {
}
func isKeyFrame(data []byte) bool {
const typeSTAPA = 24
const (
typeSTAPA = 24
typeSPS = 7
naluTypeBitmask = 0x1F
)
var word uint32
payload := bytes.NewReader(data)
err := binary.Read(payload, binary.BigEndian, &word)
if err != nil || (word&0x1F000000)>>24 != typeSTAPA {
if err := binary.Read(payload, binary.BigEndian, &word); err != nil {
return false
}
return word&0x1F == 7
naluType := (word >> 24) & naluTypeBitmask
if naluType == typeSTAPA && word&naluTypeBitmask == typeSPS {
return true
} else if naluType == typeSPS {
return true
}
return false
}

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
// Package ivfwriter implements IVF media container writer
package ivfwriter
@ -9,11 +12,21 @@ import (
"github.com/pion/rtp"
"github.com/pion/rtp/codecs"
"github.com/pion/rtp/pkg/frame"
)
var (
errFileNotOpened = errors.New("file not opened")
errInvalidNilPacket = errors.New("invalid nil packet")
errCodecAlreadySet = errors.New("codec is already set")
errNoSuchCodec = errors.New("no codec for this MimeType")
)
const (
mimeTypeVP8 = "video/VP8"
mimeTypeAV1 = "video/AV1"
ivfFileHeaderSignature = "DKIF"
)
// IVFWriter is used to take RTP packets and write them to an IVF on disk
@ -21,16 +34,23 @@ type IVFWriter struct {
ioWriter io.Writer
count uint64
seenKeyFrame bool
isVP8, isAV1 bool
// VP8
currentFrame []byte
// AV1
av1Frame frame.AV1
}
// New builds a new IVF writer
func New(fileName string) (*IVFWriter, error) {
f, err := os.Create(fileName)
func New(fileName string, opts ...Option) (*IVFWriter, error) {
f, err := os.Create(fileName) //nolint:gosec
if err != nil {
return nil, err
}
writer, err := NewWith(f)
writer, err := NewWith(f, opts...)
if err != nil {
return nil, err
}
@ -39,7 +59,7 @@ func New(fileName string) (*IVFWriter, error) {
}
// NewWith initialize a new IVF writer with an io.Writer output
func NewWith(out io.Writer) (*IVFWriter, error) {
func NewWith(out io.Writer, opts ...Option) (*IVFWriter, error) {
if out == nil {
return nil, errFileNotOpened
}
@ -48,6 +68,17 @@ func NewWith(out io.Writer) (*IVFWriter, error) {
ioWriter: out,
seenKeyFrame: false,
}
for _, o := range opts {
if err := o(writer); err != nil {
return nil, err
}
}
if !writer.isAV1 && !writer.isVP8 {
writer.isVP8 = true
}
if err := writer.writeHeader(); err != nil {
return nil, err
}
@ -56,10 +87,17 @@ func NewWith(out io.Writer) (*IVFWriter, error) {
func (i *IVFWriter) writeHeader() error {
header := make([]byte, 32)
copy(header[0:], "DKIF") // DKIF
binary.LittleEndian.PutUint16(header[4:], 0) // Version
binary.LittleEndian.PutUint16(header[6:], 32) // Header size
copy(header[8:], "VP80") // FOURCC
copy(header[0:], ivfFileHeaderSignature) // DKIF
binary.LittleEndian.PutUint16(header[4:], 0) // Version
binary.LittleEndian.PutUint16(header[6:], 32) // Header size
// FOURCC
if i.isVP8 {
copy(header[8:], "VP80")
} else if i.isAV1 {
copy(header[8:], "AV01")
}
binary.LittleEndian.PutUint16(header[12:], 640) // Width in pixels
binary.LittleEndian.PutUint16(header[14:], 480) // Height in pixels
binary.LittleEndian.PutUint32(header[16:], 30) // Framerate denominator
@ -71,47 +109,72 @@ func (i *IVFWriter) writeHeader() error {
return err
}
// WriteRTP adds a new packet and writes the appropriate headers for it
func (i *IVFWriter) WriteRTP(packet *rtp.Packet) error {
if i.ioWriter == nil {
return errFileNotOpened
}
vp8Packet := codecs.VP8Packet{}
if _, err := vp8Packet.Unmarshal(packet.Payload); err != nil {
return err
}
isKeyFrame := vp8Packet.Payload[0] & 0x01
switch {
case !i.seenKeyFrame && isKeyFrame == 1:
return nil
case i.currentFrame == nil && vp8Packet.S != 1:
return nil
}
i.seenKeyFrame = true
i.currentFrame = append(i.currentFrame, vp8Packet.Payload[0:]...)
if !packet.Marker {
return nil
} else if len(i.currentFrame) == 0 {
return nil
}
func (i *IVFWriter) writeFrame(frame []byte) error {
frameHeader := make([]byte, 12)
binary.LittleEndian.PutUint32(frameHeader[0:], uint32(len(i.currentFrame))) // Frame length
binary.LittleEndian.PutUint64(frameHeader[4:], i.count) // PTS
binary.LittleEndian.PutUint32(frameHeader[0:], uint32(len(frame))) // Frame length
binary.LittleEndian.PutUint64(frameHeader[4:], i.count) // PTS
i.count++
if _, err := i.ioWriter.Write(frameHeader); err != nil {
return err
} else if _, err := i.ioWriter.Write(i.currentFrame); err != nil {
return err
}
_, err := i.ioWriter.Write(frame)
return err
}
// WriteRTP adds a new packet and writes the appropriate headers for it
func (i *IVFWriter) WriteRTP(packet *rtp.Packet) error {
if i.ioWriter == nil {
return errFileNotOpened
} else if len(packet.Payload) == 0 {
return nil
}
if i.isVP8 {
vp8Packet := codecs.VP8Packet{}
if _, err := vp8Packet.Unmarshal(packet.Payload); err != nil {
return err
}
isKeyFrame := vp8Packet.Payload[0] & 0x01
switch {
case !i.seenKeyFrame && isKeyFrame == 1:
return nil
case i.currentFrame == nil && vp8Packet.S != 1:
return nil
}
i.seenKeyFrame = true
i.currentFrame = append(i.currentFrame, vp8Packet.Payload[0:]...)
if !packet.Marker {
return nil
} else if len(i.currentFrame) == 0 {
return nil
}
if err := i.writeFrame(i.currentFrame); err != nil {
return err
}
i.currentFrame = nil
} else if i.isAV1 {
av1Packet := &codecs.AV1Packet{}
if _, err := av1Packet.Unmarshal(packet.Payload); err != nil {
return err
}
obus, err := i.av1Frame.ReadFrames(av1Packet)
if err != nil {
return err
}
for j := range obus {
if err := i.writeFrame(obus[j]); err != nil {
return err
}
}
}
i.currentFrame = nil
return nil
}
@ -145,3 +208,26 @@ func (i *IVFWriter) Close() error {
return nil
}
// An Option configures a SampleBuilder.
type Option func(i *IVFWriter) error
// WithCodec configures if IVFWriter is writing AV1 or VP8 packets to disk
func WithCodec(mimeType string) Option {
return func(i *IVFWriter) error {
if i.isVP8 || i.isAV1 {
return errCodecAlreadySet
}
switch mimeType {
case mimeTypeVP8:
i.isVP8 = true
case mimeTypeAV1:
i.isAV1 = true
default:
return errNoSuchCodec
}
return nil
}
}

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
// Package media provides media writer and filters
package media
@ -9,9 +12,11 @@ import (
// A Sample contains encoded media and timing information
type Sample struct {
Data []byte
Timestamp time.Time
Duration time.Duration
Data []byte
Timestamp time.Time
Duration time.Duration
PacketTimestamp uint32
PrevDroppedPackets uint16
}
// Writer defines an interface to handle

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
// Package oggreader implements the Ogg media container reader
package oggreader
@ -29,7 +32,7 @@ var (
// OggReader is used to read Ogg files and return page payloads
type OggReader struct {
stream io.ReadSeeker
stream io.Reader
bytesReadSuccesfully int64
checksumTable *[256]uint32
doChecksum bool
@ -64,12 +67,12 @@ type OggPageHeader struct {
}
// NewWith returns a new Ogg reader and Ogg header
// with an io.ReadSeeker input
func NewWith(in io.ReadSeeker) (*OggReader, *OggHeader, error) {
// with an io.Reader input
func NewWith(in io.Reader) (*OggReader, *OggHeader, error) {
return newWith(in /* doChecksum */, true)
}
func newWith(in io.ReadSeeker, doChecksum bool) (*OggReader, *OggHeader, error) {
func newWith(in io.Reader, doChecksum bool) (*OggReader, *OggHeader, error) {
if in == nil {
return nil, nil, errNilStream
}
@ -192,7 +195,7 @@ func (o *OggReader) ParseNextPage() ([]byte, *OggPageHeader, error) {
// ResetReader resets the internal stream of OggReader. This is useful
// for live streams, where the end of the file might be read without the
// data being finished.
func (o *OggReader) ResetReader(reset func(bytesRead int64) io.ReadSeeker) {
func (o *OggReader) ResetReader(reset func(bytesRead int64) io.Reader) {
o.stream = reset(o.bytesReadSuccesfully)
}

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
// Package oggwriter implements OGG media container writer
package oggwriter
@ -43,7 +46,7 @@ type OggWriter struct {
// New builds a new OGG Opus writer
func New(fileName string, sampleRate uint32, channelCount uint16) (*OggWriter, error) {
f, err := os.Create(fileName)
f, err := os.Create(fileName) //nolint:gosec
if err != nil {
return nil, err
}
@ -172,6 +175,9 @@ func (i *OggWriter) WriteRTP(packet *rtp.Packet) error {
if packet == nil {
return errInvalidNilPacket
}
if len(packet.Payload) == 0 {
return nil
}
opusPacket := codecs.OpusPacket{}
if _, err := opusPacket.Unmarshal(packet.Payload); err != nil {

View file

@ -1,3 +1,6 @@
// 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

View file

@ -1,15 +1,6 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:base"
],
"postUpdateOptions": [
"gomodTidy"
],
"commitBody": "Generated by renovateBot",
"packageRules": [
{
"packagePatterns": ["^golang.org/x/"],
"schedule": ["on the first day of the month"]
}
"github>pion/renovate-config"
]
}

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package webrtc
const (

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package webrtc
import (

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package webrtc
// RTPCapabilities represents the capabilities of a transceiver

View file

@ -1,7 +1,12 @@
// 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
@ -97,19 +102,19 @@ const (
// 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
// Exact matches means fmtp line cannot be empty
for _, c := range haystack {
if strings.EqualFold(c.RTPCodecCapability.MimeType, needle.RTPCodecCapability.MimeType) &&
c.RTPCodecCapability.SDPFmtpLine == needle.RTPCodecCapability.SDPFmtpLine {
cfmtp := fmtp.Parse(c.RTPCodecCapability.MimeType, c.RTPCodecCapability.SDPFmtpLine)
if needleFmtp.Match(cfmtp) {
return c, codecMatchExact
}
}
// Fallback to just MimeType if either haystack or needle does not have fmtpline set
// Fallback to just MimeType
for _, c := range haystack {
if strings.EqualFold(c.RTPCodecCapability.MimeType, needle.RTPCodecCapability.MimeType) &&
(c.RTPCodecCapability.SDPFmtpLine == "" || needle.RTPCodecCapability.SDPFmtpLine == "") {
if strings.EqualFold(c.RTPCodecCapability.MimeType, needle.RTPCodecCapability.MimeType) {
return c, codecMatchPartial
}
}

View file

@ -1,10 +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"`
RID string `json:"rid"`
SSRC SSRC `json:"ssrc"`
PayloadType PayloadType `json:"payloadType"`
RTX RTPRtxParameters `json:"rtx"`
}

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package webrtc
// RTPDecodingParameters provides information relating to both encoding and decoding.

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package webrtc
// RTPEncodingParameters provides information relating to both encoding and decoding.

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package webrtc
// RTPReceiveParameters contains the RTP stack settings used by receivers

View file

@ -1,3 +1,7 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
//go:build !js
// +build !js
package webrtc
@ -15,15 +19,23 @@ import (
)
// trackStreams maintains a mapping of RTP/RTCP streams to a specific track
// a RTPReceiver may contain multiple streams if we are dealing with Multicast
// 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
repairRtcpReadStream *srtp.ReadStreamSRTCP
repairRtcpInterceptor interceptor.RTCPReader
}
// RTPReceiver allows an application to inspect the receipt of a TrackRemote
@ -36,6 +48,8 @@ type RTPReceiver struct {
closed, received chan interface{}
mu sync.RWMutex
tr *RTPTransceiver
// A reference to the associated api object
api *API
}
@ -58,6 +72,12 @@ func (api *API) NewRTPReceiver(kind RTPCodecType, transport *DTLSTransport) (*RT
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 {
@ -66,10 +86,20 @@ func (r *RTPReceiver) Transport() *DTLSTransport {
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 {
return r.api.mediaEngine.getRTPParametersByKind(r.kind, []RTPTransceiverDirection{RTPTransceiverDirectionRecvonly})
r.mu.RLock()
defer r.mu.RUnlock()
return r.getParameters()
}
// Track returns the RtpTransceiver TrackRemote
@ -96,8 +126,27 @@ func (r *RTPReceiver) Tracks() []*TrackRemote {
return tracks
}
// Receive initialize the track and starts all the transports
func (r *RTPReceiver) Receive(parameters RTPReceiveParameters) error {
// 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].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 {
@ -107,45 +156,59 @@ func (r *RTPReceiver) Receive(parameters RTPReceiveParameters) error {
}
defer close(r.received)
if len(parameters.Encodings) == 1 && parameters.Encodings[0].SSRC != 0 {
t := trackStreams{
track: newTrackRemote(
r.kind,
parameters.Encodings[0].SSRC,
"",
r,
),
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
}
globalParams := r.GetParameters()
codec := RTPCodecCapability{}
if len(globalParams.Codecs) != 0 {
codec = globalParams.Codecs[0].RTPCodecCapability
var t *trackStreams
for idx, ts := range r.tracks {
if ts.track != nil && parameters.Encodings[i].SSRC != 0 && 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)
}
streamInfo := createStreamInfo("", parameters.Encodings[0].SSRC, 0, codec, globalParams.HeaderExtensions)
var err error
if t.rtpReadStream, t.rtpInterceptor, t.rtcpReadStream, t.rtcpInterceptor, err = r.streamsForSSRC(parameters.Encodings[0].SSRC, streamInfo); err != nil {
return err
if parameters.Encodings[i].SSRC != 0 {
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
}
}
r.tracks = append(r.tracks, t)
} else {
for _, encoding := range parameters.Encodings {
r.tracks = append(r.tracks, trackStreams{
track: newTrackRemote(
r.kind,
0,
encoding.RID,
r,
),
})
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 {
@ -174,7 +237,7 @@ func (r *RTPReceiver) ReadSimulcast(b []byte, rid string) (n int, a interceptor.
// 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, receiveMTU)
b := make([]byte, r.api.settingEngine.getReceiveMTU())
i, attributes, err := r.Read(b)
if err != nil {
return nil, nil, err
@ -190,7 +253,7 @@ func (r *RTPReceiver) ReadRTCP() ([]rtcp.Packet, interceptor.Attributes, error)
// 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, receiveMTU)
b := make([]byte, r.api.settingEngine.getReceiveMTU())
i, attributes, err := r.ReadSimulcast(b, rid)
if err != nil {
return nil, nil, err
@ -234,6 +297,22 @@ func (r *RTPReceiver) Stop() error {
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:
@ -264,7 +343,7 @@ func (r *RTPReceiver) readRTP(b []byte, reader *TrackRemote) (n int, a intercept
// 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, ssrc SSRC) (*TrackRemote, error) {
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()
@ -274,54 +353,56 @@ func (r *RTPReceiver) receiveForRid(rid string, params RTPParameters, ssrc SSRC)
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 := createStreamInfo("", ssrc, params.Codecs[0].PayloadType, params.Codecs[0].RTPCodecCapability, params.HeaderExtensions)
r.tracks[i].track.ssrc = SSRC(streamInfo.SSRC)
r.tracks[i].track.mu.Unlock()
var err error
if r.tracks[i].rtpReadStream, r.tracks[i].rtpInterceptor, r.tracks[i].rtcpReadStream, r.tracks[i].rtcpInterceptor, err = r.streamsForSSRC(ssrc, streamInfo); err != nil {
return nil, err
}
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: %d", errRTPReceiverForSSRCTrackStreamNotFound, ssrc)
return nil, fmt.Errorf("%w: %s", errRTPReceiverForRIDTrackStreamNotFound, rid)
}
func (r *RTPReceiver) streamsForSSRC(ssrc SSRC, streamInfo interceptor.StreamInfo) (*srtp.ReadStreamSRTP, interceptor.RTPReader, *srtp.ReadStreamSRTCP, interceptor.RTCPReader, error) {
srtpSession, err := r.transport.getSRTPSession()
if err != nil {
return nil, nil, nil, nil, err
// receiveForRtx starts a routine that processes the repair stream
// These packets aren't exposed to the user yet, but we need to process them for
// TWCC
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]
}
}
}
rtpReadStream, err := srtpSession.OpenReadStream(uint32(ssrc))
if err != nil {
return nil, nil, nil, nil, err
if track == nil {
return fmt.Errorf("%w: ssrc(%d) rsid(%s)", errRTPReceiverForRIDTrackStreamNotFound, ssrc, rsid)
}
rtpInterceptor := r.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
}))
track.repairStreamInfo = streamInfo
track.repairReadStream = rtpReadStream
track.repairInterceptor = rtpInterceptor
track.repairRtcpReadStream = rtcpReadStream
track.repairRtcpInterceptor = rtcpInterceptor
srtcpSession, err := r.transport.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 := r.api.interceptor.BindRTCPReader(interceptor.RTPReaderFunc(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
go func() {
b := make([]byte, r.api.settingEngine.getReceiveMTU())
for {
if _, _, readErr := track.repairInterceptor.Read(b, nil); readErr != nil {
return
}
}
}()
return nil
}
// SetReadDeadline sets the max amount of time the RTCP stream will block before returning. 0 is forever.
@ -329,10 +410,7 @@ func (r *RTPReceiver) SetReadDeadline(t time.Time) error {
r.mu.RLock()
defer r.mu.RUnlock()
if err := r.tracks[0].rtcpReadStream.SetReadDeadline(t); err != nil {
return err
}
return nil
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.

View 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()
}
}

View 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
}

View file

@ -1,8 +1,13 @@
// 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"
@ -11,21 +16,30 @@ import (
"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 TrackLocalContext
ssrc SSRC
}
// RTPSender allows an application to control how a given Track is encoded and transmitted to a remote peer
type RTPSender struct {
track TrackLocal
srtpStream *srtpWriterFuture
rtcpInterceptor interceptor.RTCPReader
context TrackLocalContext
trackEncodings []*trackEncoding
transport *DTLSTransport
payloadType PayloadType
ssrc SSRC
kind RTPCodecType
// nolint:godox
// TODO(sgotti) remove this when in future we'll avoid replacing
@ -37,6 +51,8 @@ type RTPSender struct {
api *API
id string
rtpTransceiver *RTPTransceiver
mu sync.RWMutex
sendCalled, stopCalled chan struct{}
}
@ -55,22 +71,15 @@ func (api *API) NewRTPSender(track TrackLocal, transport *DTLSTransport) (*RTPSe
}
r := &RTPSender{
track: track,
transport: transport,
api: api,
sendCalled: make(chan struct{}),
stopCalled: make(chan struct{}),
ssrc: SSRC(randutil.NewMathRandomGenerator().Uint32()),
id: id,
srtpStream: &srtpWriterFuture{},
kind: track.Kind(),
}
r.srtpStream.rtpSender = r
r.rtcpInterceptor = r.api.interceptor.BindRTCPReader(interceptor.RTPReaderFunc(func(in []byte, a interceptor.Attributes) (n int, attributes interceptor.Attributes, err error) {
n, err = r.srtpStream.Read(in)
return n, a, err
}))
r.addEncoding(track)
return r, nil
}
@ -87,6 +96,12 @@ func (r *RTPSender) setNegotiated() {
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 {
@ -95,30 +110,119 @@ func (r *RTPSender) Transport() *DTLSTransport {
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 {
return RTPSendParameters{
RTPParameters: r.api.mediaEngine.getRTPParametersByKind(
r.track.Kind(),
[]RTPTransceiverDirection{RTPTransceiverDirectionSendonly},
),
Encodings: []RTPEncodingParameters{
{
RTPCodingParameters: RTPCodingParameters{
SSRC: r.ssrc,
PayloadType: r.payloadType,
},
},
},
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.RTPReaderFunc(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()
return r.track
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.
@ -128,27 +232,54 @@ func (r *RTPSender) ReplaceTrack(track TrackLocal) error {
r.mu.Lock()
defer r.mu.Unlock()
if r.hasSent() && r.track != nil {
if err := r.track.Unbind(r.context); err != nil {
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 *TrackLocalContext
if len(r.trackEncodings) != 0 {
replacedTrack = r.trackEncodings[0].track
context = &r.trackEncodings[0].context
}
if r.hasSent() && replacedTrack != nil {
if err := replacedTrack.Unbind(*context); err != nil {
return err
}
}
if !r.hasSent() || track == nil {
r.track = track
r.trackEncodings[0].track = track
return nil
}
if _, err := track.Bind(r.context); err != nil {
codec, err := track.Bind(TrackLocalContext{
id: context.id,
params: r.api.mediaEngine.getRTPParametersByKind(track.Kind(), []RTPTransceiverDirection{RTPTransceiverDirectionSendonly}),
ssrc: context.ssrc,
writeStream: context.writeStream,
rtcpInterceptor: context.rtcpInterceptor,
})
if err != nil {
// Re-bind the original track
if _, reBindErr := r.track.Bind(r.context); reBindErr != nil {
if _, reBindErr := replacedTrack.Bind(*context); reBindErr != nil {
return reBindErr
}
return err
}
r.track = track
// Codec has changed
if r.payloadType != codec.PayloadType {
context.params.Codecs = []RTPCodecParameters{codec}
}
r.trackEncodings[0].track = track
return nil
}
@ -157,29 +288,45 @@ func (r *RTPSender) Send(parameters RTPSendParameters) error {
r.mu.Lock()
defer r.mu.Unlock()
if r.hasSent() {
switch {
case r.hasSent():
return errRTPSenderSendAlreadyCalled
case r.trackEncodings[0].track == nil:
return errRTPSenderTrackRemoved
}
writeStream := &interceptorToTrackLocalWriter{}
r.context = TrackLocalContext{
id: r.id,
params: r.api.mediaEngine.getRTPParametersByKind(r.track.Kind(), []RTPTransceiverDirection{RTPTransceiverDirectionSendonly}),
ssrc: parameters.Encodings[0].SSRC,
writeStream: writeStream,
}
for idx, trackEncoding := range r.trackEncodings {
writeStream := &interceptorToTrackLocalWriter{}
trackEncoding.context = TrackLocalContext{
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 := r.track.Bind(r.context)
if err != nil {
return err
}
r.context.params.Codecs = []RTPCodecParameters{codec}
codec, err := trackEncoding.track.Bind(trackEncoding.context)
if err != nil {
return err
}
trackEncoding.context.params.Codecs = []RTPCodecParameters{codec}
streamInfo := createStreamInfo(r.id, parameters.Encodings[0].SSRC, codec.PayloadType, codec.RTPCodecCapability, parameters.HeaderExtensions)
rtpInterceptor := r.api.interceptor.BindLocalStream(&streamInfo, interceptor.RTPWriterFunc(func(header *rtp.Header, payload []byte, attributes interceptor.Attributes) (int, error) {
return r.srtpStream.WriteRTP(header, payload)
}))
writeStream.interceptor.Store(rtpInterceptor)
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
@ -205,14 +352,20 @@ func (r *RTPSender) Stop() error {
return err
}
return r.srtpStream.Close()
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 RTPReceiver
// 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.rtcpInterceptor.Read(b, a)
return r.trackEncodings[0].rtcpInterceptor.Read(b, a)
case <-r.stopCalled:
return 0, nil, io.ErrClosedPipe
}
@ -220,7 +373,7 @@ func (r *RTPSender) Read(b []byte) (n int, a interceptor.Attributes, err error)
// ReadRTCP is a convenience method that wraps Read and unmarshals for you.
func (r *RTPSender) ReadRTCP() ([]rtcp.Packet, interceptor.Attributes, error) {
b := make([]byte, receiveMTU)
b := make([]byte, r.api.settingEngine.getReceiveMTU())
i, attributes, err := r.Read(b)
if err != nil {
return nil, nil, err
@ -234,10 +387,50 @@ func (r *RTPSender) ReadRTCP() ([]rtcp.Packet, interceptor.Attributes, error) {
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.srtpStream.SetReadDeadline(t)
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

View 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
}

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package webrtc
// RTPSendParameters contains the RTP stack settings used by receivers

View file

@ -1,9 +1,14 @@
// 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"
@ -11,19 +16,79 @@ import (
// 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
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
}
filteredCodecs = append(filteredCodecs, codec)
}
}
return filteredCodecs
}
// Sender returns the RTPTransceiver's RTPSender if it has one
func (t *RTPTransceiver) Sender() *RTPSender {
if v := t.sender.Load(); v != nil {
return v.(*RTPSender)
if v, ok := t.sender.Load().(*RTPSender); ok {
return v
}
return nil
@ -36,20 +101,28 @@ func (t *RTPTransceiver) SetSender(s *RTPSender, track TrackLocal) error {
}
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 := t.receiver.Load(); v != nil {
return v.(*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 {
// 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)
}
@ -59,8 +132,8 @@ func (t *RTPTransceiver) setMid(mid string) error {
// 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 := t.mid.Load(); v != nil {
return v.(string)
if v, ok := t.mid.Load().(string); ok {
return v
}
return ""
}
@ -72,27 +145,39 @@ func (t *RTPTransceiver) Kind() RTPCodecType {
// Direction returns the RTPTransceiver's current direction
func (t *RTPTransceiver) Direction() RTPTransceiverDirection {
return t.direction.Load().(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 t.Sender() != nil {
if err := t.Sender().Stop(); err != nil {
if sender := t.Sender(); sender != nil {
if err := sender.Stop(); err != nil {
return err
}
}
if t.Receiver() != nil {
if err := t.Receiver().Stop(); err != nil {
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)
}
@ -100,6 +185,17 @@ 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
@ -115,6 +211,12 @@ func (t *RTPTransceiver) setSendingTrack(track TrackLocal) error {
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:
@ -140,9 +242,9 @@ func satisfyTypeAndDirection(remoteKind RTPCodecType, remoteDirection RTPTransce
getPreferredDirections := func() []RTPTransceiverDirection {
switch remoteDirection {
case RTPTransceiverDirectionSendrecv:
return []RTPTransceiverDirection{RTPTransceiverDirectionRecvonly, RTPTransceiverDirectionSendrecv}
return []RTPTransceiverDirection{RTPTransceiverDirectionRecvonly, RTPTransceiverDirectionSendrecv, RTPTransceiverDirectionSendonly}
case RTPTransceiverDirectionSendonly:
return []RTPTransceiverDirection{RTPTransceiverDirectionRecvonly}
return []RTPTransceiverDirection{RTPTransceiverDirectionRecvonly, RTPTransceiverDirectionSendrecv}
case RTPTransceiverDirectionRecvonly:
return []RTPTransceiverDirection{RTPTransceiverDirectionSendonly, RTPTransceiverDirectionSendrecv}
default:
@ -164,7 +266,7 @@ func satisfyTypeAndDirection(remoteKind RTPCodecType, remoteDirection RTPTransce
// 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 uint8) (mid, rid string, payloadType PayloadType, err error) {
func handleUnknownRTPPacket(buf []byte, midExtensionID, streamIDExtensionID, repairStreamIDExtensionID uint8, mid, rid, rsid *string) (payloadType PayloadType, err error) {
rp := &rtp.Packet{}
if err = rp.Unmarshal(buf); err != nil {
return
@ -176,11 +278,15 @@ func handleUnknownRTPPacket(buf []byte, midExtensionID, streamIDExtensionID uint
payloadType = PayloadType(rp.PayloadType)
if payload := rp.GetExtension(midExtensionID); payload != nil {
mid = string(payload)
*mid = string(payload)
}
if payload := rp.GetExtension(streamIDExtensionID); payload != nil {
rid = string(payload)
*rid = string(payload)
}
if payload := rp.GetExtension(repairStreamIDExtensionID); payload != nil {
*rsid = string(payload)
}
return

View 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}
}

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package webrtc
// RTPTransceiverDirection indicates the direction of the RTPTransceiver.
@ -5,19 +8,19 @@ type RTPTransceiverDirection int
const (
// RTPTransceiverDirectionSendrecv indicates the RTPSender will offer
// to send RTP and RTPReceiver the will offer to receive RTP.
// 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 the will
// RTPTransceiverDirectionRecvonly indicates the RTPReceiver will
// offer to receive RTP.
RTPTransceiverDirectionRecvonly
// RTPTransceiverDirectionInactive indicates the RTPSender won't offer
// to send RTP and RTPReceiver the won't offer to receive RTP.
// to send RTP and the RTPReceiver won't offer to receive RTP.
RTPTransceiverDirectionInactive
)

Some files were not shown because too many files have changed in this diff Show more