diff --git a/.github/assert-contributors.sh b/.github/generate-authors.sh similarity index 58% rename from .github/assert-contributors.sh rename to .github/generate-authors.sh index 12e6afea145..182e4f5e738 100755 --- a/.github/assert-contributors.sh +++ b/.github/generate-authors.sh @@ -12,6 +12,7 @@ set -e SCRIPT_PATH=$( cd "$(dirname "${BASH_SOURCE[0]}")" ; pwd -P ) +AUTHORS_PATH="$GITHUB_WORKSPACE/AUTHORS.txt" if [ -f ${SCRIPT_PATH}/.ci.conf ] then @@ -21,18 +22,18 @@ fi # # DO NOT EDIT THIS # -EXCLUDED_CONTRIBUTORS+=('John R. Bradley' 'renovate[bot]' 'Renovate Bot' 'Pion Bot') +EXCLUDED_CONTRIBUTORS+=('John R. Bradley' 'renovate[bot]' 'Renovate Bot' 'Pion Bot' 'pionbot') # If you want to exclude a name from all repositories, send a PR to # https://github.com/pion/.goassets instead of this repository. # If you want to exclude a name only from this repository, # add EXCLUDED_CONTRIBUTORS=('name') to .github/.ci.conf -MISSING_CONTRIBUTORS=() +CONTRIBUTORS=() shouldBeIncluded () { for i in "${EXCLUDED_CONTRIBUTORS[@]}" do - if [ "$i" == "$1" ] ; then + if [[ $1 =~ "$i" ]]; then return 1 fi done @@ -41,21 +42,25 @@ shouldBeIncluded () { IFS=$'\n' #Only split on newline -for contributor in $(git log --format='%aN' | sort -u) +for contributor in $(git log --format='%aN <%aE>' | LC_ALL=C.UTF-8 sort -uf) do if shouldBeIncluded $contributor; then - if ! grep -q "$contributor" "$SCRIPT_PATH/../README.md"; then - MISSING_CONTRIBUTORS+=("$contributor") - fi + CONTRIBUTORS+=("$contributor") fi done unset IFS -if [ ${#MISSING_CONTRIBUTORS[@]} -ne 0 ]; then - echo "Please add the following contributors to the README" - for i in "${MISSING_CONTRIBUTORS[@]}" +if [ ${#CONTRIBUTORS[@]} -ne 0 ]; then + cat >$AUTHORS_PATH <<-'EOH' +# 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 `.github/generate-authors.sh` for the scripting +EOH + for i in "${CONTRIBUTORS[@]}" do - echo "$i" + echo "$i" >> $AUTHORS_PATH done - exit 1 + exit 0 fi diff --git a/.github/hooks/pre-push.sh b/.github/hooks/pre-push.sh index 7cb23653d48..bfe65bc504a 100755 --- a/.github/hooks/pre-push.sh +++ b/.github/hooks/pre-push.sh @@ -8,6 +8,6 @@ set -e -.github/assert-contributors.sh +.github/generate-authors.sh exit 0 diff --git a/.github/workflows/browser-e2e.yaml b/.github/workflows/browser-e2e.yaml index 33016d4ea8c..b45f63710c1 100644 --- a/.github/workflows/browser-e2e.yaml +++ b/.github/workflows/browser-e2e.yaml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest steps: - name: checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: test run: | docker build -t pion-webrtc-e2e -f e2e/Dockerfile . diff --git a/.github/workflows/generate-authors.yml b/.github/workflows/generate-authors.yml new file mode 100644 index 00000000000..9a80a48d247 --- /dev/null +++ b/.github/workflows/generate-authors.yml @@ -0,0 +1,73 @@ +# +# DO NOT EDIT THIS FILE +# +# It is automatically copied from https://github.com/pion/.goassets repository. +# If this repository should have package specific CI config, +# remove the repository name from .goassets/.github/workflows/assets-sync.yml. +# +# If you want to update the shared CI config, send a PR to +# https://github.com/pion/.goassets instead of this repository. +# + +name: generate-authors + +on: + pull_request: + +jobs: + checksecret: + runs-on: ubuntu-latest + outputs: + is_PIONBOT_PRIVATE_KEY_set: ${{ steps.checksecret_job.outputs.is_PIONBOT_PRIVATE_KEY_set }} + steps: + - id: checksecret_job + env: + PIONBOT_PRIVATE_KEY: ${{ secrets.PIONBOT_PRIVATE_KEY }} + run: | + echo "is_PIONBOT_PRIVATE_KEY_set: ${{ env.PIONBOT_PRIVATE_KEY != '' }}" + echo "::set-output name=is_PIONBOT_PRIVATE_KEY_set::${{ env.PIONBOT_PRIVATE_KEY != '' }}" + + generate-authors: + needs: [checksecret] + if: needs.checksecret.outputs.is_PIONBOT_PRIVATE_KEY_set == 'true' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + ref: ${{ github.head_ref }} + fetch-depth: 0 + token: ${{ secrets.PIONBOT_PRIVATE_KEY }} + + - name: Generate the authors file + run: .github/generate-authors.sh + + - name: Add the authors file to git + run: git add AUTHORS.txt + + - name: Get last commit message + id: last-commit-message + run: | + COMMIT_MSG=$(git log -1 --pretty=%B) + COMMIT_MSG="${COMMIT_MSG//'%'/'%25'}" + COMMIT_MSG="${COMMIT_MSG//$'\n'/'%0A'}" + COMMIT_MSG="${COMMIT_MSG//$'\r'/'%0D'}" + echo "::set-output name=msg::$COMMIT_MSG" + + - name: Get last commit author + id: last-commit-author + run: | + echo "::set-output name=msg::$(git log -1 --pretty='%aN <%ae>')" + + - name: Check if AUTHORS.txt file has changed + id: git-status-output + run: | + echo "::set-output name=msg::$(git status -s | wc -l)" + + - name: Commit and push + if: ${{ steps.git-status-output.outputs.msg != '0' }} + run: | + git config user.email $(echo "${{ steps.last-commit-author.outputs.msg }}" | sed 's/\(.\+\) <\(\S\+\)>/\2/') + git config user.name $(echo "${{ steps.last-commit-author.outputs.msg }}" | sed 's/\(.\+\) <\(\S\+\)>/\1/') + git add AUTHORS.txt + git commit --amend --no-edit + git push --force https://github.com/${GITHUB_REPOSITORY} $(git symbolic-ref -q --short HEAD) diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index 8824c34d9ef..438443f112b 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -1,3 +1,14 @@ +# +# DO NOT EDIT THIS FILE +# +# It is automatically copied from https://github.com/pion/.goassets repository. +# If this repository should have package specific CI config, +# remove the repository name from .goassets/.github/workflows/assets-sync.yml. +# +# If you want to update the shared CI config, send a PR to +# https://github.com/pion/.goassets instead of this repository. +# + name: Lint on: pull_request: @@ -12,7 +23,7 @@ jobs: strategy: fail-fast: false steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 with: fetch-depth: 0 @@ -22,9 +33,6 @@ jobs: - name: File names run: .github/lint-filename.sh - - name: Contributors - run: .github/assert-contributors.sh - - name: Functions run: .github/lint-disallowed-functions-in-library.sh @@ -34,10 +42,10 @@ jobs: strategy: fail-fast: false steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: golangci-lint - uses: golangci/golangci-lint-action@v2 + uses: golangci/golangci-lint-action@v3 with: version: v1.31 args: $GOLANGCI_LINT_EXRA_ARGS diff --git a/.github/workflows/renovate-go-mod-fix.yaml b/.github/workflows/renovate-go-mod-fix.yaml index 46d2d04c1a8..59918227c74 100644 --- a/.github/workflows/renovate-go-mod-fix.yaml +++ b/.github/workflows/renovate-go-mod-fix.yaml @@ -20,7 +20,7 @@ jobs: runs-on: ubuntu-latest steps: - name: checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: fetch-depth: 2 - name: fix diff --git a/.github/workflows/standardjs.yaml b/.github/workflows/standardjs.yaml new file mode 100644 index 00000000000..3211226cd3d --- /dev/null +++ b/.github/workflows/standardjs.yaml @@ -0,0 +1,17 @@ +name: StandardJS +on: + pull_request: + types: + - opened + - edited + - synchronize +jobs: + StandardJS: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + with: + node-version: 12.x + - run: npm install standard + - run: npx standard diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index a30c38f6bfd..cd788c9bfc2 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -1,3 +1,14 @@ +# +# DO NOT EDIT THIS FILE +# +# It is automatically copied from https://github.com/pion/.goassets repository. +# If this repository should have package specific CI config, +# remove the repository name from .goassets/.github/workflows/assets-sync.yml. +# +# If you want to update the shared CI config, send a PR to +# https://github.com/pion/.goassets instead of this repository. +# + name: Test on: push: @@ -11,11 +22,11 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - go: ["1.15", "1.16"] + go: ["1.16", "1.17"] fail-fast: false name: Go ${{ matrix.go }} steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: actions/cache@v2 with: @@ -28,7 +39,7 @@ jobs: ${{ runner.os }}-amd64-go- - name: Setup Go - uses: actions/setup-go@v2 + uses: actions/setup-go@v3 with: go-version: ${{ matrix.go }} @@ -39,11 +50,19 @@ jobs: - name: Run test run: | + TEST_BENCH_OPTION="-bench=." + if [ -f .github/.ci.conf ]; then . .github/.ci.conf; fi + go-acc -o cover.out ./... -- \ - -bench=. \ - -v -race + ${TEST_BENCH_OPTION} \ + -v -race + + - name: Run TEST_HOOK + run: | + if [ -f .github/.ci.conf ]; then . .github/.ci.conf; fi + if [ -n "${TEST_HOOK}" ]; then ${TEST_HOOK}; fi - - uses: codecov/codecov-action@v1 + - uses: codecov/codecov-action@v2 with: file: ./cover.out name: codecov-umbrella @@ -54,11 +73,11 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - go: ["1.15", "1.16"] + go: ["1.16", "1.17"] fail-fast: false name: Go i386 ${{ matrix.go }} steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: actions/cache@v2 with: @@ -73,17 +92,17 @@ jobs: run: | mkdir -p $HOME/go/pkg/mod $HOME/.cache docker run \ - -u $(id -u):$(id -g) \ - -e "GO111MODULE=on" \ - -e "CGO_ENABLED=0" \ - -v $GITHUB_WORKSPACE:/go/src/github.com/pion/$(basename $GITHUB_WORKSPACE) \ - -v $HOME/go/pkg/mod:/go/pkg/mod \ - -v $HOME/.cache:/.cache \ - -w /go/src/github.com/pion/$(basename $GITHUB_WORKSPACE) \ - i386/golang:${{matrix.go}}-alpine \ - /usr/local/go/bin/go test \ - ${TEST_EXTRA_ARGS:-} \ - -v ./... + -u $(id -u):$(id -g) \ + -e "GO111MODULE=on" \ + -e "CGO_ENABLED=0" \ + -v $GITHUB_WORKSPACE:/go/src/github.com/pion/$(basename $GITHUB_WORKSPACE) \ + -v $HOME/go/pkg/mod:/go/pkg/mod \ + -v $HOME/.cache:/.cache \ + -w /go/src/github.com/pion/$(basename $GITHUB_WORKSPACE) \ + i386/golang:${{matrix.go}}-alpine \ + /usr/local/go/bin/go test \ + ${TEST_EXTRA_ARGS:-} \ + -v ./... test-wasm: runs-on: ubuntu-latest @@ -91,12 +110,12 @@ jobs: fail-fast: false name: WASM steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Use Node.js - uses: actions/setup-node@v2 + uses: actions/setup-node@v3 with: - node-version: '12.x' + node-version: '16.x' - uses: actions/cache@v2 with: @@ -110,7 +129,7 @@ jobs: - name: Download Go run: curl -sSfL https://dl.google.com/go/go${GO_VERSION}.linux-amd64.tar.gz | tar -C ~ -xzf - env: - GO_VERSION: 1.16 + GO_VERSION: 1.17 - name: Set Go Root run: echo "GOROOT=${HOME}/go" >> $GITHUB_ENV @@ -119,19 +138,20 @@ jobs: run: echo "GOPATH=${HOME}/go" >> $GITHUB_ENV - name: Set Go Path - run: echo "GO_JS_WASM_EXEC=${PWD}/test-wasm/go_js_wasm_exec" >> $GITHUB_ENV + run: echo "GO_JS_WASM_EXEC=${GOROOT}/misc/wasm/go_js_wasm_exec" >> $GITHUB_ENV - name: Insall NPM modules run: yarn install - name: Run Tests run: | + if [ -f .github/.ci.conf ]; then . .github/.ci.conf; fi GOOS=js GOARCH=wasm $GOPATH/bin/go test \ - -coverprofile=cover.out -covermode=atomic \ - -exec="${GO_JS_WASM_EXEC}" \ - -v ./... + -coverprofile=cover.out -covermode=atomic \ + -exec="${GO_JS_WASM_EXEC}" \ + -v ./... - - uses: codecov/codecov-action@v1 + - uses: codecov/codecov-action@v2 with: file: ./cover.out name: codecov-umbrella diff --git a/.github/workflows/tidy-check.yaml b/.github/workflows/tidy-check.yaml index 03b5189deba..3ab2c35219c 100644 --- a/.github/workflows/tidy-check.yaml +++ b/.github/workflows/tidy-check.yaml @@ -23,9 +23,9 @@ jobs: runs-on: ubuntu-latest steps: - name: checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Setup Go - uses: actions/setup-go@v2 + uses: actions/setup-go@v3 - name: check run: | go mod download diff --git a/.gitignore b/.gitignore index 83db74ba532..f977e748533 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,4 @@ cover.out *.wasm examples/sfu-ws/cert.pem examples/sfu-ws/key.pem +wasm_exec.js diff --git a/AUTHORS.txt b/AUTHORS.txt new file mode 100644 index 00000000000..0ef018685fa --- /dev/null +++ b/AUTHORS.txt @@ -0,0 +1,176 @@ +# 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 `.github/generate-authors.sh` for the scripting +a-wing <1@233.email> +Aaron France +Adam Kiss +adwpc +aggresss +akil +Aleksandr Razumov +aler9 <46489434+aler9@users.noreply.github.com> +Alex Browne +Alex Harford +AlexWoo(武杰) +Ali Error +Andrew N. Shalaev +Antoine Baché +Antoine Baché +Artur Shellunts +Assad Obaid +Ato Araki +Atsushi Watanabe +backkem +baiyufei +Bao Nguyen +Ben Weitzman +Benny Daon +bkim +Bo Shi +boks1971 +Brendan Rius +brian +Bryan Phelps +Cameron Elliott +Cecylia Bocovich +Cedric Fung +cgojin +Chad Retz +chenkaiC4 +Chris Hiszpanski +Christopher Fry +Clayton McCray +cnderrauber +cyannuk +David Hamilton +David Zhao +Dean Sheather +decanus <7621705+decanus@users.noreply.github.com> +Denis +digitalix +donotanswer +earle +Egon Elbre +Eric Daniels +feixiao +frank +Gareth Hayes +Guilherme +Hanjun Kim +Hendrik Hofstadt +Henry +Hongchao Ma +Hugo Arregui +Hugo Arregui +Ilya Mayorov +imalic3 +Ivan Egorov +JacobZwang <59858341+JacobZwang@users.noreply.github.com> +Jake B +Jamie Good +Jason +Jeff Tchang +Jerko Steiner +jinleileiking +John Berthels +John Bradley +JooYoung +Jorropo +juberti +Juliusz Chroboczek +Justin Okamoto +Justin Okamoto +Kevin Staunton-Lambert +Kevin Wang +Konstantin Chugalinskiy +Konstantin Itskov +krishna chiatanya +Kuzmin Vladimir +lawl +Len +Lukas Herman +Luke +Luke Curley +Luke S +Magnus Wahlstrand +Markus Tzoe +Marouane <6729798+nindolabs@users.noreply.github.com> +Marouane +Masahiro Nakamura <13937915+tsuu32@users.noreply.github.com> +Mathis Engelbart +Max Hawkins +mchlrhw <4028654+mchlrhw@users.noreply.github.com> +Michael MacDonald +Michael MacDonald +Michiel De Backker <38858977+backkem@users.noreply.github.com> +Mike Coleman +Mindgamesnl +mission-liao +mxmCherry +Nam V. Do +Nick Mykins +nindolabs <6729798+nindolabs@users.noreply.github.com> +Norman Rasmussen +notedit +o0olele +obasajujoshua31 +Oleg Kovalov +opennota +OrlandoCo +Pascal Benoit +pascal-ace <47424881+pascal-ace@users.noreply.github.com> +Patrick Lange +Patryk Rogalski +Pieere Pi +q191201771 <191201771@qq.com> +Quentin Renard +Rafael Viscarra +rahulnakre +Raphael Randschau +Raphael Randschau +Reese <3253971+figadore@users.noreply.github.com> +rob-deutsch +Robert Eperjesi +Robin Raymond +Roman Romanenko +Roman Romanenko +ronan +Ryan Shumate +salmān aljammāz +Sam Lancia +Sean DuBois +Sean DuBois +Sean DuBois +Sean DuBois +Sean Knight +Sebastian Waisbrot +Simon Eisenmann +simonacca-fotokite <47634061+simonacca-fotokite@users.noreply.github.com> +Simone Gotti +Slugalisk +soolaugust +spaceCh1mp +Suhas Gaddam +Suzuki Takeo +sylba2050 +Tarrence van As +tarrencev +Thomas Miller +Tobias Fridén +Tomek +Twometer +Vicken Simonian +wattanakorn495 +Will Forcey +Will Watson +Woodrow Douglass +xsbchen +Yuki Igarashi +yusuke +Yutaka Takeda +ZHENK +zigazeljko +Štefan Uram +박종훈 diff --git a/README.md b/README.md index 5883ae9e1f2..da1b3d67f3c 100644 --- a/README.md +++ b/README.md @@ -21,16 +21,6 @@


-### 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. @@ -96,8 +86,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 is actively being implemented, see [pion/interceptor#25](https://github.com/pion/interceptor/issues/25) #### Security * TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 and TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA for DTLS v1.2 @@ -133,149 +124,5 @@ If you need commercial support or don't want to use public methods you can conta ### 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) -* [Nam V. Do](https://github.com/namvdo) -* [Markus Tzoe](https://github.com/zyxar) -* [Benny Daon](https://github.com/daonb) -* [Tomek](https://github.com/trojek) -* [Jin Gong](https://github.com/cgojin) -* [yusuke](https://github.com/yusukem99) -* [Patryk Rogalski](https://github.com/digitalix) -* [Robin Raymond](https://github.com/robin-raymond) - ### License MIT License - see [LICENSE](LICENSE) for full text diff --git a/api.go b/api.go index 60ac72809d9..85424df4d9b 100644 --- a/api.go +++ b/api.go @@ -1,3 +1,4 @@ +//go:build !js // +build !js package webrtc @@ -7,40 +8,36 @@ 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 } @@ -48,9 +45,8 @@ func NewAPI(options ...func(*API)) *API { // Settings can be changed after passing the engine to an API. 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 +62,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{} + } } } diff --git a/api_js.go b/api_js.go index 964b7b05e09..3d81ed7b149 100644 --- a/api_js.go +++ b/api_js.go @@ -1,3 +1,4 @@ +//go:build js && wasm // +build js,wasm package webrtc diff --git a/api_test.go b/api_test.go index b63756c3da0..d2de28094bf 100644 --- a/api_test.go +++ b/api_test.go @@ -1,3 +1,4 @@ +//go:build !js // +build !js package webrtc @@ -18,6 +19,10 @@ func TestNewAPI(t *testing.T) { if api.mediaEngine == nil { t.Error("Failed to init media engine") } + + if api.interceptorRegistry == nil { + t.Error("Failed to init interceptor registry") + } } func TestNewAPI_Options(t *testing.T) { @@ -39,3 +44,14 @@ func TestNewAPI_Options(t *testing.T) { t.Error("Failed to set media engine") } } + +func TestNewAPI_OptionsDefaultize(t *testing.T) { + api := NewAPI( + WithMediaEngine(nil), + WithInterceptorRegistry(nil), + ) + + assert.NotNil(t, api.settingEngine) + assert.NotNil(t, api.mediaEngine) + assert.NotNil(t, api.interceptorRegistry) +} diff --git a/atomicbool.go b/atomicbool.go index c5ace62d0c8..1d4bf55ac99 100644 --- a/atomicbool.go +++ b/atomicbool.go @@ -18,3 +18,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 = 0 + if value { + i = 1 + } + return atomic.SwapInt32(&(b.val), i) != 0 +} diff --git a/certificate.go b/certificate.go index 30a76a1b8d4..99e359741b5 100644 --- a/certificate.go +++ b/certificate.go @@ -1,3 +1,4 @@ +//go:build !js // +build !js package webrtc @@ -10,7 +11,6 @@ import ( "crypto/x509" "crypto/x509/pkix" "encoding/base64" - "encoding/hex" "encoding/pem" "fmt" "math/big" @@ -123,12 +123,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 */ @@ -140,18 +134,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}, }) } diff --git a/certificate_test.go b/certificate_test.go index 873bee336e9..bae16bfc3ba 100644 --- a/certificate_test.go +++ b/certificate_test.go @@ -1,3 +1,4 @@ +//go:build !js // +build !js package webrtc diff --git a/configuration.go b/configuration.go index 712bab92c25..608c5ab7e01 100644 --- a/configuration.go +++ b/configuration.go @@ -1,3 +1,4 @@ +//go:build !js // +build !js package webrtc diff --git a/configuration_js.go b/configuration_js.go index 44ab9f3c5a9..2ba4d268e33 100644 --- a/configuration_js.go +++ b/configuration_js.go @@ -1,3 +1,4 @@ +//go:build js && wasm // +build js,wasm package webrtc diff --git a/constants.go b/constants.go index 95f666ee402..825601ddb6b 100644 --- a/constants.go +++ b/constants.go @@ -7,7 +7,6 @@ const ( // comparisons when no value was defined. Unknown = iota unknownStr = "unknown" - ssrcStr = "ssrc" // Equal to UDP MTU receiveMTU = 1460 @@ -23,7 +22,17 @@ 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 { diff --git a/datachannel.go b/datachannel.go index 4bcd9438312..11d09e19283 100644 --- a/datachannel.go +++ b/datachannel.go @@ -1,3 +1,4 @@ +//go:build !js // +build !js package webrtc @@ -153,10 +154,15 @@ func (d *DataChannel) open(sctpTransport *SCTPTransport, restart bool) 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(association, *d.id, cfg) if err != nil { @@ -174,7 +180,7 @@ func (d *DataChannel) open(sctpTransport *SCTPTransport, restart bool) error { dc.OnBufferedAmountLow(d.onBufferedAmountLow) d.mu.Unlock() - d.handleOpen(dc) + d.handleOpen(dc, false, d.negotiated) return nil } @@ -268,13 +274,23 @@ 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 { + d.onOpen() + } else { + dc.OnOpen(func() { + d.onOpen() + }) + } d.mu.Lock() defer d.mu.Unlock() @@ -424,7 +440,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() diff --git a/datachannel_go_test.go b/datachannel_go_test.go index 958abb206dc..bbcef83bf64 100644 --- a/datachannel_go_test.go +++ b/datachannel_go_test.go @@ -1,3 +1,4 @@ +//go:build !js // +build !js package webrtc diff --git a/datachannel_js.go b/datachannel_js.go index 7aa6d99f8fb..55214b55165 100644 --- a/datachannel_js.go +++ b/datachannel_js.go @@ -1,3 +1,4 @@ +//go:build js && wasm // +build js,wasm package webrtc @@ -162,7 +163,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 +172,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 diff --git a/datachannel_js_detach.go b/datachannel_js_detach.go index dd0069e0184..43186c5db1e 100644 --- a/datachannel_js_detach.go +++ b/datachannel_js_detach.go @@ -1,3 +1,4 @@ +//go:build js && wasm // +build js,wasm package webrtc diff --git a/datachannel_test.go b/datachannel_test.go index 5c3f78548bc..7dfaf4caf12 100644 --- a/datachannel_test.go +++ b/datachannel_test.go @@ -111,6 +111,8 @@ func benchmarkDataChannelSend(b *testing.B, numChannels int) { } func TestDataChannel_Open(t *testing.T) { + const openOnceChannelCapacity = 2 + t.Run("handler should be called once", func(t *testing.T) { report := test.CheckRoutines(t) defer report() @@ -121,7 +123,7 @@ func TestDataChannel_Open(t *testing.T) { } done := make(chan bool) - openCalls := make(chan bool, 2) + openCalls := make(chan bool, openOnceChannelCapacity) answerPC.OnDataChannel(func(d *DataChannel) { if d.Label() != expectedLabel { @@ -155,6 +157,63 @@ func TestDataChannel_Open(t *testing.T) { assert.Len(t, openCalls, 1) }) + + t.Run("handler should be called once when already negotiated", func(t *testing.T) { + report := test.CheckRoutines(t) + defer report() + + offerPC, answerPC, err := newPair() + if err != nil { + t.Fatalf("Failed to create a PC pair for testing") + } + + done := make(chan bool) + answerOpenCalls := make(chan bool, openOnceChannelCapacity) + offerOpenCalls := make(chan bool, openOnceChannelCapacity) + + negotiated := true + ordered := true + dataChannelID := uint16(0) + + answerDC, err := answerPC.CreateDataChannel(expectedLabel, &DataChannelInit{ + ID: &dataChannelID, + Negotiated: &negotiated, + Ordered: &ordered, + }) + assert.NoError(t, err) + offerDC, err := offerPC.CreateDataChannel(expectedLabel, &DataChannelInit{ + ID: &dataChannelID, + Negotiated: &negotiated, + Ordered: &ordered, + }) + assert.NoError(t, err) + + answerDC.OnMessage(func(msg DataChannelMessage) { + go func() { + // Wait a little bit to ensure all messages are processed. + time.Sleep(100 * time.Millisecond) + done <- true + }() + }) + answerDC.OnOpen(func() { + answerOpenCalls <- true + }) + + offerDC.OnOpen(func() { + offerOpenCalls <- true + e := offerDC.SendText("Ping") + if e != nil { + t.Fatalf("Failed to send string on data channel") + } + }) + + assert.NoError(t, signalPair(offerPC, answerPC)) + + closePair(t, offerPC, answerPC, done) + + assert.Len(t, answerOpenCalls, 1) + assert.Len(t, offerOpenCalls, 1) + }) } func TestDataChannel_Send(t *testing.T) { diff --git a/dtlstransport.go b/dtlstransport.go index a1a7000faf9..c9ef0d538d7 100644 --- a/dtlstransport.go +++ b/dtlstransport.go @@ -1,3 +1,4 @@ +//go:build !js // +build !js package webrtc @@ -17,6 +18,7 @@ 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" @@ -350,6 +352,10 @@ func (t *DTLSTransport) Start(remoteParameters DTLSParameters) error { dtlsConfig.ReplayProtectionWindow = int(*t.api.settingEngine.replayProtection.DTLS) } + if t.api.settingEngine.dtls.retransmissionInterval != 0 { + dtlsConfig.FlightInterval = t.api.settingEngine.dtls.retransmissionInterval + } + // Connect as DTLS Client/Server, function is blocking and we // must not hold the DTLSTransport lock if role == DTLSRoleClient { @@ -383,10 +389,6 @@ func (t *DTLSTransport) Start(remoteParameters DTLSParameters) error { return ErrNoSRTPProtectionProfile } - if t.api.settingEngine.disableCertificateFingerprintVerification { - return nil - } - // Check the fingerprint if a certificate was exchanged remoteCerts := dtlsConn.ConnectionState().PeerCertificates if len(remoteCerts) == 0 { @@ -395,23 +397,25 @@ func (t *DTLSTransport) Start(remoteParameters DTLSParameters) error { } t.remoteCertificate = remoteCerts[0] - parsedRemoteCert, err := x509.ParseCertificate(t.remoteCertificate) - if err != nil { - if closeErr := dtlsConn.Close(); closeErr != nil { - t.log.Error(err.Error()) + 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 } - t.onStateChange(DTLSTransportStateFailed) - return err - } + if err = t.validateFingerPrint(parsedRemoteCert); err != nil { + if closeErr := dtlsConn.Close(); closeErr != nil { + t.log.Error(err.Error()) + } - if err = t.validateFingerPrint(parsedRemoteCert); err != nil { - if closeErr := dtlsConn.Close(); closeErr != nil { - t.log.Error(err.Error()) + t.onStateChange(DTLSTransportStateFailed) + return err } - - t.onStateChange(DTLSTransportStateFailed) - return err } t.conn = dtlsConn @@ -471,7 +475,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 } @@ -484,3 +488,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 +} diff --git a/dtlstransport_js.go b/dtlstransport_js.go new file mode 100644 index 00000000000..d4d8611ef77 --- /dev/null +++ b/dtlstransport_js.go @@ -0,0 +1,28 @@ +//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, + } +} diff --git a/dtlstransport_test.go b/dtlstransport_test.go index a4aabe53891..4d6d39341e1 100644 --- a/dtlstransport_test.go +++ b/dtlstransport_test.go @@ -1,3 +1,4 @@ +//go:build !js // +build !js package webrtc diff --git a/e2e/Dockerfile b/e2e/Dockerfile index 751f3b7d9bf..c871222afb7 100644 --- a/e2e/Dockerfile +++ b/e2e/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.16-alpine3.13 +FROM golang:1.17-alpine3.13 RUN apk add --no-cache \ chromium \ diff --git a/e2e/e2e_test.go b/e2e/e2e_test.go index ba203baaa32..8cc51278ad1 100644 --- a/e2e/e2e_test.go +++ b/e2e/e2e_test.go @@ -1,3 +1,4 @@ +//go:build e2e // +build e2e package main @@ -333,7 +334,7 @@ func createTrack(offer webrtc.SessionDescription) (*webrtc.PeerConnection, *webr return nil, nil, nil, errPc } - track, errTrack := webrtc.NewTrackLocalStaticSample(webrtc.RTPCodecCapability{MimeType: "audio/opus"}, "audio", "pion") + track, errTrack := webrtc.NewTrackLocalStaticSample(webrtc.RTPCodecCapability{MimeType: webrtc.MimeTypeOpus}, "audio", "pion") if errTrack != nil { return nil, nil, nil, errTrack } diff --git a/errors.go b/errors.go index f37cfa06aa8..e152b27c04c 100644 --- a/errors.go +++ b/errors.go @@ -82,7 +82,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 +135,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") @@ -193,15 +203,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") @@ -219,4 +236,8 @@ var ( 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") ) diff --git a/examples/README.md b/examples/README.md index af5140cb6c9..8677a5765ef 100644 --- a/examples/README.md +++ b/examples/README.md @@ -17,18 +17,23 @@ For more full featured examples that use 3rd party libraries see our **[example- * [RTP Forwarder](rtp-forwarder): The rtp-forwarder example demonstrates how to forward your audio/video streams using RTP. * [RTP to WebRTC](rtp-to-webrtc): The rtp-to-webrtc example demonstrates how to take RTP packets sent to a Pion process into your browser. * [Simulcast](simulcast): The simulcast example demonstrates how to accept and demux 1 Track that contains 3 Simulcast streams. It then returns the media as 3 independent Tracks back to the sender. +* [Swap Tracks](swap-tracks): The swap-tracks example demonstrates deeper usage of the Pion Media API. The server accepts 3 media streams, and then dynamically routes them back as a single stream to the user. +* [RTCP Processing](rtcp-processing) The rtcp-processing example demonstrates Pion's RTCP APIs. This allow access to media statistics and control information. #### Data Channel API * [Data Channels](data-channels): The data-channels example shows how you can send/recv DataChannel messages from a web browser. -* [Data Channels Create](data-channels-create): Example data-channels-create shows how you can send/recv DataChannel messages from a web browser. The difference with the data-channels example is that the data channel is initialized from the server side in this example. -* [Data Channels Close](data-channels-close): Example data-channels-close is a variant of data-channels that allow playing with the life cycle of data channels. * [Data Channels Detach](data-channels-detach): The data-channels-detach example shows how you can send/recv DataChannel messages using the underlying DataChannel implementation directly. This provides a more idiomatic way of interacting with Data Channels. -* [Data Channels Detach Create](data-channels-detach-create): Example data-channels-detach-create shows how you can send/recv DataChannel messages using the underlying DataChannel implementation directly. This provides a more idiomatic way of interacting with Data Channels. The difference with the data-channels-detach example is that the data channel is initialized in this example. +* [Data Channels Flow Control](data-channels-flow-control): Example data-channels-flow-control shows how to use the DataChannel API efficiently. You can measure the amount the rate at which the remote peer is receiving data, and structure your application accordingly. * [ORTC](ortc): Example ortc shows how you an use the ORTC API for DataChannel communication. * [Pion to Pion](pion-to-pion): Example pion-to-pion is an example of two pion instances communicating directly! It therefore has no corresponding web page. #### Miscellaneous * [Custom Logger](custom-logger) The custom-logger demonstrates how the user can override the logging and process messages instead of printing to stdout. It has no corresponding web page. +* [ICE Restart](ice-restart) Example ice-restart demonstrates how a WebRTC connection can roam between networks. This example restarts ICE in a loop and prints the new addresses it uses each time. +* [ICE Single Port](ice-single-port) Example ice-single-port demonstrates how multiple WebRTC connections can be served from a single port. By default Pion listens on a new port for every PeerConnection. Pion can be configured to use a single port for multiple connections. +* [ICE TCP](ice-tcp) Example ice-tcp demonstrates how a WebRTC connection can be made over TCP instead of UDP. By default Pion only does UDP. Pion can be configured to use a TCP port, and this TCP port can be used for many connections. +* [Trickle ICE](trickle-ice) Example trickle-ice example demonstrates Pion WebRTC's Trickle ICE APIs. This is important to use since it allows ICE Gathering and Connecting to happen concurrently. +* [VNet](vnet) Example vnet demonstrates Pion's network virtualisation library. This example connects two PeerConnections over a virtual network and prints statistics about the data traveling over it. ### Usage We've made it easy to run the browser based examples on your local machine. diff --git a/examples/bandwidth-estimation-from-disk/README.md b/examples/bandwidth-estimation-from-disk/README.md new file mode 100644 index 00000000000..4ab6e7d5a21 --- /dev/null +++ b/examples/bandwidth-estimation-from-disk/README.md @@ -0,0 +1,48 @@ +# bandwidth-estimation-from-disk +bandwidth-estimation-from-disk demonstrates how to use Pion's Bandwidth Estimation APIs. + +Pion provides multiple Bandwidth Estimators, but they all satisfy one interface. This interface +emits an int for how much bandwidth is available to send. It is then up to the sender to meet that number. + +## Instructions +### Create IVF files named `high.ivf` `med.ivf` and `low.ivf` +``` +ffmpeg -i $INPUT_FILE -g 30 -b:v .3M -s 320x240 low.ivf +ffmpeg -i $INPUT_FILE -g 30 -b:v 1M -s 858x480 med.ivf +ffmpeg -i $INPUT_FILE -g 30 -b:v 2.5M -s 1280x720 high.ivf +``` + +### Download bandwidth-estimation-from-disk + +``` +go get github.com/pion/webrtc/v3/examples/bandwidth-estimation-from-disk +``` + +### Open bandwidth-estimation-from-disk example page +[jsfiddle.net](https://jsfiddle.net/a1cz42op/) you should see two text-areas, 'Start Session' button and 'Copy browser SessionDescription to clipboard' + +### Run bandwidth-estimation-from-disk with your browsers Session Description as stdin +The `output.ivf` you created should be in the same directory as `bandwidth-estimation-from-disk`. In the jsfiddle press 'Copy browser Session Description to clipboard' or copy the base64 string manually. + +Now use this value you just copied as the input to `bandwidth-estimation-from-disk` + +#### Linux/macOS +Run `echo $BROWSER_SDP | bandwidth-estimation-from-disk` +#### Windows +1. Paste the SessionDescription into a file. +1. Run `bandwidth-estimation-from-disk < my_file` + +### Input bandwidth-estimation-from-disk's Session Description into your browser +Copy the text that `bandwidth-estimation-from-disk` just emitted and copy into the second text area in the jsfiddle + +### Hit 'Start Session' in jsfiddle, enjoy your video! +A video should start playing in your browser above the input boxes. When `bandwidth-estimation-from-disk` switches quality levels it will print the old and new file like so. + +``` +Switching from low.ivf to med.ivf +Switching from med.ivf to high.ivf +Switching from high.ivf to med.ivf +``` + + +Congrats, you have used Pion WebRTC! Now start building something cool diff --git a/examples/bandwidth-estimation-from-disk/main.go b/examples/bandwidth-estimation-from-disk/main.go new file mode 100644 index 00000000000..b0c13f84b55 --- /dev/null +++ b/examples/bandwidth-estimation-from-disk/main.go @@ -0,0 +1,248 @@ +// +build !js + +package main + +import ( + "fmt" + "io" + "os" + "time" + + "github.com/pion/interceptor" + "github.com/pion/interceptor/pkg/cc" + "github.com/pion/interceptor/pkg/gcc" + "github.com/pion/webrtc/v3" + "github.com/pion/webrtc/v3/examples/internal/signal" + "github.com/pion/webrtc/v3/pkg/media" + "github.com/pion/webrtc/v3/pkg/media/ivfreader" +) + +const ( + lowFile = "low.ivf" + lowBitrate = 300_000 + + medFile = "med.ivf" + medBitrate = 1_000_000 + + highFile = "high.ivf" + highBitrate = 2_500_000 + + ivfHeaderSize = 32 +) + +func main() { + qualityLevels := []struct { + fileName string + bitrate int + }{ + {lowFile, lowBitrate}, + {medFile, medBitrate}, + {highFile, highBitrate}, + } + currentQuality := 0 + + for _, level := range qualityLevels { + _, err := os.Stat(level.fileName) + if os.IsNotExist(err) { + panic(fmt.Sprintf("File %s was not found", level.fileName)) + } + } + + i := &interceptor.Registry{} + m := &webrtc.MediaEngine{} + if err := m.RegisterDefaultCodecs(); err != nil { + panic(err) + } + + // Create a Congestion Controller. This analyzes inbound and outbound data and provides + // suggestions on how much we should be sending. + // + // Passing `nil` means we use the default Estimation Algorithm which is Google Congestion Control. + // You can use the other ones that Pion provides, or write your own! + congestionController, err := cc.NewInterceptor(func() (cc.BandwidthEstimator, error) { + return gcc.NewSendSideBWE(gcc.SendSideBWEInitialBitrate(lowBitrate)) + }) + if err != nil { + panic(err) + } + + estimatorChan := make(chan cc.BandwidthEstimator, 1) + congestionController.OnNewPeerConnection(func(id string, estimator cc.BandwidthEstimator) { + estimatorChan <- estimator + }) + + i.Add(congestionController) + if err = webrtc.ConfigureTWCCHeaderExtensionSender(m, i); err != nil { + panic(err) + } + + if err = webrtc.RegisterDefaultInterceptors(m, i); err != nil { + panic(err) + } + + // Create a new RTCPeerConnection + peerConnection, err := webrtc.NewAPI(webrtc.WithInterceptorRegistry(i), webrtc.WithMediaEngine(m)).NewPeerConnection(webrtc.Configuration{ + ICEServers: []webrtc.ICEServer{ + { + URLs: []string{"stun:stun.l.google.com:19302"}, + }, + }, + }) + if err != nil { + panic(err) + } + defer func() { + if cErr := peerConnection.Close(); cErr != nil { + fmt.Printf("cannot close peerConnection: %v\n", cErr) + } + }() + + // Wait until our Bandwidth Estimator has been created + estimator := <-estimatorChan + + // Create a video track + videoTrack, err := webrtc.NewTrackLocalStaticSample(webrtc.RTPCodecCapability{MimeType: webrtc.MimeTypeVP8}, "video", "pion") + if err != nil { + panic(err) + } + + rtpSender, err := peerConnection.AddTrack(videoTrack) + if err != nil { + panic(err) + } + + // Read incoming RTCP packets + // Before these packets are returned they are processed by interceptors. For things + // like NACK this needs to be called. + go func() { + rtcpBuf := make([]byte, 1500) + for { + if _, _, rtcpErr := rtpSender.Read(rtcpBuf); rtcpErr != nil { + return + } + } + }() + + // Set the handler for ICE connection state + // This will notify you when the peer has connected/disconnected + peerConnection.OnICEConnectionStateChange(func(connectionState webrtc.ICEConnectionState) { + fmt.Printf("Connection State has changed %s \n", connectionState.String()) + }) + + // Set the handler for Peer connection state + // This will notify you when the peer has connected/disconnected + peerConnection.OnConnectionStateChange(func(s webrtc.PeerConnectionState) { + fmt.Printf("Peer Connection State has changed: %s\n", s.String()) + }) + + // Wait for the offer to be pasted + offer := webrtc.SessionDescription{} + signal.Decode(signal.MustReadStdin(), &offer) + + // Set the remote SessionDescription + if err = peerConnection.SetRemoteDescription(offer); err != nil { + panic(err) + } + + // Create answer + answer, err := peerConnection.CreateAnswer(nil) + if err != nil { + panic(err) + } + + // Create channel that is blocked until ICE Gathering is complete + gatherComplete := webrtc.GatheringCompletePromise(peerConnection) + + // Sets the LocalDescription, and starts our UDP listeners + if err = peerConnection.SetLocalDescription(answer); err != nil { + panic(err) + } + + // Block until ICE Gathering is complete, disabling trickle ICE + // we do this because we only can exchange one signaling message + // in a production application you should exchange ICE Candidates via OnICECandidate + <-gatherComplete + + // Output the answer in base64 so we can paste it in browser + fmt.Println(signal.Encode(*peerConnection.LocalDescription())) + + // Open a IVF file and start reading using our IVFReader + file, err := os.Open(qualityLevels[currentQuality].fileName) + if err != nil { + panic(err) + } + + ivf, header, err := ivfreader.NewWith(file) + if err != nil { + panic(err) + } + + // Send our video file frame at a time. Pace our sending so we send it at the same speed it should be played back as. + // This isn't required since the video is timestamped, but we will such much higher loss if we send all at once. + // + // It is important to use a time.Ticker instead of time.Sleep because + // * avoids accumulating skew, just calling time.Sleep didn't compensate for the time spent parsing the data + // * works around latency issues with Sleep (see https://github.com/golang/go/issues/44343) + ticker := time.NewTicker(time.Millisecond * time.Duration((float32(header.TimebaseNumerator)/float32(header.TimebaseDenominator))*1000)) + frame := []byte{} + frameHeader := &ivfreader.IVFFrameHeader{} + currentTimestamp := uint64(0) + + switchQualityLevel := func(newQualityLevel int) { + fmt.Printf("Switching from %s to %s \n", qualityLevels[currentQuality].fileName, qualityLevels[newQualityLevel].fileName) + currentQuality = newQualityLevel + ivf.ResetReader(setReaderFile(qualityLevels[currentQuality].fileName)) + for { + if frame, frameHeader, err = ivf.ParseNextFrame(); err != nil { + break + } else if frameHeader.Timestamp >= currentTimestamp && frame[0]&0x1 == 0 { + break + } + } + } + + for ; true; <-ticker.C { + targetBitrate := estimator.GetTargetBitrate() + switch { + // If current quality level is below target bitrate drop to level below + case currentQuality != 0 && targetBitrate < qualityLevels[currentQuality].bitrate: + switchQualityLevel(currentQuality - 1) + + // If next quality level is above target bitrate move to next level + case len(qualityLevels) > (currentQuality+1) && targetBitrate > qualityLevels[currentQuality+1].bitrate: + switchQualityLevel(currentQuality + 1) + + // Adjust outbound bandwidth for probing + default: + frame, _, err = ivf.ParseNextFrame() + } + + switch err { + // No error write the video frame + case nil: + currentTimestamp = frameHeader.Timestamp + if err = videoTrack.WriteSample(media.Sample{Data: frame, Duration: time.Second}); err != nil { + panic(err) + } + // If we have reached the end of the file start again + case io.EOF: + ivf.ResetReader(setReaderFile(qualityLevels[currentQuality].fileName)) + // Error besides io.EOF that we dont know how to handle + default: + panic(err) + } + } +} + +func setReaderFile(filename string) func(_ int64) io.Reader { + return func(_ int64) io.Reader { + file, err := os.Open(filename) // nolint + if err != nil { + panic(err) + } + if _, err = file.Seek(ivfHeaderSize, io.SeekStart); err != nil { + panic(err) + } + return file + } +} diff --git a/examples/broadcast/README.md b/examples/broadcast/README.md index f9544b38acc..07fe41e2be3 100644 --- a/examples/broadcast/README.md +++ b/examples/broadcast/README.md @@ -11,7 +11,7 @@ go get github.com/pion/webrtc/v3/examples/broadcast ``` ### Open broadcast example page -[jsfiddle.net](https://jsfiddle.net/1jc4go7v/) You should see two buttons 'Publish a Broadcast' and 'Join a Broadcast' +[jsfiddle.net](https://jsfiddle.net/ypcsbnu3/) You should see two buttons `Publish a Broadcast` and `Join a Broadcast` ### Run Broadcast #### Linux/macOS @@ -20,7 +20,7 @@ Run `broadcast` OR run `main.go` in `github.com/pion/webrtc/examples/broadcast` ### Start a publisher * Click `Publish a Broadcast` -* Copy the string in the first input labelled `Browser base64 Session Description` +* Press `Copy browser SDP to clipboard` or copy the `Browser base64 Session Description` string manually * Run `curl localhost:8080/sdp -d "$BROWSER_OFFER"`. `$BROWSER_OFFER` is the value you copied in the last step. * The `broadcast` terminal application will respond with an answer, paste this into the second input field in your browser. * Press `Start Session` diff --git a/examples/broadcast/jsfiddle/demo.html b/examples/broadcast/jsfiddle/demo.html index dd6fed92d36..d873331e8d6 100644 --- a/examples/broadcast/jsfiddle/demo.html +++ b/examples/broadcast/jsfiddle/demo.html @@ -1,7 +1,12 @@