├── .cargo └── config ├── .dockerignore ├── .github └── workflows │ ├── container-build.yml │ ├── rust-build.yml │ └── rust-check.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── Dockerfile ├── LICENSE-APACHE ├── LICENSE-MIT ├── Makefile ├── README.md ├── certs ├── README.md ├── cert.der ├── cert.pem ├── key.der └── key.pem ├── docker-compose.yml ├── docs ├── .gitignore ├── archive │ └── DESIGN.md ├── book.toml └── src │ ├── SUMMARY.md │ ├── client │ ├── client.md │ ├── join-as-publisher.md │ └── join-as-subscriber.md │ ├── concepts │ ├── authentication.md │ ├── client-as-sdp-offer-first.md │ ├── concepts.md │ ├── ice.md │ ├── multistream.md │ ├── publisher-subscriber.md │ ├── room.md │ ├── sdp.md │ ├── stun.md │ ├── turn.md │ ├── webrtc-renegotiation.md │ ├── webrtc-sfu.md │ └── whip-whep.md │ ├── deployment │ ├── deployment.md │ ├── docker-compose.md │ ├── docker-swarm.md │ ├── kubernetes.md │ └── single-machine.md │ ├── getting-started │ ├── getting-started.md │ └── introduction.md │ ├── metrics │ ├── metrics.md │ └── prometheus.md │ ├── server │ ├── data-channel-api.md │ ├── dependencies.md │ ├── id-full-id.md │ ├── media-routing │ │ ├── media-routing.md │ │ └── nats.md │ ├── private-http-api.md │ ├── public-http-api.md │ ├── room-management.md │ ├── scaling.md │ ├── server.md │ └── states-sharing │ │ ├── redis.md │ │ └── states-sharing.md │ └── use-cases │ ├── broadcasting.md │ ├── private-audio-chat.md │ ├── public-audio-chat.md │ ├── use-cases.md │ └── video-conferencing.md ├── site ├── example.js ├── index.html ├── management.html └── weever-streaming.js └── src ├── cli.rs ├── helper.rs ├── main.rs ├── publisher.rs ├── state.rs ├── subscriber.rs └── web.rs /.cargo/config: -------------------------------------------------------------------------------- 1 | [build] 2 | rustflags = ["--cfg", "tokio_unstable"] # for tokio console 3 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | ** 2 | !src/** 3 | !Cargo.toml 4 | !Cargo.lock 5 | !site/** 6 | !certs/** 7 | -------------------------------------------------------------------------------- /.github/workflows/container-build.yml: -------------------------------------------------------------------------------- 1 | # based on https://github.com/actions/starter-workflows/blob/main/ci/docker-publish.yml 2 | name: Container Build 3 | 4 | on: 5 | workflow_dispatch: {} 6 | workflow_call: 7 | # schedule: 8 | # - cron: '22 18 * * *' 9 | # push: 10 | # branches: [ "develop" ] 11 | # # Publish semver tags as releases. 12 | # tags: [ 'v*.*.*' ] 13 | # pull_request: 14 | # branches: [ "develop" ] 15 | 16 | env: 17 | # # Use docker.io for Docker Hub if empty 18 | # REGISTRY: ghcr.io 19 | # github.repository as / 20 | IMAGE_NAME: ${{ github.repository }} 21 | 22 | 23 | jobs: 24 | build: 25 | name: Release Container Build 26 | 27 | runs-on: ubuntu-latest 28 | 29 | strategy: 30 | fail-fast: true 31 | matrix: 32 | include: 33 | - registry: ghcr.io 34 | # - registry: docker.io 35 | 36 | permissions: 37 | contents: read 38 | packages: write 39 | # This is used to complete the identity challenge 40 | # with sigstore/fulcio when running outside of PRs. 41 | id-token: write 42 | 43 | steps: 44 | - name: Checkout repository 45 | uses: actions/checkout@v3 46 | 47 | # # Install the cosign tool except on PR 48 | # # https://github.com/sigstore/cosign-installer 49 | # - name: Install cosign 50 | # if: github.event_name != 'pull_request' 51 | # uses: sigstore/cosign-installer@v2.8.1 52 | 53 | # Workaround: https://github.com/docker/build-push-action/issues/461 54 | - name: Setup Docker buildx 55 | uses: docker/setup-buildx-action@79abd3f86f79a9d68a23c75a09a9a85889262adf 56 | 57 | # Login against a Docker registry except on PR 58 | # https://github.com/docker/login-action 59 | - name: Log into registry ${{ matrix.registry }} 60 | if: github.event_name != 'pull_request' 61 | uses: docker/login-action@28218f9b04b4f3f62068d7b6ce6ca5b26e35336c 62 | with: 63 | registry: ${{ matrix.registry }} 64 | username: ${{ github.actor }} 65 | password: ${{ secrets.GITHUB_TOKEN }} 66 | 67 | # Extract metadata (tags, labels) for Docker 68 | # https://github.com/docker/metadata-action 69 | - name: Extract Docker metadata 70 | id: meta 71 | uses: docker/metadata-action@v4.1.1 72 | with: 73 | images: ${{ matrix.registry }}/${{ env.IMAGE_NAME }} 74 | flavor: | 75 | latest=auto 76 | prefix= 77 | suffix= 78 | tags: | 79 | # set latest tag for default branch 80 | type=raw,value=latest,enable={{is_default_branch}} 81 | 82 | # Build and push Docker image with Buildx (don't push on PR) 83 | # https://github.com/docker/build-push-action 84 | - name: Build and push Docker image 85 | id: build-and-push 86 | uses: docker/build-push-action@ac9327eae2b366085ac7f6a2d02df8aa8ead720a 87 | with: 88 | context: . 89 | push: ${{ github.event_name != 'pull_request' }} 90 | tags: ${{ steps.meta.outputs.tags }} 91 | labels: ${{ steps.meta.outputs.labels }} 92 | cache-from: type=gha 93 | cache-to: type=gha,mode=max 94 | 95 | # # Sign the resulting Docker image digest except on PRs. 96 | # # This will only write to the public Rekor transparency log when the Docker 97 | # # repository is public to avoid leaking data. If you would like to publish 98 | # # transparency data even for private images, pass --force to cosign below. 99 | # # https://github.com/sigstore/cosign 100 | # - name: Sign the published Docker image 101 | # if: ${{ github.event_name != 'pull_request' }} 102 | # env: 103 | # COSIGN_EXPERIMENTAL: "true" 104 | # # This step uses the identity token to provision an ephemeral certificate 105 | # # against the sigstore community Fulcio instance. 106 | # run: echo "${{ steps.meta.outputs.tags }}" | xargs -I {} cosign sign {}@${{ steps.build-and-push.outputs.digest }} 107 | -------------------------------------------------------------------------------- /.github/workflows/rust-build.yml: -------------------------------------------------------------------------------- 1 | name: Release Build and Upload 2 | 3 | on: 4 | workflow_dispatch: {} 5 | workflow_call: 6 | 7 | jobs: 8 | build: 9 | name: Release Binary Build 10 | runs-on: ubuntu-latest 11 | strategy: 12 | matrix: 13 | rust: 14 | - stable 15 | steps: 16 | - uses: actions/checkout@v3 17 | - uses: actions-rs/toolchain@v1 18 | with: 19 | profile: minimal 20 | toolchain: ${{ matrix.rust }} 21 | override: true 22 | - uses: actions-rs/cargo@v1 23 | with: 24 | command: build 25 | args: --release 26 | - run: | 27 | mkdir -p artifacts 28 | mv ./target/release/weever-streaming artifacts 29 | gzip -f artifacts/* 30 | - name: Upload Artifact 31 | uses: actions/upload-artifact@v3 32 | with: 33 | name: artifact.gz 34 | path: artifacts/* 35 | -------------------------------------------------------------------------------- /.github/workflows/rust-check.yml: -------------------------------------------------------------------------------- 1 | name: Check and Test and Lint 2 | 3 | on: [pull_request] 4 | 5 | jobs: 6 | check: 7 | name: Check 8 | runs-on: ubuntu-latest 9 | strategy: 10 | matrix: 11 | rust: 12 | - stable 13 | steps: 14 | - uses: actions/checkout@v3 15 | - uses: actions-rs/toolchain@v1 16 | with: 17 | profile: minimal 18 | toolchain: ${{ matrix.rust }} 19 | override: true 20 | - uses: actions-rs/cargo@v1 21 | with: 22 | command: check 23 | 24 | test: 25 | name: Test Suite 26 | runs-on: ubuntu-latest 27 | strategy: 28 | matrix: 29 | rust: 30 | - stable 31 | steps: 32 | - uses: actions/checkout@v3 33 | - uses: actions-rs/toolchain@v1 34 | with: 35 | profile: minimal 36 | toolchain: ${{ matrix.rust }} 37 | override: true 38 | - uses: actions-rs/cargo@v1 39 | with: 40 | command: test 41 | 42 | fmt: 43 | name: Rustfmt 44 | runs-on: ubuntu-latest 45 | strategy: 46 | matrix: 47 | rust: 48 | - stable 49 | steps: 50 | - uses: actions/checkout@v3 51 | - uses: actions-rs/toolchain@v1 52 | with: 53 | profile: minimal 54 | toolchain: ${{ matrix.rust }} 55 | override: true 56 | components: rustfmt 57 | - uses: actions-rs/cargo@v1 58 | with: 59 | command: fmt 60 | args: --all -- --check 61 | 62 | clippy: 63 | name: Clippy 64 | runs-on: ubuntu-latest 65 | strategy: 66 | matrix: 67 | rust: 68 | - stable 69 | steps: 70 | - uses: actions/checkout@v3 71 | - uses: actions-rs/toolchain@v1 72 | with: 73 | profile: minimal 74 | toolchain: ${{ matrix.rust }} 75 | override: true 76 | components: clippy 77 | - uses: actions-rs/cargo@v1 78 | with: 79 | command: clippy 80 | # args: -- -D warnings 81 | 82 | release-binary: 83 | name: Release Binary 84 | uses: ./.github/workflows/rust-build.yml 85 | 86 | release-container: 87 | name: Release Container 88 | if: github.event_name != 'pull_request' 89 | uses: ./.github/workflows/container-build.yml 90 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "weever-streaming" 3 | version = "0.1.0" 4 | authors = ["Chiu-Hsiang Hsu"] 5 | edition = "2021" 6 | description = "scalable WebRTC SFU server" 7 | keywords = ["webrtc"] 8 | 9 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 10 | 11 | [[bin]] 12 | name = "weever-streaming" 13 | path = "src/main.rs" 14 | 15 | [dependencies] 16 | anyhow = "1.0.68" 17 | async-trait = "0.1.60" 18 | bytes = "1.3.0" 19 | base64 = "0.13.1" 20 | log = "0.4.14" 21 | nats = "0.23.1" 22 | once_cell = "1.16.0" 23 | serde = "1.0.151" 24 | serde_json = "1.0.91" 25 | tokio = { version = "1.23.0", features = ["full"] } 26 | clap = { version = "4.0.32", features = ["derive", "env"] } 27 | redis = { version = "0.21.7", features = ["tokio-comp"] } 28 | bincode = "2.0.0-rc.2" 29 | futures = "0.3.25" 30 | # WebRTC 31 | webrtc = { git = "https://github.com/webrtc-rs/webrtc", rev = "242db9d6" } 32 | # tracing ecosystem 33 | tracing = "0.1.37" 34 | tracing-appender = "0.2.2" 35 | tracing-log = "0.1.2" 36 | tracing-subscriber = { version = "0.3.16", features = ["fmt", "env-filter"] } 37 | console-subscriber = { version = "0.1.8", optional = true } 38 | # web server 39 | actix-web = { version = "4.2.1", default-features = false, features = ["macros", "rustls"] } # web framework 40 | actix-web-httpauth = "0.6.0" # bearerer token 41 | actix-files = "0.6.2" # static files 42 | actix-cors = "0.6.4" # CORS setting (for easier frontend local development) 43 | rustls = "0.20.7" # SSL 44 | rustls-pemfile = "0.2.1" # SSL 45 | # metrics 46 | prometheus = "0.13.3" 47 | 48 | 49 | [features] 50 | release_max_level_info = ["log/release_max_level_info", "tracing/release_max_level_info"] 51 | release_max_level_debug = ["log/release_max_level_debug", "tracing/release_max_level_debug"] 52 | console = ["console-subscriber"] 53 | 54 | 55 | [profile.release] 56 | debug = false 57 | # Ref: https://doc.rust-lang.org/cargo/reference/profiles.html#lto 58 | lto = "thin" 59 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM rust:1.66.0-slim as builder 2 | 3 | ARG TRIPLE=x86_64-unknown-linux-gnu 4 | ARG PROJ=weever-streaming 5 | # build 6 | RUN rustup target add ${TRIPLE} 7 | ADD Cargo.toml Cargo.toml 8 | ADD Cargo.lock Cargo.lock 9 | # fetch all dependencies as cache 10 | RUN mkdir -p .cargo && cargo vendor > .cargo/config 11 | # dummy build to build all dependencies as cache 12 | RUN mkdir src/ && echo "fn main() {}" > src/main.rs && cargo build --bin ${PROJ} --release --target ${TRIPLE} && rm -f src/main.rs 13 | # get real code in 14 | COPY . . 15 | RUN touch src/main.rs && cargo build --release --bin ${PROJ} --target ${TRIPLE} --features release_max_level_debug 16 | RUN strip target/${TRIPLE}/release/${PROJ} 17 | 18 | ########## 19 | 20 | FROM debian:bullseye-20221205-slim 21 | 22 | ARG TRIPLE=x86_64-unknown-linux-gnu 23 | ARG PROJ=weever-streaming 24 | COPY --from=builder /target/${TRIPLE}/release/${PROJ} /usr/local/bin/ 25 | COPY site site 26 | # debug certs, you should override it in production 27 | COPY certs certs 28 | 29 | # log current git commit hash for future investigation (need to pass in from outside) 30 | ARG COMMIT_SHA 31 | RUN echo ${COMMIT_SHA} > /commit 32 | 33 | ENV RUST_LOG=info,webrtc_mdns=error,webrtc_srtp=info 34 | ENV CERT_FILE=certs/cert.pem 35 | ENV KEY_FILE=certs/key.pem 36 | CMD weever-streaming 37 | -------------------------------------------------------------------------------- /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) 2023 Embracer Freemode 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 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | help: ## show help messages 2 | @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) \ 3 | | sort \ 4 | | awk 'BEGIN {FS = ":[ \t].*?## "}; {print $$1 "|" $$2 }' \ 5 | | tr ":" "|" \ 6 | | awk 'BEGIN {FS = "|"}; {printf "\033[32m%-16s\033[0m: \033[36m%-21s\033[0m %s\n", $$1, $$2, $$3}' 7 | 8 | check: ## quick compiler check 9 | cargo check 10 | 11 | lint: ## quick linter (clippy) check 12 | cargo clippy --fix 13 | 14 | format: ## code formatter 15 | cargo fmt 16 | 17 | doc: ## generate docs 18 | cargo doc 19 | 20 | build-debug: ## debug build 21 | cargo build 22 | 23 | build-release: ## release build 24 | cargo build --release 25 | 26 | build-container: ## build container (via docker) 27 | docker build . \ 28 | --network host \ 29 | --build-arg COMMIT_SHA=$(shell git log -1 --format="%H") \ 30 | -t weever-streaming 31 | 32 | run: ## run native instance (needs Redis and NATS) 33 | env RUST_LOG=debug,actix_web=debug,webrtc_mdns=error,webrtc_srtp=debug \ 34 | cargo run -- \ 35 | --debug \ 36 | --cert-file certs/cert.pem \ 37 | --key-file certs/key.pem 38 | 39 | run-container: ## run container (needs Redis and NATS) 40 | docker run \ 41 | --network host \ 42 | --env RUST_LOG=debug \ 43 | -it weever-streaming 44 | 45 | run-compose: ## run docker-compose 46 | docker-compose up 47 | 48 | stop-compose: ## stop docker-compose 49 | docker-compose down 50 | 51 | gen-certs: ## generate debug certs 52 | @echo "=> generating debug certs in 'certs' folder" 53 | # https://github.com/est31/rcgen 54 | rcgen 55 | openssl x509 -in certs/cert.pem -text -noout 56 | 57 | 58 | _prepare: 59 | make check 60 | make lint 61 | make format 62 | make doc 63 | make build-debug 64 | make build-container 65 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Weever Streaming: Cloud Native, Horizontal Scaling, WebRTC SFU 2 | ============================================================== 3 | 4 | [![Build status](https://github.com/embracer-freemode/weever-streaming/actions/workflows/rust-check.yml/badge.svg)](https://github.com/embracer-freemode/weever-streaming/actions) 5 | 6 | Weever Streaming is a Open Source WebRTC SFU (Selective Forwarding Unit). 7 | It serves for broadcasting, video conferencing, or regular video/audio/data streaming ... etc. It's easy to deploy and scale. 8 | 9 | You can come up with your own streaming products/platforms easily with it, as long as you are familiar with html, css, javascript, and web service hosting. Client examples can be found at [site/index.html](https://github.com/embracer-freemode/weever-streaming/blob/develop/site/index.html) and [site/management.html](https://github.com/embracer-freemode/weever-streaming/blob/develop/site/management.html). 10 | 11 | Currently, Weever Streaming can be deployed with docker-compose in a single machine, or with docker swarm mode to be running on multiple machines. Last but not the least, it can also be deployed to kubernetes shall you wish to go totally cloud native. 12 | 13 | 14 | For more information, please checkout [**documentation**](https://embracer-freemode.github.io/weever-streaming). 15 | 16 | 17 | Similar or Related Projects We Know of 18 | ======================================== 19 | 20 | * [Janus](https://janus.conf.meetecho.com/): the general purpose WebRTC server 21 | * [Jitsi](https://jitsi.org/): Video Conferencing Software 22 | 23 | 24 | When we created Weever Streaming, 25 | most of the popular WebRTC SFU projects scale by "room". 26 | Different video room can be in different instance, 27 | but all the clients in same room must connect to same instance. 28 | They are not easily horizontal scalable, and sometime needs management works. 29 | 30 | Janus supports wide range features. But the horizontal scaling part was a little bit of pain. 31 | Before Janus v1.0, horizontal scaling needs to be done via RTP forwards + Streaming mountpoint. 32 | It needs some management works. 33 | After Janus v1.1, new APIs for remote publisher are added. 34 | However, it still need some management, and more complex than Weever Streaming. 35 | 36 | Jitsi comes with good and deep video conferencing integration. 37 | But it was a bit hard to tailor it for our own applications last time we checked. 38 | Jitsi without Octo (it's called relays now, cascaded bridges), can't easily do horizontal scaling. 39 | It needs to deploy with sharding architecture. 40 | After Jitsi added Octo/Relays, the streams can be routed between instances. 41 | However, it's still a complex solution. It's more complex than we need. 42 | 43 | Weever Streaming is released with MIT and Apache V2, with only 2 HTML + 2 JS code examples. 44 | Hooking the code into your own UI and integrating into your own `jwt` auth logic is also straightforward. 45 | 46 | 47 | Features 48 | ======================================== 49 | 50 | * 1 HTTP POST for connection setup 51 | * subscriber multistream 52 | - 1 connection for any amount of stream subscribing (`O(1)` port usage) 53 | - publisher is 1 connection for each stream publishing (`O(N)` port usage) 54 | * (optional) authentication via Bearer Token 55 | * [WHIP](https://datatracker.ietf.org/doc/draft-ietf-wish-whip/)-like media ingress 56 | - it's WHIP-"like" because there is no guarantee about spec compliance 57 | - but this project learned the idea from there 58 | * [WHEP](https://datatracker.ietf.org/doc/draft-murillo-whep/)-like media egress 59 | - it's WHEP-"like" because there is no guarantee about spec compliance 60 | - this project implemented similar idea before there is WHEP spec release 61 | * shared internal states across SFU instances (currently via Redis, can be extended) 62 | * internal traffic routing across SFU instances (currently via NATS, can be extended) 63 | 64 | 65 | Try It 66 | ======================================== 67 | 68 | ```sh 69 | git clone https://github.com/embracer-freemode/weever-streaming 70 | cd weever-streaming 71 | 72 | # you need to install "docker-compose" first 73 | # this will launch: 1 Redis, 3 NATS, 3 WebRTC SFU 74 | # 75 | # ┌──────┐ ┌──────┐ ┌──────┐ 76 | # │ SFU1 ├──┐ │ SFU2 ├──┐ │ SFU3 ├──┐ 77 | # └───┬──┘ │ └───┬──┘ │ └───┬──┘ │ 78 | # │ │ │ │ │ │ 79 | # ┌───▼───┐ │ ┌───▼───┐ │ ┌───▼───┐ │ 80 | # │ NATS1 ◄─┼─► NATS2 ◄─┼─► NATS3 │ │ 81 | # └───────┘ │ └───────┘ │ └───────┘ │ 82 | # │ │ │ 83 | # ┌─────────▼───────────▼───────────▼──┐ 84 | # │ Redis │ 85 | # └────────────────────────────────────┘ 86 | # 87 | docker-compose up 88 | 89 | # visit website (publisher & subscriber can be in different instance): 90 | # 91 | # * https://localhost:8443/ 92 | # * https://localhost:8444/ 93 | # * https://localhost:8445/ 94 | # 95 | # The default demo site is using self signed certs, so you need to ignore the warning in browser. 96 | ``` 97 | 98 | 99 | [![SFU demo](https://user-images.githubusercontent.com/2716047/217735060-a8805054-9bdb-4ab2-87ca-06b241ce1594.mp4)](https://user-images.githubusercontent.com/2716047/217735060-a8805054-9bdb-4ab2-87ca-06b241ce1594.mp4) 100 | 101 | ![demo-site](https://user-images.githubusercontent.com/2716047/217726798-47a3b770-30c8-4687-b149-1863e649d3dd.png) 102 | 103 | 104 | Deployment 105 | ======================================== 106 | 107 | * [Docker Compose](https://embracer-freemode.github.io/weever-streaming/deployment/docker-compose.html) 108 | * [Docker Swarm](https://embracer-freemode.github.io/weever-streaming/deployment/docker-swarm.html) 109 | * [Kubernetes](https://embracer-freemode.github.io/weever-streaming/deployment/kubernetes.html) 110 | 111 | 112 | Development 113 | ======================================== 114 | 115 | Compile 116 | ------------------------------ 117 | 118 | ```sh 119 | cargo build 120 | ``` 121 | 122 | CI 123 | ------------------------------ 124 | 125 | [GitHub Actions](https://github.com/embracer-freemode/weever-streaming/actions) 126 | 127 | 128 | TODOs 129 | ======================================== 130 | 131 | * [ ] an awesome basic UI for user to come and get impressed 132 | * [ ] a video tutorial for each deployment method 133 | * [ ] beef up the doc for Kubernetes deployment 134 | 135 | 136 | Special Thanks 137 | ======================================== 138 | 139 | Thank [Janus Gateway](https://github.com/meetecho/janus-gateway). 140 | I learned a lot from this project! 141 | -------------------------------------------------------------------------------- /certs/README.md: -------------------------------------------------------------------------------- 1 | This is generated debug certs. 2 | -------------------------------------------------------------------------------- /certs/cert.der: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embracer-freemode/weever-streaming/1e80545176afbefafe12ebf89da26d0a81effafa/certs/cert.der -------------------------------------------------------------------------------- /certs/cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIBfjCCASOgAwIBAgIIfPaQqsg++8UwCgYIKoZIzj0EAwIwMDEYMBYGA1UECgwP 3 | Q3JhYiB3aWRnaXRzIFNFMRQwEgYDVQQDDAtNYXN0ZXIgQ2VydDAgFw03NTAxMDEw 4 | MDAwMDBaGA80MDk2MDEwMTAwMDAwMFowMDEYMBYGA1UECgwPQ3JhYiB3aWRnaXRz 5 | IFNFMRQwEgYDVQQDDAtNYXN0ZXIgQ2VydDBZMBMGByqGSM49AgEGCCqGSM49AwEH 6 | A0IABFWnIt//4e4lDO4RjZLOjaZ8CdgQ8QwCRcrOIzNcOt2kww4/NAZ5uBc3p19U 7 | uhWPSH9xB0ZZ2yk291fFjaXv7fijJTAjMCEGA1UdEQQaMBiCC2NyYWJzLmNyYWJz 8 | gglsb2NhbGhvc3QwCgYIKoZIzj0EAwIDSQAwRgIhAJIncIcd1MXScApWRmsI7HDQ 9 | a5ssI5rUh9csX5xZbKA7AiEAhKFLV0fAqy1CmnI9rvm0VEy4vNkvT/f3JYddJF+5 10 | u9A= 11 | -----END CERTIFICATE----- 12 | -------------------------------------------------------------------------------- /certs/key.der: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embracer-freemode/weever-streaming/1e80545176afbefafe12ebf89da26d0a81effafa/certs/key.der -------------------------------------------------------------------------------- /certs/key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgrrWjl9r1lX6tC/pO 3 | DfJ/wdY3S/wqAvbyhYADWGBQ6uqhRANCAARVpyLf/+HuJQzuEY2Szo2mfAnYEPEM 4 | AkXKziMzXDrdpMMOPzQGebgXN6dfVLoVj0h/cQdGWdspNvdXxY2l7+34 5 | -----END PRIVATE KEY----- 6 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | # Overview: 2 | # 3 | # ┌──────┐ ┌──────┐ ┌──────┐ 4 | # │ SFU1 ├──┐ │ SFU2 ├──┐ │ SFU3 ├──┐ 5 | # └───┬──┘ │ └───┬──┘ │ └───┬──┘ │ 6 | # │ │ │ │ │ │ 7 | # ┌───▼───┐ │ ┌───▼───┐ │ ┌───▼───┐ │ 8 | # │ NATS1 ◄─┼─► NATS2 ◄─┼─► NATS3 │ │ 9 | # └───────┘ │ └───────┘ │ └───────┘ │ 10 | # │ │ │ 11 | # ┌─────────▼───────────▼───────────▼──┐ 12 | # │ Redis │ 13 | # └────────────────────────────────────┘ 14 | # 15 | # 16 | version: "3.8" 17 | services: 18 | redis: 19 | image: redis:7.0.5-alpine 20 | restart: always 21 | # ports: 22 | # - "6379:6379" 23 | healthcheck: 24 | test: ["CMD", "redis-cli", "ping"] 25 | interval: 10s 26 | timeout: 5s 27 | retries: 5 28 | command: redis-server 29 | nats1: 30 | image: nats:2.9.9-alpine 31 | restart: always 32 | # ports: 33 | # - "4222:4222" 34 | healthcheck: 35 | test: ["CMD", "nc", "-zv", "localhost", "4222"] 36 | interval: 10s 37 | timeout: 5s 38 | retries: 5 39 | command: "--cluster_name NATS --cluster nats://0.0.0.0:6222 --routes nats://nats1:6222,nats://nats2:6222,nats://nats3:6222" 40 | nats2: 41 | image: nats:2.9.9-alpine 42 | restart: always 43 | healthcheck: 44 | test: ["CMD", "nc", "-zv", "localhost", "4222"] 45 | interval: 10s 46 | timeout: 5s 47 | retries: 5 48 | command: "--cluster_name NATS --cluster nats://0.0.0.0:6222 --routes nats://nats1:6222,nats://nats2:6222,nats://nats3:6222" 49 | nats3: 50 | image: nats:2.9.9-alpine 51 | restart: always 52 | healthcheck: 53 | test: ["CMD", "nc", "-zv", "localhost", "4222"] 54 | interval: 10s 55 | timeout: 5s 56 | retries: 5 57 | command: "--cluster_name NATS --cluster nats://0.0.0.0:6222 --routes nats://nats1:6222,nats://nats2:6222,nats://nats3:6222" 58 | sfu1: 59 | image: ghcr.io/embracer-freemode/weever-streaming:latest 60 | restart: always 61 | ports: 62 | - "8443:8443" 63 | depends_on: 64 | # redis: 65 | # condition: service_healthy 66 | # nats1: 67 | # condition: service_healthy 68 | - redis 69 | - nats1 70 | command: | 71 | weever-streaming 72 | --debug 73 | --port 8443 74 | --cert-file certs/cert.pem 75 | --key-file certs/key.pem 76 | --redis redis://redis 77 | --nats nats1 78 | sfu2: 79 | image: ghcr.io/embracer-freemode/weever-streaming:latest 80 | restart: always 81 | ports: 82 | - "8444:8444" 83 | depends_on: 84 | # redis: 85 | # condition: service_healthy 86 | # nats2: 87 | # condition: service_healthy 88 | - redis 89 | - nats2 90 | command: | 91 | weever-streaming 92 | --debug 93 | --port 8444 94 | --cert-file certs/cert.pem 95 | --key-file certs/key.pem 96 | --redis redis://redis 97 | --nats nats2 98 | sfu3: 99 | image: ghcr.io/embracer-freemode/weever-streaming:latest 100 | restart: always 101 | ports: 102 | - "8445:8445" 103 | depends_on: 104 | # redis: 105 | # condition: service_healthy 106 | # nats3: 107 | # condition: service_healthy 108 | - redis 109 | - nats3 110 | command: | 111 | weever-streaming 112 | --debug 113 | --port 8445 114 | --cert-file certs/cert.pem 115 | --key-file certs/key.pem 116 | --redis redis://redis 117 | --nats nats3 118 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | book 2 | -------------------------------------------------------------------------------- /docs/archive/DESIGN.md: -------------------------------------------------------------------------------- 1 | Cloud Native, Horizontal Scaling WebRTC SFU 2 | =========================================== 3 | 4 | A WebRTC SFU (Selective Forwarding Unit) server aim to be horizontal scalable. 5 | 6 | This project combine the experience in multiple fields, 7 | including state-of-the-art WebRTC flow (e.g. multistream, WHIP), Kubernetes deployment, Rust development. 8 | 9 | You can view the table of content on GitHub like this: 10 | ![github-toc](https://user-images.githubusercontent.com/2716047/132623287-c276e4a0-19a8-44a5-bbdb-41e1cdc432e1.gif) 11 | 12 | 13 | High Level Idea 14 | ======================================== 15 | 16 | Problem 17 | ------------------------------ 18 | 19 | The [Janus](https://janus.conf.meetecho.com) server can't easily scale horizontally. 20 | It needs either manually media forward setup for publishers/subscribers, 21 | or use it in provision style (prepare big enough resource for an event). 22 | 23 | And the room creation management in Janus is not very smooth. 24 | If the instance is restarted, you need to config the room again. 25 | There is no builtin way to easily restore the states. 26 | 27 | 28 | Solution 29 | ------------------------------ 30 | 31 | Options: 32 | 33 | 1. write custom Janus plugin to meet our goal 34 | 2. survey other media servers to see if they can horizontal scale 35 | 3. write whole WebRTC SFU server by ourself (full control, but need more development) 36 | 37 | 38 | This project picks solution #3. 39 | We write our own WebRTC SFU server. 40 | We built the server with horizontal scale in mind. 41 | 42 | 43 | Pros: 44 | * full control for the media 45 | * can customize whatever we want 46 | 47 | Cons: 48 | * need more development time 49 | * need deeper media knowledge 50 | 51 | 52 | 53 | SFU Design 54 | ======================================== 55 | 56 | 1 HTTP POST to connect 57 | ------------------------------ 58 | 59 | There is no complex negotiation and setting. 60 | We use single HTTP POST request with SDP offer from client to connect WebRTC. 61 | Server will provide SDP answer in HTTP response. 62 | Then following communication will based on the data channel. 63 | 64 | During first setup, browser will be the offer side, server will be the answer side. 65 | Even in subscriber case, browser will connect with a single data channel first as offer side. 66 | Then update the media based on info from data channel. 67 | 68 | During WebRTC renegotiation for subscribers, server will be the offer side, browser will be the answer side. 69 | In this case, browser doesn't need too much code, only need to set the SDP received from server. 70 | Server will use SDP to control browser's transceivers for video/audio. 71 | And msid reuse will also be handled in this case. 72 | 73 | 74 | Connection first, renegotiate media later 75 | ----------------------------------------- 76 | 77 | We choose to setup WebRTC connection first. 78 | Don't care too much about how many media we want. 79 | We start WebRTC connection with only 1 data channel. 80 | After the WebRTC become connected, 81 | server will send current room's media info via data channel. 82 | Then we setup the media flow via WebRTC renegotiation. 83 | 84 | 85 | Every PeerConnection will use unique id 86 | --------------------------------------- 87 | 88 | WebRTC's track stop/resume/...etc are a bit complex to control. 89 | And reuse RTP sender with replace track has rendering state problem. 90 | To simplify the case, we use unique id for every PeerConnection. 91 | Same user will use a random string as suffix everytime the client connects. 92 | So the clients are actually all different. No reuse problem. 93 | 94 | However, this come with the cost that a subscriber in the room will gradually have bigger SDP when publishers come and go. 95 | 96 | 97 | Data Channel Commands List 98 | ------------------------------ 99 | 100 | Frontend can use data channel to update SDP. 101 | So we can use WebRTC renegotiation to get new video/audio. 102 | 103 | Data Channel command list: 104 | 105 | * server to browser 106 | - `PUB_JOIN `: let frontend know a publisher just join 107 | - `PUB_LEFT `: let frontend know a publisher just left 108 | - `SDP_OFFER `: new SDP offer, browser should reply SDP_ANSWER 109 | * browser to server 110 | - `SDP_ANSWER `: new SDP answer, server should then finish current renegotiation 111 | - `STOP`: tell server to close WebRTC connection and cleanup related stuffs 112 | 113 | 114 | Room States Sharing 115 | ------------------------------ 116 | 117 | Current room states are shared across pods via **Redis**. 118 | So newly created pod can grab the settings from Redis when needed. 119 | There is no need for other room preparation. 120 | 121 | Shared info includes: 122 | * list of publisher in specific room 123 | * list of subscriber in specific room 124 | * per publisher video/audio count 125 | 126 | 127 | Cross Pods Internal Commands 128 | ------------------------------ 129 | 130 | Internal commands are serialize/deserialize via bincode and send via **NATS**. 131 | All the commands are collected in an enum called `Command`. 132 | 133 | This includes: 134 | * PubJoin 135 | * PubLeft 136 | 137 | 138 | 139 | Compare to other SFUs 140 | ======================================== 141 | 142 | Advantage 143 | ------------------------------ 144 | 145 | * WebRTC connection setup flow is simple (HTTP POST) 146 | * horizontal scalable 147 | * consider Kubernetes deploy from day one 148 | * single binary for deployment, not complex components setup 149 | 150 | 151 | Disadvantage 152 | ------------------------------ 153 | 154 | * only support VP8 and Opus right now 155 | * no audio mixing 156 | * does not support bandwidth estimation yet 157 | * does not support Simulcast or SVC yet (can combine with bandwidth estimation for dynamically switching) 158 | * no admin page 159 | 160 | 161 | 162 | WebRTC specs 163 | ======================================== 164 | 165 | * [RTCWEB working group](https://datatracker.ietf.org/wg/rtcweb/about/) 166 | * [Web Real-Time Communications Working Group Charter](https://w3c.github.io/webrtc-charter/webrtc-charter.html) 167 | * [W3C - WebRTC 1.0: Real-Time Communication Between Browsers](https://www.w3.org/TR/webrtc/) 168 | * [W3C - WebRTC Next Version Use Cases](https://www.w3.org/TR/webrtc-nv-use-cases/) 169 | - multiparty online game with voice communications 170 | - mobile calling service 171 | - video conferencing with a central server 172 | - file sharing 173 | - internet of things 174 | - funny hats effect 175 | - machine learning 176 | - virtual reality gaming 177 | - requirements 178 | + N01: ICE candidates control, e.g. gathering and pruning 179 | + N02: multiple connections with one offer 180 | + N03: congestion control for audio quality and latency betweeen multiple connections 181 | + N04: move traffic between multiple ICE candidates 182 | + N05: ICE candidates will consider network cost when doing re-routing 183 | + ... 184 | * [W3C - Scalable Video Coding (SVC) Extension for WebRTC](https://www.w3.org/TR/webrtc-svc/) 185 | - SST (Single-Session Transmission) 186 | - MST (Multi-Session Transmission) 187 | - MRST (Multiple RTP stream Single Transport) 188 | - Spatial Simulcast and Temporal Scalability 189 | + "L2T3" means 2 Spatial Layers & 3 Temporal Layers & Inter-layer dependency 190 | + "S2T3" means 2 Spatial Layers & 3 Temporal Layers & No Inter-layer dependency (Simulcast) 191 | - `scalabilityMode` 192 | * [W3C - Screen Capture](https://www.w3.org/TR/screen-capture/) 193 | - `navigator.mediaDevices.getDisplayMedia` 194 | * [W3C - MediaStreamTrack Content Hints](https://www.w3.org/TR/mst-content-hint/) 195 | - audio: "", "speech", "speech-recognition", "music" 196 | - video: "", "motion", "detail", "text" 197 | - degradation preference: "maintain-framerate", "maintain-resolution", "balanced" 198 | * [W3C - Viewport Capture](https://w3c.github.io/mediacapture-viewport/) 199 | * [W3C - WebRTC Encoded Transform](https://w3c.github.io/webrtc-encoded-transform/) 200 | - manipulating the bits on MediaStreamTracks being sent via an RTCPeerConnection 201 | - e.g. funny hats effect, machine learning model, virtual reality 202 | * [W3C - Media Capture and Streams Extensions](https://w3c.github.io/mediacapture-extensions/) 203 | - semantics: "browser-chooses", "user-chooses" 204 | - deviceId 205 | * [W3C - Identifiers for WebRTC's Statistics API](https://www.w3.org/TR/webrtc-stats/) 206 | - types 207 | + codec 208 | + inbound-rtp 209 | + outbound-rtp 210 | + remote-inbound-rtp 211 | + remote-outbound-rtp 212 | + media-source 213 | + csrc 214 | + peer-connection 215 | + data-channel 216 | + stream 217 | + track 218 | + transceiver 219 | + sender 220 | + receiver 221 | + transport 222 | + sctp-transport 223 | + candidate-pair 224 | + local-candidate 225 | + remote-candidate 226 | + certificate 227 | + ice-server 228 | - RTCP Receiver Report (RR) 229 | - RTCP Extended Report (XR) 230 | - RTCP Sender Report (SR) 231 | - audio `voiceActivityFlag`: Whether the last RTP packet whose frame was delivered to the RTCRtpReceiver's MediaStreamTrack for playout contained voice activity or not based on the presence of the V bit in the extension header, as defined in RFC6464. This is the stats-equivalent of RTCRtpSynchronizationSource. 232 | - RTCQualityLimitationReason 233 | * [W3C - Identity for WebRTC 1.0](https://www.w3.org/TR/webrtc-identity/) 234 | * [W3C - Audio Output Devices API](https://www.w3.org/TR/audio-output/) 235 | * [W3C - Media Capture from DOM Elements](https://www.w3.org/TR/mediacapture-fromelement/) 236 | * [W3C - MediaStream Recording](https://www.w3.org/TR/mediastream-recording/) 237 | * [W3C - WebRTC Priority Control API](https://www.w3.org/TR/webrtc-priority/) 238 | - WebRTC uses the priority and Quality of Service (QoS) framework described in rfc8835 and rfc8837 to provide priority and DSCP marking for packets that will help provide QoS in some networking environments. 239 | * [W3C - IceTransport Extensions for WebRTC](https://w3c.github.io/webrtc-ice/) 240 | * [W3C - WebRTC 1.0 Interoperability Tests Results](https://w3c.github.io/webrtc-interop-reports/webrtc-pc-report.html) 241 | * [RFC 4566 - SDP: Session Description Protocol](https://datatracker.ietf.org/doc/rfc4566/) 242 | - example usage: SIP (Session Initiation Protocol), RTSP (Real Time Streaming Protocol), SAP (Session Announcement Protocol) 243 | - `=`, `` MUST be exactly one case-significant character and `` is structured text whose format depends on `` 244 | - Whitespace MUST NOT be used on either side of the "=" sign 245 | - Session description 246 | + v= (protocol version) 247 | + o= (originator and session identifier) 248 | + s= (session name) 249 | + i=* (session information) 250 | + u=* (URI of description) 251 | + e=* (email address) 252 | + p=* (phone number) 253 | + c=* (connection information -- not required if included in all media) 254 | + b=* (zero or more bandwidth information lines) 255 | + One or more time descriptions ("t=" and "r=" lines; see below) 256 | + z=* (time zone adjustments) 257 | + k=* (encryption key) 258 | + a=* (zero or more session attribute lines) 259 | + Zero or more media descriptions 260 | - Time description 261 | + t= (time the session is active) 262 | + r=* (zero or more repeat times) 263 | - Media description, if present 264 | + m= (media name and transport address) 265 | + i=* (media title) 266 | + c=* (connection information -- optional if included at session level) 267 | + b=* (zero or more bandwidth information lines) 268 | + k=* (encryption key) 269 | + a=* (zero or more media attribute lines) 270 | * [RFC 5245 - Interactive Connectivity Establishment (ICE): A Protocol for Network Address Translator (NAT) Traversal for Offer/Answer Protocols](https://datatracker.ietf.org/doc/rfc5245/) 271 | * [RFC 5285 - A General Mechanism for RTP Header Extensions](https://datatracker.ietf.org/doc/rfc5285/) 272 | * [RFC 6386 - VP8 Data Format and Decoding Guide](https://datatracker.ietf.org/doc/rfc6386/) 273 | * [RFC 6716 - Definition of the Opus Audio Codec](https://datatracker.ietf.org/doc/rfc6716/) 274 | - based on LPC (Linear Predictive Coding) & MDCT (Modified Discrete Cosine Transform) 275 | - in speech, LPC based (e.g. CELP) code low frequencies more efficiently than transform domain techniques (e.g. MDCT) 276 | - The Opus codec includes a number of control parameters that can be changed dynamically during regular operation of the codec, without interrupting the audio stream from the encoder to the decoder. 277 | - These parameters only affect the encoder since any impact they have on the bitstream is signaled in-band such that a decoder can decode any Opus stream without any out-of-band signaling. 278 | - Any Opus implementation can add or modify these control parameters without affecting interoperability. 279 | - Control Parameters 280 | + Bitrate (6 ~ 510 kbit/s) 281 | + Number of Channels (Mono/Stereo) 282 | + Audio Bandwidth 283 | + Frame Duration 284 | + Complexity (CPU complexity v.s. quality/bitrate) 285 | + Packet Loss Resilience 286 | + Forward Error Correction (FEC) 287 | + Constant/Variable Bitrate 288 | + Discontinuous Transmission (DTX) (can reduce the bitrate during silence or background noise) 289 | * [RFC 7478 - Web Real-Time Communication Use Cases and Requirements](https://datatracker.ietf.org/doc/rfc7478/) 290 | * [RFC 7587 - RTP Payload Format for the Opus Speech and Audio Codec](https://datatracker.ietf.org/doc/rfc7587/) 291 | * [RFC 7667 - RTP Topologies](https://datatracker.ietf.org/doc/rfc7667/) 292 | - AVPF (Audio-Visual Profile with Feedback) 293 | - topology: Point-to-Point 294 | + minimal issues for feedback messages 295 | - topology: Point-to-Point with Translator 296 | + SSRC collision detection 297 | - topology: Point-to-Point with Relay 298 | - topology: Transport Translator 299 | + do not modify the RTP stream itself 300 | - topology: Media Translator 301 | - topology: Back to Back 302 | - topology: ASM (Any-Source Multicast) 303 | - topology: SSM (Source-Specific Multicast) 304 | - topology: SSM-RAMS (Rapid Acquisition of Multicast RTP Sessions) 305 | - topology: Mesh 306 | - topology: Point-to-Multicast Transport Translator 307 | - topology: Mixer 308 | - topology: Selective Forwarding Middlebox 309 | - topology: Video-switch-MCU 310 | - topology: RTCP-terminating-MCU 311 | - topology: Split-Terminal 312 | - topology: Asymmetric 313 | - Topologies can be combined and linked to each other using mixers or translators. However, care must be taken in handling the SSRC/CSRC space. 314 | * [RFC 7741 - RTP Payload Format for VP8 Video](https://datatracker.ietf.org/doc/rfc7741/) 315 | * [RFC 7742 - WebRTC Video Processing and Codec Requirements](https://datatracker.ietf.org/doc/rfc7742/) 316 | * [RFC 7874 - WebRTC Audio Codec and Processing Requirements](https://datatracker.ietf.org/doc/rfc7874/) 317 | * [RFC 7875 - Additional WebRTC Audio Codecs for Interoperability](https://datatracker.ietf.org/doc/rfc7875/) 318 | * [RFC 8451 - Considerations for Selecting RTP Control Protocol (RTCP) Extended Report (XR) Metrics for the WebRTC Statistics API](https://datatracker.ietf.org/doc/rfc8451/) 319 | * [RFC 8826 - Security Considerations for WebRTC](https://datatracker.ietf.org/doc/rfc8826/) 320 | * [RFC 8827 - WebRTC Security Architecture](https://datatracker.ietf.org/doc/rfc8827/) 321 | * [RFC 8828 - WebRTC IP Address Handling Requirements](https://datatracker.ietf.org/doc/rfc8828/) 322 | * [RFC 8829 - JavaScript Session Establishment Protocol (JSEP)](https://datatracker.ietf.org/doc/rfc8829/) 323 | - PeerConnection.addTrack 324 | + if the PeerConnection is in the "have-remote-offer" state, the track will be attached to the first compatible transceiver that was created by the most recent call to setRemoteDescription and does not have a local track. 325 | + Otherwise, a new transceiver will be created 326 | - PeerConnection.removeTrack 327 | + The sender's track is cleared, and the sender stops sending. 328 | + Future calls to createOffer will mark the "m=" section associated with the sender as recvonly (if transceiver.direction is sendrecv) or as inactive (if transceiver.direction is sendonly). 329 | - RtpTransceiver 330 | + RtpTransceivers allow the application to control the RTP media associated with one "m=" section. 331 | + Each RtpTransceiver has an RtpSender and an RtpReceiver, which an application can use to control the sending and receiving of RTP media. 332 | + The application may also modify the RtpTransceiver directly, for instance, by stopping it. 333 | + RtpTransceivers can be created explicitly by the application or implicitly by calling setRemoteDescription with an offer that adds new "m=" sections. 334 | - RtpTransceiver.stop 335 | + The stop method stops an RtpTransceiver. 336 | + This will cause future calls to createOffer to generate a zero port for the associated "m=" section. 337 | - Subsequent Offers 338 | + If any RtpTransceiver has been added and there exists an "m=" section with a zero port in the current local description or the current remote description, that "m=" section MUST be recycled by generating an "m=" section for the added RtpTransceiver as if the "m=" section were being added to the session description (including a new MID value) and placing it at the same index as the "m=" section with a zero port. 339 | + If an RtpTransceiver is stopped and is not associated with an "m=" section, an "m=" section MUST NOT be generated for it. 340 | + If an RtpTransceiver has been stopped and is associated with an "m=" section, and the "m=" section is not being recycled as described above, an "m=" section MUST be generated for it with the port set to zero and all "a=msid" lines removed. 341 | 342 | * [RFC 8830 - WebRTC MediaStream Identification in the Session Description Protocol](https://datatracker.ietf.org/doc/rfc8830/) 343 | - grouping mechanism for RTP media streams 344 | - MediaStreamTrack: unidirectional flow of media data (either audio or video, but not both). One MediaStreamTrack can be present in zero, one, or multiple MediaStreams. (source stream) 345 | - MediaStream: assembly of MediaStreamTracks, can be different types. 346 | - Media description: a set of fields starting with an "m=" field and terminated by either the next "m=" field or the end of the session description. 347 | - each RTP stream is distinguished inside an RTP session by its Synchronization Source (SSRC) 348 | - each RTP session is distinguished from all other RTP sessions by being on a different transport association (2 transport assertions if no RTP/RTCP multiplexing) 349 | - if multiple media sources are carried in an RTP session, this is signaled using BUNDLE 350 | - RTP streams are grouped into RTP sessions and also carry a CNAME 351 | - Neither CNAME nor RTP session corresponds to a MediaStream, the association of an RTP stream to MediaStreams need to be explicitly signaled. 352 | - MediaStreams identifier (msid) associates RTP streams that are described in separate media descriptions with the right MediaStreams 353 | - the value of the "msid" attribute consists of an identifier and an optional "appdata" field 354 | - `msid-id [ SP msid-appdata ]` 355 | - There may be multiple "msid" attributes in a single media description. This represents the case where a single MediaStreamTrack is present in multiple MediaStreams. 356 | - Endpoints can update the associations between RTP streams as expressed by "msid" attributes at any time. 357 | * [RFC 8831 - WebRTC Data Channels](https://datatracker.ietf.org/doc/rfc8831/) 358 | - SCTP over DTLS over UDP 359 | - have both Reliable and Unreliable mode 360 | - U-C 6: Renegotiation of the configuration of the PeerConnection 361 | - WebRTC data channel mechanism does not support SCTP multihoming 362 | - in-order or out-of-order 363 | - the sender should disable the Nagle algorithm to minimize the latency 364 | * [RFC 8832 - WebRTC Data Channel Establishment Protocol](https://datatracker.ietf.org/doc/rfc8832/) 365 | - DCEP (Data Channel Establishment Protocol) 366 | * [RFC 8833 - Application-Layer Protocol Negotiation (ALPN) for WebRTC](https://datatracker.ietf.org/doc/rfc8833/) 367 | * [RFC 8834 - Media Transport and Use of RTP in WebRTC](https://datatracker.ietf.org/doc/rfc8834/) 368 | * [RFC 8835 - Transports for WebRTC](https://datatracker.ietf.org/doc/rfc8835/) 369 | * [RFC 8837 - Differentiated Services Code Point (DSCP) Packet Markings for WebRTC QoS](https://datatracker.ietf.org/doc/rfc8837/) 370 | * [RFC 8854 - WebRTC Forward Error Correction Requirements](https://datatracker.ietf.org/doc/rfc8854/) 371 | * [RFC 8865 - T.140 Real-Time Text Conversation over WebRTC Data Channels](https://datatracker.ietf.org/doc/rfc8865/) 372 | * [draft-ietf-rtcweb-sdp-14 - Annotated Example SDP for WebRTC](https://datatracker.ietf.org/doc/draft-ietf-rtcweb-sdp/) 373 | 374 | * [WebRTC-HTTP ingestion protocol (WHIP)](https://datatracker.ietf.org/doc/draft-ietf-wish-whip/) 375 | - HTTP based signaling to create "sendonly" PeerConnection 376 | - HTTP POST to send SDP offer, get SDP answer in response 377 | - HTTP DELETE to teardown session 378 | - Authentication and authorization via Bearer tokens 379 | - Trickle ICE and ICE restart via HTTP PATCH and SDP fragments 380 | - [WHIP and Janus @ IIT-RTC 2021](https://www.slideshare.net/LorenzoMiniero/whip-and-janus-iitrtc-2021) 381 | - [WISH (WebRTC Ingest Signaling over HTTPS) working group](https://datatracker.ietf.org/wg/wish/about/) 382 | 383 | 384 | 385 | Rust Resources 386 | ======================================== 387 | 388 | Books and Docs: 389 | 390 | * [The Rust Programming Language](https://doc.rust-lang.org/book/) (whole book online) 391 | * [Rust by Example](https://doc.rust-lang.org/rust-by-example/) 392 | * [The Cargo Book](https://doc.rust-lang.org/cargo/) (the package manager) 393 | * [Standard Library](https://doc.rust-lang.org/stable/std/index.html) 394 | * [Docs.rs](https://docs.rs) (API docs for whatever crate published in ecosystem) 395 | * [crates.io](https://crates.io) (Rust packages center) 396 | * [rust-analyzer](https://rust-analyzer.github.io) (Language Server Protocol support for the Rust, highly recommended, it provides many info for your editor) 397 | * [Tokio](https://docs.rs/tokio/latest/) (the async runtime) 398 | * [Serde](https://serde.rs) (serialize/deserialize framework) 399 | * [Clap](https://clap.rs) (CLI interface, including env loading) 400 | * [WebRTC.rs](https://github.com/webrtc-rs/webrtc) 401 | 402 | 403 | Also you can build documentation site via command: 404 | 405 | ```sh 406 | cargo doc 407 | ``` 408 | 409 | Open with your default browser 410 | 411 | ```sh 412 | # URL will look like "file:///path/to/project/target/doc/project/index.html" 413 | cargo doc --open 414 | ``` 415 | 416 | 417 | This will have API level overview of what's in the codebase. 418 | And you will get all the API docs from dependencies. 419 | 420 | 421 | 422 | How to build project 423 | ======================================== 424 | 425 | Code checking 426 | ------------------------------ 427 | 428 | This only perform types checking, lifetime checking, ... etc. 429 | But it won't ask compiler to generate binary. 430 | This will be faster than ``cargo build``, 431 | which suit well for development cycle. 432 | 433 | ```sh 434 | cargo check 435 | ``` 436 | 437 | 438 | Development build 439 | ------------------------------ 440 | 441 | ```sh 442 | cargo build 443 | ``` 444 | 445 | 446 | Release build 447 | ------------------------------ 448 | 449 | Release build will take more time than development build. 450 | 451 | ```sh 452 | cargo build --release 453 | ``` 454 | 455 | 456 | Container build 457 | ------------------------------ 458 | 459 | Related setup all live in ``Dockerfile`` 460 | 461 | ```sh 462 | docker build . 463 | ``` 464 | 465 | 466 | Run 467 | ======================================== 468 | 469 | Environment prepare 470 | ------------------------------ 471 | 472 | Environment dependencies: 473 | * [NATS server](https://nats.io) (for distributing media) 474 | * [Redis](https://redis.io) (for sharing room metadata) 475 | 476 | 477 | Run the program 478 | ------------------------------ 479 | 480 | Run with default options: 481 | 482 | ```sh 483 | cargo run 484 | ``` 485 | 486 | 487 | Run with customize options: 488 | 489 | ```sh 490 | cargo run -- ... 491 | ``` 492 | 493 | 494 | Changing log level: 495 | 496 | ```sh 497 | env RUST_LOG=info cargo run -- ... 498 | ``` 499 | 500 | 501 | Checking what arguments (and environment variables) can use: 502 | (if the CLI argument is not present, env variable will be used as fallback) 503 | 504 | ```sh 505 | cargo run -- --help 506 | ``` 507 | ![cargo-cli](https://user-images.githubusercontent.com/2716047/138387972-14e193b0-cde1-47b6-af20-374cbda5a234.png) 508 | 509 | 510 | 511 | Testing 512 | ======================================== 513 | 514 | Doctest 515 | ------------------------------ 516 | 517 | (TODO: not implmented for this repo right now) 518 | 519 | You can write some code samples and assertions in doc comments style. 520 | Those cases will be discovered and run. 521 | Here are some [samples](https://doc.rust-lang.org/rust-by-example/testing/doc_testing.html). 522 | 523 | 524 | Unit Test 525 | ------------------------------ 526 | 527 | (TODO: not implmented for this repo right now) 528 | 529 | You can put your test case into the source code file with ``#[cfg(test)]``. 530 | Those cases will be discovered and run. 531 | Here are some [samples](https://doc.rust-lang.org/rust-by-example/testing/unit_testing.html). 532 | 533 | 534 | Integration Test 535 | ------------------------------ 536 | 537 | (TODO: not implmented for this repo right now) 538 | 539 | The test cases are in the ``tests`` folder. 540 | Here are some [samples](https://doc.rust-lang.org/rust-by-example/testing/integration_testing.html). 541 | 542 | You can run the testing with this command: 543 | 544 | ```sh 545 | cargo test 546 | ``` 547 | 548 | If you need coverage report, you can install [Tarpaulin](https://github.com/xd009642/tarpaulin). 549 | Then run: 550 | 551 | ```sh 552 | # default is showing coverage report in stdout 553 | # "-o Html" will generate a HTML file so that you can have more browsing 554 | cargo tarpaulin -o Html 555 | ``` 556 | 557 | 558 | Fuzz Test 559 | ------------------------------ 560 | 561 | (TODO: not implmented for this repo right now) 562 | 563 | You can add some fuzz testing to uncover some cases that's not included in existing test cases. 564 | With engine like AFL or LLVM libFuzzer. 565 | Here is the [guide for fuzz](https://rust-fuzz.github.io/book/introduction.html). 566 | 567 | 568 | 569 | Benchmark 570 | ======================================== 571 | 572 | (TODO: not implmented for this repo right now) 573 | 574 | It's definitely good idea to write bechmark before you tweak performance. 575 | There is builtin [cargo bench](https://doc.rust-lang.org/cargo/commands/cargo-bench.html) support. 576 | If you want more advanced features, 577 | [Criterion](https://bheisler.github.io/criterion.rs/book/getting_started.html) 578 | is good one with more charting support. 579 | 580 | 581 | 582 | How to update dependencies 583 | ======================================== 584 | 585 | The dependencies are written in ``Cargo.toml``. 586 | And the ``Cargo.lock`` is the version lock for everthing being used. 587 | 588 | If you want to see if there is new version in ecosystem, 589 | you can run: 590 | 591 | ``` 592 | cargo update --dry-run 593 | ``` 594 | 595 | 596 | 597 | CI (Continuous Integration) 598 | ======================================== 599 | 600 | Currently, we are using CircleCI. 601 | The config is at ``.circleci/config.yml``. 602 | 603 | Here is the link point to [CircleCI for this project](https://app.circleci.com/pipelines/github/aioniclabs/webrtc-sfu). 604 | 605 | 606 | 607 | Code Structure 608 | ======================================== 609 | 610 | Files 611 | ------------------------------ 612 | 613 | ```sh 614 | . 615 | ├── .circleci 616 | │ └── config.yml # CircleCI config 617 | ├── site 618 | │ └── index.yml # demo website 619 | ├── Cargo.lock # Rust package dependencies lock 620 | ├── Cargo.toml # Rust package dependencies 621 | ├── Dockerfile # container build setup 622 | ├── README.md 623 | └── src # main code 624 | ├── cli.rs # CLI/env options 625 | ├── helper.rs # small utils 626 | ├── lib.rs # entry point 627 | ├── publisher.rs # WebRTC as media publisher 628 | ├── state.rs # global sharing states across instances 629 | ├── subscriber.rs # WebRTC as media subscriber 630 | └── web.rs # web server 631 | ``` 632 | 633 | Docs site 634 | ------------------------------ 635 | 636 | You can build API documentation site via command: 637 | 638 | ```sh 639 | cargo doc 640 | ``` 641 | 642 | 643 | Launching Flow in Program 644 | ------------------------------ 645 | 646 | When program launches, roughly these steps will happen: 647 | 1. parse CLI args and environment variables 648 | 2. create logger 649 | 3. load SSL certs 650 | 4. create Redis client 651 | 5. create NATS client 652 | 6. run web server 653 | 654 | 655 | Extra Learning 656 | ======================================== 657 | 658 | This project shows that: 659 | 660 | * we can leverage existing WebRTC libraries to build our SFU server 661 | * we can reuse whatever pub/sub pattern message bus for media distribution 662 | * the WebRTC signaling exchange can simplify to 1 HTTP request/response 663 | 664 | 665 | 666 | Future Works 667 | ======================================== 668 | 669 | * Media 670 | - [X] audio codec: Opus 671 | - [X] video codec: VP8 672 | - [X] RTP BUNDLE 673 | - [X] RTCP mux 674 | - [X] Multistream (1 connnetion for multiple video/audio streams pulling) 675 | - [X] Datachannel 676 | - [X] WebRTC Renegotiation 677 | - [ ] audio codec parameters config 678 | - [ ] video codec parameters config 679 | - [ ] ICE restart 680 | - [ ] SVC: VP8-SVC 681 | - [ ] SVC: AV1-SVC 682 | - [ ] Simulcast 683 | - [ ] RTX (retransmission) check 684 | - [ ] FEC (Forward Error Correction) 685 | - [ ] PLI (Picture Loss Indication) control 686 | - [ ] FIR (Full Intra Request) 687 | - [ ] NACK (Negative Acknowledgement) check 688 | - [ ] video codec: H264 689 | - [ ] video codec: VP9 690 | - [ ] video codec: AV1 691 | - [ ] Opus in-band FEC 692 | - [ ] Opus DTX (discontinuous transmission) 693 | - [ ] RTP Header Extensions check (hdrext) 694 | - [ ] Reduced-Size RTCP check (rtcp-rsize) 695 | - [ ] BWE (bandwidth estimation) / Congestion Control (goog-remb & transport-cc) check 696 | - [ ] bandwidth limitation in SDP 697 | - [ ] latency measurement 698 | - [ ] E2E (End to End) Encryption 699 | - [ ] audio mixing 700 | 701 | * Use Cases 702 | - [X] new publisher join, existing subscriber can get new streams 703 | - [X] publisher leave, existing subscriber can know and delete stuffs 704 | - [X] new subscriber join in the middle, can get existing publishers' streams 705 | - [X] publisher leave and rejoin again 706 | - [X] cover all audio room use cases 707 | - [X] screen share (via 1 extra WebRTC connection) 708 | - [X] cover all video room use cases 709 | - [ ] chatting via datachannel 710 | 711 | * Horizontal Scale 712 | - [X] shared state across instances 713 | - [X] Kubernetes readiness API 714 | - [X] Kubernetes liveness API 715 | - [X] Kubernetes preStop API 716 | - [ ] instance killed will cleanup related resource in Redis 717 | 718 | * Stability 719 | - [X] compiler warnings cleanup 720 | - [X] set TTL for all Redis key/value (1 day) 721 | - [X] don't send PUB_JOIN to subscriber if publisher is exactly the subscriber 722 | - [X] don't send RENEGOTIATION if subscriber is ongoing another renegotiation, hold and send later to avoid frontend state issue 723 | - [X] don't change transceivers if subscriber is ongoing renegotiation, hold and change later 724 | - [ ] make sure all Tokio tasks will end when clients leave 725 | - [ ] unwrap usage cleanup 726 | - [ ] WebRTC spec reading 727 | - [ ] more devices test (Windows/MacOS/Linux/Android/iOS with Chrome/Firefox/Safari/Edge) 728 | - [ ] test cases 729 | 730 | * Performance Optimization 731 | - [X] (subscriber) don't create transceiver at first hand when publisher is the same as subscriber 732 | - [X] don't pull streams for subscriber, if the publisher is with same id 733 | - [X] enable compiler LTO 734 | - [X] faster showing on subscribers' site when publisher join 735 | - [X] reduce media count via track cleanup 736 | - [ ] use same WebRTC connection for screen share (media add/remove for same publisher) 737 | - [ ] compile with `RUSTFLAGS="-Z sanitizer=leak"` and test, make sure there is no memory leak 738 | - [ ] WebRTC.rs stack digging 739 | - [ ] guarantee connection without extra TURN? 740 | - [ ] support select codec & rate from client 741 | - [ ] trigger initial ICE candidates collection after start, so we won't have first connection delay 742 | 743 | * Monitor 744 | - [X] Prometheus endpoint for per room publisher/subscriber metrics 745 | - [X] Grafana dashboard for per room publisher/subscriber 746 | - [ ] per room video/audio count metrics 747 | - [ ] use spans info to show on Grafana (by room) 748 | - [ ] use spans info to show on Grafana (by user) 749 | 750 | * Demo Site 751 | - [X] select video resolution (e.g. 720p, 240p) 752 | - [X] publisher can select enable audio/video or not 753 | - [X] video resolution setting 754 | - [X] video framerate setting 755 | 756 | * Misc 757 | - [X] in-cluster API for publishers list 758 | - [X] in-cluster API for subscribers list 759 | - [X] assign public IP from outside to show on the ICE (via set_nat_1to1_ips) 760 | - [X] show selected ICE candidate on demo site 761 | - [X] CORS setting 762 | - [X] split user API and internal setting API 763 | - [ ] force non-trickle on web 764 | - [ ] better TURN servers setup for demo site 765 | - [ ] `SUB/UNSUB ` data channel command 766 | - [ ] data channel protocol ("auto", "manual"), "auto" mode means auto subscribe all publishers, "manual" mode means browser choose which to subscribe 767 | - [ ] `SUB_MODE <0/1>` switch between "auto"/"manual" mode 768 | - [ ] in "manual" mode, server auto push media list when publishers join/leave, so browser can choose 769 | - [ ] dynamically add video/audio in existing publisher (`ADD_MEDIA