├── .github ├── actions-rs │ └── grcov.yml └── workflows │ ├── cargo.yml │ └── grcov.yml ├── .gitignore ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── codecov.yml ├── doc └── webrtc.rs.png ├── examples ├── README.md ├── broadcast │ ├── README.md │ └── broadcast.rs ├── data-channels-close │ ├── README.md │ └── data-channels-close.rs ├── data-channels-create │ ├── README.md │ └── data-channels-create.rs ├── data-channels-detach-create │ ├── README.md │ └── data-channels-detach-create.rs ├── data-channels-detach │ ├── README.md │ └── data-channels-detach.rs ├── data-channels-flow-control │ ├── README.md │ └── data-channels-flow-control.rs ├── data-channels │ ├── README.md │ └── data-channels.rs ├── ice-restart │ ├── README.md │ ├── ice-restart.rs │ └── index.html ├── insertable-streams │ ├── README.md │ └── insertable-streams.rs ├── offer-answer │ ├── README.md │ ├── answer.rs │ └── offer.rs ├── ortc │ ├── README.md │ └── ortc.rs ├── play-from-disk-h264 │ ├── README.md │ └── play-from-disk-h264.rs ├── play-from-disk-renegotiation │ ├── README.md │ ├── index.html │ └── play-from-disk-renegotiation.rs ├── play-from-disk-vpx │ ├── README.md │ └── play-from-disk-vpx.rs ├── rc-cycle │ └── rc-cycle.rs ├── reflect │ ├── README.md │ └── reflect.rs ├── rtp-forwarder │ ├── README.md │ ├── rtp-forwarder.rs │ └── rtp-forwarder.sdp ├── rtp-to-webrtc │ ├── README.md │ └── rtp-to-webrtc.rs ├── save-to-disk-h264 │ ├── README.md │ └── save-to-disk-h264.rs ├── save-to-disk-vpx │ ├── README.md │ └── save-to-disk-vpx.rs ├── signal │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── simulcast │ ├── README.md │ └── simulcast.rs ├── swap-tracks │ ├── README.md │ └── swap-tracks.rs └── test-data │ ├── output.h264 │ ├── output.ogg │ ├── output_vp8.ivf │ └── output_vp9.ivf └── src └── lib.rs /.github/actions-rs/grcov.yml: -------------------------------------------------------------------------------- 1 | branch: true 2 | ignore-not-existing: true 3 | llvm: true 4 | filter: covered 5 | output-type: lcov 6 | output-path: ./lcov.info 7 | source-dir: . 8 | ignore: 9 | - "/*" 10 | - "C:/*" 11 | - "../*" 12 | excl-line: "#\\[derive\\(" 13 | excl-start: "mod tests \\{" 14 | excl-br-line: "#\\[derive\\(" 15 | excl-br-start: "mod tests \\{" -------------------------------------------------------------------------------- /.github/workflows/cargo.yml: -------------------------------------------------------------------------------- 1 | name: cargo 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build: 14 | name: Build and test 15 | strategy: 16 | matrix: 17 | os: ['ubuntu-latest', 'macos-latest', 'windows-latest'] 18 | runs-on: ${{ matrix.os }} 19 | steps: 20 | - uses: actions/checkout@v2 21 | - name: Build 22 | run: cargo build --verbose 23 | - name: Run tests 24 | run: cargo test --verbose 25 | 26 | rustfmt_and_clippy: 27 | name: Check rustfmt style && run clippy 28 | runs-on: ubuntu-latest 29 | steps: 30 | - uses: actions/checkout@v2 31 | - uses: actions-rs/toolchain@v1 32 | with: 33 | toolchain: 1.56.0 34 | profile: minimal 35 | components: clippy, rustfmt 36 | override: true 37 | - name: Cache cargo registry 38 | uses: actions/cache@v1 39 | with: 40 | path: ~/.cargo/registry 41 | key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }} 42 | - name: Run clippy 43 | uses: actions-rs/cargo@v1 44 | with: 45 | command: clippy 46 | - name: Check formating 47 | uses: actions-rs/cargo@v1 48 | with: 49 | command: fmt 50 | args: --all -- --check 51 | -------------------------------------------------------------------------------- /.github/workflows/grcov.yml: -------------------------------------------------------------------------------- 1 | name: coverage 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | grcov: 14 | name: Coverage 15 | runs-on: ${{ matrix.os }} 16 | strategy: 17 | matrix: 18 | os: 19 | - ubuntu-latest 20 | toolchain: 21 | - nightly 22 | cargo_flags: 23 | - "--all-features" 24 | steps: 25 | - name: Checkout source code 26 | uses: actions/checkout@v2 27 | 28 | - name: Install Rust 29 | uses: actions-rs/toolchain@v1 30 | with: 31 | profile: minimal 32 | toolchain: ${{ matrix.toolchain }} 33 | override: true 34 | 35 | - name: Install grcov 36 | uses: actions-rs/install@v0.1 37 | with: 38 | crate: grcov 39 | version: latest 40 | use-tool-cache: true 41 | 42 | - name: Test 43 | uses: actions-rs/cargo@v1 44 | with: 45 | command: test 46 | args: --all --no-fail-fast ${{ matrix.cargo_flags }} 47 | env: 48 | CARGO_INCREMENTAL: "0" 49 | RUSTFLAGS: '-Zprofile -Ccodegen-units=1 -Copt-level=0 -Clink-dead-code -Coverflow-checks=off -Zpanic_abort_tests -Cpanic=abort -Cdebug-assertions=off' 50 | RUSTDOCFLAGS: '-Zprofile -Ccodegen-units=1 -Copt-level=0 -Clink-dead-code -Coverflow-checks=off -Zpanic_abort_tests -Cpanic=abort -Cdebug-assertions=off' 51 | 52 | - name: Generate coverage data 53 | id: grcov 54 | # uses: actions-rs/grcov@v0.1 55 | run: | 56 | grcov target/debug/ \ 57 | --branch \ 58 | --llvm \ 59 | --source-dir . \ 60 | --output-path lcov.info \ 61 | --ignore='/**' \ 62 | --ignore='C:/**' \ 63 | --ignore='../**' \ 64 | --ignore-not-existing \ 65 | --excl-line "#\\[derive\\(" \ 66 | --excl-br-line "#\\[derive\\(" \ 67 | --excl-start "#\\[cfg\\(test\\)\\]" \ 68 | --excl-br-start "#\\[cfg\\(test\\)\\]" \ 69 | --commit-sha ${{ github.sha }} \ 70 | --service-job-id ${{ github.job }} \ 71 | --service-name "GitHub Actions" \ 72 | --service-number ${{ github.run_id }} 73 | - name: Upload coverage as artifact 74 | uses: actions/upload-artifact@v2 75 | with: 76 | name: lcov.info 77 | # path: ${{ steps.grcov.outputs.report }} 78 | path: lcov.info 79 | 80 | - name: Upload coverage to codecov.io 81 | uses: codecov/codecov-action@v1 82 | with: 83 | # file: ${{ steps.grcov.outputs.report }} 84 | file: lcov.info 85 | fail_ci_if_error: true 86 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | /.idea/ 5 | 6 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 7 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 8 | Cargo.lock 9 | 10 | # These are backup files generated by rustfmt 11 | **/*.rs.bk 12 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "examples" 3 | version = "0.4.0" 4 | authors = ["Rain Liu "] 5 | edition = "2018" 6 | description = "Examples of WebRTC.rs stack" 7 | license = "MIT/Apache-2.0" 8 | documentation = "https://docs.rs/examples" 9 | homepage = "https://webrtc.rs" 10 | repository = "https://github.com/webrtc-rs/examples" 11 | 12 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 13 | 14 | [dependencies] 15 | 16 | 17 | [dev-dependencies] 18 | webrtc = "0.4.0" 19 | tokio = { version = "1.15.0", features = ["full"] } 20 | env_logger = "0.9.0" 21 | clap = "3.0.8" 22 | hyper = { version = "0.14.16", features = ["full"] } 23 | signal = {path = "examples/signal" } 24 | tokio-util = {version="0.6.9", features = ["codec"] } 25 | anyhow = "1.0.52" 26 | chrono = "0.4.19" 27 | log = "0.4.14" 28 | serde = { version = "1.0.133", features = ["derive"] } 29 | serde_json = "1.0.75" 30 | bytes = "1.1.0" 31 | lazy_static = "1.4.0" 32 | rand = "0.8.4" 33 | 34 | [profile.dev] 35 | opt-level = 0 36 | 37 | [[example]] 38 | name = "rc-cycle" 39 | path = "examples/rc-cycle/rc-cycle.rs" 40 | bench = false 41 | 42 | [[example]] 43 | name = "broadcast" 44 | path = "examples/broadcast/broadcast.rs" 45 | bench = false 46 | 47 | [[example]] 48 | name = "data-channels" 49 | path = "examples/data-channels/data-channels.rs" 50 | bench = false 51 | 52 | [[example]] 53 | name = "data-channels-close" 54 | path = "examples/data-channels-close/data-channels-close.rs" 55 | bench = false 56 | 57 | [[example]] 58 | name = "data-channels-create" 59 | path = "examples/data-channels-create/data-channels-create.rs" 60 | bench = false 61 | 62 | [[example]] 63 | name = "data-channels-detach" 64 | path = "examples/data-channels-detach/data-channels-detach.rs" 65 | bench = false 66 | 67 | [[example]] 68 | name = "data-channels-detach-create" 69 | path = "examples/data-channels-detach-create/data-channels-detach-create.rs" 70 | bench = false 71 | 72 | [[example]] 73 | name = "data-channels-flow-control" 74 | path = "examples/data-channels-flow-control/data-channels-flow-control.rs" 75 | bench = false 76 | 77 | [[example]] 78 | name = "insertable-streams" 79 | path = "examples/insertable-streams/insertable-streams.rs" 80 | bench = false 81 | 82 | [[example]] 83 | name = "play-from-disk-vpx" 84 | path = "examples/play-from-disk-vpx/play-from-disk-vpx.rs" 85 | bench = false 86 | 87 | [[example]] 88 | name = "play-from-disk-h264" 89 | path = "examples/play-from-disk-h264/play-from-disk-h264.rs" 90 | bench = false 91 | 92 | [[example]] 93 | name = "play-from-disk-renegotiation" 94 | path = "examples/play-from-disk-renegotiation/play-from-disk-renegotiation.rs" 95 | bench = false 96 | 97 | [[example]] 98 | name = "reflect" 99 | path = "examples/reflect/reflect.rs" 100 | bench = false 101 | 102 | [[example]] 103 | name = "rtp-forwarder" 104 | path = "examples/rtp-forwarder/rtp-forwarder.rs" 105 | bench = false 106 | 107 | [[example]] 108 | name = "rtp-to-webrtc" 109 | path = "examples/rtp-to-webrtc/rtp-to-webrtc.rs" 110 | bench = false 111 | 112 | [[example]] 113 | name = "save-to-disk-vpx" 114 | path = "examples/save-to-disk-vpx/save-to-disk-vpx.rs" 115 | bench = false 116 | 117 | [[example]] 118 | name = "save-to-disk-h264" 119 | path = "examples/save-to-disk-h264/save-to-disk-h264.rs" 120 | bench = false 121 | 122 | [[example]] 123 | name = "simulcast" 124 | path = "examples/simulcast/simulcast.rs" 125 | bench = false 126 | 127 | [[example]] 128 | name = "swap-tracks" 129 | path = "examples/swap-tracks/swap-tracks.rs" 130 | bench = false 131 | 132 | [[example]] 133 | name = "ortc" 134 | path = "examples/ortc/ortc.rs" 135 | bench = false 136 | 137 | [[example]] 138 | name = "offer" 139 | path = "examples/offer-answer/offer.rs" 140 | bench = false 141 | 142 | [[example]] 143 | name = "answer" 144 | path = "examples/offer-answer/answer.rs" 145 | bench = false 146 | 147 | [[example]] 148 | name = "ice-restart" 149 | path = "examples/ice-restart/ice-restart.rs" 150 | bench = false 151 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 WebRTC.rs 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Crate moved 2 | 3 | As of the 23rd of August 2022 this crate has been migrated to the [`webrtc-rs/webrtc`](http://github.com/webrtc-rs/webrtc/) monorepo. -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | codecov: 2 | require_ci_to_pass: yes 3 | max_report_age: off 4 | token: ec7b9766-689c-46bf-99fe-6c8e9971d20b 5 | 6 | coverage: 7 | precision: 2 8 | round: down 9 | range: 50..90 10 | status: 11 | project: 12 | default: 13 | enabled: no 14 | threshold: 0.2 15 | if_not_found: success 16 | patch: 17 | default: 18 | enabled: no 19 | if_not_found: success 20 | changes: 21 | default: 22 | enabled: no 23 | if_not_found: success 24 | -------------------------------------------------------------------------------- /doc/webrtc.rs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/webrtc-rs/examples/f91465021354995edd148e6e60e5c3b1f78a656c/doc/webrtc.rs.png -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 |

2 | Examples 3 |

4 | 5 | All examples are ported from [Pion](https://github.com/pion/webrtc/tree/master/examples#readme). Please check [Pion Examples](https://github.com/pion/webrtc/tree/master/examples#readme) for more details: 6 | 7 | #### Media API 8 | - [x] [Reflect](reflect): The reflect example demonstrates how to have webrtc-rs send back to the user exactly what it receives using the same PeerConnection. 9 | - [x] [Play from Disk VPx](play-from-disk-vpx): The play-from-disk-vp8 example demonstrates how to send VP8/VP9 video to your browser from a file saved to disk. 10 | - [x] [Play from Disk H264](play-from-disk-h264): The play-from-disk-h264 example demonstrates how to send H264 video to your browser from a file saved to disk. 11 | - [x] [Play from Disk Renegotiation](play-from-disk-renegotiation): The play-from-disk-renegotiation example is an extension of the play-from-disk example, but demonstrates how you can add/remove video tracks from an already negotiated PeerConnection. 12 | - [x] [Insertable Streams](insertable-streams): The insertable-streams example demonstrates how webrtc-rs can be used to send E2E encrypted video and decrypt via insertable streams in the browser. 13 | - [x] [Save to Disk VPx](save-to-disk-vpx): The save-to-disk example shows how to record your webcam and save the footage (VP8/VP9 for video, Opus for audio) to disk on the server side. 14 | - [x] [Save to Disk H264](save-to-disk-h264): The save-to-disk example shows how to record your webcam and save the footage (H264 for video, Opus for audio) to disk on the server side. 15 | - [x] [Broadcast](broadcast): The broadcast example demonstrates how to broadcast a video to multiple peers. A broadcaster uploads the video once and the server forwards it to all other peers. 16 | - [x] [RTP Forwarder](rtp-forwarder): The rtp-forwarder example demonstrates how to forward your audio/video streams using RTP. 17 | - [x] [RTP to WebRTC](rtp-to-webrtc): The rtp-to-webrtc example demonstrates how to take RTP packets sent to a webrtc-rs process into your browser. 18 | - [x] [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. 19 | - [x] [Swap Tracks](swap-tracks): The swap-tracks demonstrates how to swap multiple incoming tracks on a single outgoing track. 20 | 21 | #### Data Channel API 22 | - [x] [Data Channels](data-channels): The data-channels example shows how you can send/recv DataChannel messages from a web browser. 23 | - [x] [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. 24 | - [x] [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. 25 | - [x] [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. 26 | - [x] [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. 27 | - [x] [Data Channels Flow Control](data-channels-flow-control): Example data-channels-flow-control shows how to use flow control. 28 | - [x] [ORTC](ortc): Example ortc shows how to use the ORTC API for DataChannel communication. 29 | - [x] [Offer Answer](offer-answer): Example offer-answer is an example of two webrtc-rs or pion instances communicating directly! 30 | - [x] [ICE Restart](ice-restart): The ice-restart demonstrates webrtc-rs ICE Restart abilities. 31 | -------------------------------------------------------------------------------- /examples/broadcast/README.md: -------------------------------------------------------------------------------- 1 | # broadcast 2 | broadcast is a WebRTC.rs application that demonstrates how to broadcast a video to many peers, while only requiring the broadcaster to upload once. 3 | 4 | This could serve as the building block to building conferencing software, and other applications where publishers are bandwidth constrained. 5 | 6 | ## Instructions 7 | ### Build broadcast 8 | ``` 9 | cargo build --example broadcast 10 | ``` 11 | 12 | ### Open broadcast example page 13 | [jsfiddle.net](https://jsfiddle.net/1jc4go7v/) You should see two buttons 'Publish a Broadcast' and 'Join a Broadcast' 14 | 15 | ### Run Broadcast 16 | #### Linux/macOS 17 | Run `broadcast` 18 | 19 | ### Start a publisher 20 | 21 | * Click `Publish a Broadcast` 22 | * Copy the string in the first input labelled `Browser base64 Session Description` 23 | * Run `curl localhost:8080/sdp -d "$BROWSER_OFFER"`. `$BROWSER_OFFER` is the value you copied in the last step. 24 | * The `broadcast` terminal application will respond with an answer, paste this into the second input field in your browser. 25 | * Press `Start Session` 26 | * The connection state will be printed in the terminal and under `logs` in the browser. 27 | 28 | ### Join the broadcast 29 | * Click `Join a Broadcast` 30 | * Copy the string in the first input labelled `Browser base64 Session Description` 31 | * Run `curl localhost:8080/sdp -d "$BROWSER_OFFER"`. `$BROWSER_OFFER` is the value you copied in the last step. 32 | * The `broadcast` terminal application will respond with an answer, paste this into the second input field in your browser. 33 | * Press `Start Session` 34 | * The connection state will be printed in the terminal and under `logs` in the browser. 35 | 36 | You can change the listening port using `-port 8011` 37 | 38 | You can `Join the broadcast` as many times as you want. The `broadcast` application is relaying all traffic, so your browser only has to upload once. 39 | 40 | Congrats, you have used WebRTC.rs! 41 | -------------------------------------------------------------------------------- /examples/broadcast/broadcast.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use clap::{AppSettings, Arg, Command}; 3 | use std::io::Write; 4 | use std::sync::Arc; 5 | use tokio::time::Duration; 6 | use webrtc::api::interceptor_registry::register_default_interceptors; 7 | use webrtc::api::media_engine::MediaEngine; 8 | use webrtc::api::APIBuilder; 9 | use webrtc::ice_transport::ice_server::RTCIceServer; 10 | use webrtc::interceptor::registry::Registry; 11 | use webrtc::peer_connection::configuration::RTCConfiguration; 12 | use webrtc::peer_connection::peer_connection_state::RTCPeerConnectionState; 13 | use webrtc::peer_connection::sdp::session_description::RTCSessionDescription; 14 | use webrtc::rtcp::payload_feedbacks::picture_loss_indication::PictureLossIndication; 15 | use webrtc::rtp_transceiver::rtp_codec::RTPCodecType; 16 | use webrtc::rtp_transceiver::rtp_receiver::RTCRtpReceiver; 17 | use webrtc::track::track_local::track_local_static_rtp::TrackLocalStaticRTP; 18 | use webrtc::track::track_local::{TrackLocal, TrackLocalWriter}; 19 | use webrtc::track::track_remote::TrackRemote; 20 | use webrtc::Error; 21 | 22 | #[tokio::main] 23 | async fn main() -> Result<()> { 24 | let mut app = Command::new("broadcast") 25 | .version("0.1.0") 26 | .author("Rain Liu ") 27 | .about("An example of broadcast.") 28 | .setting(AppSettings::DeriveDisplayOrder) 29 | .subcommand_negates_reqs(true) 30 | .arg( 31 | Arg::new("FULLHELP") 32 | .help("Prints more detailed help information") 33 | .long("fullhelp"), 34 | ) 35 | .arg( 36 | Arg::new("debug") 37 | .long("debug") 38 | .short('d') 39 | .help("Prints debug log information"), 40 | ) 41 | .arg( 42 | Arg::new("port") 43 | .takes_value(true) 44 | .default_value("8080") 45 | .long("port") 46 | .help("http server port."), 47 | ); 48 | 49 | let matches = app.clone().get_matches(); 50 | 51 | if matches.is_present("FULLHELP") { 52 | app.print_long_help().unwrap(); 53 | std::process::exit(0); 54 | } 55 | 56 | let debug = matches.is_present("debug"); 57 | if debug { 58 | env_logger::Builder::new() 59 | .format(|buf, record| { 60 | writeln!( 61 | buf, 62 | "{}:{} [{}] {} - {}", 63 | record.file().unwrap_or("unknown"), 64 | record.line().unwrap_or(0), 65 | record.level(), 66 | chrono::Local::now().format("%H:%M:%S.%6f"), 67 | record.args() 68 | ) 69 | }) 70 | .filter(None, log::LevelFilter::Trace) 71 | .init(); 72 | } 73 | 74 | let port = matches.value_of("port").unwrap().parse::()?; 75 | let mut sdp_chan_rx = signal::http_sdp_server(port).await; 76 | 77 | // Wait for the offer 78 | println!("wait for the offer from http_sdp_server\n"); 79 | let line = sdp_chan_rx.recv().await.unwrap(); 80 | let desc_data = signal::decode(line.as_str())?; 81 | let offer = serde_json::from_str::(&desc_data)?; 82 | //println!("Receive offer from http_sdp_server:\n{:?}", offer); 83 | 84 | // Everything below is the WebRTC-rs API! Thanks for using it ❤️. 85 | 86 | // Create a MediaEngine object to configure the supported codec 87 | let mut m = MediaEngine::default(); 88 | 89 | m.register_default_codecs()?; 90 | 91 | // Create a InterceptorRegistry. This is the user configurable RTP/RTCP Pipeline. 92 | // This provides NACKs, RTCP Reports and other features. If you use `webrtc.NewPeerConnection` 93 | // this is enabled by default. If you are manually managing You MUST create a InterceptorRegistry 94 | // for each PeerConnection. 95 | let mut registry = Registry::new(); 96 | 97 | // Use the default set of Interceptors 98 | registry = register_default_interceptors(registry, &mut m)?; 99 | 100 | // Create the API object with the MediaEngine 101 | let api = APIBuilder::new() 102 | .with_media_engine(m) 103 | .with_interceptor_registry(registry) 104 | .build(); 105 | 106 | // Prepare the configuration 107 | let config = RTCConfiguration { 108 | ice_servers: vec![RTCIceServer { 109 | urls: vec!["stun:stun.l.google.com:19302".to_owned()], 110 | ..Default::default() 111 | }], 112 | ..Default::default() 113 | }; 114 | 115 | // Create a new RTCPeerConnection 116 | let peer_connection = Arc::new(api.new_peer_connection(config).await?); 117 | 118 | // Allow us to receive 1 video track 119 | peer_connection 120 | .add_transceiver_from_kind(RTPCodecType::Video, &[]) 121 | .await?; 122 | 123 | let (local_track_chan_tx, mut local_track_chan_rx) = 124 | tokio::sync::mpsc::channel::>(1); 125 | 126 | let local_track_chan_tx = Arc::new(local_track_chan_tx); 127 | // Set a handler for when a new remote track starts, this handler copies inbound RTP packets, 128 | // replaces the SSRC and sends them back 129 | let pc = Arc::downgrade(&peer_connection); 130 | peer_connection 131 | .on_track(Box::new( 132 | move |track: Option>, _receiver: Option>| { 133 | if let Some(track) = track { 134 | // Send a PLI on an interval so that the publisher is pushing a keyframe every rtcpPLIInterval 135 | // This is a temporary fix until we implement incoming RTCP events, then we would push a PLI only when a viewer requests it 136 | let media_ssrc = track.ssrc(); 137 | let pc2 = pc.clone(); 138 | tokio::spawn(async move { 139 | let mut result = Result::::Ok(0); 140 | while result.is_ok() { 141 | let timeout = tokio::time::sleep(Duration::from_secs(3)); 142 | tokio::pin!(timeout); 143 | 144 | tokio::select! { 145 | _ = timeout.as_mut() =>{ 146 | if let Some(pc) = pc2.upgrade(){ 147 | result = pc.write_rtcp(&[Box::new(PictureLossIndication{ 148 | sender_ssrc: 0, 149 | media_ssrc, 150 | })]).await.map_err(Into::into); 151 | }else{ 152 | break; 153 | } 154 | } 155 | }; 156 | } 157 | }); 158 | 159 | let local_track_chan_tx2 = Arc::clone(&local_track_chan_tx); 160 | tokio::spawn(async move { 161 | // Create Track that we send video back to browser on 162 | let local_track = Arc::new(TrackLocalStaticRTP::new( 163 | track.codec().await.capability, 164 | "video".to_owned(), 165 | "webrtc-rs".to_owned(), 166 | )); 167 | let _ = local_track_chan_tx2.send(Arc::clone(&local_track)).await; 168 | 169 | // Read RTP packets being sent to webrtc-rs 170 | while let Ok((rtp, _)) = track.read_rtp().await { 171 | if let Err(err) = local_track.write_rtp(&rtp).await { 172 | if Error::ErrClosedPipe != err { 173 | print!("output track write_rtp got error: {} and break", err); 174 | break; 175 | } else { 176 | print!("output track write_rtp got error: {}", err); 177 | } 178 | } 179 | } 180 | }); 181 | } 182 | 183 | Box::pin(async {}) 184 | }, 185 | )) 186 | .await; 187 | 188 | // Set the handler for Peer connection state 189 | // This will notify you when the peer has connected/disconnected 190 | peer_connection 191 | .on_peer_connection_state_change(Box::new(move |s: RTCPeerConnectionState| { 192 | println!("Peer Connection State has changed: {}", s); 193 | Box::pin(async {}) 194 | })) 195 | .await; 196 | 197 | // Set the remote SessionDescription 198 | peer_connection.set_remote_description(offer).await?; 199 | 200 | // Create an answer 201 | let answer = peer_connection.create_answer(None).await?; 202 | 203 | // Create channel that is blocked until ICE Gathering is complete 204 | let mut gather_complete = peer_connection.gathering_complete_promise().await; 205 | 206 | // Sets the LocalDescription, and starts our UDP listeners 207 | peer_connection.set_local_description(answer).await?; 208 | 209 | // Block until ICE Gathering is complete, disabling trickle ICE 210 | // we do this because we only can exchange one signaling message 211 | // in a production application you should exchange ICE Candidates via OnICECandidate 212 | let _ = gather_complete.recv().await; 213 | 214 | // Output the answer in base64 so we can paste it in browser 215 | if let Some(local_desc) = peer_connection.local_description().await { 216 | let json_str = serde_json::to_string(&local_desc)?; 217 | let b64 = signal::encode(&json_str); 218 | println!("{}", b64); 219 | } else { 220 | println!("generate local_description failed!"); 221 | } 222 | 223 | if let Some(local_track) = local_track_chan_rx.recv().await { 224 | loop { 225 | println!("\nCurl an base64 SDP to start sendonly peer connection"); 226 | 227 | let line = sdp_chan_rx.recv().await.unwrap(); 228 | let desc_data = signal::decode(line.as_str())?; 229 | let recv_only_offer = serde_json::from_str::(&desc_data)?; 230 | 231 | // Create a MediaEngine object to configure the supported codec 232 | let mut m = MediaEngine::default(); 233 | 234 | m.register_default_codecs()?; 235 | 236 | // Create a InterceptorRegistry. This is the user configurable RTP/RTCP Pipeline. 237 | // This provides NACKs, RTCP Reports and other features. If you use `webrtc.NewPeerConnection` 238 | // this is enabled by default. If you are manually managing You MUST create a InterceptorRegistry 239 | // for each PeerConnection. 240 | let mut registry = Registry::new(); 241 | 242 | // Use the default set of Interceptors 243 | registry = register_default_interceptors(registry, &mut m)?; 244 | 245 | // Create the API object with the MediaEngine 246 | let api = APIBuilder::new() 247 | .with_media_engine(m) 248 | .with_interceptor_registry(registry) 249 | .build(); 250 | 251 | // Prepare the configuration 252 | let config = RTCConfiguration { 253 | ice_servers: vec![RTCIceServer { 254 | urls: vec!["stun:stun.l.google.com:19302".to_owned()], 255 | ..Default::default() 256 | }], 257 | ..Default::default() 258 | }; 259 | 260 | // Create a new RTCPeerConnection 261 | let peer_connection = Arc::new(api.new_peer_connection(config).await?); 262 | 263 | let rtp_sender = peer_connection 264 | .add_track(Arc::clone(&local_track) as Arc) 265 | .await?; 266 | 267 | // Read incoming RTCP packets 268 | // Before these packets are returned they are processed by interceptors. For things 269 | // like NACK this needs to be called. 270 | tokio::spawn(async move { 271 | let mut rtcp_buf = vec![0u8; 1500]; 272 | while let Ok((_, _)) = rtp_sender.read(&mut rtcp_buf).await {} 273 | Result::<()>::Ok(()) 274 | }); 275 | 276 | // Set the handler for Peer connection state 277 | // This will notify you when the peer has connected/disconnected 278 | peer_connection 279 | .on_peer_connection_state_change(Box::new(move |s: RTCPeerConnectionState| { 280 | println!("Peer Connection State has changed: {}", s); 281 | Box::pin(async {}) 282 | })) 283 | .await; 284 | 285 | // Set the remote SessionDescription 286 | peer_connection 287 | .set_remote_description(recv_only_offer) 288 | .await?; 289 | 290 | // Create an answer 291 | let answer = peer_connection.create_answer(None).await?; 292 | 293 | // Create channel that is blocked until ICE Gathering is complete 294 | let mut gather_complete = peer_connection.gathering_complete_promise().await; 295 | 296 | // Sets the LocalDescription, and starts our UDP listeners 297 | peer_connection.set_local_description(answer).await?; 298 | 299 | // Block until ICE Gathering is complete, disabling trickle ICE 300 | // we do this because we only can exchange one signaling message 301 | // in a production application you should exchange ICE Candidates via OnICECandidate 302 | let _ = gather_complete.recv().await; 303 | 304 | if let Some(local_desc) = peer_connection.local_description().await { 305 | let json_str = serde_json::to_string(&local_desc)?; 306 | let b64 = signal::encode(&json_str); 307 | println!("{}", b64); 308 | } else { 309 | println!("generate local_description failed!"); 310 | } 311 | } 312 | } 313 | 314 | Ok(()) 315 | } 316 | -------------------------------------------------------------------------------- /examples/data-channels-close/README.md: -------------------------------------------------------------------------------- 1 | # data-channels-close 2 | data-channels-close is a variant of the data-channels example that allow playing with the life cycle of data channels. 3 | -------------------------------------------------------------------------------- /examples/data-channels-close/data-channels-close.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use clap::{AppSettings, Arg, Command}; 3 | use std::io::Write; 4 | use std::sync::atomic::{AtomicI32, Ordering}; 5 | use std::sync::Arc; 6 | use tokio::sync::Mutex; 7 | use tokio::time::Duration; 8 | use webrtc::api::interceptor_registry::register_default_interceptors; 9 | use webrtc::api::media_engine::MediaEngine; 10 | use webrtc::api::APIBuilder; 11 | use webrtc::data_channel::data_channel_message::DataChannelMessage; 12 | use webrtc::data_channel::RTCDataChannel; 13 | use webrtc::ice_transport::ice_server::RTCIceServer; 14 | use webrtc::interceptor::registry::Registry; 15 | use webrtc::peer_connection::configuration::RTCConfiguration; 16 | use webrtc::peer_connection::math_rand_alpha; 17 | use webrtc::peer_connection::peer_connection_state::RTCPeerConnectionState; 18 | use webrtc::peer_connection::sdp::session_description::RTCSessionDescription; 19 | 20 | #[tokio::main] 21 | async fn main() -> Result<()> { 22 | let mut app = Command::new("data-channels-close") 23 | .version("0.1.0") 24 | .author("Rain Liu ") 25 | .about("An example of Data-Channels-Close.") 26 | .setting(AppSettings::DeriveDisplayOrder) 27 | .subcommand_negates_reqs(true) 28 | .arg( 29 | Arg::new("FULLHELP") 30 | .help("Prints more detailed help information") 31 | .long("fullhelp"), 32 | ) 33 | .arg( 34 | Arg::new("debug") 35 | .long("debug") 36 | .short('d') 37 | .help("Prints debug log information"), 38 | ) 39 | .arg( 40 | Arg::new("close-after") 41 | .takes_value(true) 42 | .default_value("5") 43 | .long("close-after") 44 | .help("Close data channel after sending X times."), 45 | ); 46 | 47 | let matches = app.clone().get_matches(); 48 | 49 | if matches.is_present("FULLHELP") { 50 | app.print_long_help().unwrap(); 51 | std::process::exit(0); 52 | } 53 | 54 | let close_after = Arc::new(AtomicI32::new( 55 | matches 56 | .value_of("close-after") 57 | .unwrap() 58 | .to_owned() 59 | .parse::()?, 60 | )); 61 | let debug = matches.is_present("debug"); 62 | if debug { 63 | env_logger::Builder::new() 64 | .format(|buf, record| { 65 | writeln!( 66 | buf, 67 | "{}:{} [{}] {} - {}", 68 | record.file().unwrap_or("unknown"), 69 | record.line().unwrap_or(0), 70 | record.level(), 71 | chrono::Local::now().format("%H:%M:%S.%6f"), 72 | record.args() 73 | ) 74 | }) 75 | .filter(None, log::LevelFilter::Trace) 76 | .init(); 77 | } 78 | 79 | // Everything below is the WebRTC-rs API! Thanks for using it ❤️. 80 | 81 | // Create a MediaEngine object to configure the supported codec 82 | let mut m = MediaEngine::default(); 83 | 84 | // Register default codecs 85 | m.register_default_codecs()?; 86 | 87 | // Create a InterceptorRegistry. This is the user configurable RTP/RTCP Pipeline. 88 | // This provides NACKs, RTCP Reports and other features. If you use `webrtc.NewPeerConnection` 89 | // this is enabled by default. If you are manually managing You MUST create a InterceptorRegistry 90 | // for each PeerConnection. 91 | let mut registry = Registry::new(); 92 | 93 | // Use the default set of Interceptors 94 | registry = register_default_interceptors(registry, &mut m)?; 95 | 96 | // Create the API object with the MediaEngine 97 | let api = APIBuilder::new() 98 | .with_media_engine(m) 99 | .with_interceptor_registry(registry) 100 | .build(); 101 | 102 | // Prepare the configuration 103 | let config = RTCConfiguration { 104 | ice_servers: vec![RTCIceServer { 105 | urls: vec!["stun:stun.l.google.com:19302".to_owned()], 106 | ..Default::default() 107 | }], 108 | ..Default::default() 109 | }; 110 | 111 | // Create a new RTCPeerConnection 112 | let peer_connection = Arc::new(api.new_peer_connection(config).await?); 113 | 114 | let (done_tx, mut done_rx) = tokio::sync::mpsc::channel::<()>(1); 115 | 116 | // Set the handler for Peer connection state 117 | // This will notify you when the peer has connected/disconnected 118 | peer_connection 119 | .on_peer_connection_state_change(Box::new(move |s: RTCPeerConnectionState| { 120 | println!("Peer Connection State has changed: {}", s); 121 | 122 | if s == RTCPeerConnectionState::Failed { 123 | // Wait until PeerConnection has had no network activity for 30 seconds or another failure. It may be reconnected using an ICE Restart. 124 | // Use webrtc.PeerConnectionStateDisconnected if you are interested in detecting faster timeout. 125 | // Note that the PeerConnection may come back from PeerConnectionStateDisconnected. 126 | println!("Peer Connection has gone to failed exiting"); 127 | let _ = done_tx.try_send(()); 128 | } 129 | 130 | Box::pin(async {}) 131 | })) 132 | .await; 133 | 134 | // Register data channel creation handling 135 | peer_connection 136 | .on_data_channel(Box::new(move |d: Arc| { 137 | let d_label = d.label().to_owned(); 138 | let d_id = d.id(); 139 | println!("New DataChannel {} {}", d_label, d_id); 140 | 141 | let close_after2 = Arc::clone(&close_after); 142 | 143 | // Register channel opening handling 144 | Box::pin(async move { 145 | let d2 = Arc::clone(&d); 146 | let d_label2 = d_label.clone(); 147 | let d_id2 = d_id; 148 | d.on_open(Box::new(move || { 149 | println!("Data channel '{}'-'{}' open. Random messages will now be sent to any connected DataChannels every 5 seconds", d_label2, d_id2); 150 | let (done_tx, mut done_rx) = tokio::sync::mpsc::channel::<()>(1); 151 | let done_tx = Arc::new(Mutex::new(Some(done_tx))); 152 | Box::pin(async move { 153 | d2.on_close(Box::new(move || { 154 | println!("Data channel '{}'-'{}' closed.", d_label2, d_id2); 155 | let done_tx2 = Arc::clone(&done_tx); 156 | Box::pin(async move{ 157 | let mut done = done_tx2.lock().await; 158 | done.take(); 159 | }) 160 | })).await; 161 | 162 | let mut result = Result::::Ok(0); 163 | while result.is_ok() { 164 | let timeout = tokio::time::sleep(Duration::from_secs(5)); 165 | tokio::pin!(timeout); 166 | 167 | tokio::select! { 168 | _ = done_rx.recv() => { 169 | break; 170 | } 171 | _ = timeout.as_mut() =>{ 172 | let message = math_rand_alpha(15); 173 | println!("Sending '{}'", message); 174 | result = d2.send_text(message).await.map_err(Into::into); 175 | 176 | let cnt = close_after2.fetch_sub(1, Ordering::SeqCst); 177 | if cnt <= 0 { 178 | println!("Sent times out. Closing data channel '{}'-'{}'.", d2.label(), d2.id()); 179 | let _ = d2.close().await; 180 | break; 181 | } 182 | } 183 | }; 184 | } 185 | }) 186 | })).await; 187 | 188 | // Register text message handling 189 | d.on_message(Box::new(move |msg: DataChannelMessage| { 190 | let msg_str = String::from_utf8(msg.data.to_vec()).unwrap(); 191 | println!("Message from DataChannel '{}': '{}'", d_label, msg_str); 192 | Box::pin(async {}) 193 | })).await; 194 | }) 195 | })) 196 | .await; 197 | 198 | // Wait for the offer to be pasted 199 | let line = signal::must_read_stdin()?; 200 | let desc_data = signal::decode(line.as_str())?; 201 | let offer = serde_json::from_str::(&desc_data)?; 202 | 203 | // Set the remote SessionDescription 204 | peer_connection.set_remote_description(offer).await?; 205 | 206 | // Create an answer 207 | let answer = peer_connection.create_answer(None).await?; 208 | 209 | // Create channel that is blocked until ICE Gathering is complete 210 | let mut gather_complete = peer_connection.gathering_complete_promise().await; 211 | 212 | // Sets the LocalDescription, and starts our UDP listeners 213 | peer_connection.set_local_description(answer).await?; 214 | 215 | // Block until ICE Gathering is complete, disabling trickle ICE 216 | // we do this because we only can exchange one signaling message 217 | // in a production application you should exchange ICE Candidates via OnICECandidate 218 | let _ = gather_complete.recv().await; 219 | 220 | // Output the answer in base64 so we can paste it in browser 221 | if let Some(local_desc) = peer_connection.local_description().await { 222 | let json_str = serde_json::to_string(&local_desc)?; 223 | let b64 = signal::encode(&json_str); 224 | println!("{}", b64); 225 | } else { 226 | println!("generate local_description failed!"); 227 | } 228 | 229 | println!("Press ctrl-c to stop"); 230 | tokio::select! { 231 | _ = done_rx.recv() => { 232 | println!("received done signal!"); 233 | } 234 | _ = tokio::signal::ctrl_c() => { 235 | println!(""); 236 | } 237 | }; 238 | 239 | peer_connection.close().await?; 240 | 241 | Ok(()) 242 | } 243 | -------------------------------------------------------------------------------- /examples/data-channels-create/README.md: -------------------------------------------------------------------------------- 1 | # data-channels-create 2 | data-channels-create is a WebRTC.rs application that shows how you can send/recv DataChannel messages from a web browser. The difference with the data-channels example is that the datachannel is initialized from the WebRTC.rs side in this example. 3 | 4 | ## Instructions 5 | ### Build data-channels-create 6 | ``` 7 | cargo build --example data-channels-create 8 | ``` 9 | 10 | ### Open data-channels-create example page 11 | [jsfiddle.net](https://jsfiddle.net/swgxrp94/20/) 12 | 13 | ### Run data-channels-create 14 | Just run `data-channels-create`. 15 | 16 | ### Input data-channels-create's SessionDescription into your browser 17 | Copy the text that `data-channels-create` just emitted and copy into first text area of the jsfiddle. 18 | 19 | ### Hit 'Start Session' in jsfiddle 20 | Hit the 'Start Session' button in the browser. You should see `have-remote-offer` below the `Send Message` button. 21 | 22 | ### Input browser's SessionDescription into data-channels-create 23 | Meanwhile text has appeared in the second text area of the jsfiddle. Copy the text and paste it into `data-channels-create` and hit ENTER. 24 | In the browser you'll now see `connected` as the connection is created. If everything worked you should see `New DataChannel data`. 25 | 26 | Now you can put whatever you want in the `Message` textarea, and when you hit `Send Message` it should appear in your terminal! 27 | 28 | WebRTC.rs will send random messages every 5 seconds that will appear in your browser. 29 | 30 | Congrats, you have used WebRTC.rs! -------------------------------------------------------------------------------- /examples/data-channels-create/data-channels-create.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use clap::{AppSettings, Arg, Command}; 3 | use std::io::Write; 4 | use std::sync::Arc; 5 | use tokio::time::Duration; 6 | use webrtc::api::interceptor_registry::register_default_interceptors; 7 | use webrtc::api::media_engine::MediaEngine; 8 | use webrtc::api::APIBuilder; 9 | use webrtc::data_channel::data_channel_message::DataChannelMessage; 10 | use webrtc::ice_transport::ice_server::RTCIceServer; 11 | use webrtc::interceptor::registry::Registry; 12 | use webrtc::peer_connection::configuration::RTCConfiguration; 13 | use webrtc::peer_connection::math_rand_alpha; 14 | use webrtc::peer_connection::peer_connection_state::RTCPeerConnectionState; 15 | use webrtc::peer_connection::sdp::session_description::RTCSessionDescription; 16 | 17 | #[tokio::main] 18 | async fn main() -> Result<()> { 19 | let mut app = Command::new("data-channels-create") 20 | .version("0.1.0") 21 | .author("Rain Liu ") 22 | .about("An example of Data-Channels-Create.") 23 | .setting(AppSettings::DeriveDisplayOrder) 24 | .subcommand_negates_reqs(true) 25 | .arg( 26 | Arg::new("FULLHELP") 27 | .help("Prints more detailed help information") 28 | .long("fullhelp"), 29 | ) 30 | .arg( 31 | Arg::new("debug") 32 | .long("debug") 33 | .short('d') 34 | .help("Prints debug log information"), 35 | ); 36 | 37 | let matches = app.clone().get_matches(); 38 | 39 | if matches.is_present("FULLHELP") { 40 | app.print_long_help().unwrap(); 41 | std::process::exit(0); 42 | } 43 | 44 | let debug = matches.is_present("debug"); 45 | if debug { 46 | env_logger::Builder::new() 47 | .format(|buf, record| { 48 | writeln!( 49 | buf, 50 | "{}:{} [{}] {} - {}", 51 | record.file().unwrap_or("unknown"), 52 | record.line().unwrap_or(0), 53 | record.level(), 54 | chrono::Local::now().format("%H:%M:%S.%6f"), 55 | record.args() 56 | ) 57 | }) 58 | .filter(None, log::LevelFilter::Trace) 59 | .init(); 60 | } 61 | 62 | // Everything below is the WebRTC-rs API! Thanks for using it ❤️. 63 | 64 | // Create a MediaEngine object to configure the supported codec 65 | let mut m = MediaEngine::default(); 66 | 67 | // Register default codecs 68 | m.register_default_codecs()?; 69 | 70 | // Create a InterceptorRegistry. This is the user configurable RTP/RTCP Pipeline. 71 | // This provides NACKs, RTCP Reports and other features. If you use `webrtc.NewPeerConnection` 72 | // this is enabled by default. If you are manually managing You MUST create a InterceptorRegistry 73 | // for each PeerConnection. 74 | let mut registry = Registry::new(); 75 | 76 | // Use the default set of Interceptors 77 | registry = register_default_interceptors(registry, &mut m)?; 78 | 79 | // Create the API object with the MediaEngine 80 | let api = APIBuilder::new() 81 | .with_media_engine(m) 82 | .with_interceptor_registry(registry) 83 | .build(); 84 | 85 | // Prepare the configuration 86 | let config = RTCConfiguration { 87 | ice_servers: vec![RTCIceServer { 88 | urls: vec!["stun:stun.l.google.com:19302".to_owned()], 89 | ..Default::default() 90 | }], 91 | ..Default::default() 92 | }; 93 | 94 | // Create a new RTCPeerConnection 95 | let peer_connection = Arc::new(api.new_peer_connection(config).await?); 96 | 97 | // Create a datachannel with label 'data' 98 | let data_channel = peer_connection.create_data_channel("data", None).await?; 99 | 100 | let (done_tx, mut done_rx) = tokio::sync::mpsc::channel::<()>(1); 101 | 102 | // Set the handler for Peer connection state 103 | // This will notify you when the peer has connected/disconnected 104 | peer_connection 105 | .on_peer_connection_state_change(Box::new(move |s: RTCPeerConnectionState| { 106 | println!("Peer Connection State has changed: {}", s); 107 | 108 | if s == RTCPeerConnectionState::Failed { 109 | // Wait until PeerConnection has had no network activity for 30 seconds or another failure. It may be reconnected using an ICE Restart. 110 | // Use webrtc.PeerConnectionStateDisconnected if you are interested in detecting faster timeout. 111 | // Note that the PeerConnection may come back from PeerConnectionStateDisconnected. 112 | println!("Peer Connection has gone to failed exiting"); 113 | let _ = done_tx.try_send(()); 114 | } 115 | 116 | Box::pin(async {}) 117 | })) 118 | .await; 119 | 120 | // Register channel opening handling 121 | let d1 = Arc::clone(&data_channel); 122 | data_channel.on_open(Box::new(move || { 123 | println!("Data channel '{}'-'{}' open. Random messages will now be sent to any connected DataChannels every 5 seconds", d1.label(), d1.id()); 124 | 125 | let d2 = Arc::clone(&d1); 126 | Box::pin(async move { 127 | let mut result = Result::::Ok(0); 128 | while result.is_ok() { 129 | let timeout = tokio::time::sleep(Duration::from_secs(5)); 130 | tokio::pin!(timeout); 131 | 132 | tokio::select! { 133 | _ = timeout.as_mut() =>{ 134 | let message = math_rand_alpha(15); 135 | println!("Sending '{}'", message); 136 | result = d2.send_text(message).await.map_err(Into::into); 137 | } 138 | }; 139 | } 140 | }) 141 | })).await; 142 | 143 | // Register text message handling 144 | let d_label = data_channel.label().to_owned(); 145 | data_channel 146 | .on_message(Box::new(move |msg: DataChannelMessage| { 147 | let msg_str = String::from_utf8(msg.data.to_vec()).unwrap(); 148 | println!("Message from DataChannel '{}': '{}'", d_label, msg_str); 149 | Box::pin(async {}) 150 | })) 151 | .await; 152 | 153 | // Create an offer to send to the browser 154 | let offer = peer_connection.create_offer(None).await?; 155 | 156 | // Create channel that is blocked until ICE Gathering is complete 157 | let mut gather_complete = peer_connection.gathering_complete_promise().await; 158 | 159 | // Sets the LocalDescription, and starts our UDP listeners 160 | peer_connection.set_local_description(offer).await?; 161 | 162 | // Block until ICE Gathering is complete, disabling trickle ICE 163 | // we do this because we only can exchange one signaling message 164 | // in a production application you should exchange ICE Candidates via OnICECandidate 165 | let _ = gather_complete.recv().await; 166 | 167 | // Output the answer in base64 so we can paste it in browser 168 | if let Some(local_desc) = peer_connection.local_description().await { 169 | let json_str = serde_json::to_string(&local_desc)?; 170 | let b64 = signal::encode(&json_str); 171 | println!("{}", b64); 172 | } else { 173 | println!("generate local_description failed!"); 174 | } 175 | 176 | // Wait for the answer to be pasted 177 | let line = signal::must_read_stdin()?; 178 | let desc_data = signal::decode(line.as_str())?; 179 | let answer = serde_json::from_str::(&desc_data)?; 180 | 181 | // Apply the answer as the remote description 182 | peer_connection.set_remote_description(answer).await?; 183 | 184 | println!("Press ctrl-c to stop"); 185 | tokio::select! { 186 | _ = done_rx.recv() => { 187 | println!("received done signal!"); 188 | } 189 | _ = tokio::signal::ctrl_c() => { 190 | println!(""); 191 | } 192 | }; 193 | 194 | peer_connection.close().await?; 195 | 196 | Ok(()) 197 | } 198 | -------------------------------------------------------------------------------- /examples/data-channels-detach-create/README.md: -------------------------------------------------------------------------------- 1 | # data-channels-detach-create 2 | data-channels-detach-create is an example that shows how you can detach a data channel. 3 | This allows direct access the the underlying [webrtc-rs/data](https://github.com/webrtc-rs/data). 4 | 5 | The example mirrors the data-channels-create example. 6 | 7 | ## Install 8 | ``` 9 | cargo build --example data-channels-detach-create 10 | ``` 11 | 12 | ## Usage 13 | The example can be used in the same way as the [Data Channels Create](data-channels-create) example. 14 | -------------------------------------------------------------------------------- /examples/data-channels-detach-create/data-channels-detach-create.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use bytes::Bytes; 3 | use clap::{AppSettings, Arg, Command}; 4 | use std::io::Write; 5 | use std::sync::Arc; 6 | use tokio::time::Duration; 7 | use webrtc::api::interceptor_registry::register_default_interceptors; 8 | use webrtc::api::media_engine::MediaEngine; 9 | use webrtc::api::setting_engine::SettingEngine; 10 | use webrtc::api::APIBuilder; 11 | use webrtc::ice_transport::ice_server::RTCIceServer; 12 | use webrtc::interceptor::registry::Registry; 13 | use webrtc::peer_connection::configuration::RTCConfiguration; 14 | use webrtc::peer_connection::math_rand_alpha; 15 | use webrtc::peer_connection::peer_connection_state::RTCPeerConnectionState; 16 | use webrtc::peer_connection::sdp::session_description::RTCSessionDescription; 17 | 18 | const MESSAGE_SIZE: usize = 1500; 19 | 20 | #[tokio::main] 21 | async fn main() -> Result<()> { 22 | let mut app = Command::new("data-channels-detach-create") 23 | .version("0.1.0") 24 | .author("Rain Liu ") 25 | .about("An example of Data-Channels-Detach-Create.") 26 | .setting(AppSettings::DeriveDisplayOrder) 27 | .subcommand_negates_reqs(true) 28 | .arg( 29 | Arg::new("FULLHELP") 30 | .help("Prints more detailed help information") 31 | .long("fullhelp"), 32 | ) 33 | .arg( 34 | Arg::new("debug") 35 | .long("debug") 36 | .short('d') 37 | .help("Prints debug log information"), 38 | ); 39 | 40 | let matches = app.clone().get_matches(); 41 | 42 | if matches.is_present("FULLHELP") { 43 | app.print_long_help().unwrap(); 44 | std::process::exit(0); 45 | } 46 | 47 | let debug = matches.is_present("debug"); 48 | if debug { 49 | env_logger::Builder::new() 50 | .format(|buf, record| { 51 | writeln!( 52 | buf, 53 | "{}:{} [{}] {} - {}", 54 | record.file().unwrap_or("unknown"), 55 | record.line().unwrap_or(0), 56 | record.level(), 57 | chrono::Local::now().format("%H:%M:%S.%6f"), 58 | record.args() 59 | ) 60 | }) 61 | .filter(None, log::LevelFilter::Trace) 62 | .init(); 63 | } 64 | 65 | // Everything below is the WebRTC-rs API! Thanks for using it ❤️. 66 | 67 | // Create a MediaEngine object to configure the supported codec 68 | let mut m = MediaEngine::default(); 69 | 70 | // Register default codecs 71 | m.register_default_codecs()?; 72 | 73 | // Create a InterceptorRegistry. This is the user configurable RTP/RTCP Pipeline. 74 | // This provides NACKs, RTCP Reports and other features. If you use `webrtc.NewPeerConnection` 75 | // this is enabled by default. If you are manually managing You MUST create a InterceptorRegistry 76 | // for each PeerConnection. 77 | let mut registry = Registry::new(); 78 | 79 | // Use the default set of Interceptors 80 | registry = register_default_interceptors(registry, &mut m)?; 81 | 82 | // Since this behavior diverges from the WebRTC API it has to be 83 | // enabled using a settings engine. Mixing both detached and the 84 | // OnMessage DataChannel API is not supported. 85 | 86 | // Create a SettingEngine and enable Detach 87 | let mut s = SettingEngine::default(); 88 | s.detach_data_channels(); 89 | 90 | // Create the API object with the MediaEngine 91 | let api = APIBuilder::new() 92 | .with_media_engine(m) 93 | .with_interceptor_registry(registry) 94 | .with_setting_engine(s) 95 | .build(); 96 | 97 | // Prepare the configuration 98 | let config = RTCConfiguration { 99 | ice_servers: vec![RTCIceServer { 100 | urls: vec!["stun:stun.l.google.com:19302".to_owned()], 101 | ..Default::default() 102 | }], 103 | ..Default::default() 104 | }; 105 | 106 | // Create a new RTCPeerConnection 107 | let peer_connection = Arc::new(api.new_peer_connection(config).await?); 108 | 109 | // Create a datachannel with label 'data' 110 | let data_channel = peer_connection.create_data_channel("data", None).await?; 111 | 112 | let (done_tx, mut done_rx) = tokio::sync::mpsc::channel::<()>(1); 113 | 114 | // Set the handler for Peer connection state 115 | // This will notify you when the peer has connected/disconnected 116 | peer_connection 117 | .on_peer_connection_state_change(Box::new(move |s: RTCPeerConnectionState| { 118 | println!("Peer Connection State has changed: {}", s); 119 | 120 | if s == RTCPeerConnectionState::Failed { 121 | // Wait until PeerConnection has had no network activity for 30 seconds or another failure. It may be reconnected using an ICE Restart. 122 | // Use webrtc.PeerConnectionStateDisconnected if you are interested in detecting faster timeout. 123 | // Note that the PeerConnection may come back from PeerConnectionStateDisconnected. 124 | println!("Peer Connection has gone to failed exiting"); 125 | let _ = done_tx.try_send(()); 126 | } 127 | 128 | Box::pin(async {}) 129 | })) 130 | .await; 131 | 132 | // Register channel opening handling 133 | let d = Arc::clone(&data_channel); 134 | data_channel 135 | .on_open(Box::new(move || { 136 | println!("Data channel '{}'-'{}' open.", d.label(), d.id()); 137 | 138 | let d2 = Arc::clone(&d); 139 | Box::pin(async move { 140 | let raw = match d2.detach().await { 141 | Ok(raw) => raw, 142 | Err(err) => { 143 | println!("data channel detach got err: {}", err); 144 | return; 145 | } 146 | }; 147 | 148 | // Handle reading from the data channel 149 | let r = Arc::clone(&raw); 150 | tokio::spawn(async move { 151 | let _ = read_loop(r).await; 152 | }); 153 | 154 | // Handle writing to the data channel 155 | tokio::spawn(async move { 156 | let _ = write_loop(raw).await; 157 | }); 158 | }) 159 | })) 160 | .await; 161 | 162 | // Create an offer to send to the browser 163 | let offer = peer_connection.create_offer(None).await?; 164 | 165 | // Create channel that is blocked until ICE Gathering is complete 166 | let mut gather_complete = peer_connection.gathering_complete_promise().await; 167 | 168 | // Sets the LocalDescription, and starts our UDP listeners 169 | peer_connection.set_local_description(offer).await?; 170 | 171 | // Block until ICE Gathering is complete, disabling trickle ICE 172 | // we do this because we only can exchange one signaling message 173 | // in a production application you should exchange ICE Candidates via OnICECandidate 174 | let _ = gather_complete.recv().await; 175 | 176 | // Output the offer in base64 so we can paste it in browser 177 | if let Some(local_desc) = peer_connection.local_description().await { 178 | let json_str = serde_json::to_string(&local_desc)?; 179 | let b64 = signal::encode(&json_str); 180 | println!("{}", b64); 181 | } else { 182 | println!("generate local_description failed!"); 183 | } 184 | 185 | // Wait for the answer to be pasted 186 | let line = signal::must_read_stdin()?; 187 | let desc_data = signal::decode(line.as_str())?; 188 | let answer = serde_json::from_str::(&desc_data)?; 189 | 190 | // Apply the answer as the remote description 191 | peer_connection.set_remote_description(answer).await?; 192 | 193 | println!("Press ctrl-c to stop"); 194 | tokio::select! { 195 | _ = done_rx.recv() => { 196 | println!("received done signal!"); 197 | } 198 | _ = tokio::signal::ctrl_c() => { 199 | println!(""); 200 | } 201 | }; 202 | 203 | peer_connection.close().await?; 204 | 205 | Ok(()) 206 | } 207 | 208 | // read_loop shows how to read from the datachannel directly 209 | async fn read_loop(d: Arc) -> Result<()> { 210 | let mut buffer = vec![0u8; MESSAGE_SIZE]; 211 | loop { 212 | let n = match d.read(&mut buffer).await { 213 | Ok(n) => n, 214 | Err(err) => { 215 | println!("Datachannel closed; Exit the read_loop: {}", err); 216 | return Ok(()); 217 | } 218 | }; 219 | 220 | println!( 221 | "Message from DataChannel: {}", 222 | String::from_utf8(buffer[..n].to_vec())? 223 | ); 224 | } 225 | } 226 | 227 | // write_loop shows how to write to the datachannel directly 228 | async fn write_loop(d: Arc) -> Result<()> { 229 | let mut result = Result::::Ok(0); 230 | while result.is_ok() { 231 | let timeout = tokio::time::sleep(Duration::from_secs(5)); 232 | tokio::pin!(timeout); 233 | 234 | tokio::select! { 235 | _ = timeout.as_mut() =>{ 236 | let message = math_rand_alpha(15); 237 | println!("Sending '{}'", message); 238 | result = d.write(&Bytes::from(message)).await.map_err(Into::into); 239 | } 240 | }; 241 | } 242 | 243 | Ok(()) 244 | } 245 | -------------------------------------------------------------------------------- /examples/data-channels-detach/README.md: -------------------------------------------------------------------------------- 1 | # data-channels 2 | data-channels is a WebRTC.rs application that shows how you can send/recv DataChannel messages from a web browser 3 | 4 | ## Instructions 5 | ### Build data-channels-detach 6 | ``` 7 | cargo build --example data-channels-detach 8 | ``` 9 | 10 | ### Open data-channels-detach example page 11 | [jsfiddle.net](https://jsfiddle.net/9tsx15mg/90/) 12 | 13 | ### Run data-channels-detach, with your browsers SessionDescription as stdin 14 | In the jsfiddle the top textarea is your browser's session description, copy that and: 15 | #### Linux/macOS 16 | Run `echo $BROWSER_SDP | ./target/debug/examples/data-channels-detach` 17 | #### Windows 18 | 1. Paste the SessionDescription into a file. 19 | 1. Run `./target/debug/examples/data-channels-detach < my_file` 20 | 21 | ### Input data-channels-detach's SessionDescription into your browser 22 | Copy the text that `data-channels` just emitted and copy into second text area 23 | 24 | ### Hit 'Start Session' in jsfiddle 25 | Under Start Session you should see 'Checking' as it starts connecting. If everything worked you should see `New DataChannel foo 1` 26 | 27 | Now you can put whatever you want in the `Message` textarea, and when you hit `Send Message` it should appear in your terminal! 28 | 29 | WebRTC.rs will send random messages every 5 seconds that will appear in your browser. 30 | 31 | Congrats, you have used WebRTC.rs! 32 | -------------------------------------------------------------------------------- /examples/data-channels-detach/data-channels-detach.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use bytes::Bytes; 3 | use clap::{AppSettings, Arg, Command}; 4 | use std::io::Write; 5 | use std::sync::Arc; 6 | use tokio::time::Duration; 7 | use webrtc::api::interceptor_registry::register_default_interceptors; 8 | use webrtc::api::media_engine::MediaEngine; 9 | use webrtc::api::setting_engine::SettingEngine; 10 | use webrtc::api::APIBuilder; 11 | use webrtc::data_channel::RTCDataChannel; 12 | use webrtc::ice_transport::ice_server::RTCIceServer; 13 | use webrtc::interceptor::registry::Registry; 14 | use webrtc::peer_connection::configuration::RTCConfiguration; 15 | use webrtc::peer_connection::math_rand_alpha; 16 | use webrtc::peer_connection::peer_connection_state::RTCPeerConnectionState; 17 | use webrtc::peer_connection::sdp::session_description::RTCSessionDescription; 18 | 19 | const MESSAGE_SIZE: usize = 1500; 20 | 21 | #[tokio::main] 22 | async fn main() -> Result<()> { 23 | let mut app = Command::new("data-channels-detach") 24 | .version("0.1.0") 25 | .author("Rain Liu ") 26 | .about("An example of Data-Channels-Detach.") 27 | .setting(AppSettings::DeriveDisplayOrder) 28 | .subcommand_negates_reqs(true) 29 | .arg( 30 | Arg::new("FULLHELP") 31 | .help("Prints more detailed help information") 32 | .long("fullhelp"), 33 | ) 34 | .arg( 35 | Arg::new("debug") 36 | .long("debug") 37 | .short('d') 38 | .help("Prints debug log information"), 39 | ); 40 | 41 | let matches = app.clone().get_matches(); 42 | 43 | if matches.is_present("FULLHELP") { 44 | app.print_long_help().unwrap(); 45 | std::process::exit(0); 46 | } 47 | 48 | let debug = matches.is_present("debug"); 49 | if debug { 50 | env_logger::Builder::new() 51 | .format(|buf, record| { 52 | writeln!( 53 | buf, 54 | "{}:{} [{}] {} - {}", 55 | record.file().unwrap_or("unknown"), 56 | record.line().unwrap_or(0), 57 | record.level(), 58 | chrono::Local::now().format("%H:%M:%S.%6f"), 59 | record.args() 60 | ) 61 | }) 62 | .filter(None, log::LevelFilter::Trace) 63 | .init(); 64 | } 65 | 66 | // Everything below is the WebRTC-rs API! Thanks for using it ❤️. 67 | 68 | // Create a MediaEngine object to configure the supported codec 69 | let mut m = MediaEngine::default(); 70 | 71 | // Register default codecs 72 | m.register_default_codecs()?; 73 | 74 | // Create a InterceptorRegistry. This is the user configurable RTP/RTCP Pipeline. 75 | // This provides NACKs, RTCP Reports and other features. If you use `webrtc.NewPeerConnection` 76 | // this is enabled by default. If you are manually managing You MUST create a InterceptorRegistry 77 | // for each PeerConnection. 78 | let mut registry = Registry::new(); 79 | 80 | // Use the default set of Interceptors 81 | registry = register_default_interceptors(registry, &mut m)?; 82 | 83 | // Since this behavior diverges from the WebRTC API it has to be 84 | // enabled using a settings engine. Mixing both detached and the 85 | // OnMessage DataChannel API is not supported. 86 | 87 | // Create a SettingEngine and enable Detach 88 | let mut s = SettingEngine::default(); 89 | s.detach_data_channels(); 90 | 91 | // Create the API object with the MediaEngine 92 | let api = APIBuilder::new() 93 | .with_media_engine(m) 94 | .with_interceptor_registry(registry) 95 | .with_setting_engine(s) 96 | .build(); 97 | 98 | // Prepare the configuration 99 | let config = RTCConfiguration { 100 | ice_servers: vec![RTCIceServer { 101 | urls: vec!["stun:stun.l.google.com:19302".to_owned()], 102 | ..Default::default() 103 | }], 104 | ..Default::default() 105 | }; 106 | 107 | // Create a new RTCPeerConnection 108 | let peer_connection = Arc::new(api.new_peer_connection(config).await?); 109 | 110 | let (done_tx, mut done_rx) = tokio::sync::mpsc::channel::<()>(1); 111 | 112 | // Set the handler for Peer connection state 113 | // This will notify you when the peer has connected/disconnected 114 | peer_connection 115 | .on_peer_connection_state_change(Box::new(move |s: RTCPeerConnectionState| { 116 | println!("Peer Connection State has changed: {}", s); 117 | 118 | if s == RTCPeerConnectionState::Failed { 119 | // Wait until PeerConnection has had no network activity for 30 seconds or another failure. It may be reconnected using an ICE Restart. 120 | // Use webrtc.PeerConnectionStateDisconnected if you are interested in detecting faster timeout. 121 | // Note that the PeerConnection may come back from PeerConnectionStateDisconnected. 122 | println!("Peer Connection has gone to failed exiting"); 123 | let _ = done_tx.try_send(()); 124 | } 125 | 126 | Box::pin(async {}) 127 | })) 128 | .await; 129 | 130 | // Register data channel creation handling 131 | peer_connection 132 | .on_data_channel(Box::new(move |d: Arc| { 133 | let d_label = d.label().to_owned(); 134 | let d_id = d.id(); 135 | println!("New DataChannel {} {}", d_label, d_id); 136 | 137 | // Register channel opening handling 138 | Box::pin(async move { 139 | let d2 = Arc::clone(&d); 140 | let d_label2 = d_label.clone(); 141 | let d_id2 = d_id; 142 | d.on_open(Box::new(move || { 143 | println!("Data channel '{}'-'{}' open.", d_label2, d_id2); 144 | 145 | Box::pin(async move { 146 | let raw = match d2.detach().await { 147 | Ok(raw) => raw, 148 | Err(err) => { 149 | println!("data channel detach got err: {}", err); 150 | return; 151 | } 152 | }; 153 | 154 | // Handle reading from the data channel 155 | let r = Arc::clone(&raw); 156 | tokio::spawn(async move { 157 | let _ = read_loop(r).await; 158 | }); 159 | 160 | // Handle writing to the data channel 161 | tokio::spawn(async move { 162 | let _ = write_loop(raw).await; 163 | }); 164 | }) 165 | })) 166 | .await; 167 | }) 168 | })) 169 | .await; 170 | 171 | // Wait for the offer to be pasted 172 | let line = signal::must_read_stdin()?; 173 | let desc_data = signal::decode(line.as_str())?; 174 | let offer = serde_json::from_str::(&desc_data)?; 175 | 176 | // Set the remote SessionDescription 177 | peer_connection.set_remote_description(offer).await?; 178 | 179 | // Create an answer 180 | let answer = peer_connection.create_answer(None).await?; 181 | 182 | // Create channel that is blocked until ICE Gathering is complete 183 | let mut gather_complete = peer_connection.gathering_complete_promise().await; 184 | 185 | // Sets the LocalDescription, and starts our UDP listeners 186 | peer_connection.set_local_description(answer).await?; 187 | 188 | // Block until ICE Gathering is complete, disabling trickle ICE 189 | // we do this because we only can exchange one signaling message 190 | // in a production application you should exchange ICE Candidates via OnICECandidate 191 | let _ = gather_complete.recv().await; 192 | 193 | // Output the answer in base64 so we can paste it in browser 194 | if let Some(local_desc) = peer_connection.local_description().await { 195 | let json_str = serde_json::to_string(&local_desc)?; 196 | let b64 = signal::encode(&json_str); 197 | println!("{}", b64); 198 | } else { 199 | println!("generate local_description failed!"); 200 | } 201 | 202 | println!("Press ctrl-c to stop"); 203 | tokio::select! { 204 | _ = done_rx.recv() => { 205 | println!("received done signal!"); 206 | } 207 | _ = tokio::signal::ctrl_c() => { 208 | println!(""); 209 | } 210 | }; 211 | 212 | peer_connection.close().await?; 213 | 214 | Ok(()) 215 | } 216 | 217 | // read_loop shows how to read from the datachannel directly 218 | async fn read_loop(d: Arc) -> Result<()> { 219 | let mut buffer = vec![0u8; MESSAGE_SIZE]; 220 | loop { 221 | let n = match d.read(&mut buffer).await { 222 | Ok(n) => n, 223 | Err(err) => { 224 | println!("Datachannel closed; Exit the read_loop: {}", err); 225 | return Ok(()); 226 | } 227 | }; 228 | 229 | println!( 230 | "Message from DataChannel: {}", 231 | String::from_utf8(buffer[..n].to_vec())? 232 | ); 233 | } 234 | } 235 | 236 | // write_loop shows how to write to the datachannel directly 237 | async fn write_loop(d: Arc) -> Result<()> { 238 | let mut result = Result::::Ok(0); 239 | while result.is_ok() { 240 | let timeout = tokio::time::sleep(Duration::from_secs(5)); 241 | tokio::pin!(timeout); 242 | 243 | tokio::select! { 244 | _ = timeout.as_mut() =>{ 245 | let message = math_rand_alpha(15); 246 | println!("Sending '{}'", message); 247 | result = d.write(&Bytes::from(message)).await.map_err(Into::into); 248 | } 249 | }; 250 | } 251 | 252 | Ok(()) 253 | } 254 | -------------------------------------------------------------------------------- /examples/data-channels-flow-control/README.md: -------------------------------------------------------------------------------- 1 | # data-channels-flow-control 2 | This example demonstrates how to use the following property / methods. 3 | 4 | * pub async fn buffered_amount(&self) -> usize 5 | * pub async fn set_buffered_amount_low_threshold(&self, th: usize) 6 | * pub async fn buffered_amount_low_threshold(&self) -> usize 7 | * pub async fn on_buffered_amount_low(&self, f: OnBufferedAmountLowFn) 8 | 9 | These methods are equivalent to that of JavaScript WebRTC API. 10 | See https://developer.mozilla.org/en-US/docs/Web/API/RTCDataChannel for more details. 11 | 12 | ## When do we need it? 13 | Send or SendText methods are called on DataChannel to send data to the connected peer. 14 | The methods return immediately, but it does not mean the data was actually sent onto 15 | the wire. Instead, it is queued in a buffer until it actually gets sent out to the wire. 16 | 17 | When you have a large amount of data to send, it is an application's responsibility to 18 | control the buffered amount in order not to indefinitely grow the buffer size to eventually 19 | exhaust the memory. 20 | 21 | The rate you wish to send data might be much higher than the rate the data channel can 22 | actually send to the peer over the Internet. The above properties/methods help your 23 | application to pace the amount of data to be pushed into the data channel. 24 | 25 | 26 | ## How to run the example code 27 | 28 | The demo code implements two endpoints (offer_pc and answer_pc) in it. 29 | 30 | ``` 31 | signaling messages 32 | +----------------------------------------+ 33 | | | 34 | v v 35 | +---------------+ +---------------+ 36 | | | data | | 37 | | offer_pc |----------------------->| answer_pc | 38 | |:PeerConnection| |:PeerConnection| 39 | +---------------+ +---------------+ 40 | ``` 41 | 42 | First offer_pc and answer_pc will exchange signaling message to establish a peer-to-peer 43 | connection, and data channel (label: "data"). 44 | 45 | Once the data channel is successfully opened, offer_pc will start sending a series of 46 | 1024-byte packets to answer_pc as fast as it can, until you kill the process by Ctrl-c. 47 | 48 | 49 | Here's how to run the code. 50 | 51 | At the root of the example: 52 | ``` 53 | $ cargo run 54 | Peer Connection State has changed: connected (offerer) 55 | Peer Connection State has changed: connected (answerer) 56 | OnOpen: data-1. Start sending a series of 1024-byte packets as fast as it can 57 | OnOpen: data-1. Start receiving data 58 | Throughput: 12.990 Mbps 59 | Throughput: 13.698 Mbps 60 | Throughput: 13.559 Mbps 61 | Throughput: 13.345 Mbps 62 | Throughput: 13.565 Mbps 63 | : 64 | ``` 65 | -------------------------------------------------------------------------------- /examples/data-channels/README.md: -------------------------------------------------------------------------------- 1 | # data-channels 2 | data-channels is a WebRTC.rs application that shows how you can send/recv DataChannel messages from a web browser 3 | 4 | ## Instructions 5 | ### Build data-channels 6 | ``` 7 | cargo build --example data-channels 8 | ``` 9 | 10 | ### Open data-channels example page 11 | [jsfiddle.net](https://jsfiddle.net/9tsx15mg/90/) 12 | 13 | ### Run data-channels, with your browsers SessionDescription as stdin 14 | In the jsfiddle the top textarea is your browser's session description, copy that and: 15 | #### Linux/macOS 16 | Run `echo $BROWSER_SDP | ./target/debug/examples/data-channels` 17 | #### Windows 18 | 1. Paste the SessionDescription into a file. 19 | 1. Run `./target/debug/examples/data-channels < my_file` 20 | 21 | ### Input data-channels's SessionDescription into your browser 22 | Copy the text that `data-channels` just emitted and copy into second text area 23 | 24 | ### Hit 'Start Session' in jsfiddle 25 | Under Start Session you should see 'Checking' as it starts connecting. If everything worked you should see `New DataChannel foo 1` 26 | 27 | Now you can put whatever you want in the `Message` textarea, and when you hit `Send Message` it should appear in your terminal! 28 | 29 | WebRTC.rs will send random messages every 5 seconds that will appear in your browser. 30 | 31 | Congrats, you have used WebRTC.rs! 32 | -------------------------------------------------------------------------------- /examples/data-channels/data-channels.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use clap::{AppSettings, Arg, Command}; 3 | use std::io::Write; 4 | use std::sync::Arc; 5 | use tokio::time::Duration; 6 | use webrtc::api::interceptor_registry::register_default_interceptors; 7 | use webrtc::api::media_engine::MediaEngine; 8 | use webrtc::api::APIBuilder; 9 | use webrtc::data_channel::data_channel_message::DataChannelMessage; 10 | use webrtc::data_channel::RTCDataChannel; 11 | use webrtc::ice_transport::ice_server::RTCIceServer; 12 | use webrtc::interceptor::registry::Registry; 13 | use webrtc::peer_connection::configuration::RTCConfiguration; 14 | use webrtc::peer_connection::math_rand_alpha; 15 | use webrtc::peer_connection::peer_connection_state::RTCPeerConnectionState; 16 | use webrtc::peer_connection::sdp::session_description::RTCSessionDescription; 17 | 18 | #[tokio::main] 19 | async fn main() -> Result<()> { 20 | let mut app = Command::new("data-channels") 21 | .version("0.1.0") 22 | .author("Rain Liu ") 23 | .about("An example of Data-Channels.") 24 | .setting(AppSettings::DeriveDisplayOrder) 25 | .subcommand_negates_reqs(true) 26 | .arg( 27 | Arg::new("FULLHELP") 28 | .help("Prints more detailed help information") 29 | .long("fullhelp"), 30 | ) 31 | .arg( 32 | Arg::new("debug") 33 | .long("debug") 34 | .short('d') 35 | .help("Prints debug log information"), 36 | ); 37 | 38 | let matches = app.clone().get_matches(); 39 | 40 | if matches.is_present("FULLHELP") { 41 | app.print_long_help().unwrap(); 42 | std::process::exit(0); 43 | } 44 | 45 | let debug = matches.is_present("debug"); 46 | if debug { 47 | env_logger::Builder::new() 48 | .format(|buf, record| { 49 | writeln!( 50 | buf, 51 | "{}:{} [{}] {} - {}", 52 | record.file().unwrap_or("unknown"), 53 | record.line().unwrap_or(0), 54 | record.level(), 55 | chrono::Local::now().format("%H:%M:%S.%6f"), 56 | record.args() 57 | ) 58 | }) 59 | .filter(None, log::LevelFilter::Trace) 60 | .init(); 61 | } 62 | 63 | // Everything below is the WebRTC-rs API! Thanks for using it ❤️. 64 | 65 | // Create a MediaEngine object to configure the supported codec 66 | let mut m = MediaEngine::default(); 67 | 68 | // Register default codecs 69 | m.register_default_codecs()?; 70 | 71 | // Create a InterceptorRegistry. This is the user configurable RTP/RTCP Pipeline. 72 | // This provides NACKs, RTCP Reports and other features. If you use `webrtc.NewPeerConnection` 73 | // this is enabled by default. If you are manually managing You MUST create a InterceptorRegistry 74 | // for each PeerConnection. 75 | let mut registry = Registry::new(); 76 | 77 | // Use the default set of Interceptors 78 | registry = register_default_interceptors(registry, &mut m)?; 79 | 80 | // Create the API object with the MediaEngine 81 | let api = APIBuilder::new() 82 | .with_media_engine(m) 83 | .with_interceptor_registry(registry) 84 | .build(); 85 | 86 | // Prepare the configuration 87 | let config = RTCConfiguration { 88 | ice_servers: vec![RTCIceServer { 89 | urls: vec!["stun:stun.l.google.com:19302".to_owned()], 90 | ..Default::default() 91 | }], 92 | ..Default::default() 93 | }; 94 | 95 | // Create a new RTCPeerConnection 96 | let peer_connection = Arc::new(api.new_peer_connection(config).await?); 97 | 98 | let (done_tx, mut done_rx) = tokio::sync::mpsc::channel::<()>(1); 99 | 100 | // Set the handler for Peer connection state 101 | // This will notify you when the peer has connected/disconnected 102 | peer_connection 103 | .on_peer_connection_state_change(Box::new(move |s: RTCPeerConnectionState| { 104 | println!("Peer Connection State has changed: {}", s); 105 | 106 | if s == RTCPeerConnectionState::Failed { 107 | // Wait until PeerConnection has had no network activity for 30 seconds or another failure. It may be reconnected using an ICE Restart. 108 | // Use webrtc.PeerConnectionStateDisconnected if you are interested in detecting faster timeout. 109 | // Note that the PeerConnection may come back from PeerConnectionStateDisconnected. 110 | println!("Peer Connection has gone to failed exiting"); 111 | let _ = done_tx.try_send(()); 112 | } 113 | 114 | Box::pin(async {}) 115 | })) 116 | .await; 117 | 118 | // Register data channel creation handling 119 | peer_connection 120 | .on_data_channel(Box::new(move |d: Arc| { 121 | let d_label = d.label().to_owned(); 122 | let d_id = d.id(); 123 | println!("New DataChannel {} {}", d_label, d_id); 124 | 125 | // Register channel opening handling 126 | Box::pin(async move { 127 | let d2 = Arc::clone(&d); 128 | let d_label2 = d_label.clone(); 129 | let d_id2 = d_id; 130 | d.on_open(Box::new(move || { 131 | println!("Data channel '{}'-'{}' open. Random messages will now be sent to any connected DataChannels every 5 seconds", d_label2, d_id2); 132 | 133 | Box::pin(async move { 134 | let mut result = Result::::Ok(0); 135 | while result.is_ok() { 136 | let timeout = tokio::time::sleep(Duration::from_secs(5)); 137 | tokio::pin!(timeout); 138 | 139 | tokio::select! { 140 | _ = timeout.as_mut() =>{ 141 | let message = math_rand_alpha(15); 142 | println!("Sending '{}'", message); 143 | result = d2.send_text(message).await.map_err(Into::into); 144 | } 145 | }; 146 | } 147 | }) 148 | })).await; 149 | 150 | // Register text message handling 151 | d.on_message(Box::new(move |msg: DataChannelMessage| { 152 | let msg_str = String::from_utf8(msg.data.to_vec()).unwrap(); 153 | println!("Message from DataChannel '{}': '{}'", d_label, msg_str); 154 | Box::pin(async {}) 155 | })).await; 156 | }) 157 | })) 158 | .await; 159 | 160 | // Wait for the offer to be pasted 161 | let line = signal::must_read_stdin()?; 162 | let desc_data = signal::decode(line.as_str())?; 163 | let offer = serde_json::from_str::(&desc_data)?; 164 | 165 | // Set the remote SessionDescription 166 | peer_connection.set_remote_description(offer).await?; 167 | 168 | // Create an answer 169 | let answer = peer_connection.create_answer(None).await?; 170 | 171 | // Create channel that is blocked until ICE Gathering is complete 172 | let mut gather_complete = peer_connection.gathering_complete_promise().await; 173 | 174 | // Sets the LocalDescription, and starts our UDP listeners 175 | peer_connection.set_local_description(answer).await?; 176 | 177 | // Block until ICE Gathering is complete, disabling trickle ICE 178 | // we do this because we only can exchange one signaling message 179 | // in a production application you should exchange ICE Candidates via OnICECandidate 180 | let _ = gather_complete.recv().await; 181 | 182 | // Output the answer in base64 so we can paste it in browser 183 | if let Some(local_desc) = peer_connection.local_description().await { 184 | let json_str = serde_json::to_string(&local_desc)?; 185 | let b64 = signal::encode(&json_str); 186 | println!("{}", b64); 187 | } else { 188 | println!("generate local_description failed!"); 189 | } 190 | 191 | println!("Press ctrl-c to stop"); 192 | tokio::select! { 193 | _ = done_rx.recv() => { 194 | println!("received done signal!"); 195 | } 196 | _ = tokio::signal::ctrl_c() => { 197 | println!(""); 198 | } 199 | }; 200 | 201 | peer_connection.close().await?; 202 | 203 | Ok(()) 204 | } 205 | -------------------------------------------------------------------------------- /examples/ice-restart/README.md: -------------------------------------------------------------------------------- 1 | # ice-restart 2 | ice-restart demonstrates WebRTC.rs ICE Restart abilities. 3 | 4 | ## Instructions 5 | 6 | ### Build ice-restart 7 | ``` 8 | cargo build --example ice-restart 9 | ``` 10 | 11 | ### Run ice-restart 12 | ``` 13 | cargo run --example ice-restart 14 | ``` 15 | 16 | ### Open the Web UI 17 | Open [http://localhost:8080](http://localhost:8080). This will automatically start a PeerConnection. This page will now prints stats about the PeerConnection 18 | and allow you to do an ICE Restart at anytime. 19 | 20 | * `ICE Restart` is the button that causes a new offer to be made wih `iceRestart: true`. 21 | * `ICE Connection States` will contain all the connection states the PeerConnection moves through. 22 | * `ICE Selected Pairs` will print the selected pair every 3 seconds. Note how the uFrag/uPwd/Port change everytime you start the Restart process. 23 | * `Inbound DataChannel Messages` containing the current time sent by the Pion process every 3 seconds. 24 | 25 | Congrats, you have used WebRTC.rs! 26 | -------------------------------------------------------------------------------- /examples/ice-restart/ice-restart.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use clap::{AppSettings, Arg, Command}; 3 | use hyper::service::{make_service_fn, service_fn}; 4 | use hyper::{Body, Method, Request, Response, Server, StatusCode}; 5 | use std::io::Write; 6 | use std::net::SocketAddr; 7 | use std::str::FromStr; 8 | use std::sync::Arc; 9 | use tokio::sync::Mutex; 10 | use tokio::time::Duration; 11 | use tokio_util::codec::{BytesCodec, FramedRead}; 12 | use webrtc::api::interceptor_registry::register_default_interceptors; 13 | use webrtc::api::media_engine::MediaEngine; 14 | use webrtc::api::APIBuilder; 15 | use webrtc::data_channel::RTCDataChannel; 16 | use webrtc::ice_transport::ice_connection_state::RTCIceConnectionState; 17 | use webrtc::interceptor::registry::Registry; 18 | use webrtc::peer_connection::configuration::RTCConfiguration; 19 | use webrtc::peer_connection::sdp::session_description::RTCSessionDescription; 20 | use webrtc::peer_connection::RTCPeerConnection; 21 | 22 | #[macro_use] 23 | extern crate lazy_static; 24 | 25 | lazy_static! { 26 | static ref PEER_CONNECTION_MUTEX: Arc>>> = 27 | Arc::new(Mutex::new(None)); 28 | } 29 | 30 | static INDEX: &str = "examples/ice-restart/index.html"; 31 | static NOTFOUND: &[u8] = b"Not Found"; 32 | 33 | /// HTTP status code 404 34 | fn not_found() -> Response { 35 | Response::builder() 36 | .status(StatusCode::NOT_FOUND) 37 | .body(NOTFOUND.into()) 38 | .unwrap() 39 | } 40 | 41 | async fn simple_file_send(filename: &str) -> Result, hyper::Error> { 42 | // Serve a file by asynchronously reading it by chunks using tokio-util crate. 43 | 44 | if let Ok(file) = tokio::fs::File::open(filename).await { 45 | let stream = FramedRead::new(file, BytesCodec::new()); 46 | let body = Body::wrap_stream(stream); 47 | return Ok(Response::new(body)); 48 | } 49 | 50 | Ok(not_found()) 51 | } 52 | 53 | // HTTP Listener to get ICE Credentials/Candidate from remote Peer 54 | async fn remote_handler(req: Request) -> Result, hyper::Error> { 55 | match (req.method(), req.uri().path()) { 56 | (&Method::GET, "/") | (&Method::GET, "/index.html") => simple_file_send(INDEX).await, 57 | 58 | (&Method::POST, "/doSignaling") => do_signaling(req).await, 59 | 60 | // Return the 404 Not Found for other routes. 61 | _ => { 62 | let mut not_found = Response::default(); 63 | *not_found.status_mut() = StatusCode::NOT_FOUND; 64 | Ok(not_found) 65 | } 66 | } 67 | } 68 | 69 | // do_signaling exchanges all state of the local PeerConnection and is called 70 | // every time a video is added or removed 71 | async fn do_signaling(req: Request) -> Result, hyper::Error> { 72 | let pc = { 73 | let mut peer_connection = PEER_CONNECTION_MUTEX.lock().await; 74 | if let Some(pc) = &*peer_connection { 75 | Arc::clone(pc) 76 | } else { 77 | // Create a MediaEngine object to configure the supported codec 78 | let mut m = MediaEngine::default(); 79 | 80 | match m.register_default_codecs() { 81 | Ok(_) => {} 82 | Err(err) => panic!("{}", err), 83 | }; 84 | 85 | // Create a InterceptorRegistry. This is the user configurable RTP/RTCP Pipeline. 86 | // This provides NACKs, RTCP Reports and other features. If you use `webrtc.NewPeerConnection` 87 | // this is enabled by default. If you are manually managing You MUST create a InterceptorRegistry 88 | // for each PeerConnection. 89 | let mut registry = Registry::new(); 90 | 91 | // Use the default set of Interceptors 92 | registry = match register_default_interceptors(registry, &mut m) { 93 | Ok(r) => r, 94 | Err(err) => panic!("{}", err), 95 | }; 96 | 97 | // Create the API object with the MediaEngine 98 | let api = APIBuilder::new() 99 | .with_media_engine(m) 100 | .with_interceptor_registry(registry) 101 | .build(); 102 | 103 | // Create a new RTCPeerConnection 104 | let pc = match api.new_peer_connection(RTCConfiguration::default()).await { 105 | Ok(p) => p, 106 | Err(err) => panic!("{}", err), 107 | }; 108 | let pc = Arc::new(pc); 109 | 110 | // Set the handler for ICE connection state 111 | // This will notify you when the peer has connected/disconnected 112 | pc.on_ice_connection_state_change(Box::new( 113 | |connection_state: RTCIceConnectionState| { 114 | println!("ICE Connection State has changed: {}", connection_state); 115 | Box::pin(async {}) 116 | }, 117 | )) 118 | .await; 119 | 120 | // Send the current time via a DataChannel to the remote peer every 3 seconds 121 | pc.on_data_channel(Box::new(|d: Arc| { 122 | Box::pin(async move { 123 | let d2 = Arc::clone(&d); 124 | d.on_open(Box::new(move || { 125 | Box::pin(async move { 126 | while d2 127 | .send_text(format!("{:?}", tokio::time::Instant::now())) 128 | .await 129 | .is_ok() 130 | { 131 | tokio::time::sleep(Duration::from_secs(3)).await; 132 | } 133 | }) 134 | })) 135 | .await; 136 | }) 137 | })) 138 | .await; 139 | 140 | *peer_connection = Some(Arc::clone(&pc)); 141 | pc 142 | } 143 | }; 144 | 145 | let sdp_str = match std::str::from_utf8(&hyper::body::to_bytes(req.into_body()).await?) { 146 | Ok(s) => s.to_owned(), 147 | Err(err) => panic!("{}", err), 148 | }; 149 | let offer = match serde_json::from_str::(&sdp_str) { 150 | Ok(s) => s, 151 | Err(err) => panic!("{}", err), 152 | }; 153 | 154 | if let Err(err) = pc.set_remote_description(offer).await { 155 | panic!("{}", err); 156 | } 157 | 158 | // Create channel that is blocked until ICE Gathering is complete 159 | let mut gather_complete = pc.gathering_complete_promise().await; 160 | 161 | // Create an answer 162 | let answer = match pc.create_answer(None).await { 163 | Ok(answer) => answer, 164 | Err(err) => panic!("{}", err), 165 | }; 166 | 167 | // Sets the LocalDescription, and starts our UDP listeners 168 | if let Err(err) = pc.set_local_description(answer).await { 169 | panic!("{}", err); 170 | } 171 | 172 | // Block until ICE Gathering is complete, disabling trickle ICE 173 | // we do this because we only can exchange one signaling message 174 | // in a production application you should exchange ICE Candidates via OnICECandidate 175 | let _ = gather_complete.recv().await; 176 | 177 | let payload = if let Some(local_desc) = pc.local_description().await { 178 | match serde_json::to_string(&local_desc) { 179 | Ok(p) => p, 180 | Err(err) => panic!("{}", err), 181 | } 182 | } else { 183 | panic!("generate local_description failed!"); 184 | }; 185 | 186 | let mut response = match Response::builder() 187 | .header("content-type", "application/json") 188 | .body(Body::from(payload)) 189 | { 190 | Ok(res) => res, 191 | Err(err) => panic!("{}", err), 192 | }; 193 | 194 | *response.status_mut() = StatusCode::OK; 195 | Ok(response) 196 | } 197 | 198 | #[tokio::main] 199 | async fn main() -> Result<()> { 200 | let mut app = Command::new("ice-restart") 201 | .version("0.1.0") 202 | .author("Rain Liu ") 203 | .about("An example of ice-restart.") 204 | .setting(AppSettings::DeriveDisplayOrder) 205 | .subcommand_negates_reqs(true) 206 | .arg( 207 | Arg::new("FULLHELP") 208 | .help("Prints more detailed help information") 209 | .long("fullhelp"), 210 | ) 211 | .arg( 212 | Arg::new("debug") 213 | .long("debug") 214 | .short('d') 215 | .help("Prints debug log information"), 216 | ); 217 | 218 | let matches = app.clone().get_matches(); 219 | 220 | if matches.is_present("FULLHELP") { 221 | app.print_long_help().unwrap(); 222 | std::process::exit(0); 223 | } 224 | 225 | let debug = matches.is_present("debug"); 226 | if debug { 227 | env_logger::Builder::new() 228 | .format(|buf, record| { 229 | writeln!( 230 | buf, 231 | "{}:{} [{}] {} - {}", 232 | record.file().unwrap_or("unknown"), 233 | record.line().unwrap_or(0), 234 | record.level(), 235 | chrono::Local::now().format("%H:%M:%S.%6f"), 236 | record.args() 237 | ) 238 | }) 239 | .filter(None, log::LevelFilter::Trace) 240 | .init(); 241 | } 242 | 243 | tokio::spawn(async move { 244 | println!("Open http://localhost:8080 to access this demo"); 245 | 246 | let addr = SocketAddr::from_str("0.0.0.0:8080").unwrap(); 247 | let service = 248 | make_service_fn(|_| async { Ok::<_, hyper::Error>(service_fn(remote_handler)) }); 249 | let server = Server::bind(&addr).serve(service); 250 | // Run this server for... forever! 251 | if let Err(e) = server.await { 252 | eprintln!("server error: {}", e); 253 | } 254 | }); 255 | 256 | println!("Press ctrl-c to stop"); 257 | tokio::signal::ctrl_c().await.unwrap(); 258 | 259 | Ok(()) 260 | } 261 | -------------------------------------------------------------------------------- /examples/ice-restart/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | ice-restart 4 | 5 | 6 | 7 |
8 | 9 | 10 |

ICE Connection States

11 |

12 | 13 |

ICE Selected Pairs

14 |

15 | 16 |

Inbound DataChannel Messages

17 |
18 | 19 | 20 | 82 | 83 | -------------------------------------------------------------------------------- /examples/insertable-streams/README.md: -------------------------------------------------------------------------------- 1 | # insertable-streams 2 | insertable-streams demonstrates how to use insertable streams with WebRTC.rs. 3 | This example modifies the video with a single-byte XOR cipher before sending, and then 4 | decrypts in Javascript. 5 | 6 | insertable-streams allows the browser to process encoded video. You could implement 7 | E2E encyption, add metadata or insert a completely different video feed! 8 | 9 | ## Instructions 10 | ### Create IVF named `output.ivf` that contains a VP8 track 11 | ``` 12 | ffmpeg -i $INPUT_FILE -g 30 output.ivf 13 | ``` 14 | 15 | ### Build insertable-streams 16 | ``` 17 | cargo build --example insertable-streams 18 | ``` 19 | 20 | ### Open insertable-streams example page 21 | [jsfiddle.net](https://jsfiddle.net/uqr80Lak/) you should see two text-areas and a 'Start Session' button. You will also have a 'Decrypt' checkbox. 22 | When unchecked the browser will not decrypt the incoming video stream, so it will stop playing or display certificates. 23 | 24 | ### Run insertable-streams with your browsers SessionDescription as stdin 25 | The `output.ivf` you created should be in the same directory as `insertable-streams`. In the jsfiddle the top textarea is your browser, copy that and: 26 | 27 | #### Linux/macOS 28 | Run `echo $BROWSER_SDP | ./target/debug/examples/insertable-streams` 29 | #### Windows 30 | 1. Paste the SessionDescription into a file. 31 | 1. Run `./target/debug/examples/insertable-streams < my_file` 32 | 33 | ### Input insertable-streams's SessionDescription into your browser 34 | Copy the text that `insertable-streams` just emitted and copy into second text area 35 | 36 | ### Hit 'Start Session' in jsfiddle, enjoy your video! 37 | A video should start playing in your browser above the input boxes. `insertable-streams` will exit when the file reaches the end. 38 | 39 | To stop decrypting the stream uncheck the box and the video will not be viewable. 40 | 41 | Congrats, you have used WebRTC.rs! 42 | -------------------------------------------------------------------------------- /examples/insertable-streams/insertable-streams.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use clap::{AppSettings, Arg, Command}; 3 | use std::fs::File; 4 | use std::io::BufReader; 5 | use std::io::Write; 6 | use std::path::Path; 7 | use std::sync::Arc; 8 | use tokio::sync::Notify; 9 | use tokio::time::Duration; 10 | use webrtc::api::interceptor_registry::register_default_interceptors; 11 | use webrtc::api::media_engine::{MediaEngine, MIME_TYPE_VP8}; 12 | use webrtc::api::APIBuilder; 13 | use webrtc::ice_transport::ice_connection_state::RTCIceConnectionState; 14 | use webrtc::ice_transport::ice_server::RTCIceServer; 15 | use webrtc::interceptor::registry::Registry; 16 | use webrtc::media::io::ivf_reader::IVFReader; 17 | use webrtc::media::Sample; 18 | use webrtc::peer_connection::configuration::RTCConfiguration; 19 | use webrtc::peer_connection::peer_connection_state::RTCPeerConnectionState; 20 | use webrtc::peer_connection::sdp::session_description::RTCSessionDescription; 21 | use webrtc::rtp_transceiver::rtp_codec::RTCRtpCodecCapability; 22 | use webrtc::track::track_local::track_local_static_sample::TrackLocalStaticSample; 23 | use webrtc::track::track_local::TrackLocal; 24 | use webrtc::Error; 25 | 26 | const CIPHER_KEY: u8 = 0xAA; 27 | 28 | #[tokio::main] 29 | async fn main() -> Result<()> { 30 | let mut app = Command::new("insertable-streams") 31 | .version("0.1.0") 32 | .author("Rain Liu ") 33 | .about("An example of insertable-streams.") 34 | .setting(AppSettings::DeriveDisplayOrder) 35 | .subcommand_negates_reqs(true) 36 | .arg( 37 | Arg::new("FULLHELP") 38 | .help("Prints more detailed help information") 39 | .long("fullhelp"), 40 | ) 41 | .arg( 42 | Arg::new("debug") 43 | .long("debug") 44 | .short('d') 45 | .help("Prints debug log information"), 46 | ) 47 | .arg( 48 | Arg::new("video") 49 | .required_unless_present("FULLHELP") 50 | .takes_value(true) 51 | .short('v') 52 | .long("video") 53 | .help("Video file to be streaming."), 54 | ); 55 | 56 | let matches = app.clone().get_matches(); 57 | 58 | if matches.is_present("FULLHELP") { 59 | app.print_long_help().unwrap(); 60 | std::process::exit(0); 61 | } 62 | 63 | let debug = matches.is_present("debug"); 64 | if debug { 65 | env_logger::Builder::new() 66 | .format(|buf, record| { 67 | writeln!( 68 | buf, 69 | "{}:{} [{}] {} - {}", 70 | record.file().unwrap_or("unknown"), 71 | record.line().unwrap_or(0), 72 | record.level(), 73 | chrono::Local::now().format("%H:%M:%S.%6f"), 74 | record.args() 75 | ) 76 | }) 77 | .filter(None, log::LevelFilter::Trace) 78 | .init(); 79 | } 80 | 81 | let video_file = matches.value_of("video").unwrap(); 82 | if !Path::new(video_file).exists() { 83 | return Err(Error::new(format!("video file: '{}' not exist", video_file)).into()); 84 | } 85 | 86 | // Everything below is the WebRTC-rs API! Thanks for using it ❤️. 87 | 88 | // Create a MediaEngine object to configure the supported codec 89 | let mut m = MediaEngine::default(); 90 | 91 | m.register_default_codecs()?; 92 | 93 | // Create a InterceptorRegistry. This is the user configurable RTP/RTCP Pipeline. 94 | // This provides NACKs, RTCP Reports and other features. If you use `webrtc.NewPeerConnection` 95 | // this is enabled by default. If you are manually managing You MUST create a InterceptorRegistry 96 | // for each PeerConnection. 97 | let mut registry = Registry::new(); 98 | 99 | // Use the default set of Interceptors 100 | registry = register_default_interceptors(registry, &mut m)?; 101 | 102 | // Create the API object with the MediaEngine 103 | let api = APIBuilder::new() 104 | .with_media_engine(m) 105 | .with_interceptor_registry(registry) 106 | .build(); 107 | 108 | // Prepare the configuration 109 | let config = RTCConfiguration { 110 | ice_servers: vec![RTCIceServer { 111 | urls: vec!["stun:stun.l.google.com:19302".to_owned()], 112 | ..Default::default() 113 | }], 114 | ..Default::default() 115 | }; 116 | 117 | // Create a new RTCPeerConnection 118 | let peer_connection = Arc::new(api.new_peer_connection(config).await?); 119 | 120 | let (done_tx, mut done_rx) = tokio::sync::mpsc::channel::<()>(1); 121 | let video_done_tx = done_tx.clone(); 122 | 123 | // Create a video track 124 | let video_track = Arc::new(TrackLocalStaticSample::new( 125 | RTCRtpCodecCapability { 126 | mime_type: MIME_TYPE_VP8.to_owned(), 127 | ..Default::default() 128 | }, 129 | "video".to_owned(), 130 | "webrtc-rs".to_owned(), 131 | )); 132 | 133 | // Add this newly created track to the PeerConnection 134 | let rtp_sender = peer_connection 135 | .add_track(Arc::clone(&video_track) as Arc) 136 | .await?; 137 | 138 | // Read incoming RTCP packets 139 | // Before these packets are returned they are processed by interceptors. For things 140 | // like NACK this needs to be called. 141 | tokio::spawn(async move { 142 | let mut rtcp_buf = vec![0u8; 1500]; 143 | while let Ok((_, _)) = rtp_sender.read(&mut rtcp_buf).await {} 144 | Result::<()>::Ok(()) 145 | }); 146 | 147 | let notify_tx = Arc::new(Notify::new()); 148 | let notify_video = notify_tx.clone(); 149 | 150 | let video_file_name = video_file.to_owned(); 151 | tokio::spawn(async move { 152 | // Open a IVF file and start reading using our IVFReader 153 | let file = File::open(video_file_name)?; 154 | let reader = BufReader::new(file); 155 | let (mut ivf, header) = IVFReader::new(reader)?; 156 | 157 | // Wait for connection established 158 | let _ = notify_video.notified().await; 159 | 160 | println!("play video from disk file output.ivf"); 161 | 162 | // 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. 163 | // This isn't required since the video is timestamped, but we will such much higher loss if we send all at once. 164 | let sleep_time = Duration::from_millis( 165 | ((1000 * header.timebase_numerator) / header.timebase_denominator) as u64, 166 | ); 167 | loop { 168 | let mut frame = match ivf.parse_next_frame() { 169 | Ok((frame, _)) => frame, 170 | Err(err) => { 171 | println!("All video frames parsed and sent: {}", err); 172 | break; 173 | } 174 | }; 175 | 176 | // Encrypt video using XOR Cipher 177 | for b in &mut frame[..] { 178 | *b ^= CIPHER_KEY; 179 | } 180 | 181 | tokio::time::sleep(sleep_time).await; 182 | 183 | video_track 184 | .write_sample(&Sample { 185 | data: frame.freeze(), 186 | duration: Duration::from_secs(1), 187 | ..Default::default() 188 | }) 189 | .await?; 190 | } 191 | 192 | let _ = video_done_tx.try_send(()); 193 | 194 | Result::<()>::Ok(()) 195 | }); 196 | 197 | // Set the handler for ICE connection state 198 | // This will notify you when the peer has connected/disconnected 199 | peer_connection 200 | .on_ice_connection_state_change(Box::new(move |connection_state: RTCIceConnectionState| { 201 | println!("Connection State has changed {}", connection_state); 202 | if connection_state == RTCIceConnectionState::Connected { 203 | notify_tx.notify_waiters(); 204 | } 205 | Box::pin(async {}) 206 | })) 207 | .await; 208 | 209 | // Set the handler for Peer connection state 210 | // This will notify you when the peer has connected/disconnected 211 | peer_connection 212 | .on_peer_connection_state_change(Box::new(move |s: RTCPeerConnectionState| { 213 | println!("Peer Connection State has changed: {}", s); 214 | 215 | if s == RTCPeerConnectionState::Failed { 216 | // Wait until PeerConnection has had no network activity for 30 seconds or another failure. It may be reconnected using an ICE Restart. 217 | // Use webrtc.PeerConnectionStateDisconnected if you are interested in detecting faster timeout. 218 | // Note that the PeerConnection may come back from PeerConnectionStateDisconnected. 219 | println!("Peer Connection has gone to failed exiting"); 220 | let _ = done_tx.try_send(()); 221 | } 222 | 223 | Box::pin(async {}) 224 | })) 225 | .await; 226 | 227 | // Wait for the offer to be pasted 228 | let line = signal::must_read_stdin()?; 229 | let desc_data = signal::decode(line.as_str())?; 230 | let offer = serde_json::from_str::(&desc_data)?; 231 | 232 | // Set the remote SessionDescription 233 | peer_connection.set_remote_description(offer).await?; 234 | 235 | // Create an answer 236 | let answer = peer_connection.create_answer(None).await?; 237 | 238 | // Create channel that is blocked until ICE Gathering is complete 239 | let mut gather_complete = peer_connection.gathering_complete_promise().await; 240 | 241 | // Sets the LocalDescription, and starts our UDP listeners 242 | peer_connection.set_local_description(answer).await?; 243 | 244 | // Block until ICE Gathering is complete, disabling trickle ICE 245 | // we do this because we only can exchange one signaling message 246 | // in a production application you should exchange ICE Candidates via OnICECandidate 247 | let _ = gather_complete.recv().await; 248 | 249 | // Output the answer in base64 so we can paste it in browser 250 | if let Some(local_desc) = peer_connection.local_description().await { 251 | let json_str = serde_json::to_string(&local_desc)?; 252 | let b64 = signal::encode(&json_str); 253 | println!("{}", b64); 254 | } else { 255 | println!("generate local_description failed!"); 256 | } 257 | 258 | println!("Press ctrl-c to stop"); 259 | tokio::select! { 260 | _ = done_rx.recv() => { 261 | println!("received done signal!"); 262 | } 263 | _ = tokio::signal::ctrl_c() => { 264 | println!(""); 265 | } 266 | }; 267 | 268 | peer_connection.close().await?; 269 | 270 | Ok(()) 271 | } 272 | -------------------------------------------------------------------------------- /examples/offer-answer/README.md: -------------------------------------------------------------------------------- 1 | # offer-answer 2 | offer-answer is an example of two webrtc-rs or pion instances communicating directly! 3 | 4 | The SDP offer and answer are exchanged automatically over HTTP. 5 | The `answer` side acts like a HTTP server and should therefore be ran first. 6 | 7 | ## Instructions 8 | First run `answer`: 9 | ```sh 10 | cargo build --example answer 11 | ./target/debug/examples/answer 12 | ``` 13 | Next, run `offer`: 14 | ```sh 15 | cargo build --example offer 16 | ./target/debug/examples/offer 17 | ``` 18 | 19 | You should see them connect and start to exchange messages. 20 | -------------------------------------------------------------------------------- /examples/ortc/README.md: -------------------------------------------------------------------------------- 1 | # ortc 2 | ortc demonstrates WebRTC.rs's [ORTC](https://ortc.org/) capabilities. Instead of using the Session Description Protocol 3 | to configure and communicate ORTC provides APIs. Users then can implement signaling with whatever protocol they wish. 4 | ORTC can then be used to implement WebRTC. A ORTC implementation can parse/emit Session Description and act as a WebRTC 5 | implementation. 6 | 7 | In this example we have defined a simple JSON based signaling protocol. 8 | 9 | ## Instructions 10 | ### Build ortc 11 | ``` 12 | cargo build --example ortc 13 | ``` 14 | 15 | ### Run first client as offerer 16 | `ortc --offer` this will emit a base64 message. Copy this message to your clipboard. 17 | 18 | ## Run the second client as answerer 19 | Run the second client. This should be launched with the message you copied in the previous step as stdin. 20 | 21 | `echo BASE64_MESSAGE_YOU_COPIED | ortc` 22 | 23 | ### Enjoy 24 | If everything worked you will see `Data channel 'Foo'-'' open.` in each terminal. 25 | 26 | Each client will send random messages every 5 seconds that will appear in the terminal 27 | 28 | Congrats, you have used WebRTC.rs! 29 | -------------------------------------------------------------------------------- /examples/ortc/ortc.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use clap::{AppSettings, Arg, Command}; 3 | use serde::{Deserialize, Serialize}; 4 | use std::io::Write; 5 | use std::sync::Arc; 6 | use tokio::sync::Notify; 7 | use tokio::time::Duration; 8 | use webrtc::api::APIBuilder; 9 | use webrtc::data_channel::data_channel_message::DataChannelMessage; 10 | use webrtc::data_channel::data_channel_parameters::DataChannelParameters; 11 | use webrtc::data_channel::RTCDataChannel; 12 | use webrtc::dtls_transport::dtls_parameters::DTLSParameters; 13 | use webrtc::ice_transport::ice_candidate::RTCIceCandidate; 14 | use webrtc::ice_transport::ice_gatherer::RTCIceGatherOptions; 15 | use webrtc::ice_transport::ice_parameters::RTCIceParameters; 16 | use webrtc::ice_transport::ice_role::RTCIceRole; 17 | use webrtc::ice_transport::ice_server::RTCIceServer; 18 | use webrtc::peer_connection::math_rand_alpha; 19 | use webrtc::sctp_transport::sctp_transport_capabilities::SCTPTransportCapabilities; 20 | 21 | #[tokio::main] 22 | async fn main() -> Result<()> { 23 | let mut app = Command::new("ortc") 24 | .version("0.1.0") 25 | .author("Rain Liu ") 26 | .about("An example of ORTC.") 27 | .setting(AppSettings::DeriveDisplayOrder) 28 | .subcommand_negates_reqs(true) 29 | .arg( 30 | Arg::new("FULLHELP") 31 | .help("Prints more detailed help information") 32 | .long("fullhelp"), 33 | ) 34 | .arg( 35 | Arg::new("debug") 36 | .long("debug") 37 | .short('d') 38 | .help("Prints debug log information"), 39 | ) 40 | .arg( 41 | Arg::new("offer") 42 | .long("offer") 43 | .help("Act as the offerer if set."), 44 | ); 45 | 46 | let matches = app.clone().get_matches(); 47 | 48 | if matches.is_present("FULLHELP") { 49 | app.print_long_help().unwrap(); 50 | std::process::exit(0); 51 | } 52 | 53 | let is_offer = matches.is_present("offer"); 54 | let debug = matches.is_present("debug"); 55 | if debug { 56 | env_logger::Builder::new() 57 | .format(|buf, record| { 58 | writeln!( 59 | buf, 60 | "{}:{} [{}] {} - {}", 61 | record.file().unwrap_or("unknown"), 62 | record.line().unwrap_or(0), 63 | record.level(), 64 | chrono::Local::now().format("%H:%M:%S.%6f"), 65 | record.args() 66 | ) 67 | }) 68 | .filter(None, log::LevelFilter::Trace) 69 | .init(); 70 | } 71 | 72 | // Everything below is the Pion WebRTC (ORTC) API! Thanks for using it ❤️. 73 | 74 | // Prepare ICE gathering options 75 | let ice_options = RTCIceGatherOptions { 76 | ice_servers: vec![RTCIceServer { 77 | urls: vec!["stun:stun.l.google.com:19302".to_owned()], 78 | ..Default::default() 79 | }], 80 | ..Default::default() 81 | }; 82 | 83 | // Create an API object 84 | let api = APIBuilder::new().build(); 85 | 86 | // Create the ICE gatherer 87 | let gatherer = Arc::new(api.new_ice_gatherer(ice_options)?); 88 | 89 | // Construct the ICE transport 90 | let ice = Arc::new(api.new_ice_transport(Arc::clone(&gatherer))); 91 | 92 | // Construct the DTLS transport 93 | let dtls = Arc::new(api.new_dtls_transport(Arc::clone(&ice), vec![])?); 94 | 95 | // Construct the SCTP transport 96 | let sctp = Arc::new(api.new_sctp_transport(Arc::clone(&dtls))?); 97 | 98 | let done = Arc::new(Notify::new()); 99 | let done_answer = done.clone(); 100 | let done_offer = done.clone(); 101 | 102 | // Handle incoming data channels 103 | sctp.on_data_channel(Box::new(move |d: Arc| { 104 | let d_label = d.label().to_owned(); 105 | let d_id = d.id(); 106 | println!("New DataChannel {} {}", d_label, d_id); 107 | 108 | let done_answer1 = done_answer.clone(); 109 | // Register the handlers 110 | Box::pin(async move { 111 | // no need to downgrade this to Weak, since on_open is FnOnce callback 112 | let d2 = Arc::clone(&d); 113 | let done_answer2 = done_answer1.clone(); 114 | d.on_open(Box::new(move || { 115 | Box::pin(async move { 116 | tokio::select! { 117 | _ = done_answer2.notified() => { 118 | println!("received done_answer signal!"); 119 | } 120 | _ = handle_on_open(d2) => {} 121 | }; 122 | 123 | println!("exit data answer"); 124 | }) 125 | })) 126 | .await; 127 | 128 | // Register text message handling 129 | d.on_message(Box::new(move |msg: DataChannelMessage| { 130 | let msg_str = String::from_utf8(msg.data.to_vec()).unwrap(); 131 | println!("Message from DataChannel '{}': '{}'", d_label, msg_str); 132 | Box::pin(async {}) 133 | })) 134 | .await; 135 | }) 136 | })) 137 | .await; 138 | 139 | let (gather_finished_tx, mut gather_finished_rx) = tokio::sync::mpsc::channel::<()>(1); 140 | let mut gather_finished_tx = Some(gather_finished_tx); 141 | gatherer 142 | .on_local_candidate(Box::new(move |c: Option| { 143 | if c.is_none() { 144 | gather_finished_tx.take(); 145 | } 146 | Box::pin(async {}) 147 | })) 148 | .await; 149 | 150 | // Gather candidates 151 | gatherer.gather().await?; 152 | 153 | let _ = gather_finished_rx.recv().await; 154 | 155 | let ice_candidates = gatherer.get_local_candidates().await?; 156 | 157 | let ice_parameters = gatherer.get_local_parameters().await?; 158 | 159 | let dtls_parameters = dtls.get_local_parameters()?; 160 | 161 | let sctp_capabilities = sctp.get_capabilities(); 162 | 163 | let local_signal = Signal { 164 | ice_candidates, 165 | ice_parameters, 166 | dtls_parameters, 167 | sctp_capabilities, 168 | }; 169 | 170 | // Exchange the information 171 | let json_str = serde_json::to_string(&local_signal)?; 172 | let b64 = signal::encode(&json_str); 173 | println!("{}", b64); 174 | 175 | let line = signal::must_read_stdin()?; 176 | let json_str = signal::decode(line.as_str())?; 177 | let remote_signal = serde_json::from_str::(&json_str)?; 178 | 179 | let ice_role = if is_offer { 180 | RTCIceRole::Controlling 181 | } else { 182 | RTCIceRole::Controlled 183 | }; 184 | 185 | ice.set_remote_candidates(&remote_signal.ice_candidates) 186 | .await?; 187 | 188 | // Start the ICE transport 189 | ice.start(&remote_signal.ice_parameters, Some(ice_role)) 190 | .await?; 191 | 192 | // Start the DTLS transport 193 | dtls.start(remote_signal.dtls_parameters).await?; 194 | 195 | // Start the SCTP transport 196 | sctp.start(remote_signal.sctp_capabilities).await?; 197 | 198 | // Construct the data channel as the offerer 199 | if is_offer { 200 | let id = 1u16; 201 | 202 | let dc_params = DataChannelParameters { 203 | label: "Foo".to_owned(), 204 | id, 205 | ..Default::default() 206 | }; 207 | 208 | let d = Arc::new(api.new_data_channel(Arc::clone(&sctp), dc_params).await?); 209 | 210 | // Register the handlers 211 | // channel.OnOpen(handleOnOpen(channel)) // TODO: OnOpen on handle ChannelAck 212 | // Temporary alternative 213 | 214 | // no need to downgrade this to Weak 215 | let d2 = Arc::clone(&d); 216 | tokio::spawn(async move { 217 | tokio::select! { 218 | _ = done_offer.notified() => { 219 | println!("received done_offer signal!"); 220 | } 221 | _ = handle_on_open(d2) => {} 222 | }; 223 | 224 | println!("exit data offer"); 225 | }); 226 | 227 | let d_label = d.label().to_owned(); 228 | d.on_message(Box::new(move |msg: DataChannelMessage| { 229 | let msg_str = String::from_utf8(msg.data.to_vec()).unwrap(); 230 | println!("Message from DataChannel '{}': '{}'", d_label, msg_str); 231 | Box::pin(async {}) 232 | })) 233 | .await; 234 | } 235 | 236 | println!("Press ctrl-c to stop"); 237 | tokio::signal::ctrl_c().await.unwrap(); 238 | done.notify_waiters(); 239 | 240 | sctp.stop().await?; 241 | dtls.stop().await?; 242 | ice.stop().await?; 243 | 244 | Ok(()) 245 | } 246 | 247 | // Signal is used to exchange signaling info. 248 | // This is not part of the ORTC spec. You are free 249 | // to exchange this information any way you want. 250 | #[derive(Debug, Clone, Serialize, Deserialize)] 251 | struct Signal { 252 | #[serde(rename = "iceCandidates")] 253 | ice_candidates: Vec, // `json:"iceCandidates"` 254 | 255 | #[serde(rename = "iceParameters")] 256 | ice_parameters: RTCIceParameters, // `json:"iceParameters"` 257 | 258 | #[serde(rename = "dtlsParameters")] 259 | dtls_parameters: DTLSParameters, // `json:"dtlsParameters"` 260 | 261 | #[serde(rename = "sctpCapabilities")] 262 | sctp_capabilities: SCTPTransportCapabilities, // `json:"sctpCapabilities"` 263 | } 264 | 265 | async fn handle_on_open(d: Arc) -> Result<()> { 266 | println!("Data channel '{}'-'{}' open. Random messages will now be sent to any connected DataChannels every 5 seconds", d.label(), d.id()); 267 | 268 | let mut result = Result::::Ok(0); 269 | while result.is_ok() { 270 | let timeout = tokio::time::sleep(Duration::from_secs(5)); 271 | tokio::pin!(timeout); 272 | 273 | tokio::select! { 274 | _ = timeout.as_mut() =>{ 275 | let message = math_rand_alpha(15); 276 | println!("Sending '{}'", message); 277 | result = d.send_text(message).await.map_err(Into::into); 278 | } 279 | }; 280 | } 281 | 282 | Ok(()) 283 | } 284 | -------------------------------------------------------------------------------- /examples/play-from-disk-h264/README.md: -------------------------------------------------------------------------------- 1 | # play-from-disk-h264 2 | play-from-disk-h264 demonstrates how to send h264 video and/or audio to your browser from files saved to disk. 3 | 4 | ## Instructions 5 | ### Create IVF named `output.264` that contains a H264 track and/or `output.ogg` that contains a Opus track 6 | ``` 7 | ffmpeg -i $INPUT_FILE -an -c:v libx264 -bsf:v h264_mp4toannexb -b:v 2M -max_delay 0 -bf 0 output.h264 8 | ffmpeg -i $INPUT_FILE -c:a libopus -page_duration 20000 -vn output.ogg 9 | ``` 10 | 11 | ### Build play-from-disk-h264 12 | ``` 13 | cargo build --example play-from-disk-h264 14 | ``` 15 | 16 | ### Open play-from-disk-h264 example page 17 | [jsfiddle.net](https://jsfiddle.net/9s10amwL/) you should see two text-areas and a 'Start Session' button 18 | 19 | ### Run play-from-disk-h264 with your browsers SessionDescription as stdin 20 | The `output.h264` you created should be in the same directory as `play-from-disk-h264`. In the jsfiddle the top textarea is your browser, copy that and: 21 | 22 | #### Linux/macOS 23 | Run `echo $BROWSER_SDP | ./target/debug/examples/play-from-disk-h264 -v examples/test-data/output.h264 -a examples/test-data/output.ogg` 24 | #### Windows 25 | 1. Paste the SessionDescription into a file. 26 | 1. Run `./target/debug/examples/play-from-disk-h264 -v examples/test-data/output.h264 -a examples/test-data/output.ogg < my_file` 27 | 28 | ### Input play-from-disk-h264's SessionDescription into your browser 29 | Copy the text that `play-from-disk-h264` just emitted and copy into second text area 30 | 31 | ### Hit 'Start Session' in jsfiddle, enjoy your video! 32 | A video should start playing in your browser above the input boxes. `play-from-disk-h264` will exit when the file reaches the end 33 | 34 | Congrats, you have used WebRTC.rs! 35 | -------------------------------------------------------------------------------- /examples/play-from-disk-renegotiation/README.md: -------------------------------------------------------------------------------- 1 | # play-from-disk-renegotiation 2 | play-from-disk-renegotiation demonstrates WebRTC.rs's renegotiation abilities. 3 | 4 | For a simpler example of playing a file from disk we also have [examples/play-from-disk](/examples/play-from-disk) 5 | 6 | ## Instructions 7 | 8 | ### Build play-from-disk-renegotiation 9 | ``` 10 | cargo build --example play-from-disk-renegotiation 11 | ``` 12 | 13 | ### Create IVF named `output.ivf` that contains a VP8 track 14 | ``` 15 | ffmpeg -i $INPUT_FILE -g 30 output.ivf 16 | ``` 17 | 18 | ### Run play-from-disk-renegotiation 19 | The `output.ivf` you created should be in the same directory as `play-from-disk-renegotiation`. 20 | 21 | ### Open the Web UI 22 | Open [http://localhost:8080](http://localhost:8080) and you should have a `Add Track` and `Remove Track` button. Press these to add as many tracks as you want, or to remove as many as you wish. 23 | 24 | Congrats, you have used WebRTC.rs! 25 | -------------------------------------------------------------------------------- /examples/play-from-disk-renegotiation/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | play-from-disk-renegotiation 4 | 5 | 6 | 7 | 8 |
9 | 10 |
11 | 12 | 13 |

Video

14 |
15 |
16 | 17 |

Logs

18 |
19 | 20 | 21 | 80 | 81 | -------------------------------------------------------------------------------- /examples/play-from-disk-vpx/README.md: -------------------------------------------------------------------------------- 1 | # play-from-disk-vpx 2 | play-from-disk-vpx demonstrates how to send vp8/vp8 video and/or audio to your browser from files saved to disk. 3 | 4 | ## Instructions 5 | ### Create IVF named `output_vp8.ivf` or `output_vp9.ivf` that contains a VP8/VP9 track and/or `output.ogg` that contains a Opus track 6 | ``` 7 | ffmpeg -i $INPUT_FILE -g 30 output_vp8.ivf 8 | ffmpeg -i $INPUT_FILE -g 30 -c libvpx-vp9 output_vp9.ivf 9 | ffmpeg -i $INPUT_FILE -map 0:a -c:a dca -ac 2 -c:a libopus -page_duration 20000 -vn output.ogg 10 | ``` 11 | 12 | ### Build play-from-disk-vpx 13 | ``` 14 | cargo build --example play-from-disk-vpx 15 | ``` 16 | 17 | ### Open play-from-disk-vpx example page 18 | [jsfiddle.net](https://jsfiddle.net/9s10amwL/) you should see two text-areas and a 'Start Session' button 19 | 20 | ### Run play-from-disk-vpx with your browsers SessionDescription as stdin 21 | The `output_vp8.ivf`/`output_vp9.ivf` you created should be in the same directory as `play-from-disk-vpx`. In the jsfiddle the top textarea is your browser, copy that and: 22 | 23 | #### Linux/macOS 24 | 1. Run `echo $BROWSER_SDP | ./target/debug/examples/play-from-disk-vpx -v examples/test-data/output_vp8.ivf -a examples/test-data/output.ogg` 25 | 2. Run `echo $BROWSER_SDP | ./target/debug/examples/play-from-disk-vpx -v examples/test-data/output_vp9.ivf -a examples/test-data/output.ogg --vp9` 26 | #### Windows 27 | 1. Paste the SessionDescription into a file. 28 | 2. Run `./target/debug/examples/play-from-disk-vpx -v examples/test-data/output_vp8.ivf -a examples/test-data/output.ogg < my_file` 29 | 3. Run `./target/debug/examples/play-from-disk-vpx -v examples/test-data/output_vp9.ivf -a examples/test-data/output.ogg --vp9 < my_file` 30 | 31 | ### Input play-from-disk-vpx's SessionDescription into your browser 32 | Copy the text that `play-from-disk-vpx` just emitted and copy into second text area 33 | 34 | ### Hit 'Start Session' in jsfiddle, enjoy your video! 35 | A video should start playing in your browser above the input boxes. `play-from-disk-vpx` will exit when the file reaches the end 36 | 37 | Congrats, you have used WebRTC.rs! 38 | -------------------------------------------------------------------------------- /examples/rc-cycle/rc-cycle.rs: -------------------------------------------------------------------------------- 1 | use std::cell::RefCell; 2 | use std::rc::Rc; 3 | 4 | #[derive(Clone)] 5 | struct Cycle { 6 | cell: RefCell>>, 7 | } 8 | 9 | impl Drop for Cycle { 10 | fn drop(&mut self) { 11 | println!("freed"); 12 | } 13 | } 14 | 15 | #[tokio::main] 16 | async fn main() { 17 | let cycle = Rc::new(Cycle { 18 | cell: RefCell::new(None), 19 | }); 20 | *cycle.cell.borrow_mut() = Some(cycle.clone()); 21 | } 22 | 23 | // use nightly rust 24 | // RUSTFLAGS="-Z sanitizer=leak" cargo build --example rc-cycle 25 | // ./target/debug/example/rc-cycle 26 | // ================================================================= 27 | // ==1457719==ERROR: LeakSanitizer: detected memory leaks 28 | // 29 | // Direct leak of 32 byte(s) in 1 object(s) allocated from: 30 | // #0 0x55d4688e1b58 in malloc /rustc/llvm/src/llvm-project/compiler-rt/lib/lsan/lsan_interceptors.cpp:56:3 31 | // #1 0x55d4689db6cb in alloc::alloc::alloc::h1ab42fe6949393de /rustc/e269e6bf47f40c9046cd44ab787881d700099252/library/alloc/src/alloc.rs:86:14 32 | // 33 | // SUMMARY: LeakSanitizer: 32 byte(s) leaked in 1 allocation(s). 34 | -------------------------------------------------------------------------------- /examples/reflect/README.md: -------------------------------------------------------------------------------- 1 | # reflect 2 | reflect demonstrates how with one PeerConnection you can send video to webrtc-rs and have the packets sent back. This example could be easily extended to do server side processing. 3 | 4 | ## Instructions 5 | ### Build reflect 6 | ``` 7 | cargo build --example reflect 8 | ``` 9 | 10 | ### Open reflect example page 11 | [jsfiddle.net](https://jsfiddle.net/9jgukzt1/) you should see two text-areas and a 'Start Session' button. 12 | 13 | ### Run reflect, with your browsers SessionDescription as stdin 14 | In the jsfiddle the top textarea is your browser, copy that and: 15 | #### Linux/macOS 16 | Run `echo $BROWSER_SDP | ./target/debug/examples/reflect -a -v` 17 | #### Windows 18 | 1. Paste the SessionDescription into a file. 19 | 1. Run `./target/debug/examples/reflect -a -v < my_file` 20 | 21 | ### Input reflect's SessionDescription into your browser 22 | Copy the text that `reflect` just emitted and copy into second text area 23 | 24 | ### Hit 'Start Session' in jsfiddle, enjoy your video! 25 | Your browser should send video to webrtc-rs, and then it will be relayed right back to you. 26 | 27 | Congrats, you have used WebRTC.rs! 28 | -------------------------------------------------------------------------------- /examples/reflect/reflect.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use clap::{AppSettings, Arg, Command}; 3 | use std::collections::HashMap; 4 | use std::io::Write; 5 | use std::sync::Arc; 6 | use tokio::time::Duration; 7 | use webrtc::api::interceptor_registry::register_default_interceptors; 8 | use webrtc::api::media_engine::{MediaEngine, MIME_TYPE_OPUS, MIME_TYPE_VP8}; 9 | use webrtc::api::APIBuilder; 10 | use webrtc::ice_transport::ice_server::RTCIceServer; 11 | use webrtc::interceptor::registry::Registry; 12 | use webrtc::peer_connection::configuration::RTCConfiguration; 13 | use webrtc::peer_connection::peer_connection_state::RTCPeerConnectionState; 14 | use webrtc::peer_connection::sdp::session_description::RTCSessionDescription; 15 | use webrtc::rtcp::payload_feedbacks::picture_loss_indication::PictureLossIndication; 16 | use webrtc::rtp_transceiver::rtp_codec::{ 17 | RTCRtpCodecCapability, RTCRtpCodecParameters, RTPCodecType, 18 | }; 19 | use webrtc::rtp_transceiver::rtp_receiver::RTCRtpReceiver; 20 | use webrtc::track::track_local::track_local_static_rtp::TrackLocalStaticRTP; 21 | use webrtc::track::track_local::{TrackLocal, TrackLocalWriter}; 22 | use webrtc::track::track_remote::TrackRemote; 23 | 24 | #[tokio::main] 25 | async fn main() -> Result<()> { 26 | let mut app = Command::new("reflect") 27 | .version("0.1.0") 28 | .author("Rain Liu ") 29 | .about("An example of how to send back to the user exactly what it receives using the same PeerConnection.") 30 | .setting(AppSettings::DeriveDisplayOrder) 31 | .subcommand_negates_reqs(true) 32 | .arg( 33 | Arg::new("FULLHELP") 34 | .help("Prints more detailed help information") 35 | .long("fullhelp"), 36 | ) 37 | .arg( 38 | Arg::new("debug") 39 | .long("debug") 40 | .short('d') 41 | .help("Prints debug log information"), 42 | ).arg( 43 | Arg::new("audio") 44 | .long("audio") 45 | .short('a') 46 | .help("Enable audio reflect"), 47 | ).arg( 48 | Arg::new("video") 49 | .long("video") 50 | .short('v') 51 | .help("Enable video reflect"), 52 | ); 53 | 54 | let matches = app.clone().get_matches(); 55 | 56 | if matches.is_present("FULLHELP") { 57 | app.print_long_help().unwrap(); 58 | std::process::exit(0); 59 | } 60 | 61 | let audio = matches.is_present("audio"); 62 | let video = matches.is_present("video"); 63 | if !audio && !video { 64 | println!("one of audio or video must be enabled"); 65 | std::process::exit(0); 66 | } 67 | let debug = matches.is_present("debug"); 68 | if debug { 69 | env_logger::Builder::new() 70 | .format(|buf, record| { 71 | writeln!( 72 | buf, 73 | "{}:{} [{}] {} - {}", 74 | record.file().unwrap_or("unknown"), 75 | record.line().unwrap_or(0), 76 | record.level(), 77 | chrono::Local::now().format("%H:%M:%S.%6f"), 78 | record.args() 79 | ) 80 | }) 81 | .filter(None, log::LevelFilter::Trace) 82 | .init(); 83 | } 84 | 85 | // Everything below is the WebRTC-rs API! Thanks for using it ❤️. 86 | 87 | // Create a MediaEngine object to configure the supported codec 88 | let mut m = MediaEngine::default(); 89 | 90 | // Setup the codecs you want to use. 91 | if audio { 92 | m.register_codec( 93 | RTCRtpCodecParameters { 94 | capability: RTCRtpCodecCapability { 95 | mime_type: MIME_TYPE_OPUS.to_owned(), 96 | ..Default::default() 97 | }, 98 | payload_type: 120, 99 | ..Default::default() 100 | }, 101 | RTPCodecType::Audio, 102 | )?; 103 | } 104 | 105 | // We'll use a VP8 and Opus but you can also define your own 106 | if video { 107 | m.register_codec( 108 | RTCRtpCodecParameters { 109 | capability: RTCRtpCodecCapability { 110 | mime_type: MIME_TYPE_VP8.to_owned(), 111 | clock_rate: 90000, 112 | channels: 0, 113 | sdp_fmtp_line: "".to_owned(), 114 | rtcp_feedback: vec![], 115 | }, 116 | payload_type: 96, 117 | ..Default::default() 118 | }, 119 | RTPCodecType::Video, 120 | )?; 121 | } 122 | 123 | // Create a InterceptorRegistry. This is the user configurable RTP/RTCP Pipeline. 124 | // This provides NACKs, RTCP Reports and other features. If you use `webrtc.NewPeerConnection` 125 | // this is enabled by default. If you are manually managing You MUST create a InterceptorRegistry 126 | // for each PeerConnection. 127 | let mut registry = Registry::new(); 128 | 129 | // Use the default set of Interceptors 130 | registry = register_default_interceptors(registry, &mut m)?; 131 | 132 | // Create the API object with the MediaEngine 133 | let api = APIBuilder::new() 134 | .with_media_engine(m) 135 | .with_interceptor_registry(registry) 136 | .build(); 137 | 138 | // Prepare the configuration 139 | let config = RTCConfiguration { 140 | ice_servers: vec![RTCIceServer { 141 | urls: vec!["stun:stun.l.google.com:19302".to_owned()], 142 | ..Default::default() 143 | }], 144 | ..Default::default() 145 | }; 146 | 147 | // Create a new RTCPeerConnection 148 | let peer_connection = Arc::new(api.new_peer_connection(config).await?); 149 | let mut output_tracks = HashMap::new(); 150 | let mut media = vec![]; 151 | if audio { 152 | media.push("audio"); 153 | } 154 | if video { 155 | media.push("video"); 156 | }; 157 | for s in media { 158 | let output_track = Arc::new(TrackLocalStaticRTP::new( 159 | RTCRtpCodecCapability { 160 | mime_type: if s == "video" { 161 | MIME_TYPE_VP8.to_owned() 162 | } else { 163 | MIME_TYPE_OPUS.to_owned() 164 | }, 165 | ..Default::default() 166 | }, 167 | format!("track-{}", s), 168 | "webrtc-rs".to_owned(), 169 | )); 170 | 171 | // Add this newly created track to the PeerConnection 172 | let rtp_sender = peer_connection 173 | .add_track(Arc::clone(&output_track) as Arc) 174 | .await?; 175 | 176 | // Read incoming RTCP packets 177 | // Before these packets are returned they are processed by interceptors. For things 178 | // like NACK this needs to be called. 179 | let m = s.to_owned(); 180 | tokio::spawn(async move { 181 | let mut rtcp_buf = vec![0u8; 1500]; 182 | while let Ok((_, _)) = rtp_sender.read(&mut rtcp_buf).await {} 183 | println!("{} rtp_sender.read loop exit", m); 184 | Result::<()>::Ok(()) 185 | }); 186 | 187 | output_tracks.insert(s.to_owned(), output_track); 188 | } 189 | 190 | // Wait for the offer to be pasted 191 | let line = signal::must_read_stdin()?; 192 | let desc_data = signal::decode(line.as_str())?; 193 | let offer = serde_json::from_str::(&desc_data)?; 194 | 195 | // Set the remote SessionDescription 196 | peer_connection.set_remote_description(offer).await?; 197 | 198 | // Set a handler for when a new remote track starts, this handler copies inbound RTP packets, 199 | // replaces the SSRC and sends them back 200 | let pc = Arc::downgrade(&peer_connection); 201 | peer_connection 202 | .on_track(Box::new( 203 | move |track: Option>, _receiver: Option>| { 204 | if let Some(track) = track { 205 | // Send a PLI on an interval so that the publisher is pushing a keyframe every rtcpPLIInterval 206 | // This is a temporary fix until we implement incoming RTCP events, then we would push a PLI only when a viewer requests it 207 | let media_ssrc = track.ssrc(); 208 | 209 | if track.kind() == RTPCodecType::Video { 210 | let pc2 = pc.clone(); 211 | tokio::spawn(async move { 212 | let mut result = Result::::Ok(0); 213 | while result.is_ok() { 214 | let timeout = tokio::time::sleep(Duration::from_secs(3)); 215 | tokio::pin!(timeout); 216 | 217 | tokio::select! { 218 | _ = timeout.as_mut() =>{ 219 | if let Some(pc) = pc2.upgrade(){ 220 | result = pc.write_rtcp(&[Box::new(PictureLossIndication{ 221 | sender_ssrc: 0, 222 | media_ssrc, 223 | })]).await.map_err(Into::into); 224 | }else{ 225 | break; 226 | } 227 | } 228 | }; 229 | } 230 | }); 231 | } 232 | 233 | let kind = if track.kind() == RTPCodecType::Audio { 234 | "audio" 235 | } else { 236 | "video" 237 | }; 238 | let output_track = if let Some(output_track) = output_tracks.get(kind) { 239 | Arc::clone(output_track) 240 | } else { 241 | println!("output_track not found for type = {}", kind); 242 | return Box::pin(async {}); 243 | }; 244 | 245 | let output_track2 = Arc::clone(&output_track); 246 | tokio::spawn(async move { 247 | println!( 248 | "Track has started, of type {}: {}", 249 | track.payload_type(), 250 | track.codec().await.capability.mime_type 251 | ); 252 | // Read RTP packets being sent to webrtc-rs 253 | while let Ok((rtp, _)) = track.read_rtp().await { 254 | if let Err(err) = output_track2.write_rtp(&rtp).await { 255 | println!("output track write_rtp got error: {}", err); 256 | break; 257 | } 258 | } 259 | 260 | println!( 261 | "on_track finished, of type {}: {}", 262 | track.payload_type(), 263 | track.codec().await.capability.mime_type 264 | ); 265 | }); 266 | } 267 | Box::pin(async {}) 268 | }, 269 | )) 270 | .await; 271 | 272 | let (done_tx, mut done_rx) = tokio::sync::mpsc::channel::<()>(1); 273 | 274 | // Set the handler for Peer connection state 275 | // This will notify you when the peer has connected/disconnected 276 | peer_connection 277 | .on_peer_connection_state_change(Box::new(move |s: RTCPeerConnectionState| { 278 | println!("Peer Connection State has changed: {}", s); 279 | 280 | if s == RTCPeerConnectionState::Failed { 281 | // Wait until PeerConnection has had no network activity for 30 seconds or another failure. It may be reconnected using an ICE Restart. 282 | // Use webrtc.PeerConnectionStateDisconnected if you are interested in detecting faster timeout. 283 | // Note that the PeerConnection may come back from PeerConnectionStateDisconnected. 284 | println!("Peer Connection has gone to failed exiting"); 285 | let _ = done_tx.try_send(()); 286 | } 287 | 288 | Box::pin(async {}) 289 | })) 290 | .await; 291 | 292 | // Create an answer 293 | let answer = peer_connection.create_answer(None).await?; 294 | 295 | // Create channel that is blocked until ICE Gathering is complete 296 | let mut gather_complete = peer_connection.gathering_complete_promise().await; 297 | 298 | // Sets the LocalDescription, and starts our UDP listeners 299 | peer_connection.set_local_description(answer).await?; 300 | 301 | // Block until ICE Gathering is complete, disabling trickle ICE 302 | // we do this because we only can exchange one signaling message 303 | // in a production application you should exchange ICE Candidates via OnICECandidate 304 | let _ = gather_complete.recv().await; 305 | 306 | // Output the answer in base64 so we can paste it in browser 307 | if let Some(local_desc) = peer_connection.local_description().await { 308 | let json_str = serde_json::to_string(&local_desc)?; 309 | let b64 = signal::encode(&json_str); 310 | println!("{}", b64); 311 | } else { 312 | println!("generate local_description failed!"); 313 | } 314 | 315 | println!("Press ctrl-c to stop"); 316 | //let timeout = tokio::time::sleep(Duration::from_secs(20)); 317 | //tokio::pin!(timeout); 318 | 319 | tokio::select! { 320 | //_ = timeout.as_mut() => { 321 | // println!("received timeout signal!"); 322 | //} 323 | _ = done_rx.recv() => { 324 | println!("received done signal!"); 325 | } 326 | _ = tokio::signal::ctrl_c() => { 327 | println!(""); 328 | } 329 | }; 330 | 331 | peer_connection.close().await?; 332 | 333 | Ok(()) 334 | } 335 | -------------------------------------------------------------------------------- /examples/rtp-forwarder/README.md: -------------------------------------------------------------------------------- 1 | # rtp-forwarder 2 | rtp-forwarder is a simple application that shows how to forward your webcam/microphone via RTP using WebRTC.rs. 3 | 4 | ## Instructions 5 | ### Build rtp-forwarder 6 | ``` 7 | cargo build --example rtp-forwarder 8 | ``` 9 | 10 | ### Open rtp-forwarder example page 11 | [jsfiddle.net](https://jsfiddle.net/1qva2zd8/) you should see your Webcam, two text-areas and a 'Start Session' button 12 | 13 | ### Run rtp-forwarder, with your browsers SessionDescription as stdin 14 | In the jsfiddle the top textarea is your browser, copy that and: 15 | #### Linux/macOS 16 | Run `echo $BROWSER_SDP | ./target/debug/examples/rtp-forwarder` 17 | #### Windows 18 | 1. Paste the SessionDescription into a file. 19 | 1. Run `./target/debug/examples/rtp-forwarder < my_file` 20 | 21 | ### Input rtp-forwarder's SessionDescription into your browser 22 | Copy the text that `rtp-forwarder` just emitted and copy into second text area 23 | 24 | ### Hit 'Start Session' in jsfiddle and enjoy your RTP forwarded stream! 25 | You can run any of these commands at anytime. The media is live/stateless, you can switch commands without restarting Pion. 26 | 27 | #### VLC 28 | Open `rtp-forwarder.sdp` with VLC and enjoy your live video! 29 | 30 | #### ffmpeg/ffprobe 31 | Run `ffprobe -i rtp-forwarder.sdp -protocol_whitelist file,udp,rtp` to get more details about your streams 32 | 33 | Run `ffplay -i rtp-forwarder.sdp -protocol_whitelist file,udp,rtp` to play your streams 34 | 35 | You can add `-fflags nobuffer` to lower the latency. You will have worse playback in networks with jitter. 36 | 37 | #### Twitch/RTMP 38 | `ffmpeg -protocol_whitelist file,udp,rtp -i rtp-forwarder.sdp -c:v libx264 -preset veryfast -b:v 3000k -maxrate 3000k -bufsize 6000k -pix_fmt yuv420p -g 50 -c:a aac -b:a 160k -ac 2 -ar 44100 -f flv rtmp://live.twitch.tv/app/$STREAM_KEY` Make sure to replace `$STREAM_KEY` at the end of the URL first. 39 | -------------------------------------------------------------------------------- /examples/rtp-forwarder/rtp-forwarder.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use clap::{AppSettings, Arg, Command}; 3 | use std::collections::HashMap; 4 | use std::io::Write; 5 | use std::sync::Arc; 6 | use tokio::net::UdpSocket; 7 | use tokio::time::Duration; 8 | use webrtc::api::interceptor_registry::register_default_interceptors; 9 | use webrtc::api::media_engine::{MediaEngine, MIME_TYPE_OPUS, MIME_TYPE_VP8}; 10 | use webrtc::api::APIBuilder; 11 | use webrtc::ice_transport::ice_connection_state::RTCIceConnectionState; 12 | use webrtc::ice_transport::ice_server::RTCIceServer; 13 | use webrtc::interceptor::registry::Registry; 14 | use webrtc::peer_connection::configuration::RTCConfiguration; 15 | use webrtc::peer_connection::peer_connection_state::RTCPeerConnectionState; 16 | use webrtc::peer_connection::sdp::session_description::RTCSessionDescription; 17 | use webrtc::rtcp::payload_feedbacks::picture_loss_indication::PictureLossIndication; 18 | use webrtc::rtp_transceiver::rtp_codec::{ 19 | RTCRtpCodecCapability, RTCRtpCodecParameters, RTPCodecType, 20 | }; 21 | use webrtc::rtp_transceiver::rtp_receiver::RTCRtpReceiver; 22 | use webrtc::track::track_remote::TrackRemote; 23 | use webrtc::util::{Conn, Marshal, Unmarshal}; 24 | 25 | #[derive(Clone)] 26 | struct UdpConn { 27 | conn: Arc, 28 | payload_type: u8, 29 | } 30 | 31 | #[tokio::main] 32 | async fn main() -> Result<()> { 33 | let mut app = Command::new("rtp-forwarder") 34 | .version("0.1.0") 35 | .author("Rain Liu ") 36 | .about("An example of rtp-forwarder.") 37 | .setting(AppSettings::DeriveDisplayOrder) 38 | .subcommand_negates_reqs(true) 39 | .arg( 40 | Arg::new("FULLHELP") 41 | .help("Prints more detailed help information") 42 | .long("fullhelp"), 43 | ) 44 | .arg( 45 | Arg::new("debug") 46 | .long("debug") 47 | .short('d') 48 | .help("Prints debug log information"), 49 | ); 50 | 51 | let matches = app.clone().get_matches(); 52 | 53 | if matches.is_present("FULLHELP") { 54 | app.print_long_help().unwrap(); 55 | std::process::exit(0); 56 | } 57 | 58 | let debug = matches.is_present("debug"); 59 | if debug { 60 | env_logger::Builder::new() 61 | .format(|buf, record| { 62 | writeln!( 63 | buf, 64 | "{}:{} [{}] {} - {}", 65 | record.file().unwrap_or("unknown"), 66 | record.line().unwrap_or(0), 67 | record.level(), 68 | chrono::Local::now().format("%H:%M:%S.%6f"), 69 | record.args() 70 | ) 71 | }) 72 | .filter(None, log::LevelFilter::Trace) 73 | .init(); 74 | } 75 | 76 | // Everything below is the WebRTC-rs API! Thanks for using it ❤️. 77 | 78 | // Create a MediaEngine object to configure the supported codec 79 | let mut m = MediaEngine::default(); 80 | 81 | // Setup the codecs you want to use. 82 | // We'll use a VP8 and Opus but you can also define your own 83 | m.register_codec( 84 | RTCRtpCodecParameters { 85 | capability: RTCRtpCodecCapability { 86 | mime_type: MIME_TYPE_VP8.to_owned(), 87 | clock_rate: 90000, 88 | channels: 0, 89 | sdp_fmtp_line: "".to_owned(), 90 | rtcp_feedback: vec![], 91 | }, 92 | payload_type: 96, 93 | ..Default::default() 94 | }, 95 | RTPCodecType::Video, 96 | )?; 97 | 98 | m.register_codec( 99 | RTCRtpCodecParameters { 100 | capability: RTCRtpCodecCapability { 101 | mime_type: MIME_TYPE_OPUS.to_owned(), 102 | clock_rate: 48000, 103 | channels: 2, 104 | sdp_fmtp_line: "".to_owned(), 105 | rtcp_feedback: vec![], 106 | }, 107 | payload_type: 111, 108 | ..Default::default() 109 | }, 110 | RTPCodecType::Audio, 111 | )?; 112 | 113 | // Create a InterceptorRegistry. This is the user configurable RTP/RTCP Pipeline. 114 | // This provides NACKs, RTCP Reports and other features. If you use `webrtc.NewPeerConnection` 115 | // this is enabled by default. If you are manually managing You MUST create a InterceptorRegistry 116 | // for each PeerConnection. 117 | let mut registry = Registry::new(); 118 | 119 | // Use the default set of Interceptors 120 | registry = register_default_interceptors(registry, &mut m)?; 121 | 122 | // Create the API object with the MediaEngine 123 | let api = APIBuilder::new() 124 | .with_media_engine(m) 125 | .with_interceptor_registry(registry) 126 | .build(); 127 | 128 | // Prepare the configuration 129 | let config = RTCConfiguration { 130 | ice_servers: vec![RTCIceServer { 131 | urls: vec!["stun:stun.l.google.com:19302".to_owned()], 132 | ..Default::default() 133 | }], 134 | ..Default::default() 135 | }; 136 | 137 | // Create a new RTCPeerConnection 138 | let peer_connection = Arc::new(api.new_peer_connection(config).await?); 139 | 140 | // Allow us to receive 1 audio track, and 1 video track 141 | peer_connection 142 | .add_transceiver_from_kind(RTPCodecType::Audio, &[]) 143 | .await?; 144 | peer_connection 145 | .add_transceiver_from_kind(RTPCodecType::Video, &[]) 146 | .await?; 147 | 148 | // Prepare udp conns 149 | // Also update incoming packets with expected PayloadType, the browser may use 150 | // a different value. We have to modify so our stream matches what rtp-forwarder.sdp expects 151 | let mut udp_conns = HashMap::new(); 152 | udp_conns.insert( 153 | "audio".to_owned(), 154 | UdpConn { 155 | conn: { 156 | let sock = UdpSocket::bind("127.0.0.1:0").await?; 157 | sock.connect(format!("127.0.0.1:{}", 4000)).await?; 158 | Arc::new(sock) 159 | }, 160 | payload_type: 111, 161 | }, 162 | ); 163 | udp_conns.insert( 164 | "video".to_owned(), 165 | UdpConn { 166 | conn: { 167 | let sock = UdpSocket::bind("127.0.0.1:0").await?; 168 | sock.connect(format!("127.0.0.1:{}", 4002)).await?; 169 | Arc::new(sock) 170 | }, 171 | payload_type: 96, 172 | }, 173 | ); 174 | 175 | // Set a handler for when a new remote track starts, this handler will forward data to 176 | // our UDP listeners. 177 | // In your application this is where you would handle/process audio/video 178 | let pc = Arc::downgrade(&peer_connection); 179 | peer_connection 180 | .on_track(Box::new( 181 | move |track: Option>, _receiver: Option>| { 182 | if let Some(track) = track { 183 | // Retrieve udp connection 184 | let c = if let Some(c) = udp_conns.get(&track.kind().to_string()) { 185 | c.clone() 186 | } else { 187 | return Box::pin(async {}); 188 | }; 189 | 190 | // Send a PLI on an interval so that the publisher is pushing a keyframe every rtcpPLIInterval 191 | let media_ssrc = track.ssrc(); 192 | let pc2 = pc.clone(); 193 | tokio::spawn(async move { 194 | let mut result = Result::::Ok(0); 195 | while result.is_ok() { 196 | let timeout = tokio::time::sleep(Duration::from_secs(3)); 197 | tokio::pin!(timeout); 198 | 199 | tokio::select! { 200 | _ = timeout.as_mut() =>{ 201 | if let Some(pc) = pc2.upgrade(){ 202 | result = pc.write_rtcp(&[Box::new(PictureLossIndication{ 203 | sender_ssrc: 0, 204 | media_ssrc, 205 | })]).await.map_err(Into::into); 206 | }else{ 207 | break; 208 | } 209 | } 210 | }; 211 | } 212 | }); 213 | 214 | tokio::spawn(async move { 215 | let mut b = vec![0u8; 1500]; 216 | while let Ok((n, _)) = track.read(&mut b).await { 217 | // Unmarshal the packet and update the PayloadType 218 | let mut buf = &b[..n]; 219 | let mut rtp_packet = webrtc::rtp::packet::Packet::unmarshal(&mut buf)?; 220 | rtp_packet.header.payload_type = c.payload_type; 221 | 222 | // Marshal into original buffer with updated PayloadType 223 | 224 | let n = rtp_packet.marshal_to(&mut b)?; 225 | 226 | // Write 227 | if let Err(err) = c.conn.send(&b[..n]).await { 228 | // For this particular example, third party applications usually timeout after a short 229 | // amount of time during which the user doesn't have enough time to provide the answer 230 | // to the browser. 231 | // That's why, for this particular example, the user first needs to provide the answer 232 | // to the browser then open the third party application. Therefore we must not kill 233 | // the forward on "connection refused" errors 234 | //if opError, ok := err.(*net.OpError); ok && opError.Err.Error() == "write: connection refused" { 235 | // continue 236 | //} 237 | //panic(err) 238 | if err.to_string().contains("Connection refused") { 239 | continue; 240 | } else { 241 | println!("conn send err: {}", err); 242 | break; 243 | } 244 | } 245 | } 246 | 247 | Result::<()>::Ok(()) 248 | }); 249 | } 250 | 251 | Box::pin(async {}) 252 | }, 253 | )) 254 | .await; 255 | 256 | // Set the handler for ICE connection state 257 | // This will notify you when the peer has connected/disconnected 258 | peer_connection 259 | .on_ice_connection_state_change(Box::new(move |connection_state: RTCIceConnectionState| { 260 | println!("Connection State has changed {}", connection_state); 261 | if connection_state == RTCIceConnectionState::Connected { 262 | println!("Ctrl+C the remote client to stop the demo"); 263 | } 264 | Box::pin(async {}) 265 | })) 266 | .await; 267 | 268 | let (done_tx, mut done_rx) = tokio::sync::mpsc::channel::<()>(1); 269 | 270 | // Set the handler for Peer connection state 271 | // This will notify you when the peer has connected/disconnected 272 | peer_connection 273 | .on_peer_connection_state_change(Box::new(move |s: RTCPeerConnectionState| { 274 | println!("Peer Connection State has changed: {}", s); 275 | 276 | if s == RTCPeerConnectionState::Failed { 277 | // Wait until PeerConnection has had no network activity for 30 seconds or another failure. It may be reconnected using an ICE Restart. 278 | // Use webrtc.PeerConnectionStateDisconnected if you are interested in detecting faster timeout. 279 | // Note that the PeerConnection may come back from PeerConnectionStateDisconnected. 280 | println!("Peer Connection has gone to failed exiting: Done forwarding"); 281 | let _ = done_tx.try_send(()); 282 | } 283 | 284 | Box::pin(async {}) 285 | })) 286 | .await; 287 | 288 | // Wait for the offer to be pasted 289 | let line = signal::must_read_stdin()?; 290 | let desc_data = signal::decode(line.as_str())?; 291 | let offer = serde_json::from_str::(&desc_data)?; 292 | 293 | // Set the remote SessionDescription 294 | peer_connection.set_remote_description(offer).await?; 295 | 296 | // Create an answer 297 | let answer = peer_connection.create_answer(None).await?; 298 | 299 | // Create channel that is blocked until ICE Gathering is complete 300 | let mut gather_complete = peer_connection.gathering_complete_promise().await; 301 | 302 | // Sets the LocalDescription, and starts our UDP listeners 303 | peer_connection.set_local_description(answer).await?; 304 | 305 | // Block until ICE Gathering is complete, disabling trickle ICE 306 | // we do this because we only can exchange one signaling message 307 | // in a production application you should exchange ICE Candidates via OnICECandidate 308 | let _ = gather_complete.recv().await; 309 | 310 | // Output the answer in base64 so we can paste it in browser 311 | if let Some(local_desc) = peer_connection.local_description().await { 312 | let json_str = serde_json::to_string(&local_desc)?; 313 | let b64 = signal::encode(&json_str); 314 | println!("{}", b64); 315 | } else { 316 | println!("generate local_description failed!"); 317 | } 318 | 319 | println!("Press ctrl-c to stop"); 320 | tokio::select! { 321 | _ = done_rx.recv() => { 322 | println!("received done signal!"); 323 | } 324 | _ = tokio::signal::ctrl_c() => { 325 | println!(""); 326 | } 327 | }; 328 | 329 | peer_connection.close().await?; 330 | 331 | Ok(()) 332 | } 333 | -------------------------------------------------------------------------------- /examples/rtp-forwarder/rtp-forwarder.sdp: -------------------------------------------------------------------------------- 1 | v=0 2 | o=- 0 0 IN IP4 127.0.0.1 3 | s=WebRTC.rs 4 | c=IN IP4 127.0.0.1 5 | t=0 0 6 | m=audio 4000 RTP/AVP 111 7 | a=rtpmap:111 OPUS/48000/2 8 | m=video 4002 RTP/AVP 96 9 | a=rtpmap:96 VP8/90000 -------------------------------------------------------------------------------- /examples/rtp-to-webrtc/README.md: -------------------------------------------------------------------------------- 1 | # rtp-to-webrtc 2 | rtp-to-webrtc demonstrates how to consume a RTP stream video UDP, and then send to a WebRTC client. 3 | 4 | With this example we have pre-made GStreamer and ffmpeg pipelines, but you can use any tool you like! 5 | 6 | ## Instructions 7 | ### Build rtp-to-webrtc 8 | ``` 9 | cargo build --example rtp-to-webrtc 10 | ``` 11 | 12 | ### Open jsfiddle example page 13 | [jsfiddle.net](https://jsfiddle.net/z7ms3u5r/) you should see two text-areas and a 'Start Session' button 14 | 15 | 16 | ### Run rtp-to-webrtc with your browsers SessionDescription as stdin 17 | In the jsfiddle the top textarea is your browser's SessionDescription, copy that and: 18 | 19 | #### Linux/macOS 20 | Run `echo $BROWSER_SDP | ./target/debug/examples/rtp-to-webrtc` 21 | 22 | #### Windows 23 | 1. Paste the SessionDescription into a file. 24 | 1. Run `./target/debug/examples/rtp-to-webrtc < my_file` 25 | 26 | ### Send RTP to listening socket 27 | You can use any software to send VP8 packets to port 5004. We also have the pre made examples below 28 | 29 | 30 | #### GStreamer 31 | ``` 32 | gst-launch-1.0 videotestsrc ! video/x-raw,width=640,height=480,format=I420 ! vp8enc error-resilient=partitions keyframe-max-dist=10 auto-alt-ref=true cpu-used=5 deadline=1 ! rtpvp8pay ! udpsink host=127.0.0.1 port=5004 33 | ``` 34 | 35 | #### ffmpeg 36 | ``` 37 | ffmpeg -re -f lavfi -i testsrc=size=640x480:rate=30 -vcodec libvpx -cpu-used 5 -deadline 1 -g 10 -error-resilient 1 -auto-alt-ref 1 -f rtp rtp://127.0.0.1:5004?pkt_size=1200 38 | ``` 39 | 40 | ### Input rtp-to-webrtc's SessionDescription into your browser 41 | Copy the text that `rtp-to-webrtc` just emitted and copy into second text area 42 | 43 | ### Hit 'Start Session' in jsfiddle, enjoy your video! 44 | A video should start playing in your browser above the input boxes. 45 | 46 | Congrats, you have used WebRTC.rs! -------------------------------------------------------------------------------- /examples/rtp-to-webrtc/rtp-to-webrtc.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use clap::{AppSettings, Arg, Command}; 3 | use std::io::Write; 4 | use std::sync::Arc; 5 | use tokio::net::UdpSocket; 6 | use webrtc::api::interceptor_registry::register_default_interceptors; 7 | use webrtc::api::media_engine::{MediaEngine, MIME_TYPE_VP8}; 8 | use webrtc::api::APIBuilder; 9 | use webrtc::ice_transport::ice_connection_state::RTCIceConnectionState; 10 | use webrtc::ice_transport::ice_server::RTCIceServer; 11 | use webrtc::interceptor::registry::Registry; 12 | use webrtc::peer_connection::configuration::RTCConfiguration; 13 | use webrtc::peer_connection::peer_connection_state::RTCPeerConnectionState; 14 | use webrtc::peer_connection::sdp::session_description::RTCSessionDescription; 15 | use webrtc::rtp_transceiver::rtp_codec::RTCRtpCodecCapability; 16 | use webrtc::track::track_local::track_local_static_rtp::TrackLocalStaticRTP; 17 | use webrtc::track::track_local::{TrackLocal, TrackLocalWriter}; 18 | use webrtc::Error; 19 | 20 | #[tokio::main] 21 | async fn main() -> Result<()> { 22 | let mut app = Command::new("rtp-forwarder") 23 | .version("0.1.0") 24 | .author("Rain Liu ") 25 | .about("An example of rtp-forwarder.") 26 | .setting(AppSettings::DeriveDisplayOrder) 27 | .subcommand_negates_reqs(true) 28 | .arg( 29 | Arg::new("FULLHELP") 30 | .help("Prints more detailed help information") 31 | .long("fullhelp"), 32 | ) 33 | .arg( 34 | Arg::new("debug") 35 | .long("debug") 36 | .short('d') 37 | .help("Prints debug log information"), 38 | ); 39 | 40 | let matches = app.clone().get_matches(); 41 | 42 | if matches.is_present("FULLHELP") { 43 | app.print_long_help().unwrap(); 44 | std::process::exit(0); 45 | } 46 | 47 | let debug = matches.is_present("debug"); 48 | if debug { 49 | env_logger::Builder::new() 50 | .format(|buf, record| { 51 | writeln!( 52 | buf, 53 | "{}:{} [{}] {} - {}", 54 | record.file().unwrap_or("unknown"), 55 | record.line().unwrap_or(0), 56 | record.level(), 57 | chrono::Local::now().format("%H:%M:%S.%6f"), 58 | record.args() 59 | ) 60 | }) 61 | .filter(None, log::LevelFilter::Trace) 62 | .init(); 63 | } 64 | 65 | // Everything below is the WebRTC-rs API! Thanks for using it ❤️. 66 | 67 | // Create a MediaEngine object to configure the supported codec 68 | let mut m = MediaEngine::default(); 69 | 70 | m.register_default_codecs()?; 71 | 72 | // Create a InterceptorRegistry. This is the user configurable RTP/RTCP Pipeline. 73 | // This provides NACKs, RTCP Reports and other features. If you use `webrtc.NewPeerConnection` 74 | // this is enabled by default. If you are manually managing You MUST create a InterceptorRegistry 75 | // for each PeerConnection. 76 | let mut registry = Registry::new(); 77 | 78 | // Use the default set of Interceptors 79 | registry = register_default_interceptors(registry, &mut m)?; 80 | 81 | // Create the API object with the MediaEngine 82 | let api = APIBuilder::new() 83 | .with_media_engine(m) 84 | .with_interceptor_registry(registry) 85 | .build(); 86 | 87 | // Prepare the configuration 88 | let config = RTCConfiguration { 89 | ice_servers: vec![RTCIceServer { 90 | urls: vec!["stun:stun.l.google.com:19302".to_owned()], 91 | ..Default::default() 92 | }], 93 | ..Default::default() 94 | }; 95 | 96 | // Create a new RTCPeerConnection 97 | let peer_connection = Arc::new(api.new_peer_connection(config).await?); 98 | 99 | // Create Track that we send video back to browser on 100 | let video_track = Arc::new(TrackLocalStaticRTP::new( 101 | RTCRtpCodecCapability { 102 | mime_type: MIME_TYPE_VP8.to_owned(), 103 | ..Default::default() 104 | }, 105 | "video".to_owned(), 106 | "webrtc-rs".to_owned(), 107 | )); 108 | 109 | // Add this newly created track to the PeerConnection 110 | let rtp_sender = peer_connection 111 | .add_track(Arc::clone(&video_track) as Arc) 112 | .await?; 113 | 114 | // Read incoming RTCP packets 115 | // Before these packets are returned they are processed by interceptors. For things 116 | // like NACK this needs to be called. 117 | tokio::spawn(async move { 118 | let mut rtcp_buf = vec![0u8; 1500]; 119 | while let Ok((_, _)) = rtp_sender.read(&mut rtcp_buf).await {} 120 | Result::<()>::Ok(()) 121 | }); 122 | 123 | let (done_tx, mut done_rx) = tokio::sync::mpsc::channel::<()>(1); 124 | 125 | let done_tx1 = done_tx.clone(); 126 | // Set the handler for ICE connection state 127 | // This will notify you when the peer has connected/disconnected 128 | peer_connection 129 | .on_ice_connection_state_change(Box::new(move |connection_state: RTCIceConnectionState| { 130 | println!("Connection State has changed {}", connection_state); 131 | if connection_state == RTCIceConnectionState::Failed { 132 | let _ = done_tx1.try_send(()); 133 | } 134 | Box::pin(async {}) 135 | })) 136 | .await; 137 | 138 | let done_tx2 = done_tx.clone(); 139 | // Set the handler for Peer connection state 140 | // This will notify you when the peer has connected/disconnected 141 | peer_connection 142 | .on_peer_connection_state_change(Box::new(move |s: RTCPeerConnectionState| { 143 | println!("Peer Connection State has changed: {}", s); 144 | 145 | if s == RTCPeerConnectionState::Failed { 146 | // Wait until PeerConnection has had no network activity for 30 seconds or another failure. It may be reconnected using an ICE Restart. 147 | // Use webrtc.PeerConnectionStateDisconnected if you are interested in detecting faster timeout. 148 | // Note that the PeerConnection may come back from PeerConnectionStateDisconnected. 149 | println!("Peer Connection has gone to failed exiting: Done forwarding"); 150 | let _ = done_tx2.try_send(()); 151 | } 152 | 153 | Box::pin(async {}) 154 | })) 155 | .await; 156 | 157 | // Wait for the offer to be pasted 158 | let line = signal::must_read_stdin()?; 159 | let desc_data = signal::decode(line.as_str())?; 160 | let offer = serde_json::from_str::(&desc_data)?; 161 | 162 | // Set the remote SessionDescription 163 | peer_connection.set_remote_description(offer).await?; 164 | 165 | // Create an answer 166 | let answer = peer_connection.create_answer(None).await?; 167 | 168 | // Create channel that is blocked until ICE Gathering is complete 169 | let mut gather_complete = peer_connection.gathering_complete_promise().await; 170 | 171 | // Sets the LocalDescription, and starts our UDP listeners 172 | peer_connection.set_local_description(answer).await?; 173 | 174 | // Block until ICE Gathering is complete, disabling trickle ICE 175 | // we do this because we only can exchange one signaling message 176 | // in a production application you should exchange ICE Candidates via OnICECandidate 177 | let _ = gather_complete.recv().await; 178 | 179 | // Output the answer in base64 so we can paste it in browser 180 | if let Some(local_desc) = peer_connection.local_description().await { 181 | let json_str = serde_json::to_string(&local_desc)?; 182 | let b64 = signal::encode(&json_str); 183 | println!("{}", b64); 184 | } else { 185 | println!("generate local_description failed!"); 186 | } 187 | 188 | // Open a UDP Listener for RTP Packets on port 5004 189 | let listener = UdpSocket::bind("127.0.0.1:5004").await?; 190 | 191 | let done_tx3 = done_tx.clone(); 192 | // Read RTP packets forever and send them to the WebRTC Client 193 | tokio::spawn(async move { 194 | let mut inbound_rtp_packet = vec![0u8; 1600]; // UDP MTU 195 | while let Ok((n, _)) = listener.recv_from(&mut inbound_rtp_packet).await { 196 | if let Err(err) = video_track.write(&inbound_rtp_packet[..n]).await { 197 | if Error::ErrClosedPipe == err { 198 | // The peerConnection has been closed. 199 | } else { 200 | println!("video_track write err: {}", err); 201 | } 202 | let _ = done_tx3.try_send(()); 203 | return; 204 | } 205 | } 206 | }); 207 | 208 | println!("Press ctrl-c to stop"); 209 | tokio::select! { 210 | _ = done_rx.recv() => { 211 | println!("received done signal!"); 212 | } 213 | _ = tokio::signal::ctrl_c() => { 214 | println!(""); 215 | } 216 | }; 217 | 218 | peer_connection.close().await?; 219 | 220 | Ok(()) 221 | } 222 | -------------------------------------------------------------------------------- /examples/save-to-disk-h264/README.md: -------------------------------------------------------------------------------- 1 | # save-to-disk-h264 2 | save-to-disk-h264 is a simple application that shows how to record your webcam/microphone using WebRTC.rs and save H264 and Opus to disk. 3 | 4 | ## Instructions 5 | ### Build save-to-disk-h264 6 | ``` 7 | cargo build --example save-to-disk-h264 8 | ``` 9 | 10 | ### Open save-to-disk example page 11 | [jsfiddle.net](https://jsfiddle.net/vfmcg8rk/1/) you should see your Webcam, two text-areas and a 'Start Session' button 12 | 13 | ### Run save-to-disk-h264, with your browsers SessionDescription as stdin 14 | In the jsfiddle the top textarea is your browser, copy that and: 15 | #### Linux/macOS 16 | Run `echo $BROWSER_SDP | ./target/debug/examples/save-to-disk-h264` 17 | #### Windows 18 | 1. Paste the SessionDescription into a file. 19 | 1. Run `./target/debug/examples/save-to-disk-h264 < my_file` 20 | 21 | ### Input save-to-disk-h264's SessionDescription into your browser 22 | Copy the text that `save-to-disk-h264` just emitted and copy into second text area 23 | 24 | ### Hit 'Start Session' in jsfiddle, wait, close jsfiddle, enjoy your video! 25 | In the folder you ran `save-to-disk-h264` you should now have a file `output.h264` play with your video player of choice! 26 | 27 | Congrats, you have used WebRTC.rs! 28 | -------------------------------------------------------------------------------- /examples/save-to-disk-h264/save-to-disk-h264.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use clap::{AppSettings, Arg, Command}; 3 | use std::fs::File; 4 | use std::io::Write; 5 | use std::sync::Arc; 6 | use tokio::sync::{Mutex, Notify}; 7 | use tokio::time::Duration; 8 | use webrtc::api::interceptor_registry::register_default_interceptors; 9 | use webrtc::api::media_engine::{MediaEngine, MIME_TYPE_H264, MIME_TYPE_OPUS}; 10 | use webrtc::api::APIBuilder; 11 | use webrtc::ice_transport::ice_connection_state::RTCIceConnectionState; 12 | use webrtc::ice_transport::ice_server::RTCIceServer; 13 | use webrtc::interceptor::registry::Registry; 14 | use webrtc::media::io::h264_writer::H264Writer; 15 | use webrtc::media::io::ogg_writer::OggWriter; 16 | use webrtc::peer_connection::configuration::RTCConfiguration; 17 | use webrtc::peer_connection::sdp::session_description::RTCSessionDescription; 18 | use webrtc::rtcp::payload_feedbacks::picture_loss_indication::PictureLossIndication; 19 | use webrtc::rtp_transceiver::rtp_codec::{ 20 | RTCRtpCodecCapability, RTCRtpCodecParameters, RTPCodecType, 21 | }; 22 | use webrtc::rtp_transceiver::rtp_receiver::RTCRtpReceiver; 23 | use webrtc::track::track_remote::TrackRemote; 24 | 25 | async fn save_to_disk( 26 | writer: Arc>, 27 | track: Arc, 28 | notify: Arc, 29 | ) -> Result<()> { 30 | loop { 31 | tokio::select! { 32 | result = track.read_rtp() => { 33 | if let Ok((rtp_packet, _)) = result { 34 | let mut w = writer.lock().await; 35 | w.write_rtp(&rtp_packet)?; 36 | }else{ 37 | println!("file closing begin after read_rtp error"); 38 | let mut w = writer.lock().await; 39 | if let Err(err) = w.close() { 40 | println!("file close err: {}", err); 41 | } 42 | println!("file closing end after read_rtp error"); 43 | return Ok(()); 44 | } 45 | } 46 | _ = notify.notified() => { 47 | println!("file closing begin after notified"); 48 | let mut w = writer.lock().await; 49 | if let Err(err) = w.close() { 50 | println!("file close err: {}", err); 51 | } 52 | println!("file closing end after notified"); 53 | return Ok(()); 54 | } 55 | } 56 | } 57 | } 58 | 59 | #[tokio::main] 60 | async fn main() -> Result<()> { 61 | let mut app = Command::new("save-to-disk-h264") 62 | .version("0.1.0") 63 | .author("Rain Liu ") 64 | .about("An example of save-to-disk-h264.") 65 | .setting(AppSettings::DeriveDisplayOrder) 66 | .subcommand_negates_reqs(true) 67 | .arg( 68 | Arg::new("FULLHELP") 69 | .help("Prints more detailed help information") 70 | .long("fullhelp"), 71 | ) 72 | .arg( 73 | Arg::new("debug") 74 | .long("debug") 75 | .short('d') 76 | .help("Prints debug log information"), 77 | ) 78 | .arg( 79 | Arg::new("video") 80 | .required_unless_present("FULLHELP") 81 | .takes_value(true) 82 | .short('v') 83 | .long("video") 84 | .help("Video file to be streaming."), 85 | ) 86 | .arg( 87 | Arg::new("audio") 88 | .required_unless_present("FULLHELP") 89 | .takes_value(true) 90 | .short('a') 91 | .long("audio") 92 | .help("Audio file to be streaming."), 93 | ); 94 | 95 | let matches = app.clone().get_matches(); 96 | 97 | if matches.is_present("FULLHELP") { 98 | app.print_long_help().unwrap(); 99 | std::process::exit(0); 100 | } 101 | 102 | let debug = matches.is_present("debug"); 103 | if debug { 104 | env_logger::Builder::new() 105 | .format(|buf, record| { 106 | writeln!( 107 | buf, 108 | "{}:{} [{}] {} - {}", 109 | record.file().unwrap_or("unknown"), 110 | record.line().unwrap_or(0), 111 | record.level(), 112 | chrono::Local::now().format("%H:%M:%S.%6f"), 113 | record.args() 114 | ) 115 | }) 116 | .filter(None, log::LevelFilter::Trace) 117 | .init(); 118 | } 119 | 120 | let video_file = matches.value_of("video").unwrap(); 121 | let audio_file = matches.value_of("audio").unwrap(); 122 | 123 | let h264_writer: Arc> = 124 | Arc::new(Mutex::new(H264Writer::new(File::create(video_file)?))); 125 | let ogg_writer: Arc> = Arc::new(Mutex::new( 126 | OggWriter::new(File::create(audio_file)?, 48000, 2)?, 127 | )); 128 | 129 | // Everything below is the WebRTC-rs API! Thanks for using it ❤️. 130 | 131 | // Create a MediaEngine object to configure the supported codec 132 | let mut m = MediaEngine::default(); 133 | 134 | // Setup the codecs you want to use. 135 | // We'll use a H264 and Opus but you can also define your own 136 | m.register_codec( 137 | RTCRtpCodecParameters { 138 | capability: RTCRtpCodecCapability { 139 | mime_type: MIME_TYPE_H264.to_owned(), 140 | clock_rate: 90000, 141 | channels: 0, 142 | sdp_fmtp_line: "".to_owned(), 143 | rtcp_feedback: vec![], 144 | }, 145 | payload_type: 102, 146 | ..Default::default() 147 | }, 148 | RTPCodecType::Video, 149 | )?; 150 | 151 | m.register_codec( 152 | RTCRtpCodecParameters { 153 | capability: RTCRtpCodecCapability { 154 | mime_type: MIME_TYPE_OPUS.to_owned(), 155 | clock_rate: 48000, 156 | channels: 2, 157 | sdp_fmtp_line: "".to_owned(), 158 | rtcp_feedback: vec![], 159 | }, 160 | payload_type: 111, 161 | ..Default::default() 162 | }, 163 | RTPCodecType::Audio, 164 | )?; 165 | 166 | // Create a InterceptorRegistry. This is the user configurable RTP/RTCP Pipeline. 167 | // This provides NACKs, RTCP Reports and other features. If you use `webrtc.NewPeerConnection` 168 | // this is enabled by default. If you are manually managing You MUST create a InterceptorRegistry 169 | // for each PeerConnection. 170 | let mut registry = Registry::new(); 171 | 172 | // Use the default set of Interceptors 173 | registry = register_default_interceptors(registry, &mut m)?; 174 | 175 | // Create the API object with the MediaEngine 176 | let api = APIBuilder::new() 177 | .with_media_engine(m) 178 | .with_interceptor_registry(registry) 179 | .build(); 180 | 181 | // Prepare the configuration 182 | let config = RTCConfiguration { 183 | ice_servers: vec![RTCIceServer { 184 | urls: vec!["stun:stun.l.google.com:19302".to_owned()], 185 | ..Default::default() 186 | }], 187 | ..Default::default() 188 | }; 189 | 190 | // Create a new RTCPeerConnection 191 | let peer_connection = Arc::new(api.new_peer_connection(config).await?); 192 | 193 | // Allow us to receive 1 audio track, and 1 video track 194 | peer_connection 195 | .add_transceiver_from_kind(RTPCodecType::Audio, &[]) 196 | .await?; 197 | peer_connection 198 | .add_transceiver_from_kind(RTPCodecType::Video, &[]) 199 | .await?; 200 | 201 | let notify_tx = Arc::new(Notify::new()); 202 | let notify_rx = notify_tx.clone(); 203 | 204 | // Set a handler for when a new remote track starts, this handler saves buffers to disk as 205 | // an ivf file, since we could have multiple video tracks we provide a counter. 206 | // In your application this is where you would handle/process video 207 | let pc = Arc::downgrade(&peer_connection); 208 | peer_connection.on_track(Box::new(move |track: Option>, _receiver: Option>| { 209 | if let Some(track) = track { 210 | // Send a PLI on an interval so that the publisher is pushing a keyframe every rtcpPLIInterval 211 | let media_ssrc = track.ssrc(); 212 | let pc2 = pc.clone(); 213 | tokio::spawn(async move { 214 | let mut result = Result::::Ok(0); 215 | while result.is_ok() { 216 | let timeout = tokio::time::sleep(Duration::from_secs(3)); 217 | tokio::pin!(timeout); 218 | 219 | tokio::select! { 220 | _ = timeout.as_mut() =>{ 221 | if let Some(pc) = pc2.upgrade(){ 222 | result = pc.write_rtcp(&[Box::new(PictureLossIndication{ 223 | sender_ssrc: 0, 224 | media_ssrc, 225 | })]).await.map_err(Into::into); 226 | }else { 227 | break; 228 | } 229 | } 230 | }; 231 | } 232 | }); 233 | 234 | let notify_rx2 = Arc::clone(¬ify_rx); 235 | let h264_writer2 = Arc::clone(&h264_writer); 236 | let ogg_writer2 = Arc::clone(&ogg_writer); 237 | Box::pin(async move { 238 | let codec = track.codec().await; 239 | let mime_type = codec.capability.mime_type.to_lowercase(); 240 | if mime_type == MIME_TYPE_OPUS.to_lowercase() { 241 | println!("Got Opus track, saving to disk as output.opus (48 kHz, 2 channels)"); 242 | tokio::spawn(async move { 243 | let _ = save_to_disk(ogg_writer2, track, notify_rx2).await; 244 | }); 245 | } else if mime_type == MIME_TYPE_H264.to_lowercase() { 246 | println!("Got h264 track, saving to disk as output.h264"); 247 | tokio::spawn(async move { 248 | let _ = save_to_disk(h264_writer2, track, notify_rx2).await; 249 | }); 250 | } 251 | }) 252 | }else { 253 | Box::pin(async {}) 254 | } 255 | })).await; 256 | 257 | let (done_tx, mut done_rx) = tokio::sync::mpsc::channel::<()>(1); 258 | 259 | // Set the handler for ICE connection state 260 | // This will notify you when the peer has connected/disconnected 261 | peer_connection 262 | .on_ice_connection_state_change(Box::new(move |connection_state: RTCIceConnectionState| { 263 | println!("Connection State has changed {}", connection_state); 264 | 265 | if connection_state == RTCIceConnectionState::Connected { 266 | println!("Ctrl+C the remote client to stop the demo"); 267 | } else if connection_state == RTCIceConnectionState::Failed { 268 | notify_tx.notify_waiters(); 269 | 270 | println!("Done writing media files"); 271 | 272 | let _ = done_tx.try_send(()); 273 | } 274 | Box::pin(async {}) 275 | })) 276 | .await; 277 | 278 | // Wait for the offer to be pasted 279 | let line = signal::must_read_stdin()?; 280 | let desc_data = signal::decode(line.as_str())?; 281 | let offer = serde_json::from_str::(&desc_data)?; 282 | 283 | // Set the remote SessionDescription 284 | peer_connection.set_remote_description(offer).await?; 285 | 286 | // Create an answer 287 | let answer = peer_connection.create_answer(None).await?; 288 | 289 | // Create channel that is blocked until ICE Gathering is complete 290 | let mut gather_complete = peer_connection.gathering_complete_promise().await; 291 | 292 | // Sets the LocalDescription, and starts our UDP listeners 293 | peer_connection.set_local_description(answer).await?; 294 | 295 | // Block until ICE Gathering is complete, disabling trickle ICE 296 | // we do this because we only can exchange one signaling message 297 | // in a production application you should exchange ICE Candidates via OnICECandidate 298 | let _ = gather_complete.recv().await; 299 | 300 | // Output the answer in base64 so we can paste it in browser 301 | if let Some(local_desc) = peer_connection.local_description().await { 302 | let json_str = serde_json::to_string(&local_desc)?; 303 | let b64 = signal::encode(&json_str); 304 | println!("{}", b64); 305 | } else { 306 | println!("generate local_description failed!"); 307 | } 308 | 309 | println!("Press ctrl-c to stop"); 310 | tokio::select! { 311 | _ = done_rx.recv() => { 312 | println!("received done signal!"); 313 | } 314 | _ = tokio::signal::ctrl_c() => { 315 | println!(""); 316 | } 317 | }; 318 | 319 | peer_connection.close().await?; 320 | 321 | Ok(()) 322 | } 323 | -------------------------------------------------------------------------------- /examples/save-to-disk-vpx/README.md: -------------------------------------------------------------------------------- 1 | # save-to-disk-vpx 2 | save-to-disk-vpx is a simple application that shows how to record your webcam/microphone using WebRTC.rs and save VP8/VP9 and Opus to disk. 3 | 4 | ## Instructions 5 | ### Build save-to-disk-vpx 6 | ``` 7 | cargo build --example save-to-disk-vpx 8 | ``` 9 | 10 | ### Open save-to-disk-vpx example page 11 | [jsfiddle.net](https://jsfiddle.net/vfmcg8rk/1/) you should see your Webcam, two text-areas and a 'Start Session' button 12 | 13 | ### Run save-to-disk-vpx, with your browsers SessionDescription as stdin 14 | In the jsfiddle the top textarea is your browser, copy that and: 15 | #### Linux/macOS 16 | Run `echo $BROWSER_SDP | ./target/debug/examples/save-to-disk-vpx` 17 | #### Windows 18 | 1. Paste the SessionDescription into a file. 19 | 1. Run `./target/debug/examples/save-to-disk-vpx < my_file` 20 | 21 | ### Input save-to-disk-vpx's SessionDescription into your browser 22 | Copy the text that `save-to-disk-vpx` just emitted and copy into second text area 23 | 24 | ### Hit 'Start Session' in jsfiddle, wait, close jsfiddle, enjoy your video! 25 | In the folder you ran `save-to-disk-vpx` you should now have a file `output_vpx.ivf` play with your video player of choice! 26 | 27 | Congrats, you have used WebRTC.rs! 28 | -------------------------------------------------------------------------------- /examples/signal/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "signal" 3 | version = "0.1.0" 4 | edition = "2018" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | tokio = { version = "1.15.0", features = ["full"] } 10 | anyhow = "1.0.52" 11 | base64 = "0.13.0" 12 | lazy_static = "1.4" 13 | hyper = { version = "0.14.16", features = ["full"] } -------------------------------------------------------------------------------- /examples/signal/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![warn(rust_2018_idioms)] 2 | #![allow(dead_code)] 3 | 4 | use anyhow::Result; 5 | use hyper::service::{make_service_fn, service_fn}; 6 | use hyper::{Body, Method, Request, Response, Server, StatusCode}; 7 | use std::net::SocketAddr; 8 | use std::str::FromStr; 9 | use std::sync::Arc; 10 | use tokio::sync::{mpsc, Mutex}; 11 | 12 | #[macro_use] 13 | extern crate lazy_static; 14 | 15 | lazy_static! { 16 | static ref SDP_CHAN_TX_MUTEX: Arc>>> = 17 | Arc::new(Mutex::new(None)); 18 | } 19 | 20 | // HTTP Listener to get sdp 21 | async fn remote_handler(req: Request) -> Result, hyper::Error> { 22 | match (req.method(), req.uri().path()) { 23 | // A HTTP handler that processes a SessionDescription given to us from the other WebRTC-rs or Pion process 24 | (&Method::POST, "/sdp") => { 25 | //println!("remote_handler receive from /sdp"); 26 | let sdp_str = match std::str::from_utf8(&hyper::body::to_bytes(req.into_body()).await?) 27 | { 28 | Ok(s) => s.to_owned(), 29 | Err(err) => panic!("{}", err), 30 | }; 31 | 32 | { 33 | let sdp_chan_tx = SDP_CHAN_TX_MUTEX.lock().await; 34 | if let Some(tx) = &*sdp_chan_tx { 35 | let _ = tx.send(sdp_str).await; 36 | } 37 | } 38 | 39 | let mut response = Response::new(Body::empty()); 40 | *response.status_mut() = StatusCode::OK; 41 | Ok(response) 42 | } 43 | // Return the 404 Not Found for other routes. 44 | _ => { 45 | let mut not_found = Response::default(); 46 | *not_found.status_mut() = StatusCode::NOT_FOUND; 47 | Ok(not_found) 48 | } 49 | } 50 | } 51 | 52 | /// http_sdp_server starts a HTTP Server that consumes SDPs 53 | pub async fn http_sdp_server(port: u16) -> mpsc::Receiver { 54 | let (sdp_chan_tx, sdp_chan_rx) = mpsc::channel::(1); 55 | { 56 | let mut tx = SDP_CHAN_TX_MUTEX.lock().await; 57 | *tx = Some(sdp_chan_tx); 58 | } 59 | 60 | tokio::spawn(async move { 61 | let addr = SocketAddr::from_str(&format!("0.0.0.0:{}", port)).unwrap(); 62 | let service = 63 | make_service_fn(|_| async { Ok::<_, hyper::Error>(service_fn(remote_handler)) }); 64 | let server = Server::bind(&addr).serve(service); 65 | // Run this server for... forever! 66 | if let Err(e) = server.await { 67 | eprintln!("server error: {}", e); 68 | } 69 | }); 70 | 71 | sdp_chan_rx 72 | } 73 | 74 | /// must_read_stdin blocks until input is received from stdin 75 | pub fn must_read_stdin() -> Result { 76 | let mut line = String::new(); 77 | 78 | std::io::stdin().read_line(&mut line)?; 79 | line = line.trim().to_owned(); 80 | println!(); 81 | 82 | Ok(line) 83 | } 84 | 85 | // Allows compressing offer/answer to bypass terminal input limits. 86 | // const COMPRESS: bool = false; 87 | 88 | /// encode encodes the input in base64 89 | /// It can optionally zip the input before encoding 90 | pub fn encode(b: &str) -> String { 91 | //if COMPRESS { 92 | // b = zip(b) 93 | //} 94 | 95 | base64::encode(b) 96 | } 97 | 98 | /// decode decodes the input from base64 99 | /// It can optionally unzip the input after decoding 100 | pub fn decode(s: &str) -> Result { 101 | let b = base64::decode(s)?; 102 | 103 | //if COMPRESS { 104 | // b = unzip(b) 105 | //} 106 | 107 | let s = String::from_utf8(b)?; 108 | Ok(s) 109 | } 110 | /* 111 | func zip(in []byte) []byte { 112 | var b bytes.Buffer 113 | gz := gzip.NewWriter(&b) 114 | _, err := gz.Write(in) 115 | if err != nil { 116 | panic(err) 117 | } 118 | err = gz.Flush() 119 | if err != nil { 120 | panic(err) 121 | } 122 | err = gz.Close() 123 | if err != nil { 124 | panic(err) 125 | } 126 | return b.Bytes() 127 | } 128 | 129 | func unzip(in []byte) []byte { 130 | var b bytes.Buffer 131 | _, err := b.Write(in) 132 | if err != nil { 133 | panic(err) 134 | } 135 | r, err := gzip.NewReader(&b) 136 | if err != nil { 137 | panic(err) 138 | } 139 | res, err := ioutil.ReadAll(r) 140 | if err != nil { 141 | panic(err) 142 | } 143 | return res 144 | } 145 | */ 146 | -------------------------------------------------------------------------------- /examples/simulcast/README.md: -------------------------------------------------------------------------------- 1 | # simulcast 2 | demonstrates of how to handle incoming track with multiple simulcast rtp streams and show all them back. 3 | 4 | The browser will not send higher quality streams unless it has the available bandwidth. You can look at 5 | the bandwidth estimation in `chrome://webrtc-internals`. It is under `VideoBwe` when `Read Stats From: Legacy non-Standard` 6 | is selected. 7 | 8 | ## Instructions 9 | ### Build simulcast 10 | ``` 11 | cargo build --example simulcast 12 | ``` 13 | 14 | ### Open simulcast example page 15 | [jsfiddle.net](https://jsfiddle.net/rxk4bftc) you should see two text-areas and a 'Start Session' button. 16 | 17 | ### Run simulcast, with your browsers SessionDescription as stdin 18 | In the jsfiddle the top textarea is your browser, copy that and: 19 | #### Linux/macOS 20 | Run `echo $BROWSER_SDP | ./target/debug/examples/simulcast` 21 | #### Windows 22 | 1. Paste the SessionDescription into a file. 23 | 1. Run `./target/debug/examples/simulcast < my_file` 24 | 25 | ### Input simulcast's SessionDescription into your browser 26 | Copy the text that `simulcast` just emitted and copy into second text area 27 | 28 | ### Hit 'Start Session' in jsfiddle, enjoy your video! 29 | Your browser should send a simulcast track to WebRTC.rs, and then all 3 incoming streams will be relayed back. 30 | 31 | Congrats, you have used WebRTC.rs! 32 | -------------------------------------------------------------------------------- /examples/simulcast/simulcast.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use clap::{AppSettings, Arg, Command}; 3 | use std::collections::HashMap; 4 | use std::io::Write; 5 | use std::sync::Arc; 6 | use tokio::time::Duration; 7 | use webrtc::api::interceptor_registry::register_default_interceptors; 8 | use webrtc::api::media_engine::{MediaEngine, MIME_TYPE_VP8}; 9 | use webrtc::api::APIBuilder; 10 | use webrtc::ice_transport::ice_server::RTCIceServer; 11 | use webrtc::interceptor::registry::Registry; 12 | use webrtc::peer_connection::configuration::RTCConfiguration; 13 | use webrtc::peer_connection::peer_connection_state::RTCPeerConnectionState; 14 | use webrtc::peer_connection::sdp::session_description::RTCSessionDescription; 15 | use webrtc::rtcp::payload_feedbacks::picture_loss_indication::PictureLossIndication; 16 | use webrtc::rtp_transceiver::rtp_codec::{ 17 | RTCRtpCodecCapability, RTCRtpHeaderExtensionCapability, RTPCodecType, 18 | }; 19 | use webrtc::rtp_transceiver::rtp_receiver::RTCRtpReceiver; 20 | use webrtc::track::track_local::track_local_static_rtp::TrackLocalStaticRTP; 21 | use webrtc::track::track_local::{TrackLocal, TrackLocalWriter}; 22 | use webrtc::track::track_remote::TrackRemote; 23 | use webrtc::Error; 24 | 25 | #[tokio::main] 26 | async fn main() -> Result<()> { 27 | let mut app = Command::new("simulcast") 28 | .version("0.1.0") 29 | .author("Rain Liu ") 30 | .about("An example of simulcast.") 31 | .setting(AppSettings::DeriveDisplayOrder) 32 | .subcommand_negates_reqs(true) 33 | .arg( 34 | Arg::new("FULLHELP") 35 | .help("Prints more detailed help information") 36 | .long("fullhelp"), 37 | ) 38 | .arg( 39 | Arg::new("debug") 40 | .long("debug") 41 | .short('d') 42 | .help("Prints debug log information"), 43 | ); 44 | 45 | let matches = app.clone().get_matches(); 46 | 47 | if matches.is_present("FULLHELP") { 48 | app.print_long_help().unwrap(); 49 | std::process::exit(0); 50 | } 51 | 52 | let debug = matches.is_present("debug"); 53 | if debug { 54 | env_logger::Builder::new() 55 | .format(|buf, record| { 56 | writeln!( 57 | buf, 58 | "{}:{} [{}] {} - {}", 59 | record.file().unwrap_or("unknown"), 60 | record.line().unwrap_or(0), 61 | record.level(), 62 | chrono::Local::now().format("%H:%M:%S.%6f"), 63 | record.args() 64 | ) 65 | }) 66 | .filter(None, log::LevelFilter::Trace) 67 | .init(); 68 | } 69 | 70 | // Everything below is the WebRTC-rs API! Thanks for using it ❤️. 71 | 72 | // Create a MediaEngine object to configure the supported codec 73 | let mut m = MediaEngine::default(); 74 | 75 | m.register_default_codecs()?; 76 | 77 | // Enable Extension Headers needed for Simulcast 78 | for extension in vec![ 79 | "urn:ietf:params:rtp-hdrext:sdes:mid", 80 | "urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id", 81 | "urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id", 82 | ] { 83 | m.register_header_extension( 84 | RTCRtpHeaderExtensionCapability { 85 | uri: extension.to_owned(), 86 | }, 87 | RTPCodecType::Video, 88 | vec![], 89 | )?; 90 | } 91 | // Create a InterceptorRegistry. This is the user configurable RTP/RTCP Pipeline. 92 | // This provides NACKs, RTCP Reports and other features. If you use `webrtc.NewPeerConnection` 93 | // this is enabled by default. If you are manually managing You MUST create a InterceptorRegistry 94 | // for each PeerConnection. 95 | let mut registry = Registry::new(); 96 | 97 | // Use the default set of Interceptors 98 | registry = register_default_interceptors(registry, &mut m)?; 99 | 100 | // Create the API object with the MediaEngine 101 | let api = APIBuilder::new() 102 | .with_media_engine(m) 103 | .with_interceptor_registry(registry) 104 | .build(); 105 | 106 | // Prepare the configuration 107 | let config = RTCConfiguration { 108 | ice_servers: vec![RTCIceServer { 109 | urls: vec!["stun:stun.l.google.com:19302".to_owned()], 110 | ..Default::default() 111 | }], 112 | ..Default::default() 113 | }; 114 | 115 | // Create a new RTCPeerConnection 116 | let peer_connection = Arc::new(api.new_peer_connection(config).await?); 117 | 118 | // Create Track that we send video back to browser on 119 | let mut output_tracks = HashMap::new(); 120 | for s in vec!["q", "h", "f"] { 121 | let output_track = Arc::new(TrackLocalStaticRTP::new( 122 | RTCRtpCodecCapability { 123 | mime_type: MIME_TYPE_VP8.to_owned(), 124 | ..Default::default() 125 | }, 126 | format!("video_{}", s), 127 | format!("webrtc-rs_{}", s), 128 | )); 129 | 130 | // Add this newly created track to the PeerConnection 131 | let rtp_sender = peer_connection 132 | .add_track(Arc::clone(&output_track) as Arc) 133 | .await?; 134 | 135 | // Read incoming RTCP packets 136 | // Before these packets are returned they are processed by interceptors. For things 137 | // like NACK this needs to be called. 138 | tokio::spawn(async move { 139 | let mut rtcp_buf = vec![0u8; 1500]; 140 | while let Ok((_, _)) = rtp_sender.read(&mut rtcp_buf).await {} 141 | Result::<()>::Ok(()) 142 | }); 143 | 144 | output_tracks.insert(s.to_owned(), output_track); 145 | } 146 | 147 | // Wait for the offer to be pasted 148 | let line = signal::must_read_stdin()?; 149 | let desc_data = signal::decode(line.as_str())?; 150 | let offer = serde_json::from_str::(&desc_data)?; 151 | 152 | // Set the remote SessionDescription 153 | peer_connection.set_remote_description(offer).await?; 154 | 155 | // Set a handler for when a new remote track starts 156 | let pc = Arc::downgrade(&peer_connection); 157 | peer_connection 158 | .on_track(Box::new( 159 | move |track: Option>, _receiver: Option>| { 160 | if let Some(track) = track { 161 | println!("Track has started"); 162 | 163 | let rid = track.rid().to_owned(); 164 | let output_track = if let Some(output_track) = output_tracks.get(&rid) { 165 | Arc::clone(output_track) 166 | } else { 167 | println!("output_track not found for rid = {}", rid); 168 | return Box::pin(async {}); 169 | }; 170 | 171 | // Start reading from all the streams and sending them to the related output track 172 | let media_ssrc = track.ssrc(); 173 | let pc2 = pc.clone(); 174 | tokio::spawn(async move { 175 | let mut result = Result::::Ok(0); 176 | while result.is_ok() { 177 | println!( 178 | "Sending pli for stream with rid: {}, ssrc: {}", 179 | rid, media_ssrc 180 | ); 181 | 182 | let timeout = tokio::time::sleep(Duration::from_secs(3)); 183 | tokio::pin!(timeout); 184 | 185 | tokio::select! { 186 | _ = timeout.as_mut() =>{ 187 | if let Some(pc) = pc2.upgrade(){ 188 | result = pc.write_rtcp(&[Box::new(PictureLossIndication{ 189 | sender_ssrc: 0, 190 | media_ssrc, 191 | })]).await.map_err(Into::into); 192 | }else{ 193 | break; 194 | } 195 | } 196 | }; 197 | } 198 | }); 199 | 200 | tokio::spawn(async move { 201 | // Read RTP packets being sent to webrtc-rs 202 | println!("enter track loop {}", track.rid()); 203 | while let Ok((rtp, _)) = track.read_rtp().await { 204 | if let Err(err) = output_track.write_rtp(&rtp).await { 205 | if Error::ErrClosedPipe != err { 206 | println!("output track write_rtp got error: {} and break", err); 207 | break; 208 | } else { 209 | println!("output track write_rtp got error: {}", err); 210 | } 211 | } 212 | } 213 | println!("exit track loop {}", track.rid()); 214 | }); 215 | } 216 | Box::pin(async {}) 217 | }, 218 | )) 219 | .await; 220 | 221 | let (done_tx, mut done_rx) = tokio::sync::mpsc::channel::<()>(1); 222 | 223 | // Set the handler for Peer connection state 224 | // This will notify you when the peer has connected/disconnected 225 | peer_connection 226 | .on_peer_connection_state_change(Box::new(move |s: RTCPeerConnectionState| { 227 | print!("Peer Connection State has changed: {}\n", s); 228 | 229 | if s == RTCPeerConnectionState::Failed { 230 | // Wait until PeerConnection has had no network activity for 30 seconds or another failure. It may be reconnected using an ICE Restart. 231 | // Use webrtc.PeerConnectionStateDisconnected if you are interested in detecting faster timeout. 232 | // Note that the PeerConnection may come back from PeerConnectionStateDisconnected. 233 | println!("Peer Connection has gone to failed exiting"); 234 | let _ = done_tx.try_send(()); 235 | } 236 | 237 | Box::pin(async {}) 238 | })) 239 | .await; 240 | 241 | // Create an answer 242 | let answer = peer_connection.create_answer(None).await?; 243 | 244 | // Create channel that is blocked until ICE Gathering is complete 245 | let mut gather_complete = peer_connection.gathering_complete_promise().await; 246 | 247 | // Sets the LocalDescription, and starts our UDP listeners 248 | peer_connection.set_local_description(answer).await?; 249 | 250 | // Block until ICE Gathering is complete, disabling trickle ICE 251 | // we do this because we only can exchange one signaling message 252 | // in a production application you should exchange ICE Candidates via OnICECandidate 253 | let _ = gather_complete.recv().await; 254 | 255 | // Output the answer in base64 so we can paste it in browser 256 | if let Some(local_desc) = peer_connection.local_description().await { 257 | let json_str = serde_json::to_string(&local_desc)?; 258 | let b64 = signal::encode(&json_str); 259 | println!("{}", b64); 260 | } else { 261 | println!("generate local_description failed!"); 262 | } 263 | 264 | println!("Press ctrl-c to stop"); 265 | tokio::select! { 266 | _ = done_rx.recv() => { 267 | println!("received done signal!"); 268 | } 269 | _ = tokio::signal::ctrl_c() => { 270 | println!(""); 271 | } 272 | }; 273 | 274 | peer_connection.close().await?; 275 | 276 | Ok(()) 277 | } 278 | -------------------------------------------------------------------------------- /examples/swap-tracks/README.md: -------------------------------------------------------------------------------- 1 | # swap-tracks 2 | swap-tracks demonstrates how to swap multiple incoming tracks on a single outgoing track. 3 | 4 | ## Instructions 5 | ### Build swap-tracks 6 | ``` 7 | cargo build --example swap-tracks 8 | ``` 9 | 10 | ### Open swap-tracks example page 11 | [jsfiddle.net](https://jsfiddle.net/dzc17fga/) you should see two text-areas and a 'Start Session' button. 12 | 13 | ### Run swap-tracks, with your browsers SessionDescription as stdin 14 | In the jsfiddle the top textarea is your browser, copy that and: 15 | #### Linux/macOS 16 | Run `echo $BROWSER_SDP | ./target/debug/examples/swap-tracks` 17 | #### Windows 18 | 1. Paste the SessionDescription into a file. 19 | 1. Run `./target/debug/examples/swap-tracks < my_file` 20 | 21 | ### Input swap-tracks's SessionDescription into your browser 22 | Copy the text that `swap-tracks` just emitted and copy into second text area 23 | 24 | ### Hit 'Start Session' in jsfiddle, enjoy your video! 25 | Your browser should send streams to webrtc-rs, and then a stream will be relayed back, changing every 5 seconds. 26 | 27 | Congrats, you have used WebRTC.rs! -------------------------------------------------------------------------------- /examples/test-data/output.h264: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/webrtc-rs/examples/f91465021354995edd148e6e60e5c3b1f78a656c/examples/test-data/output.h264 -------------------------------------------------------------------------------- /examples/test-data/output.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/webrtc-rs/examples/f91465021354995edd148e6e60e5c3b1f78a656c/examples/test-data/output.ogg -------------------------------------------------------------------------------- /examples/test-data/output_vp8.ivf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/webrtc-rs/examples/f91465021354995edd148e6e60e5c3b1f78a656c/examples/test-data/output_vp8.ivf -------------------------------------------------------------------------------- /examples/test-data/output_vp9.ivf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/webrtc-rs/examples/f91465021354995edd148e6e60e5c3b1f78a656c/examples/test-data/output_vp9.ivf -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod tests { 3 | #[test] 4 | fn it_works() { 5 | assert_eq!(2 + 2, 4); 6 | } 7 | } 8 | --------------------------------------------------------------------------------