diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md deleted file mode 100644 index c9aa7e2bfe..0000000000 --- a/.github/CONTRIBUTING.md +++ /dev/null @@ -1,73 +0,0 @@ -# Contributing to mediasoup - -Thanks for taking the time to contribute to mediasoup! đđ - - -## License - -By contributing to mediasoup, you agree that your contributions will be licensed under its ISC License. - - -## Reporting Bugs - -We primarily use GitHub as an issue tracker. Just open an issue in GitHub if you have encountered a bug in mediasoup. - -If you have questions or doubts about mediasoup or need support, please use the mediasoup Discourse Group instead: - -* https://mediasoup.discourse.group - -If you got a crash in mediasoup, please try to provide a core dump into the issue report: - -* https://mediasoup.org/support/#crashes-in-mediasoup-get-a-core-dump - - -## Pull Request Process - -When creating a Pull Request for mediasoup, ensure that you run the following commands to verify that the code in your PR conforms to the code syntax of the project and does not break existing funtionality: - -* `npm run lint`: Check JavaScript and C++ linting rules. -* `npm run typescript:build`: Compile TypeScript code (under `src/` folder) into JavaScript code (under `lib/` folder). -* `npm run test`: Run JavaScript and C++ test units. - -The full list of `npm` scripts (and `make` tasks) is available in the [doc/Building.md](/doc/Building.md) file. - -Once all these commands succeed, wait for the Travis CI checks to complete and verify they run successfully (otherwise the PR won't be accepted). - - -## Coding Style - -In adition to automatic checks performed by commands above, we also enforce other minor things related to coding style: - -### Comments in JavaScript and C++ - -We use `//` for inline comments in both JavaScript and C++ source files. - -* Comments must start with upercase letter. -* Comments must not exceed 80 columns (split into different lines if necessary). -* Comments must end with a dot. - -Example (good): - -```js -// Calculate foo based on bar value. -const foo = bar / 2; -``` - -Example (bad): - -```js -// calculate foo based on bar value -const foo = bar / 2; -``` - -When adding inline documentation for methods or functions, we use `/** */` syntax. Example: - -```js -/** - * Calculates current score for foo and bar. - */ -function calculateScore(): number -{ - // [...] -} -``` diff --git a/.github/ISSUE_TEMPLATE/Bug_Report.md b/.github/ISSUE_TEMPLATE/Bug_Report.md index 590d6de3eb..1abdb77562 100644 --- a/.github/ISSUE_TEMPLATE/Bug_Report.md +++ b/.github/ISSUE_TEMPLATE/Bug_Report.md @@ -16,7 +16,6 @@ If you got a crash in mediasoup, please try to provide a core dump into the issu https://mediasoup.org/support/#crashes-in-mediasoup-get-a-core-dump - ### Your environment - Operating system: @@ -26,5 +25,4 @@ https://mediasoup.org/support/#crashes-in-mediasoup-get-a-core-dump - mediasoup version: - mediasoup-client version: - ### Issue description diff --git a/.github/ISSUE_TEMPLATE/Support_Question.md b/.github/ISSUE_TEMPLATE/Support_Question.md deleted file mode 100644 index fe989f59fd..0000000000 --- a/.github/ISSUE_TEMPLATE/Support_Question.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -name: đ Support Question -about: If you have any questions use the mediasoup Discourse Group ---- - -**IMPORTANT:** We primarily use GitHub as an issue tracker. Please, use the mediasoup Discourse Group if you have questions or doubts or if you need support about mediasoup and its ecosystem: - -https://mediasoup.discourse.group - -Before asking any questions, please check the mediasoup official documentation: - -https://mediasoup.org/documentation/ diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000000..415576c32d --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,7 @@ +blank_issues_enabled: true +contact_links: + - name: đ Support Question + url: https://mediasoup.discourse.group + about: | + We primarily use GitHub as an issue tracker. Please, use the mediasoup Discourse Group if you have questions or doubts or if you need support about mediasoup and its ecosystem. + Before asking any questions, please check the mediasoup official documentation at https://mediasoup.org/documentation diff --git a/.github/workflows/codeql.yaml b/.github/workflows/codeql.yaml new file mode 100644 index 0000000000..331661b59c --- /dev/null +++ b/.github/workflows/codeql.yaml @@ -0,0 +1,89 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: CodeQL + +on: [pull_request, workflow_dispatch] + +concurrency: + # Cancel a currently running workflow from the same PR, branch or tag when a + # new workflow is triggered. + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + timeout-minutes: 360 + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: ['c-cpp', 'javascript-typescript', 'python'] + # CodeQL supports [ 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'swift' ] + # Use only 'java-kotlin' to analyze code written in Java, Kotlin or both. + # Use only 'javascript-typescript' to analyze code written in JavaScript, + # TypeScript or both. + # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support. + + env: + MEDIASOUP_SKIP_WORKER_PREBUILT_DOWNLOAD: 'true' + MEDIASOUP_LOCAL_DEV: 'true' + MEDIASOUP_BUILDTYPE: 'Release' + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v3 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a + # config file. By default, queries listed here will override any + # specified in a config file. Prefix the list here with "+" to use + # these queries and those in the config file. + # + # Details on CodeQL's query packs refer to: + # https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs + # queries: security-extended,security-and-quality + + # Autobuild attempts to build any compiled languages (C/C++, C#, Go, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + # - name: Autobuild + # uses: github/codeql-action/autobuild@v3 + + # âšī¸ Command-line programs to run using the OS shell. + # đ See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun + + # If the Autobuild fails above, remove it and uncomment the following + # three lines. Modify them (or add more) to build your code if your + # project, please refer to the EXAMPLE below for guidance. + + # - run: | + # echo "Run, Build Application using script" + # ./location_of_script_within_repo/buildscript.sh + + # Use npm ci to build mediasoup Node and worker instead of relying on + # Autobuild. + - name: npm ci + run: npm ci --foreground-scripts + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v3 + with: + category: '/language:${{matrix.language}}' diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml deleted file mode 100644 index 9ec056a646..0000000000 --- a/.github/workflows/codeql.yml +++ /dev/null @@ -1,74 +0,0 @@ -# For most projects, this workflow file will not need changing; you simply need -# to commit it to your repository. -# -# You may wish to alter this file to override the set of languages analyzed, -# or to provide custom queries or build logic. -# -# ******** NOTE ******** -# We have attempted to detect the languages in your repository. Please check -# the `language` matrix defined below to confirm you have the correct set of -# supported CodeQL languages. -# -name: "CodeQL" - -on: - push: - branches: [ "v3" ] - pull_request: - # The branches below must be a subset of the branches above - branches: [ "v3" ] - schedule: - - cron: '37 9 * * 5' - -jobs: - analyze: - name: Analyze - runs-on: ubuntu-latest - permissions: - actions: read - contents: read - security-events: write - - strategy: - fail-fast: false - matrix: - language: [ 'cpp', 'javascript', 'python' ] - # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] - # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support - - steps: - - name: Checkout repository - uses: actions/checkout@v3 - - # Initializes the CodeQL tools for scanning. - - name: Initialize CodeQL - uses: github/codeql-action/init@v2 - with: - languages: ${{ matrix.language }} - # If you wish to specify custom queries, you can do so here or in a config file. - # By default, queries listed here will override any specified in a config file. - # Prefix the list here with "+" to use these queries and those in the config file. - - # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs - # queries: security-extended,security-and-quality - - - # Autobuild attempts to build any compiled languages (C/C++, C#, Go, or Java). - # If this step fails, then you should remove it and run the build manually (see below) - - name: Autobuild - uses: github/codeql-action/autobuild@v2 - - # âšī¸ Command-line programs to run using the OS shell. - # đ See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun - - # If the Autobuild fails above, remove it and uncomment the following three lines. - # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. - - # - run: | - # echo "Run, Build Application using script" - # ./location_of_script_within_repo/buildscript.sh - - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 - with: - category: "/language:${{matrix.language}}" diff --git a/.github/workflows/mediasoup-node.yaml b/.github/workflows/mediasoup-node.yaml index 6757c20211..d9e0e634f6 100644 --- a/.github/workflows/mediasoup-node.yaml +++ b/.github/workflows/mediasoup-node.yaml @@ -1,41 +1,106 @@ name: mediasoup-node -on: [push, pull_request] +on: [pull_request, workflow_dispatch] + +concurrency: + # Cancel a currently running workflow from the same PR, branch or tag when a + # new workflow is triggered. + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true jobs: ci: strategy: matrix: - # Different Node versions on Ubuntu, the latest Node on other platforms - ci: + build: + - os: ubuntu-20.04 + node: 18 + cc: gcc + cxx: g++ - os: ubuntu-22.04 - node: 16 + node: 20 + cc: gcc + cxx: g++ - os: ubuntu-22.04 + node: 22 + cc: gcc + cxx: g++ + - os: ubuntu-24.04 + node: 22 + cc: gcc + cxx: g++ + meson_args: '-Db_sanitize=address' + - os: ubuntu-24.04 + node: 22 + cc: clang + cxx: clang++ + meson_args: '-Db_sanitize=undefined' + - os: ubuntu-24.04 + node: 22 + cc: gcc + cxx: g++ + meson_args: '-Db_sanitize=thread' + - os: macos-13 node: 18 - - os: macos-12 - node: 18 + cc: clang + cxx: clang++ + - os: macos-14 + node: 20 + cc: clang + cxx: clang++ + - os: macos-15 + node: 22 + cc: clang + cxx: clang++ - os: windows-2022 - node: 18 - runs-on: ${{ matrix.ci.os }} + node: 20 + cc: cl + cxx: cl + - os: windows-2022 + node: 22 + cc: cl + cxx: cl + build-type: + - Release + - Debug + + runs-on: ${{ matrix.build.os }} + + env: + CC: ${{ matrix.build.cc }} + CXX: ${{ matrix.build.cxx }} + MEDIASOUP_SKIP_WORKER_PREBUILT_DOWNLOAD: 'true' + MEDIASOUP_LOCAL_DEV: 'true' + MEDIASOUP_BUILDTYPE: ${{ matrix.build-type }} + MESON_ARGS: ${{ matrix.build.meson_args }} steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Node.js - uses: actions/setup-node@v2 + uses: actions/setup-node@v4 with: - node-version: ${{ matrix.ci.node }} + node-version: ${{ matrix.build.node }} - name: Configure cache - uses: actions/cache@v2 + uses: actions/cache@v3 with: path: | ~/.npm - key: ${{ matrix.ci.os }}-node-${{ hashFiles('**/package.json') }} + key: ${{ matrix.build.os }}-node-${{ hashFiles('**/package.json') }} restore-keys: | - ${{ matrix.ci.os }}-node- + ${{ matrix.build.os }}-node- + + - name: npm ci + run: npm ci --foreground-scripts + env: + # Disable leak detection because it's detected by the tool flatc uses + # to build. + ASAN_OPTIONS: 'detect_leaks=0' + + - name: npm run lint:node + run: npm run lint:node - - run: npm ci - - run: npm run lint:node - - run: npm run test:node + - name: npm run test:node + run: npm run test:node diff --git a/.github/workflows/mediasoup-rust.yaml b/.github/workflows/mediasoup-rust.yaml index 1cf1584694..cf0db637d5 100644 --- a/.github/workflows/mediasoup-rust.yaml +++ b/.github/workflows/mediasoup-rust.yaml @@ -1,6 +1,12 @@ name: mediasoup-rust -on: [push, pull_request] +on: [pull_request, workflow_dispatch] + +concurrency: + # Cancel a currently running workflow from the same PR, branch or tag when a + # new workflow is triggered. + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true env: CARGO_TERM_COLOR: always @@ -9,42 +15,47 @@ jobs: ci: strategy: matrix: - os: - - ubuntu-20.04 - - ubuntu-22.04 - - macos-12 - - windows-2022 + build: + - os: ubuntu-20.04 + - os: ubuntu-22.04 + - os: ubuntu-24.04 + - os: macos-13 + - os: macos-14 + - os: macos-15 + - os: windows-2022 + + runs-on: ${{ matrix.build.os }} - runs-on: ${{ matrix.os }} + env: + KEEP_BUILD_ARTIFACTS: '1' steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Configure cache - uses: actions/cache@v2 + uses: actions/cache@v3 with: path: | ~/.cargo/registry ~/.cargo/git - key: ${{ matrix.os }}-cargo-${{ hashFiles('**/Cargo.toml') }} + key: ${{ matrix.build.os }}-cargo-${{ hashFiles('**/Cargo.toml') }} - name: cargo fmt - uses: actions-rs/cargo@v1 - with: - command: fmt - args: --all -- --check + run: cargo fmt --all -- --check - name: cargo clippy - uses: actions-rs/cargo@v1 - with: - command: clippy - args: --all-targets -- -D warnings + run: cargo clippy --all-targets -- -D warnings + # NOTE: In Windows this will build and test libmediasoupworker in release + # mode twice since build.rs doesn't allow debug mode on Windows. - name: cargo test - uses: actions-rs/cargo@v1 + run: | + cargo test --verbose + cargo test --release --verbose + + - name: cargo doc + run: cargo doc --locked --all --no-deps --lib env: - KEEP_BUILD_ARTIFACTS: '1' - with: - command: test - args: --verbose + DOCS_RS: '1' + RUSTDOCFLAGS: '-D rustdoc::broken-intra-doc-links -D rustdoc::private_intra_doc_links' diff --git a/.github/workflows/mediasoup-worker-fuzzer.yaml b/.github/workflows/mediasoup-worker-fuzzer.yaml new file mode 100644 index 0000000000..c748ce6e67 --- /dev/null +++ b/.github/workflows/mediasoup-worker-fuzzer.yaml @@ -0,0 +1,53 @@ +name: mediasoup-worker-fuzzer + +on: [pull_request, workflow_dispatch] + +concurrency: + # Cancel a currently running workflow from the same PR, branch or tag when a + # new workflow is triggered. + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + ci: + strategy: + matrix: + build: + - os: ubuntu-24.04 + cc: clang + cxx: clang++ + pip-break-system-packages: true + build-type: + - Release + - Debug + + runs-on: ${{ matrix.build.os }} + + env: + CC: ${{ matrix.build.cc }} + CXX: ${{ matrix.build.cxx }} + MEDIASOUP_SKIP_WORKER_PREBUILT_DOWNLOAD: 'true' + MEDIASOUP_LOCAL_DEV: 'true' + MEDIASOUP_BUILDTYPE: ${{ matrix.build-type }} + + steps: + - name: Checkout + uses: actions/checkout@v4 + + # We need to install pip invoke manually. + - if: ${{ !matrix.build.pip-break-system-packages }} + name: pip3 install invoke + run: pip3 install invoke + + # In modern OSs we need to run pip with this option. + - if: ${{ matrix.build.pip-break-system-packages }} + name: pip3 install --break-system-packages invoke + run: pip3 install --break-system-packages invoke + + # Build the mediasoup-worker-fuzzer binary (which uses libFuzzer). + - name: invoke -r worker fuzzer + run: invoke -r worker fuzzer + + # Run mediasoup-worker-fuzzer for 5 minutes. + - name: run-fuzzer.sh 300 + run: cd worker && ./scripts/run-fuzzer.sh 300 diff --git a/.github/workflows/mediasoup-worker-prebuild.yaml b/.github/workflows/mediasoup-worker-prebuild.yaml new file mode 100644 index 0000000000..49d260a811 --- /dev/null +++ b/.github/workflows/mediasoup-worker-prebuild.yaml @@ -0,0 +1,75 @@ +name: mediasoup-worker-prebuild + +# Only trigger on GitHub releases. +on: + release: + types: [published] + +jobs: + ci: + strategy: + matrix: + build: + # Worker prebuild for Linux with kernel version 5 Ubuntu (20.04). + # Let's use an old version of Ubuntu (20.04) that builds the + # mediasoup-worker binary using an old version of GLib, so it will work + # on Linux hosts running more modern GLib versions. + # See https://github.com/versatica/mediasoup/issues/1089. + - os: ubuntu-20.04 + cc: gcc + cxx: g++ + # Worker prebuild for Linux with kernel version 6 Ubuntu (22.04). + # Let's not use Ubutu 24.04 to avoid same potential problem as described + # above. + - os: ubuntu-22.04 + cc: gcc + cxx: g++ + - os: macos-13 + cc: clang + cxx: clang++ + - os: macos-14 + cc: clang + cxx: clang++ + - os: macos-15 + cc: clang + cxx: clang++ + - os: windows-2022 + cc: cl + cxx: cl + node: + - 22 + + runs-on: ${{ matrix.build.os }} + + env: + CC: ${{ matrix.build.cc }} + CXX: ${{ matrix.build.cxx }} + MEDIASOUP_SKIP_WORKER_PREBUILT_DOWNLOAD: 'true' + MEDIASOUP_LOCAL_DEV: 'true' + MEDIASOUP_BUILDTYPE: 'Release' + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Node.js + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node }} + + # We need to install some NPM production deps for npm-scripts.mjs to + # work. + - name: npm ci --ignore-scripts + run: npm ci --ignore-scripts --omit=dev --foreground-scripts + + - name: npm run worker:build + run: npm run worker:build + + # Publish prebuild binaries on tag. + - name: npm run worker:prebuild + run: npm run worker:prebuild + + - name: Upload mediasoup-worker prebuilt binary + uses: softprops/action-gh-release@v1 + with: + files: worker/prebuild/mediasoup-worker-*.tgz diff --git a/.github/workflows/mediasoup-worker.yaml b/.github/workflows/mediasoup-worker.yaml index c879093f62..78c7ddde6d 100644 --- a/.github/workflows/mediasoup-worker.yaml +++ b/.github/workflows/mediasoup-worker.yaml @@ -1,68 +1,121 @@ name: mediasoup-worker -on: [push, pull_request] +on: [pull_request, workflow_dispatch] + +concurrency: + # Cancel a currently running workflow from the same PR, branch or tag when a + # new workflow is triggered. + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true jobs: ci: strategy: + # Here we want to see all errors, not just the first one. fail-fast: false matrix: build: - - os: ubuntu-18.04 - cc: gcc - cxx: g++ - - os: ubuntu-18.04 - cc: clang - cxx: clang++ - os: ubuntu-20.04 cc: gcc cxx: g++ + # Workaround for this issue in Ubunt 20.04: + # https://github.com/versatica/mediasoup/actions/runs/9992113733/job/27616379442?pr=1427 + workaround-ubuntu-20-04: true + run-test-asan-address: true + # Skip test-asan-undefined in Linux with GCC due to a bug in GCC + # that affects abseil-cpp: + # https://github.com/abseil/abseil-cpp/issues/1634 + run-test-asan-undefined: false + run-test-asan-thread: true - os: ubuntu-20.04 cc: clang cxx: clang++ + workaround-ubuntu-20-04: true + run-test-asan-address: true + run-test-asan-undefined: true + run-test-asan-thread: true - os: ubuntu-22.04 cc: gcc cxx: g++ + run-test-asan-address: true + run-test-asan-undefined: false + run-test-asan-thread: true - os: ubuntu-22.04 cc: clang cxx: clang++ - - os: macos-12 + run-test-asan-address: true + run-test-asan-undefined: true + run-test-asan-thread: true + - os: ubuntu-24.04 cc: gcc cxx: g++ - - os: macos-12 + pip-break-system-packages: true + run-test-asan-address: true + run-test-asan-undefined: false + run-test-asan-thread: true + - os: ubuntu-24.04 + cc: clang + cxx: clang++ + pip-break-system-packages: true + run-test-asan-address: true + run-test-asan-undefined: true + run-test-asan-thread: true + - os: macos-13 + cc: gcc + cxx: g++ + pip-break-system-packages: true + # Address Sanitizer does not work on MacOS. + run-test-asan-address: false + run-test-asan-undefined: false + run-test-asan-thread: false + - os: macos-14 + cc: clang + cxx: clang++ + pip-break-system-packages: true + run-test-asan-address: false + run-test-asan-undefined: false + run-test-asan-thread: false + - os: macos-15 cc: clang cxx: clang++ + pip-break-system-packages: true + run-test-asan-address: false + run-test-asan-undefined: false + run-test-asan-thread: false - os: windows-2022 cc: cl cxx: cl + # Address Sanitizer does not work on Windows. + run-test-asan-address: false + run-test-asan-undefined: false + run-test-asan-thread: false # A single Node.js version should be fine for C++. node: - - 16 + - 22 + build-type: + - Release + - Debug runs-on: ${{ matrix.build.os }} env: CC: ${{ matrix.build.cc }} CXX: ${{ matrix.build.cxx }} + MEDIASOUP_SKIP_WORKER_PREBUILT_DOWNLOAD: 'true' + MEDIASOUP_LOCAL_DEV: 'false' + MEDIASOUP_BUILDTYPE: ${{ matrix.build-type }} steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Node.js - uses: actions/setup-node@v2 + uses: actions/setup-node@v4 with: node-version: ${{ matrix.node }} - - run: npm run install-clang-tools - # TODO: Maybe fix this one day. - if: runner.os != 'Windows' - - run: npm run lint:worker - # TODO: Maybe fix this one day. - if: runner.os != 'Windows' - - name: Configure cache - uses: actions/cache@v2 + uses: actions/cache@v3 with: path: | ~/.npm @@ -70,6 +123,51 @@ jobs: restore-keys: | ${{ matrix.build.os }}-node-${{matrix.build.cc}}- - - run: npm run worker:build - - run: npm run test:worker - if: runner.os != 'Windows' + # We need to install pip invoke manually. + - if: ${{ !matrix.build.pip-break-system-packages }} + name: pip3 install invoke + run: pip3 install invoke + + # In modern OSs we need to run pip with this option. + - if: ${{ matrix.build.pip-break-system-packages }} + name: pip3 install --break-system-packages invoke + run: pip3 install --break-system-packages invoke + + # We need to install npm deps of worker/scripts/package.json. + - if: runner.os != 'Windows' + name: npm ci --prefix worker/scripts + run: npm ci --prefix worker/scripts --foreground-scripts + + # Workaround for this issue in Ubuntu 20.04: + # https://github.com/versatica/mediasoup/actions/runs/9992113733/job/27616379442?pr=1427 + - if: ${{ matrix.build.workaround-ubuntu-20-04 }} + name: workaround for Ubuntu 20.04 + run: sudo apt install -y libgcc-10-dev && sudo ln -s /usr/lib/gcc/x86_64-linux-gnu/10/libtsan_preinit.o /usr/lib/libtsan_preinit.o + + # NOTE: Maybe make it work on Windows someday. + - if: runner.os != 'Windows' + name: invoke -r worker lint + run: invoke -r worker lint + + - name: invoke -r worker mediasoup-worker + run: invoke -r worker mediasoup-worker + + # NOTE: Maybe make it work on Windows someday. + - if: runner.os != 'Windows' + name: invoke -r worker test + run: invoke -r worker test + + # Let's clean everything before rebuilding worker tests with ASAN. + - if: ${{ matrix.build.run-test-asan-address }} + name: invoke -r worker test-asan-address + run: invoke -r worker clean-all && invoke -r worker test-asan-address + + # Let's clean everything before rebuilding worker tests with ASAN. + - if: ${{ matrix.build.run-test-asan-undefined }} + name: invoke -r worker test-asan-undefined + run: invoke -r worker clean-all && invoke -r worker test-asan-undefined + + # Let's clean everything before rebuilding worker tests with ASAN. + - if: ${{ matrix.build.run-test-asan-thread }} + name: invoke -r worker test-asan-thread + run: invoke -r worker clean-all && invoke -r worker test-asan-thread diff --git a/.github/workflows/pull-request-stats.yml b/.github/workflows/pull-request-stats.yml deleted file mode 100644 index bbc6a41f3a..0000000000 --- a/.github/workflows/pull-request-stats.yml +++ /dev/null @@ -1,20 +0,0 @@ -name: Pull Request Stats - -on: - pull_request: - types: [opened] - schedule: - - cron: "0 13 * * 1" # Run it every Monday at 1PM UTC. - -jobs: - stats: - runs-on: ubuntu-latest - steps: - - name: Run pull request stats - uses: flowwer-dev/pull-request-stats@master - with: - period: 365 - charts: true - disable-links: false - sort-by: 'REVIEWS' - telemetry: false diff --git a/.gitignore b/.gitignore index abfd211219..e2f65fca29 100644 --- a/.gitignore +++ b/.gitignore @@ -1,24 +1,30 @@ ## Meson. +/worker/out /worker/subprojects/* !/worker/subprojects/*.wrap +!/worker/subprojects/.clang-tidy ## Node. /node_modules /node/lib +# flatc generated files. +/node/src/fbs ## Rust. -/Cargo.lock /rust/examples-frontend/*/node_modules /rust/examples-frontend/*/package-lock.json /target ## Worker. -/worker/out /worker/scripts/node_modules -# Vistual Studio generated Stuff. +# Flatc generated files. +/worker/include/FBS +/worker/prebuild +# Python invoke. +/worker/pip_invoke +# Build artifacts. /worker/**/Debug /worker/**/Release -/worker/.vs # clang-fuzzer stuff is too big. /worker/deps/clang-fuzzer # Ignore all fuzzer generated test inputs. @@ -28,11 +34,5 @@ ## Others. /coverage /.cache -/NO_GIT -*.swp -*.swo -.DS_Store -# Vistual Studio Code stuff. -/.vscode -# JetBrains IDE stuff. -/.idea +# Packaged module. +*.tgz diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 0000000000..a824e54211 --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,10 @@ +{ + "useTabs": true, + "tabWidth": 2, + "arrowParens": "avoid", + "bracketSpacing": true, + "semi": true, + "singleQuote": true, + "trailingComma": "es5", + "endOfLine": "auto" +} diff --git a/CHANGELOG.md b/CHANGELOG.md index 80818464cc..e549f4b45e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,1517 +1,1603 @@ # Changelog +### NEXT -### 3.11.8 +### 3.15.2 -* `SimulcastConsumer::GetDesiredBitrate()`: Choose the highest bitrate among all Producer streams ([PR #992](https://github.com/versatica/mediasoup/pull/992)). -* `SimulcastConsumer`: Fix frozen video when syncing keyframe is discarded due to too high RTP timestamp extra offset needed ([PR #999](https://github.com/versatica/mediasoup/pull/999), thanks to @satoren for properly reporting the issue and helping with the solution). -* Update NPM deps. +- `Worker`: Fix crash when using colliding `portRange` values in different transports ([PR #1469](https://github.com/versatica/mediasoup/pull/1469)). +### 3.15.1 -### 3.11.7 +- Expose `extras` namespace which exports `EnhancedEventEmitter` and `enhancedOnce()` for now ([PR #1464](https://github.com/versatica/mediasoup/pull/1464)). -* libwebrtc: Fix crash due to invalid `arrival_time` value ([PR #985](https://github.com/versatica/mediasoup/pull/985) by @ggarber). -* libwebrtc: Replace `MS_ASSERT()` with `MS_ERROR()` ([PR #988](https://github.com/versatica/mediasoup/pull/988)). -* Update NPM deps. +### 3.15.0 +- Node: Add TypeScript interfaces for all exported classes ([PR #1463](https://github.com/versatica/mediasoup/pull/1463)). +- Node: Add new `transport.type` getter than returns `'webrtc' | 'plain' | 'pipe' | 'direct'` ([PR #1463](https://github.com/versatica/mediasoup/pull/1463)). +- Node: Add new `rtpObserver.type` getter than returns `'activespeaker' | 'audiolevel'` ([PR #1463](https://github.com/versatica/mediasoup/pull/1463)). -### 3.11.6 +### 3.14.16 -* Fix wrong `PictureID` rolling over in VP9 and VP8 ([PR #984](https://github.com/versatica/mediasoup/pull/984) by @jcague). -* Update NPM deps. +- `SimulcastConsumer`: Fix cannot switch layers if initial `tsReferenceSpatialLayer disappears` disappears ([PR #1459](https://github.com/versatica/mediasoup/pull/1459) by @Lynnworld). +### 3.14.15 -### 3.11.5 +- Update worker abseil-cpp dependency to 20240722.0 LTS (fixes compilation for FreeBSD systems) ([PR #1457](https://github.com/versatica/mediasoup/pull/1457), credits to @garrettboone). -* Require Node.js >= 16 ([PR #973](https://github.com/versatica/mediasoup/pull/973)). -* Fix wrong `Consumer` bandwidth estimation under `Producer` packet loss ([PR #962](https://github.com/versatica/mediasoup/pull/962) by @ggarber). -* Update NPM deps. +### 3.14.14 +- Sign self generated DTLS certificate with SHA256 ([PR #1450](https://github.com/versatica/mediasoup/pull/1450)). +- Node: Fix `mediasoup.types` exported types are empty ([PR #1453](https://github.com/versatica/mediasoup/pull/1453)). -### 3.11.4 +### 3.14.13 -* Node: Migrate tests to TypeScript ([PR #958](https://github.com/versatica/mediasoup/pull/958)). -* Node: Remove compiled JavaScript from repository and compile TypeScript code on NPM `prepare` script on demand when installed via git ([PR #954](https://github.com/versatica/mediasoup/pull/954)). -* `Worker`: Add `RTC::Shared` singleton for RTC entities ([PR #953](https://github.com/versatica/mediasoup/pull/953)). -* Update OpenSSL to 3.0.7. -* Update NPM deps. +- Node: Fix regression in exported `mediasoup.types` (classes are now exported as classes instead of types). +### 3.14.12 -### 3.11.3 +- `Worker`: Fix `io_uring` support detection ([PR #1445](https://github.com/versatica/mediasoup/pull/1445)). +- Mitigate libsrtp wraparound with loss decryption failure ([PR #1438](https://github.com/versatica/mediasoup/pull/1438)). +- Node: New `setLogEventListeners()` utility to get log events ([PR #1448](https://github.com/versatica/mediasoup/pull/1448)). -* `ChannelMessageHandlers`: Make `RegisterHandler()` not remove the existing handler if another one with same `id` is given ([PR #952](https://github.com/versatica/mediasoup/pull/952)). +### 3.14.11 +- `Worker`: Fix `disableLiburing` option in `WorkerSettings` ([PR #1444](https://github.com/versatica/mediasoup/pull/1444)). -### 3.11.2 +### 3.14.10 -* Fix installation issue in Linux due to a bug in ninja latest version 1.11.1 ([PR #948](https://github.com/versatica/mediasoup/pull/948)). +- CI: Support Node 22 ([PR #1434](https://github.com/versatica/mediasoup/pull/1434)). +- Update ESLint to version 9 ([PR #1435](https://github.com/versatica/mediasoup/pull/1435)). +- `Worker`: Add `disableLiburing` boolean option (`false` by default) to disable `io_uring` even if it's supported by the prebuilt `mediasoup-worker` and by current host ([PR #1442](https://github.com/versatica/mediasoup/pull/1442)). +### 3.14.9 -### 3.11.1 +- Worker: Test, fix buffer overflow ([PR #1419](https://github.com/versatica/mediasoup/pull/1419)). +- Bump up Meson from 1.3.0 to 1.5.0 ([PR #1424](https://github.com/versatica/mediasoup/pull/1424)). +- Node: Export new `WorkerObserver`, `ProducerObserver`, etc. TypeScript types ([PR #1430](https://github.com/versatica/mediasoup/pull/1430)). +- Fix frozen video in simulcast due to wrong dropping of padding only packets ([PR #1431](https://github.com/versatica/mediasoup/pull/1431), thanks to @quanli168). -* `ActiveSpeakerObserver`: Revert 'dominantspeaker' event changes in [PR #941](https://github.com/versatica/mediasoup/pull/941) to avoid breaking changes ([PR #947](https://github.com/versatica/mediasoup/pull/947)). +### 3.14.8 +- Add support for 'playout-delay' RTP extension ([PR #1412](https://github.com/versatica/mediasoup/pull/1412) by @DavidNegro). -### 3.11.0 +### 3.14.7 -* `Transport`: Remove duplicate call to method ([PR #931](https://github.com/versatica/mediasoup/pull/931)). -* RTCP: Adjust maximum compound packet size ([PR #934](https://github.com/versatica/mediasoup/pull/934)). -* `DataConsumer`: Fix `bufferedAmount` type to be a number again ([PR #936](https://github.com/versatica/mediasoup/pull/936)). -* `ActiveSpeakerObserver`: Fix 'dominantspeaker' event by having a single `Producer` as argument rather than an array with a single `Producer` into it ([PR #941](https://github.com/versatica/mediasoup/pull/941)). -* `ActiveSpeakerObserver`: Fix memory leak ([PR #942](https://github.com/versatica/mediasoup/pull/942)). -* Fix some libwebrtc issues ([PR #944](https://github.com/versatica/mediasoup/pull/944)). -* Tests: Normalize hexadecimal data representation ([PR #945](https://github.com/versatica/mediasoup/pull/945)). -* Update NPM deps. -* `SctpAssociation`: Fix memory violation ([PR #943](https://github.com/versatica/mediasoup/pull/943)). +- `SimulcastConsumer`: Fix increase layer when current layer has not receive SR ([PR #1098](https://github.com/versatica/mediasoup/pull/1098) by @penguinol). +- Ignore RTP packets with empty payload ([PR #1403](https://github.com/versatica/mediasoup/pull/1403), credits to @ggarber). +### 3.14.6 -### 3.10.12 +- Worker: Fix potential double free when ICE consent check fails ([PR #1393](https://github.com/versatica/mediasoup/pull/1393)). -* Fix worker crash due to `std::out_of_range` exception ([PR #933](https://github.com/versatica/mediasoup/pull/933)). -* Update NPM deps. +### 3.14.5 +- Worker: Fix memory leak when using `WebRtcServer` with TCP enabled ([PR #1389](https://github.com/versatica/mediasoup/pull/1389)). +- Worker: Fix crash when closing `WebRtcServer` with active `WebRtcTransports` ([PR #1390](https://github.com/versatica/mediasoup/pull/1390)). -### 3.10.11 +### 3.14.4 -* RTCP: Fix trailing space needed by `srtp_protect_rtcp()` ([PR #929](https://github.com/versatica/mediasoup/pull/929)). +- Worker: Fix crash. `RtcpFeedback` parameter is optional ([PR #1387](https://github.com/versatica/mediasoup/pull/1387), credits to @Lynnworld). +### 3.14.3 -### 3.10.10 +- Worker: Fix possible value overflow in `FeedbackRtpTransport.cpp` ([PR #1386](https://github.com/versatica/mediasoup/pull/1386), credits to @Lynnworld). -* Fix the JSON serialization for the payload channel `rtp` event ([PR #926](https://github.com/versatica/mediasoup/pull/926) by @mhammo). -* Update NPM deps. +### 3.14.2 +- Update worker subprojects ([PR #1376](https://github.com/versatica/mediasoup/pull/1376)). +- OPUS: Fix DTX detection ([PR #1357](https://github.com/versatica/mediasoup/pull/1357)). +- Worker: Fix sending callback leaks ([PR #1383](https://github.com/versatica/mediasoup/pull/1383), credits to @Lynnworld). -### 3.10.9 +### 3.14.1 -* RTCP enhancements ([PR #914](https://github.com/versatica/mediasoup/pull/914)). +- Node: Bring transport `rtpPacketLossReceived` and `rtpPacketLossSent` stats back ([PR #1371](https://github.com/versatica/mediasoup/pull/1371)). +### 3.14.0 -### 3.10.8 +- `TransportListenInfo`: Add `portRange` (deprecate worker port range) ([PR #1365](https://github.com/versatica/mediasoup/pull/1365)). +- Require Node.js >= 18 ([PR #1365](https://github.com/versatica/mediasoup/pull/1365)). -* `Consumer`: use a bitset instead of a set for supported payload types ([PR #919](https://github.com/versatica/mediasoup/pull/919)). -* RtpPacket: optimize UpdateMid() ([PR #920](https://github.com/versatica/mediasoup/pull/920)). -* Little optimizations and modernization ([PR #916](https://github.com/versatica/mediasoup/pull/916)). -* Fix SIGSEGV at `RTC::WebRtcTransport::OnIceServerTupleRemoved()` ([PR #915](https://github.com/versatica/mediasoup/pull/915), credits to @ybybwdwd). -* `WebRtcServer`: Make `port` optional (if not given, a random available port from the `Worker` port range is used) ([PR #908](https://github.com/versatica/mediasoup/pull/908) by @satoren). +### 3.13.24 +- Node: Fix missing `bitrateByLayer` field in stats of `RtpRecvStream` in Node ([PR #1349](https://github.com/versatica/mediasoup/pull/1349)). +- Update worker dependency libuv to 1.48.0. +- Update worker FlatBuffers to 24.3.6-1 (fix cannot set temporal layer 0) ([PR #1348](https://github.com/versatica/mediasoup/pull/1348)). -### 3.10.7 +### 3.13.23 -* Forward `abs-capture-time` RTP extension also for audio packets ([PR #911](https://github.com/versatica/mediasoup/pull/911)). -* Update NPM deps. +- Fix DTLS packets do not honor configured DTLS MTU (attempt 3) ([PR #1345](https://github.com/versatica/mediasoup/pull/1345)). +### 3.13.22 -### 3.10.6 +- Fix wrong publication of mediasoup NPM 3.13.21. + +### 3.13.21 + +- Revert ([PR #1156](https://github.com/versatica/mediasoup/pull/1156)) "Make DTLS fragment stay within MTU size range" because it causes a memory leak ([PR #1342](https://github.com/versatica/mediasoup/pull/1342)). + +### 3.13.20 + +- Add server side ICE consent checks to detect silent WebRTC disconnections ([PR #1332](https://github.com/versatica/mediasoup/pull/1332)). +- Fix regression (crash) in transport-cc feedback generation ([PR #1339](https://github.com/versatica/mediasoup/pull/1339)). + +### 3.13.19 + +- Node: Fix `router.createWebRtcTransport()` with `listenIps` ([PR #1330](https://github.com/versatica/mediasoup/pull/1330)). + +### 3.13.18 + +- Make transport-cc feedback work similarly to libwebrtc ([PR #1088](https://github.com/versatica/mediasoup/pull/1088) by @penguinol). +- `TransportListenInfo`: "announced ip" can also be a hostname ([PR #1322](https://github.com/versatica/mediasoup/pull/1322)). +- `TransportListenInfo`: Rename "announced ip" to "announced address" ([PR #1324](https://github.com/versatica/mediasoup/pull/1324)). +- CI: Add `macos-14`. + +### 3.13.17 + +- Fix prebuilt worker download ([PR #1319](https://github.com/versatica/mediasoup/pull/1319) by @brynrichards). +- libsrtp: Update to v3.0-alpha version in our fork. + +### 3.13.16 + +- Node: Add new `worker.on('subprocessclose')` event ([PR #1307](https://github.com/versatica/mediasoup/pull/1307)). + +### 3.13.15 + +- Add worker prebuild binary for Linux kernel 6 ([PR #1300](https://github.com/versatica/mediasoup/pull/1300)). + +### 3.13.14 + +- Avoid modification of user input data ([PR #1285](https://github.com/versatica/mediasoup/pull/1285)). +- `TransportListenInfo`: Add transport socket flags ([PR #1291](https://github.com/versatica/mediasoup/pull/1291)). + - Note that `flags.ipv6Only` is `false` by default. +- `TransportListenInfo`: Ignore given socket flags if not suitable for given IP family or transport ([PR #1294](https://github.com/versatica/mediasoup/pull/1294)). +- Meson: Remove `-Db_pie=true -Db_staticpic=true` args ([PR #1293](https://github.com/versatica/mediasoup/pull/1293)). +- Add RTCP Sender Report trace event ([PR #1267](https://github.com/versatica/mediasoup/pull/1267) by @GithubUser8080). + +### 3.13.13 + +- Worker: Do not use references for async callbacks ([PR #1274](https://github.com/versatica/mediasoup/pull/1274)). +- liburing: Enable zero copy ([PR #1273](https://github.com/versatica/mediasoup/pull/1273)). +- Fix build on musl based systems (such as Alpine Linux) ([PR #1279](https://github.com/versatica/mediasoup/pull/1279)). + +### 3.13.12 + +- Worker: Disable `RtcLogger` usage if not enabled ([PR #1264](https://github.com/versatica/mediasoup/pull/1264)). +- npm installation: Don't require Python if valid worker prebuilt binary is fetched ([PR #1265](https://github.com/versatica/mediasoup/pull/1265)). +- Update h264-profile-level-id NPM dependency to 1.1.0. + +### 3.13.11 + +- liburing: Avoid extra memcpy on RTP ([PR #1258](https://github.com/versatica/mediasoup/pull/1258)). +- libsrtp: Use our own fork with performance gain ([PR #1260](https://github.com/versatica/mediasoup/pull/1260)). +- `DataConsumer`: Add `addSubchannel()` and `removeSubchannel()` methods ([PR #1263](https://github.com/versatica/mediasoup/pull/1263)). +- Fix Rust `DataConsumer` ([PR #1262](https://github.com/versatica/mediasoup/pull/1262)). + +### 3.13.10 + +- `tasks.py`: Always include `--no-user` in `pip install` commands to avoid the "can not combine --user and --target" error in Windows ([PR #1257](https://github.com/versatica/mediasoup/pull/1257)). + +### 3.13.9 + +- Update worker liburing dependency to 2.4-2 ([PR #1254](https://github.com/versatica/mediasoup/pull/1254)). +- liburing: Enable by default ([PR 1255](https://github.com/versatica/mediasoup/pull/1255)). + +### 3.13.8 + +- liburing: Enable liburing usage for SCTP data delivery ([PR 1249](https://github.com/versatica/mediasoup/pull/1249)). +- liburing: Disable by default ([PR 1253](https://github.com/versatica/mediasoup/pull/1253)). + +### 3.13.7 + +- Update worker dependencies ([PR #1201](https://github.com/versatica/mediasoup/pull/1201)): + - abseil-cpp 20230802.0-2 + - libuv 1.47.0-1 + - OpenSSL 3.0.8-2 + - usrsctp snapshot ebb18adac6501bad4501b1f6dccb67a1c85cc299 +- Enable `liburing` usage for Linux (kernel versions >= 6) ([PR #1218](https://github.com/versatica/mediasoup/pull/1218)). + +### 3.13.6 + +- Replace make + Makefile with Python Invoke library + tasks.py (also fix installation under path with whitespaces) ([PR #1239](https://github.com/versatica/mediasoup/pull/1239)). + +### 3.13.5 + +- Fix RTCP SDES packet size calculation ([PR #1236](https://github.com/versatica/mediasoup/pull/1236) based on PR [PR #1234](https://github.com/versatica/mediasoup/pull/1234) by @ybybwdwd). +- RTCP Compound Packet: Use a single DLRR report to hold all ssrc info sub-blocks ([PR #1237](https://github.com/versatica/mediasoup/pull/1237)). + +### 3.13.4 + +- Fix RTCP DLRR (Delay Since Last Receiver Report) block parsing ([PR #1234](https://github.com/versatica/mediasoup/pull/1234)). + +### 3.13.3 + +- Node: Fix issue when 'pause'/'resume' events are not emitted ([PR #1231](https://github.com/versatica/mediasoup/pull/1231) by @douglaseel). + +### 3.13.2 + +- FBS: `LayersChangeNotification` body must be optional (fixes a crash) ([PR #1227](https://github.com/versatica/mediasoup/pull/1227)). + +### 3.13.1 + +- Node: Extract version from `package.json` using `require()` ([PR #1217](https://github.com/versatica/mediasoup/pull/1217) by @arcinston). + +### 3.13.0 + +- Switch from JSON based messages to FlatBuffers ([PR #1064](https://github.com/versatica/mediasoup/pull/1064)). +- Add `TransportListenInfo` in all transports and send/recv buffer size options ([PR #1084](https://github.com/versatica/mediasoup/pull/1084)). +- Add optional `rtcpListenInfo` in `PlainTransportOptions` ([PR #1099](https://github.com/versatica/mediasoup/pull/1099)). +- Add pause/resume API in `DataProducer` and `DataConsumer` ([PR #1104](https://github.com/versatica/mediasoup/pull/1104)). +- DataChannel subchannels feature ([PR #1152](https://github.com/versatica/mediasoup/pull/1152)). +- Worker: Make DTLS fragment stay within MTU size range ([PR #1156](https://github.com/versatica/mediasoup/pull/1156), based on [PR #1143](https://github.com/versatica/mediasoup/pull/1143) by @vpnts-se). + +### 3.12.16 + +- Fix `IceServer` crash when client uses ICE renomination ([PR #1182](https://github.com/versatica/mediasoup/pull/1182)). + +### 3.12.15 + +- Fix NPM "postinstall" task in Windows ([PR #1187](https://github.com/versatica/mediasoup/pull/1187)). + +### 3.12.14 + +- CI: Use Node.js version 20 ([PR #1177](https://github.com/versatica/mediasoup/pull/1177)). +- Use given `PYTHON` environment variable (if given) when running `worker/scripts/getmake.py` ([PR #1186](https://github.com/versatica/mediasoup/pull/1186)). + +### 3.12.13 + +- Bump up Meson from 1.1.0 to 1.2.1 (fixes Xcode 15 build issues) ([PR #1163](https://github.com/versatica/mediasoup/pull/1163) by @arcinston). + +### 3.12.12 + +- Support C++20 ([PR #1150](https://github.com/versatica/mediasoup/pull/1150) by @o-u-p). + +### 3.12.11 + +- Google Transport Feedback: Read Reference Time field as 24bits signed as per spec ([PR #1145](https://github.com/versatica/mediasoup/pull/1145)). + +### 3.12.10 + +- Node: Rename `WebRtcTransport.webRtcServerClosed()` to `listenServerClosed()` ([PR #1141](https://github.com/versatica/mediasoup/pull/1141) by @piranna). + +### 3.12.9 + +- Fix RTCP SDES ([PR #1139](https://github.com/versatica/mediasoup/pull/1139)). + +### 3.12.8 + +- Export `workerBin` absolute path ([PR #1123](https://github.com/versatica/mediasoup/pull/1123)). + +### 3.12.7 + +- `SimulcastConsumer`: Fix lack of "layerschange" event when all streams in the producer die ([PR #1122](https://github.com/versatica/mediasoup/pull/1122)). + +### 3.12.6 + +- Worker: Add `Transport::Destroying()` protected method ([PR #1114](https://github.com/versatica/mediasoup/pull/1114)). +- `RtpStreamRecv`: Fix jitter calculation ([PR #1117](https://github.com/versatica/mediasoup/pull/1117), thanks to @penguinol). +- Revert "Node: make types.ts only export types rather than the entire class/code" ([PR #1109](https://github.com/versatica/mediasoup/pull/1109)) because it requires `typescript` >= 5 in the apps that import mediasoup and we don't want to be that strict yet. + +### 3.12.5 + +- `DataConsumer`: Fix removed 'bufferedamountlow' notification ([PR #1113](https://github.com/versatica/mediasoup/pull/1113)). + +### 3.12.4 + +- Fix downloaded prebuilt binary check on Windows ([PR #1105](https://github.com/versatica/mediasoup/pull/1105) by @woodfe). + +### 3.12.3 + +Migrate `npm-scripts.js` to `npm-scripts.mjs` (ES Module) ([PR #1093](https://github.com/versatica/mediasoup/pull/1093)). + +### 3.12.2 + +- CI: Use `ubuntu-20.04` to build `mediasoup-worker` prebuilt on Linux ([PR #1092](https://github.com/versatica/mediasoup/pull/1092)). + +### 3.12.1 + +- `mediasoup-worker` prebuild: Fallback to local building if fetched binary doesn't run on current host ([PR #1090](https://github.com/versatica/mediasoup/pull/1090)). + +### 3.12.0 + +- Automate and publish prebuilt `mediasoup-worker` binaries ([PR #1087](https://github.com/versatica/mediasoup/pull/1087), thanks to @barlock for his work in ([PR #1083](https://github.com/versatica/mediasoup/pull/1083)). + +### 3.11.26 + +- Worker: Fix NACK timer and avoid negative RTT ([PR #1082](https://github.com/versatica/mediasoup/pull/1082), thanks to @o-u-p for his work in ([PR #1076](https://github.com/versatica/mediasoup/pull/1076)). + +### 3.11.25 + +- Worker: Require C++17, Meson >= 1.1.0 and update subprojects ([PR #1081](https://github.com/versatica/mediasoup/pull/1081)). + +### 3.11.24 + +- `SeqManager`: Fix performance regression ([PR #1068](https://github.com/versatica/mediasoup/pull/1068), thanks to @vpalmisano for properly reporting). -* Node: Define TypeScript types for `internal` and `data` objects ([PR #891](https://github.com/versatica/mediasoup/pull/891)). -* `Channel` and `PayloadChannel`: Refactor `internal` with a single `handlerId` ([PR #889](https://github.com/versatica/mediasoup/pull/889)). -* `Channel` and `PayloadChannel`: Optimize message format and JSON generation ([PR #893](https://github.com/versatica/mediasoup/pull/893)). -* New C++ `ChannelMessageHandlers` class ([PR #894](https://github.com/versatica/mediasoup/pull/894)). -* Fix Rust support after recent changes ([PR #898](https://github.com/versatica/mediasoup/pull/898)). -* Modify `FeedbackRtpTransport` and tests to be compliant with latest libwebrtc code, make reference time to be unsigned ([PR #899](https://github.com/versatica/mediasoup/pull/899) by @penguinol and @sarumjanuch). -* Update NPM deps. +### 3.11.23 +- Node: Fix `appData` for `Transport` and `RtpObserver` parent classes ([PR #1066](https://github.com/versatica/mediasoup/pull/1066)). + +### 3.11.22 + +- `RtpStreamRecv`: Only perform RTP inactivity check on simulcast streams ([PR #1061](https://github.com/versatica/mediasoup/pull/1061)). +- `SeqManager`: Properly remove old dropped entries ([PR #1054](https://github.com/versatica/mediasoup/pull/1054)). +- libwebrtc: Upgrade trendline estimator to improve low bandwidth conditions ([PR #1055](https://github.com/versatica/mediasoup/pull/1055) by @ggarber). +- libwebrtc: Fix bandwidth probation dead state ([PR #1031](https://github.com/versatica/mediasoup/pull/1031) by @vpalmisano). + +### 3.11.21 + +- Fix check division by zero in transport congestion control ([PR #1049](https://github.com/versatica/mediasoup/pull/1049) by @ggarber). +- Fix lost pending statuses in transport CC feedback ([PR #1050](https://github.com/versatica/mediasoup/pull/1050) by @ggarber). + +### 3.11.20 + +- `RtpStreamSend`: Reset RTP retransmission buffer upon RTP sequence number reset ([PR #1041](https://github.com/versatica/mediasoup/pull/1041)). +- `RtpRetransmissionBuffer`: Handle corner case in which received packet has lower seq than newest packet in the buffer but higher timestamp ([PR #1044](https://github.com/versatica/mediasoup/pull/1044)). +- `SeqManager`: Fix crash and add fuzzer ([PR #1045](https://github.com/versatica/mediasoup/pull/1045)). +- Node: Make `appData` TS typed and writable ([PR #1046](https://github.com/versatica/mediasoup/pull/1046), credits to @mango-martin). + +### 3.11.19 + +- `SvcConsumer`: Properly handle VP9 K-SVC bandwidth allocation ([PR #1036](https://github.com/versatica/mediasoup/pull/1036) by @vpalmisano). + +### 3.11.18 + +- `RtpRetransmissionBuffer`: Consider the case of packet with newest timestamp but "old" seq number ([PR #1039](https://github.com/versatica/mediasoup/pull/1039)). + +### 3.11.17 + +- Add `transport.setMinOutgoingBitrate()` method ([PR #1038](https://github.com/versatica/mediasoup/pull/1038), credits to @ jcague). +- `RTC::RetransmissionBuffer`: Increase `RetransmissionBufferMaxItems` from 2500 to 5000. + +### 3.11.16 + +- Fix `SeqManager`: Properly consider previous cycle dropped inputs ([PR #1032](https://github.com/versatica/mediasoup/pull/1032)). +- `RtpRetransmissionBuffer`: Get rid of not necessary `startSeq` private member ([PR #1029](https://github.com/versatica/mediasoup/pull/1029)). +- Node: Upgrade TypeScript to 5.0.2. + +### 3.11.15 + +- `RtpRetransmissionBuffer`: Fix crash and add fuzzer ([PR #1028](https://github.com/versatica/mediasoup/pull/1028)). + +### 3.11.14 + +- Refactor RTP retransmission buffer in a separate and testable `RTC::RetransmissionBuffer` class ([PR #1023](https://github.com/versatica/mediasoup/pull/1023)). + +### 3.11.13 + +- `AudioLevelObserver`: Use multimap rather than map to avoid conflict if various Producers generate same audio level ([PR #1021](https://github.com/versatica/mediasoup/pull/1021), issue reported by @buptlsp). + +### 3.11.12 + +- Fix jitter calculation ([PR #1019](https://github.com/versatica/mediasoup/pull/1019), credits to @alexciarlillo and @snnz). + +### 3.11.11 + +- Add support for RTCP NACK in OPUS ([PR #1015](https://github.com/versatica/mediasoup/pull/1015)). + +### 3.11.10 + +- Download and use MSYS/make locally for Windows postinstall ([PR #792](https://github.com/versatica/mediasoup/pull/792) by @snnz). + +### 3.11.9 + +- Allow simulcast with a single encoding (and N temporal layers) ([PR #1013](https://github.com/versatica/mediasoup/pull/1013)). +- Update libsrtp to 2.5.0. + +### 3.11.8 + +- `SimulcastConsumer::GetDesiredBitrate()`: Choose the highest bitrate among all Producer streams ([PR #992](https://github.com/versatica/mediasoup/pull/992)). +- `SimulcastConsumer`: Fix frozen video when syncing keyframe is discarded due to too high RTP timestamp extra offset needed ([PR #999](https://github.com/versatica/mediasoup/pull/999), thanks to @satoren for properly reporting the issue and helping with the solution). + +### 3.11.7 + +- libwebrtc: Fix crash due to invalid `arrival_time` value ([PR #985](https://github.com/versatica/mediasoup/pull/985) by @ggarber). +- libwebrtc: Replace `MS_ASSERT()` with `MS_ERROR()` ([PR #988](https://github.com/versatica/mediasoup/pull/988)). + +### 3.11.6 + +- Fix wrong `PictureID` rolling over in VP9 and VP8 ([PR #984](https://github.com/versatica/mediasoup/pull/984) by @jcague). + +### 3.11.5 + +- Require Node.js >= 16 ([PR #973](https://github.com/versatica/mediasoup/pull/973)). +- Fix wrong `Consumer` bandwidth estimation under `Producer` packet loss ([PR #962](https://github.com/versatica/mediasoup/pull/962) by @ggarber). + +### 3.11.4 + +- Node: Migrate tests to TypeScript ([PR #958](https://github.com/versatica/mediasoup/pull/958)). +- Node: Remove compiled JavaScript from repository and compile TypeScript code on NPM `prepare` script on demand when installed via git ([PR #954](https://github.com/versatica/mediasoup/pull/954)). +- Worker: Add `RTC::Shared` singleton for RTC entities ([PR #953](https://github.com/versatica/mediasoup/pull/953)). +- Update OpenSSL to 3.0.7. + +### 3.11.3 + +- `ChannelMessageHandlers`: Make `RegisterHandler()` not remove the existing handler if another one with same `id` is given ([PR #952](https://github.com/versatica/mediasoup/pull/952)). + +### 3.11.2 + +- Fix installation issue in Linux due to a bug in ninja latest version 1.11.1 ([PR #948](https://github.com/versatica/mediasoup/pull/948)). + +### 3.11.1 + +- `ActiveSpeakerObserver`: Revert 'dominantspeaker' event changes in [PR #941](https://github.com/versatica/mediasoup/pull/941) to avoid breaking changes ([PR #947](https://github.com/versatica/mediasoup/pull/947)). + +### 3.11.0 + +- `Transport`: Remove duplicate call to method ([PR #931](https://github.com/versatica/mediasoup/pull/931)). +- RTCP: Adjust maximum compound packet size ([PR #934](https://github.com/versatica/mediasoup/pull/934)). +- `DataConsumer`: Fix `bufferedAmount` type to be a number again ([PR #936](https://github.com/versatica/mediasoup/pull/936)). +- `ActiveSpeakerObserver`: Fix 'dominantspeaker' event by having a single `Producer` as argument rather than an array with a single `Producer` into it ([PR #941](https://github.com/versatica/mediasoup/pull/941)). +- `ActiveSpeakerObserver`: Fix memory leak ([PR #942](https://github.com/versatica/mediasoup/pull/942)). +- Fix some libwebrtc issues ([PR #944](https://github.com/versatica/mediasoup/pull/944)). +- Tests: Normalize hexadecimal data representation ([PR #945](https://github.com/versatica/mediasoup/pull/945)). +- `SctpAssociation`: Fix memory violation ([PR #943](https://github.com/versatica/mediasoup/pull/943)). + +### 3.10.12 + +- Fix worker crash due to `std::out_of_range` exception ([PR #933](https://github.com/versatica/mediasoup/pull/933)). + +### 3.10.11 + +- RTCP: Fix trailing space needed by `srtp_protect_rtcp()` ([PR #929](https://github.com/versatica/mediasoup/pull/929)). + +### 3.10.10 + +- Fix the JSON serialization for the payload channel `rtp` event ([PR #926](https://github.com/versatica/mediasoup/pull/926) by @mhammo). + +### 3.10.9 + +- RTCP enhancements ([PR #914](https://github.com/versatica/mediasoup/pull/914)). + +### 3.10.8 + +- `Consumer`: use a bitset instead of a set for supported payload types ([PR #919](https://github.com/versatica/mediasoup/pull/919)). +- RtpPacket: optimize UpdateMid() ([PR #920](https://github.com/versatica/mediasoup/pull/920)). +- Little optimizations and modernization ([PR #916](https://github.com/versatica/mediasoup/pull/916)). +- Fix SIGSEGV at `RTC::WebRtcTransport::OnIceServerTupleRemoved()` ([PR #915](https://github.com/versatica/mediasoup/pull/915), credits to @ybybwdwd). +- `WebRtcServer`: Make `port` optional (if not given, a random available port from the `Worker` port range is used) ([PR #908](https://github.com/versatica/mediasoup/pull/908) by @satoren). + +### 3.10.7 + +- Forward `abs-capture-time` RTP extension also for audio packets ([PR #911](https://github.com/versatica/mediasoup/pull/911)). + +### 3.10.6 + +- Node: Define TypeScript types for `internal` and `data` objects ([PR #891](https://github.com/versatica/mediasoup/pull/891)). +- `Channel` and `PayloadChannel`: Refactor `internal` with a single `handlerId` ([PR #889](https://github.com/versatica/mediasoup/pull/889)). +- `Channel` and `PayloadChannel`: Optimize message format and JSON generation ([PR #893](https://github.com/versatica/mediasoup/pull/893)). +- New C++ `ChannelMessageHandlers` class ([PR #894](https://github.com/versatica/mediasoup/pull/894)). +- Fix Rust support after recent changes ([PR #898](https://github.com/versatica/mediasoup/pull/898)). +- Modify `FeedbackRtpTransport` and tests to be compliant with latest libwebrtc code, make reference time to be unsigned ([PR #899](https://github.com/versatica/mediasoup/pull/899) by @penguinol and @sarumjanuch). ### 3.10.5 -* `RtpStreamSend`: Do not store too old RTP packets ([PR #885](https://github.com/versatica/mediasoup/pull/885)). -* Log error details in channel socket. ([PR #875](https://github.com/versatica/mediasoup/pull/875) by @mstyura). +- `RtpStreamSend`: Do not store too old RTP packets ([PR #885](https://github.com/versatica/mediasoup/pull/885)). +- Log error details in channel socket. ([PR #875](https://github.com/versatica/mediasoup/pull/875) by @mstyura). ### 3.10.4 -* Do not clone RTP packets if not needed ([PR #850](https://github.com/versatica/mediasoup/pull/850)). -* Fix DTLS related crash ([PR #867](https://github.com/versatica/mediasoup/pull/867)). -* Update NPM deps. - +- Do not clone RTP packets if not needed ([PR #850](https://github.com/versatica/mediasoup/pull/850)). +- Fix DTLS related crash ([PR #867](https://github.com/versatica/mediasoup/pull/867)). ### 3.10.3 -* `SimpleConsumer`: Fix. Only process Opus codec ([PR #865](https://github.com/versatica/mediasoup/pull/865)). -* TypeScript: Improve `WebRtcTransportOptions` type to not allow `webRtcServer` and `listenIps`options at the same time ([PR #852](https://github.com/versatica/mediasoup/pull/852)). - +- `SimpleConsumer`: Fix. Only process Opus codec ([PR #865](https://github.com/versatica/mediasoup/pull/865)). +- TypeScript: Improve `WebRtcTransportOptions` type to not allow `webRtcServer` and `listenIps`options at the same time ([PR #852](https://github.com/versatica/mediasoup/pull/852)). ### 3.10.2 -* Fix release contents by including meson_options.txt ([PR #863](https://github.com/versatica/mediasoup/pull/863)). - +- Fix release contents by including `meson_options.txt` ([PR #863](https://github.com/versatica/mediasoup/pull/863)). ### 3.10.1 -* `RtpStreamSend`: Memory optimizations ([PR #840](https://github.com/versatica/mediasoup/pull/840)). Extracted from #675, by @nazar-pc. -* `SimpleConsumer`: Opus DTX ignore capabilities ([PR #846](https://github.com/versatica/mediasoup/pull/846)). -* Update `libuv` to 1.44.1: Fixes `libuv` build ([PR #857](https://github.com/versatica/mediasoup/pull/857)). -* Update NPM deps. - +- `RtpStreamSend`: Memory optimizations ([PR #840](https://github.com/versatica/mediasoup/pull/840)). Extracted from #675, by @nazar-pc. +- `SimpleConsumer`: Opus DTX ignore capabilities ([PR #846](https://github.com/versatica/mediasoup/pull/846)). +- Update `libuv` to 1.44.1: Fixes `libuv` build ([PR #857](https://github.com/versatica/mediasoup/pull/857)). ### 3.10.0 -* `WebRtcServer`: A new class that brings to `WebRtcTransports` the ability to listen on a single UDP/TCP port ([PR #834](https://github.com/versatica/mediasoup/pull/834)). -* More SRTP crypto suites ([PR #837](https://github.com/versatica/mediasoup/pull/837)). -* Improve `EnhancedEventEmitter` ([PR #836](https://github.com/versatica/mediasoup/pull/836)). -* `TransportCongestionControlClient`: Allow setting max outgoing bitrate before `tccClient` is created ([PR #833](https://github.com/versatica/mediasoup/pull/833)). -* Update NPM deps and TypeScript version. - +- `WebRtcServer`: A new class that brings to `WebRtcTransports` the ability to listen on a single UDP/TCP port ([PR #834](https://github.com/versatica/mediasoup/pull/834)). +- More SRTP crypto suites ([PR #837](https://github.com/versatica/mediasoup/pull/837)). +- Improve `EnhancedEventEmitter` ([PR #836](https://github.com/versatica/mediasoup/pull/836)). +- `TransportCongestionControlClient`: Allow setting max outgoing bitrate before `tccClient` is created ([PR #833](https://github.com/versatica/mediasoup/pull/833)). +- Update TypeScript version. ### 3.9.17 -* `RateCalculator`: Fix old buffer items cleanup ([PR #830](https://github.com/versatica/mediasoup/pull/830) by @dsdolzhenko). -* Update NPM deps and TypeScript version. - +- `RateCalculator`: Fix old buffer items cleanup ([PR #830](https://github.com/versatica/mediasoup/pull/830) by @dsdolzhenko). +- Update TypeScript version. ### 3.9.16 -* `SimulcastConsumer`: Fix spatial layer switch with unordered packets ([PR #823](https://github.com/versatica/mediasoup/pull/823) by @jcague). -* Update NPM deps and TypeScript version. - +- `SimulcastConsumer`: Fix spatial layer switch with unordered packets ([PR #823](https://github.com/versatica/mediasoup/pull/823) by @jcague). +- Update TypeScript version. ### 3.9.15 -* `RateCalculator`: Revert Fix old buffer items cleanup ([PR #819](https://github.com/versatica/mediasoup/pull/819) by @dsdolzhenko). - +- `RateCalculator`: Revert Fix old buffer items cleanup ([PR #819](https://github.com/versatica/mediasoup/pull/819) by @dsdolzhenko). ### 3.9.14 -* `NackGenerator`: Add a configurable delay before sending NACK ([PR #827](https://github.com/versatica/mediasoup/pull/827), credits to @penguinol). -* `SimulcastConsumer`: Fix a race condition in SimulcastConsumer ([PR #825](https://github.com/versatica/mediasoup/pull/825) by @dsdolzhenko). -* Add support for H264 SVC (#798 by @prtmD). -* `RtpStreamSend`: Support receive RTCP-XR RRT and send RTCP-XR DLRR ([PR #781](https://github.com/versatica/mediasoup/pull/781) by @aggresss). -* `RateCalculator`: Fix old buffer items cleanup ([PR #819](https://github.com/versatica/mediasoup/pull/819) by @dsdolzhenko). -* `DirectTransport`: Create a buffer to process RTP packets ([PR #730](https://github.com/versatica/mediasoup/pull/730) by @rtctt). -* Node: Improve `appData` TypeScript syntax and initialization. -* Allow setting max outgoing bitrate below the initial value ([PR #826](https://github.com/versatica/mediasoup/pull/826) by @ggarber). -* Update NPM deps and TypeScript version. - +- `NackGenerator`: Add a configurable delay before sending NACK ([PR #827](https://github.com/versatica/mediasoup/pull/827), credits to @penguinol). +- `SimulcastConsumer`: Fix a race condition in SimulcastConsumer ([PR #825](https://github.com/versatica/mediasoup/pull/825) by @dsdolzhenko). +- Add support for H264 SVC (#798 by @prtmD). +- `RtpStreamSend`: Support receive RTCP-XR RRT and send RTCP-XR DLRR ([PR #781](https://github.com/versatica/mediasoup/pull/781) by @aggresss). +- `RateCalculator`: Fix old buffer items cleanup ([PR #819](https://github.com/versatica/mediasoup/pull/819) by @dsdolzhenko). +- `DirectTransport`: Create a buffer to process RTP packets ([PR #730](https://github.com/versatica/mediasoup/pull/730) by @rtctt). +- Node: Improve `appData` TypeScript syntax and initialization. +- Allow setting max outgoing bitrate below the initial value ([PR #826](https://github.com/versatica/mediasoup/pull/826) by @ggarber). +- Update TypeScript version. ### 3.9.13 -* `VP8`: Do not discard `TL0PICIDX` from Temporal Layers higher than 0 (PR @817 by @jcague). -* Update NPM deps and TypeScript version. - +- `VP8`: Do not discard `TL0PICIDX` from Temporal Layers higher than 0 (PR @817 by @jcague). +- Update TypeScript version. ### 3.9.12 -* `DtlsTransport`: Make DTLS negotiation run immediately ([PR #815](https://github.com/versatica/mediasoup/pull/815)). -* Update NPM deps and TypeScript version. - +- `DtlsTransport`: Make DTLS negotiation run immediately ([PR #815](https://github.com/versatica/mediasoup/pull/815)). +- Update TypeScript version. ### 3.9.11 -* Modify `SimulcastConsumer` to keep using layers without good scores ([PR #804](https://github.com/versatica/mediasoup/pull/804) by @ggarber). -* Update NPM deps. - +- Modify `SimulcastConsumer` to keep using layers without good scores ([PR #804](https://github.com/versatica/mediasoup/pull/804) by @ggarber). ### 3.9.10 -* Update worker dependencies: - * OpenSSL 3.0.2. - * abseil-cpp 20211102.0. - * nlohmann_json 3.10.5. - * usrsctp snapshot 4e06feb01cadcd127d119486b98a4bd3d64aa1e7. - * wingetopt 1.00. -* Update NPM deps and TypeScript version. -* Fix RTP marker bit not being reseted after mangling in each `Consumer` ([PR #811](https://github.com/versatica/mediasoup/pull/811) by @ggarber). - +- Update worker dependencies: + - OpenSSL 3.0.2. + - abseil-cpp 20211102.0. + - nlohmann_json 3.10.5. + - usrsctp snapshot 4e06feb01cadcd127d119486b98a4bd3d64aa1e7. + - wingetopt 1.00. +- Update TypeScript version. +- Fix RTP marker bit not being reseted after mangling in each `Consumer` ([PR #811](https://github.com/versatica/mediasoup/pull/811) by @ggarber). ### 3.9.9 -* Optimize RTP header extension handling ([PR #786](https://github.com/versatica/mediasoup/pull/786)). -* `RateCalculator`: Reset optimization ([PR #785](https://github.com/versatica/mediasoup/pull/785)). -* Fix frozen video due to double call to `Consumer::UserOnTransportDisconnected()` ([PR #788](https://github.com/versatica/mediasoup/pull/788), thanks to @ggarber for exposing this issue in [PR #787](https://github.com/versatica/mediasoup/pull/787)). - +- Optimize RTP header extension handling ([PR #786](https://github.com/versatica/mediasoup/pull/786)). +- `RateCalculator`: Reset optimization ([PR #785](https://github.com/versatica/mediasoup/pull/785)). +- Fix frozen video due to double call to `Consumer::UserOnTransportDisconnected()` ([PR #788](https://github.com/versatica/mediasoup/pull/788), thanks to @ggarber for exposing this issue in [PR #787](https://github.com/versatica/mediasoup/pull/787)). ### 3.9.8 -* Fix VP9 kSVC forwarding logic to not forward lower unneded layers ([PR #778](https://github.com/versatica/mediasoup/pull/778) by @ggarber). -* Fix update bandwidth estimation configuration and available bitrate when updating max outgoing bitrate ([PR #779](https://github.com/versatica/mediasoup/pull/779) by @ggarber). -* Replace outdated `random-numbers` package by native `crypto.randomInt()` ([PR #776](https://github.com/versatica/mediasoup/pull/776) by @piranna). -* Update NPM deps and TypeScript version. - +- Fix VP9 kSVC forwarding logic to not forward lower unneded layers ([PR #778](https://github.com/versatica/mediasoup/pull/778) by @ggarber). +- Fix update bandwidth estimation configuration and available bitrate when updating max outgoing bitrate ([PR #779](https://github.com/versatica/mediasoup/pull/779) by @ggarber). +- Replace outdated `random-numbers` package by native `crypto.randomInt()` ([PR #776](https://github.com/versatica/mediasoup/pull/776) by @piranna). +- Update TypeScript version. ### 3.9.7 -* Typing event emitters in mediasoup Node ([PR #764](https://github.com/versatica/mediasoup/pull/764) by @unao). -* Update NPM deps. - +- Typing event emitters in mediasoup Node ([PR #764](https://github.com/versatica/mediasoup/pull/764) by @unao). ### 3.9.6 -* TCC client optimizations for faster and more stable BWE ([PR #712](https://github.com/versatica/mediasoup/pull/712) by @ggarber). -* Added support for RTP abs-capture-time header ([PR #761](https://github.com/versatica/mediasoup/pull/761) by @oto313). -* Update NPM deps. - +- TCC client optimizations for faster and more stable BWE ([PR #712](https://github.com/versatica/mediasoup/pull/712) by @ggarber). +- Added support for RTP abs-capture-time header ([PR #761](https://github.com/versatica/mediasoup/pull/761) by @oto313). ### 3.9.5 -* ICE renomination support ([PR #756](https://github.com/versatica/mediasoup/pull/756)). -* Update `libuv` to 1.43.0. -* Update NPM deps. - +- ICE renomination support ([PR #756](https://github.com/versatica/mediasoup/pull/756)). +- Update `libuv` to 1.43.0. ### 3.9.4 -* `Worker`: Fix bad printing of error messages from Worker ([PR #750](https://github.com/versatica/mediasoup/pull/750) by @j1elo). -* Update NPM deps. - +- Worker: Fix bad printing of error messages from Worker ([PR #750](https://github.com/versatica/mediasoup/pull/750) by @j1elo). ### 3.9.3 -* Single H264/H265 codec configuration in `supportedRtpCapabilities` ([PR #718](https://github.com/versatica/mediasoup/pull/718)). -* Improve Windows support by not requiring MSVC configuration ([PR #741](https://github.com/versatica/mediasoup/pull/741)). -* Update NPM deps. - +- Single H264/H265 codec configuration in `supportedRtpCapabilities` ([PR #718](https://github.com/versatica/mediasoup/pull/718)). +- Improve Windows support by not requiring MSVC configuration ([PR #741](https://github.com/versatica/mediasoup/pull/741)). ### 3.9.2 -* `pipeToRouter()`: Reuse same `PipeTransport` when possible ([PR #697](https://github.com/versatica/mediasoup/pull/697)). -* Add `worker.died` boolean getter. -* Update TypeScript version to 4.X.X and use `target: "esnext"` so transpilation of ECMAScript private fields (`#xxxxx`) don't use `WeakMaps` tricks but use standard syntax instead. -* Use more than one core for compilation on Windows ([PR #709](https://github.com/versatica/mediasoup/pull/709)). -* `Consumer`: Modification of bitrate allocation algorithm ([PR #708](https://github.com/versatica/mediasoup/pull/708)). -* Update NPM deps. - +- `pipeToRouter()`: Reuse same `PipeTransport` when possible ([PR #697](https://github.com/versatica/mediasoup/pull/697)). +- Add `worker.died` boolean getter. +- Update TypeScript version to 4.X.X and use `target: "esnext"` so transpilation of ECMAScript private fields (`#xxxxx`) don't use `WeakMaps` tricks but use standard syntax instead. +- Use more than one core for compilation on Windows ([PR #709](https://github.com/versatica/mediasoup/pull/709)). +- `Consumer`: Modification of bitrate allocation algorithm ([PR #708](https://github.com/versatica/mediasoup/pull/708)). ### 3.9.1 -* NixOS friendly build process ([PR #683](https://github.com/versatica/mediasoup/pull/683)). -* `Worker`: Emit "died" event before observer "close" ([PR #684](https://github.com/versatica/mediasoup/pull/684)). -* Transport: Hide debug message for RTX RTCP-RR packets ([PR #688](https://github.com/versatica/mediasoup/pull/688)). -* Update `libuv` to 1.42.0. -* Improve Windows support ([PR #692](https://github.com/versatica/mediasoup/pull/692)). -* Avoid build commands when MEDIASOUP_WORKER_BIN is set ([PR #695](https://github.com/versatica/mediasoup/pull/695)). -* Update NPM deps. - +- NixOS friendly build process ([PR #683](https://github.com/versatica/mediasoup/pull/683)). +- Worker: Emit "died" event before observer "close" ([PR #684](https://github.com/versatica/mediasoup/pull/684)). +- Transport: Hide debug message for RTX RTCP-RR packets ([PR #688](https://github.com/versatica/mediasoup/pull/688)). +- Update `libuv` to 1.42.0. +- Improve Windows support ([PR #692](https://github.com/versatica/mediasoup/pull/692)). +- Avoid build commands when MEDIASOUP_WORKER_BIN is set ([PR #695](https://github.com/versatica/mediasoup/pull/695)). ### 3.9.0 -* Replaces GYP build system with fully-functional Meson build system ([PR #622](https://github.com/versatica/mediasoup/pull/622)). -* Worker communication optimization (aka removing netstring dependency) ([PR #644](https://github.com/versatica/mediasoup/pull/644)). -* Move TypeScript and compiled JavaScript code to a new `node` folder. -* Use ES6 private fields. -* Require Node.js version >= 12. - +- Replaces GYP build system with fully-functional Meson build system ([PR #622](https://github.com/versatica/mediasoup/pull/622)). +- Worker communication optimization (aka removing netstring dependency) ([PR #644](https://github.com/versatica/mediasoup/pull/644)). +- Move TypeScript and compiled JavaScript code to a new `node` folder. +- Use ES6 private fields. +- Require Node.js version >= 12. ### 3.8.4 -* OPUS multi-channel (Surround sound) support ([PR #647](https://github.com/versatica/mediasoup/pull/647)). -* Add `packetLoss` stats to transport ([PR #648](https://github.com/versatica/mediasoup/pull/648) by @ggarber). -* Fixes for active speaker observer ([PR #655](https://github.com/versatica/mediasoup/pull/655) by @ggarber). -* Fix big endian issues ([PR #639](https://github.com/versatica/mediasoup/pull/639)). -* Update NPM deps. - +- OPUS multi-channel (Surround sound) support ([PR #647](https://github.com/versatica/mediasoup/pull/647)). +- Add `packetLoss` stats to transport ([PR #648](https://github.com/versatica/mediasoup/pull/648) by @ggarber). +- Fixes for active speaker observer ([PR #655](https://github.com/versatica/mediasoup/pull/655) by @ggarber). +- Fix big endian issues ([PR #639](https://github.com/versatica/mediasoup/pull/639)). ### 3.8.3 -* Fix wrong `size_t*` to `int*` conversion in 64bit Big-Endian hosts ([PR #637](https://github.com/versatica/mediasoup/pull/637)). - +- Fix wrong `size_t*` to `int*` conversion in 64bit Big-Endian hosts ([PR #637](https://github.com/versatica/mediasoup/pull/637)). ### 3.8.2 -* `ActiveSpeakerObserver`: Fix crash due to a `nullptr` ([PR #634](https://github.com/versatica/mediasoup/pull/634)). -* Update NPM deps. - +- `ActiveSpeakerObserver`: Fix crash due to a `nullptr` ([PR #634](https://github.com/versatica/mediasoup/pull/634)). ### 3.8.1 -* `SimulcastConsumer`: Fix RTP timestamp when switching layers ([PR #626](https://github.com/versatica/mediasoup/pull/626) by @penguinol). -* Update NPM deps. - +- `SimulcastConsumer`: Fix RTP timestamp when switching layers ([PR #626](https://github.com/versatica/mediasoup/pull/626) by @penguinol). ### 3.8.0 -* Update `libuv` to 1.42.0. -* Use non-ASM OpenSSL on Windows ([PR #614](https://github.com/versatica/mediasoup/pull/614)). -* Fix minor memory leak caused by non-virtual destructor ([PR #625](https://github.com/versatica/mediasoup/pull/625)). -* Dominant Speaker Event ([PR #603](https://github.com/versatica/mediasoup/pull/603) by @SteveMcFarlin). -* Update NPM deps. - +- Update `libuv` to 1.42.0. +- Use non-ASM OpenSSL on Windows ([PR #614](https://github.com/versatica/mediasoup/pull/614)). +- Fix minor memory leak caused by non-virtual destructor ([PR #625](https://github.com/versatica/mediasoup/pull/625)). +- Dominant Speaker Event ([PR #603](https://github.com/versatica/mediasoup/pull/603) by @SteveMcFarlin). ### 3.7.19 -* Update `libuv` to 1.41.0. -* Update NPM deps. -* C++: +- Update `libuv` to 1.41.0. +- C++: - Move header includes ([PR #608](https://github.com/versatica/mediasoup/pull/608)). - Enhance debugging on channel request/notification error ([PR #607](https://github.com/versatica/mediasoup/pull/607)). - ### 3.7.18 -* Support for optional fixed port on transports ([PR #593](https://github.com/versatica/mediasoup/pull/593) by @nazar-pc). -* Upgrade and optimize OpenSSL dependency ([PR #598](https://github.com/versatica/mediasoup/pull/598) by @vpalmisano): +- Support for optional fixed port on transports ([PR #593](https://github.com/versatica/mediasoup/pull/593) by @nazar-pc). +- Upgrade and optimize OpenSSL dependency ([PR #598](https://github.com/versatica/mediasoup/pull/598) by @vpalmisano): - OpenSSL upgraded to version 1.1.1k. - Enable the compilation of assembly extensions for OpenSSL. - Optimize the worker build (`-O3`) and disable the debug flag (`-g`). -* Update NPM deps. - ### 3.7.17 -* Introduce `PipeConsumerOptions` to avoid incorrect type information on `PipeTransport.consume()` arguments. -* Make `ConsumerOptions.rtpCapabilities` field required as it should have always been. - +- Introduce `PipeConsumerOptions` to avoid incorrect type information on `PipeTransport.consume()` arguments. +- Make `ConsumerOptions.rtpCapabilities` field required as it should have always been. ### 3.7.16 -* Add `mid` option in `ConsumerOptions` to provide way to override MID ([PR #586](https://github.com/versatica/mediasoup/pull/586) by @mstyura). -* Update NPM deps. - +- Add `mid` option in `ConsumerOptions` to provide way to override MID ([PR #586](https://github.com/versatica/mediasoup/pull/586) by @mstyura). ### 3.7.15 -* `kind` field of `RtpHeaderExtension` is no longer optional. It must be 'audio' or 'video'. -* Refactor API inconsistency in internal RTP Observer communication with worker. - +- `kind` field of `RtpHeaderExtension` is no longer optional. It must be 'audio' or 'video'. +- Refactor API inconsistency in internal RTP Observer communication with worker. ### 3.7.14 -* Update `usrsctp` to include a "possible use after free bug" fix (commit [here](https://github.com/sctplab/usrsctp/commit/0f8d58300b1fdcd943b4a9dd3fbd830825390d4d)). -* Update NPM deps. - +- Update `usrsctp` to include a "possible use after free bug" fix (commit [here](https://github.com/sctplab/usrsctp/commit/0f8d58300b1fdcd943b4a9dd3fbd830825390d4d)). ### 3.7.13 -* Fix build on FreeBSD ([PR #585](https://github.com/versatica/mediasoup/pull/585) by @smortex). - +- Fix build on FreeBSD ([PR #585](https://github.com/versatica/mediasoup/pull/585) by @smortex). ### 3.7.12 -* `mediasoup-worker`: Fix memory leaks on error exit ([PR #581](https://github.com/versatica/mediasoup/pull/581)). -* Update NPM deps. - +- `mediasoup-worker`: Fix memory leaks on error exit ([PR #581](https://github.com/versatica/mediasoup/pull/581)). ### 3.7.11 -* Fix `DepUsrSCTP::Checker::timer` not being freed on `Worker` close ([PR #576](https://github.com/versatica/mediasoup/pull/576)). Thanks @nazar-pc for discovering this. -* Update NPM deps. - +- Fix `DepUsrSCTP::Checker::timer` not being freed on `Worker` close ([PR #576](https://github.com/versatica/mediasoup/pull/576)). Thanks @nazar-pc for discovering this. ### 3.7.10 -* Remove clang tools binaries from regular installation. - +- Remove clang tools binaries from regular installation. ### 3.7.9 -* Code clean up. - +- Code clean up. ### 3.7.8 -* `PayloadChannel`: Copy received messages into a separate buffer to avoid memory corruption if the message is later modified ([PR #570](https://github.com/versatica/mediasoup/pull/570) by @aggresss). - +- `PayloadChannel`: Copy received messages into a separate buffer to avoid memory corruption if the message is later modified ([PR #570](https://github.com/versatica/mediasoup/pull/570) by @aggresss). ### 3.7.7 -* Thread and memory safety fixes needed for mediasoup-rust ([PR #562](https://github.com/versatica/mediasoup/pull/562) by @nazar-pc). -* mediasoup-rust support on macOS ([PR #567](https://github.com/versatica/mediasoup/pull/567) by @nazar-pc). -* mediasoup-rust release 0.7.2. -* Update NPM deps. - +- Thread and memory safety fixes needed for mediasoup-rust ([PR #562](https://github.com/versatica/mediasoup/pull/562) by @nazar-pc). +- mediasoup-rust support on macOS ([PR #567](https://github.com/versatica/mediasoup/pull/567) by @nazar-pc). +- mediasoup-rust release 0.7.2. ### 3.7.6 -* `Transport`: Implement new `setMaxOutgoingBitrate()` method ([PR #555](https://github.com/versatica/mediasoup/pull/555) by @t-mullen). -* `SctpAssociation`: Don't warn if SCTP send buffer is full. -* Rust: Update modules structure and other minor improvements for Rust version ([PR #558](https://github.com/versatica/mediasoup/pull/558)). -* `mediasoup-worker`: Avoid duplicated basenames so that libmediasoup-worker is compilable on macOS ([PR #557](https://github.com/versatica/mediasoup/pull/557)). -* Update NPM deps. - +- `Transport`: Implement new `setMaxOutgoingBitrate()` method ([PR #555](https://github.com/versatica/mediasoup/pull/555) by @t-mullen). +- `SctpAssociation`: Don't warn if SCTP send buffer is full. +- Rust: Update modules structure and other minor improvements for Rust version ([PR #558](https://github.com/versatica/mediasoup/pull/558)). +- `mediasoup-worker`: Avoid duplicated basenames so that `libmediasoup-worker` is compilable on macOS ([PR #557](https://github.com/versatica/mediasoup/pull/557)). ### 3.7.5 -* SctpAssociation: provide 'sctpsendbufferfull' reason on send error (#552). - +- SctpAssociation: provide 'sctpsendbufferfull' reason on send error (#552). ### 3.7.4 -* Improve `RateCalculator` ([PR #547](https://github.com/versatica/mediasoup/pull/547) by @vpalmisano). -* Update NPM deps. - +- Improve `RateCalculator` ([PR #547](https://github.com/versatica/mediasoup/pull/547) by @vpalmisano). ### 3.7.3 -* Make worker M1 compilable. - +- Make worker M1 compilable. ### 3.7.2 -* `RateCalculator` optimization ([PR #538](https://github.com/versatica/mediasoup/pull/538) by @vpalmisano). -* Update `Catch` to 2.13.5. -* Update NPM deps. - +- `RateCalculator` optimization ([PR #538](https://github.com/versatica/mediasoup/pull/538) by @vpalmisano). ### 3.7.1 -* `SimulcastConsumer`: Fix miscalculation when increasing layer ([PR #541](https://github.com/versatica/mediasoup/pull/541) by @penguinol). -* Rust version with thread-based worker ([PR #540](https://github.com/versatica/mediasoup/pull/540)). -* Update NPM deps. - +- `SimulcastConsumer`: Fix miscalculation when increasing layer ([PR #541](https://github.com/versatica/mediasoup/pull/541) by @penguinol). +- Rust version with thread-based worker ([PR #540](https://github.com/versatica/mediasoup/pull/540)). ### 3.7.0 -* Welcome to `mediasoup-rust`! Authored by @nazar-pc (PRs #518 and #533). -* Update NPM deps. -* Update `usrsctp`. - +- Welcome to `mediasoup-rust`! Authored by @nazar-pc (PRs #518 and #533). +- Update `usrsctp`. ### 3.6.37 -* Fix crash if empty `fingerprints` array is given in `webrtcTransport.connect()` (issue #537). +- Fix crash if empty `fingerprints` array is given in `webrtcTransport.connect()` (issue #537). ### 3.6.36 -* `Producer`: Add new stats field 'rtxPacketsDiscarded' ([PR #536](https://github.com/versatica/mediasoup/pull/536)). - +- `Producer`: Add new stats field 'rtxPacketsDiscarded' ([PR #536](https://github.com/versatica/mediasoup/pull/536)). ### 3.6.35 -* `XxxxConsumer.hpp`: make `IsActive()` return `true` (even if `Producer`'s score is 0) when DTX is enabled ([PR #534](https://github.com/versatica/mediasoup/pull/534) due to issue #532). -* Update NPM deps. - +- `XxxxConsumer.hpp`: make `IsActive()` return `true` (even if `Producer`'s score is 0) when DTX is enabled ([PR #534](https://github.com/versatica/mediasoup/pull/534) due to issue #532). ### 3.6.34 -* Fix crash (regression, issue #529). - +- Fix crash (regression, issue #529). ### 3.6.33 -* Add missing `delete cb` that otherwise would leak ([PR #527](https://github.com/versatica/mediasoup/pull/527) based on [PR #526](https://github.com/versatica/mediasoup/pull/526) by @vpalmisano). -* `router.pipeToRouter()`: Fix possible inconsistency in `pipeProducer.paused` status (as discussed in this [thread](https://mediasoup.discourse.group/t/concurrency-architecture/2515/) in the mediasoup forum). -* Update `nlohmann/json` to 3.9.1. -* Update `usrsctp`. -* Update NPM deps. -* Enhance Jitter calculation. - +- Add missing `delete cb` that otherwise would leak ([PR #527](https://github.com/versatica/mediasoup/pull/527) based on [PR #526](https://github.com/versatica/mediasoup/pull/526) by @vpalmisano). +- `router.pipeToRouter()`: Fix possible inconsistency in `pipeProducer.paused` status (as discussed in this [thread](https://mediasoup.discourse.group/t/concurrency-architecture/2515/) in the mediasoup forum). +- Update `nlohmann/json` to 3.9.1. +- Update `usrsctp`. +- Enhance Jitter calculation. ### 3.6.32 -* Fix notifications from `mediasoup-worker` being processed before responses received before them (issue #501). - +- Fix notifications from `mediasoup-worker` being processed before responses received before them (issue #501). ### 3.6.31 -* Move `bufferedAmount` from `dataConsumer.dump()` to `dataConsumer.getStats()`. -* Update NPM deps. - +- Move `bufferedAmount` from `dataConsumer.dump()` to `dataConsumer.getStats()`. ### 3.6.30 -* Add `pipe` option to `transport.consume()`([PR #494](https://github.com/versatica/mediasoup/pull/494)). +- Add `pipe` option to `transport.consume()`([PR #494](https://github.com/versatica/mediasoup/pull/494)). - So the receiver will get all streams from the `Producer`. - It works for any kind of transport (but `PipeTransport` which is always like this). -* Update NPM deps. -* Add `LICENSE` and `PATENTS` files in `libwebrtc` dependency (issue #495). -* Added `worker/src/Utils/README_BASE64_UTILS` (issue #497). -* Update `Catch` to 2.13.4. -* Update `usrsctp`. - +- Add `LICENSE` and `PATENTS` files in `libwebrtc` dependency (issue #495). +- Added `worker/src/Utils/README_BASE64_UTILS` (issue #497). +- Update `usrsctp`. ### 3.6.29 -* Fix wrong message about `rtcMinPort` and `rtcMaxPort`. -* Update deps. -* Improve `EnhancedEventEmitter.safeAsPromise()` (although not used). - +- Fix wrong message about `rtcMinPort` and `rtcMaxPort`. +- Update deps. +- Improve `EnhancedEventEmitter.safeAsPromise()` (although not used). ### 3.6.28 -* Fix replacement of `__MEDIASOUP_VERSION__` in `lib/index.d.ts` (issue #483). -* Update NPM deps. -* `worker/scripts/configure.py`: Handle 'mips64' ([PR #485](https://github.com/versatica/mediasoup/pull/485)). - +- Fix replacement of `__MEDIASOUP_VERSION__` in `lib/index.d.ts` (issue #483). +- `worker/scripts/configure.py`: Handle 'mips64' ([PR #485](https://github.com/versatica/mediasoup/pull/485)). ### 3.6.27 -* Update NPM deps. -* Allow the `mediasoup-worker` process to inherit all environment variables (issue #480). - +- Allow the `mediasoup-worker` process to inherit all environment variables (issue #480). ### 3.6.26 -* BWE tweaks and debug logs. -* Update NPM deps. - +- BWE tweaks and debug logs. ### 3.6.25 -* Update `Catch` to 2.13.2. -* Update NPM deps. -* sctp fixes #479. - +- SCTP fixes ([PR #479](https://github.com/versatica/mediasoup/pull/479)). ### 3.6.24 -* Update `awaitqueue` dependency. - +- Update `awaitqueue` dependency. ### 3.6.23 -* Fix yet another memory leak in Node.js layer due to `PayloadChannel` event listener not being removed. -* Update NPM deps. - +- Fix yet another memory leak in Node.js layer due to `PayloadChannel` event listener not being removed. ### 3.6.22 -* `Transport.cpp`: Provide transport congestion client with RTCP Receiver Reports (#464). -* Update `libuv` to 1.40.0. -* Update Node deps. -* `SctpAssociation.cpp`: increase `sctpBufferedAmount` before sending any data (#472). - +- `Transport.cpp`: Provide transport congestion client with RTCP Receiver Reports (#464). +- Update `libuv` to 1.40.0. +- Update Node deps. +- `SctpAssociation.cpp`: increase `sctpBufferedAmount` before sending any data (#472). ### 3.6.21 -* Fix memory leak in Node.js layer due to `PayloadChannel` event listener not being removed (related to #463). - +- Fix memory leak in Node.js layer due to `PayloadChannel` event listener not being removed (related to #463). ### 3.6.20 -* Remove `-fwrapv` when building mediasoup-worker in `Debug` mode (issue #460). -* Add `MEDIASOUP_MAX_CORES` to limit `NUM_CORES` during mediasoup-worker build ([PR #462](https://github.com/versatica/mediasoup/pull/462)). - +- Remove `-fwrapv` when building `mediasoup-worker` in `Debug` mode (issue #460). +- Add `MEDIASOUP_MAX_CORES` to limit `NUM_CORES` during `mediasoup-worker` build ([PR #462](https://github.com/versatica/mediasoup/pull/462)). ### 3.6.19 -* Update `usrsctp` dependency. -* Update `typescript-eslint` deps. -* Update Node deps. - +- Update `usrsctp` dependency. +- Update `typescript-eslint` deps. +- Update Node deps. ### 3.6.18 -* Fix `ortc.getConsumerRtpParameters()` RTX codec comparison issue ([PR #453](https://github.com/versatica/mediasoup/pull/453)). -* RtpObserver: expose `RtpObserverAddRemoveProducerOptions` for `addProducer()` and `removeProducer()` methods. - +- Fix `ortc.getConsumerRtpParameters()` RTX codec comparison issue ([PR #453](https://github.com/versatica/mediasoup/pull/453)). +- RtpObserver: expose `RtpObserverAddRemoveProducerOptions` for `addProducer()` and `removeProducer()` methods. ### 3.6.17 -* Update `libuv` to 1.39.0. -* Update Node deps. -* SimulcastConsumer: Prefer the highest spatial layer initially ([PR #450](https://github.com/versatica/mediasoup/pull/450)). -* RtpStreamRecv: Set RtpDataCounter window size to 6 secs if DTX (#451) - +- Update `libuv` to 1.39.0. +- Update Node deps. +- SimulcastConsumer: Prefer the highest spatial layer initially ([PR #450](https://github.com/versatica/mediasoup/pull/450)). +- RtpStreamRecv: Set RtpDataCounter window size to 6 secs if DTX (#451) ### 3.6.16 -* `SctpAssociation.cpp`: Fix `OnSctpAssociationBufferedAmount()` call. -* Update deps. -* New API to send data from Node throught SCTP DataConsumer. - +- `SctpAssociation.cpp`: Fix `OnSctpAssociationBufferedAmount()` call. +- Update deps. +- New API to send data from Node throught SCTP DataConsumer. ### 3.6.15 -* Avoid SRTP leak by deleting invalid SSRCs after STRP decryption (issue #437, thanks to @penguinol for reporting). -* Update `usrsctp` dep. -* DataConsumer 'bufferedAmount' implementation ([PR #442](https://github.com/versatica/mediasoup/pull/442)). +- Avoid SRTP leak by deleting invalid SSRCs after STRP decryption (issue #437, thanks to @penguinol for reporting). +- Update `usrsctp` dep. +- DataConsumer 'bufferedAmount' implementation ([PR #442](https://github.com/versatica/mediasoup/pull/442)). ### 3.6.14 -* Fix `usrsctp` vulnerability ([PR #439](https://github.com/versatica/mediasoup/pull/439)). -* Fix issue #435 (thanks to @penguinol for reporting). -* `TransportCongestionControlClient.cpp`: Enable periodic ALR probing to recover faster from network issues. -* Update NPM deps. -* Update `nlohmann::json` C++ dep to 3.9.0. -* Update `Catch` to 2.13.0. +- Fix `usrsctp` vulnerability ([PR #439](https://github.com/versatica/mediasoup/pull/439)). +- Fix issue #435 (thanks to @penguinol for reporting). +- `TransportCongestionControlClient.cpp`: Enable periodic ALR probing to recover faster from network issues. +- Update `nlohmann::json` C++ dep to 3.9.0. ### 3.6.13 -* RTP on `DirectTransport` (issue #433, [PR #434](https://github.com/versatica/mediasoup/pull/434)): +- RTP on `DirectTransport` (issue #433, [PR #434](https://github.com/versatica/mediasoup/pull/434)): - New API `producer.send(rtpPacket: Buffer)`. - New API `consumer.on('rtp', (rtpPacket: Buffer)`. - New API `directTransport.sendRtcp(rtcpPacket: Buffer)`. - New API `directTransport.on('rtcp', (rtpPacket: Buffer)`. - ### 3.6.12 -* Release script. - +- Release script. ### 3.6.11 -* `Transport`: rename `maxSctpSendBufferSize` to `sctpSendBufferSize`. - +- `Transport`: rename `maxSctpSendBufferSize` to `sctpSendBufferSize`. ### 3.6.10 -* `Transport`: Implement `maxSctpSendBufferSize`. -* Update `libuv` to 1.38.1. -* Update `Catch` to 2.12.4. -* Update NPM deps. - +- `Transport`: Implement `maxSctpSendBufferSize`. +- Update `libuv` to 1.38.1. ### 3.6.9 -* `Transport::ReceiveRtpPacket()`: Call `RecvStreamClosed(packet->GetSsrc())` if received RTP packet does not match any `Producer`. -* `Transport::HandleRtcpPacket()`: Ensure `Consumer` is found for received NACK Feedback packets. -* Update NPM deps. -* Update C++ `Catch` dep. -* Fix issue #408. - +- `Transport::ReceiveRtpPacket()`: Call `RecvStreamClosed(packet->GetSsrc())` if received RTP packet does not match any `Producer`. +- `Transport::HandleRtcpPacket()`: Ensure `Consumer` is found for received NACK Feedback packets. +- Fix issue #408. ### 3.6.8 -* Fix SRTP leak due to streams not being removed when a `Producer` or `Consumer` is closed. - - [PR #428](https://github.com/versatica/mediasoup/pull/428) (fixes issues #426). +- Fix SRTP leak due to streams not being removed when a `Producer` or `Consumer` is closed. + - [PR #428](https://github.com/versatica/mediasoup/pull/428) (fixes issues #426). - Credits to credits to @penguinol for reporting and initial work at [PR #427](https://github.com/versatica/mediasoup/pull/427). -* Update `nlohmann::json` C++ dep to 3.8.0. -* C++: Enhance `const` correctness. -* Update NPM deps. - +- Update `nlohmann::json` C++ dep to 3.8.0. +- C++: Enhance `const` correctness. ### 3.6.7 -* `ConsumerScore`: Add `producerScores`, scores of all RTP streams in the producer ordered by encoding (just useful when the producer uses simulcast). +- `ConsumerScore`: Add `producerScores`, scores of all RTP streams in the producer ordered by encoding (just useful when the producer uses simulcast). - [PR #421](https://github.com/versatica/mediasoup/pull/421) (fixes issues #420). -* Hide worker executable console in Windows. +- Hide worker executable console in Windows. - [PR #419](https://github.com/versatica/mediasoup/pull/419) (credits to @BlueMagnificent). -* `RtpStream.cpp`: Fix wrong `std::round()` usage. +- `RtpStream.cpp`: Fix wrong `std::round()` usage. - Issue #423. - ### 3.6.6 -* Update `usrsctp` library. -* Update ESlint and TypeScript related dependencies. - +- Update `usrsctp` library. +- Update ESLint and TypeScript related dependencies. ### 3.6.5 -* Set `score:0` when `dtx:true` is set in an `encoding` and there is no RTP for some seconds for that RTP stream. +- Set `score:0` when `dtx:true` is set in an `encoding` and there is no RTP for some seconds for that RTP stream. - Fixes #415. - ### 3.6.4 -* `gyp`: Fix CLT version detection in OSX Catalina when XCode app is not installed. +- `gyp`: Fix CLT version detection in OSX Catalina when XCode app is not installed. - [PR #413](https://github.com/versatica/mediasoup/pull/413) (credits to @enimo). - ### 3.6.3 -* Modernize TypeScript. - +- Modernize TypeScript. ### 3.6.2 -* Fix crash in `Transport.ts` when closing a `DataConsumer` created on a `DirectTransport`. - +- Fix crash in `Transport.ts` when closing a `DataConsumer` created on a `DirectTransport`. ### 3.6.1 -* Export new `DirectTransport` in `types`. -* Make `DataProducerOptions` optional (not needed when in a `DirectTransport`). - +- Export new `DirectTransport` in `types`. +- Make `DataProducerOptions` optional (not needed when in a `DirectTransport`). ### 3.6.0 -* SCTP/DataChannel termination: +- SCTP/DataChannel termination: - [PR #409](https://github.com/versatica/mediasoup/pull/409) - - Allow the Node application to directly send text/binary messages to mediasoup-worker C++ process so others can consume them using `DataConsumers`. + - Allow the Node application to directly send text/binary messages to `mediasoup-worker` C++ process so others can consume them using `DataConsumers`. - And vice-versa: allow the Node application to directly consume in Node messages send by `DataProducers`. -* Add `WorkerLogTag` TypeScript enum and also add a new 'message' tag into it. - +- Add `WorkerLogTag` TypeScript enum and also add a new 'message' tag into it. ### 3.5.15 -* Simulcast and SVC: Better computation of desired bitrate based on `maxBitrate` field in the `producer.rtpParameters.encodings`. - +- Simulcast and SVC: Better computation of desired bitrate based on `maxBitrate` field in the `producer.rtpParameters.encodings`. ### 3.5.14 -* Update deps, specially `uuid` and `@types/uuid` that had a TypeScript related bug. -* `TransportCongestionClient.cpp`: Improve sender side bandwidth estimation by do not reporting `this->initialAvailableBitrate` as available bitrate due to strange behavior in the algorithm. - +- Update deps, specially `uuid` and `@types/uuid` that had a TypeScript related bug. +- `TransportCongestionClient.cpp`: Improve sender side bandwidth estimation by do not reporting `this->initialAvailableBitrate` as available bitrate due to strange behavior in the algorithm. ### 3.5.13 -* Simplify `GetDesiredBitrate()` in `SimulcastConsumer` and `SvcConsumer`. -* Update `libuv` to 1.38.0. - +- Simplify `GetDesiredBitrate()` in `SimulcastConsumer` and `SvcConsumer`. +- Update `libuv` to 1.38.0. ### 3.5.12 -* `SeqManager.cpp`: Improve performance. +- `SeqManager.cpp`: Improve performance. - [PR #398](https://github.com/versatica/mediasoup/pull/398) (credits to @penguinol). - ### 3.5.11 -* `SeqManager.cpp`: Fix a bug and improve performance. +- `SeqManager.cpp`: Fix a bug and improve performance. - Fixes issue #395 via [PR #396](https://github.com/versatica/mediasoup/pull/396) (credits to @penguinol). -* Drop Node.js 8 support. Minimum supported Node.js version is now 10. -* Upgrade `eslint` and `jest` major versions. - +- Drop Node.js 8 support. Minimum supported Node.js version is now 10. +- Upgrade `eslint` and `jest` major versions. ### 3.5.10 -* `SimulcastConsumer.cpp`: Fix `IncreaseLayer()` method (fixes #394). -* Udpate Node deps. - +- `SimulcastConsumer.cpp`: Fix `IncreaseLayer()` method (fixes #394). +- Udpate Node deps. ### 3.5.9 -* `libwebrtc`: Apply patch by @sspanak and @Ivaka to avoid crash. Related issue: #357. -* `PortManager.cpp`: Do not use `UV_UDP_RECVMMSG` in Windows due to a bug in `libuv` 1.37.0. -* Update Node deps. - +- `libwebrtc`: Apply patch by @sspanak and @Ivaka to avoid crash. Related issue: #357. +- `PortManager.cpp`: Do not use `UV_UDP_RECVMMSG` in Windows due to a bug in `libuv` 1.37.0. +- Update Node deps. ### 3.5.8 -* Enable `UV_UDP_RECVMMSG`: +- Enable `UV_UDP_RECVMMSG`: - Upgrade `libuv` to 1.37.0. - Use `uv_udp_init_ex()` with `UV_UDP_RECVMMSG` flag. - Add our own `uv.gyp` now that `libuv` has removed support for GYP (fixes #384). - ### 3.5.7 -* Fix crash in mediasoup-worker due to conversion from `uint64_t` to `int64_t` (used within `libwebrtc` code. Fixes #357. -* Update `usrsctp` library. -* Update Node deps. - +- Fix crash in `mediasoup-worker` due to conversion from `uint64_t` to `int64_t` (used within `libwebrtc` code. Fixes #357. +- Update `usrsctp` library. +- Update Node deps. ### 3.5.6 -* `SeqManager.cpp`: Fix video lag after a long time. +- `SeqManager.cpp`: Fix video lag after a long time. - Fixes #372 (thanks @penguinol for reporting it and giving the solution). - ### 3.5.5 -* `UdpSocket.cpp`: Revert `uv__udp_recvmmsg()` usage since it notifies about received UDP packets in reverse order. Feature on hold until fixed. - +- `UdpSocket.cpp`: Revert `uv__udp_recvmmsg()` usage since it notifies about received UDP packets in reverse order. Feature on hold until fixed. ### 3.5.4 -* `Transport.cpp`: Enable transport congestion client for the first video Consumer, no matter it's uses simulcast, SVC or a single stream. -* Update `libuv` to 1.35.0. -* `UdpSocket.cpp`: Ensure the new libuv's `uv__udp_recvmmsg()` is used, which is more efficient. - +- `Transport.cpp`: Enable transport congestion client for the first video Consumer, no matter it's uses simulcast, SVC or a single stream. +- Update `libuv` to 1.35.0. +- `UdpSocket.cpp`: Ensure the new libuv's `uv__udp_recvmmsg()` is used, which is more efficient. ### 3.5.3 -* `PlainTransport`: Remove `multiSource` option. It was a hack nobody should use. - +- `PlainTransport`: Remove `multiSource` option. It was a hack nobody should use. ### 3.5.2 -* Enable MID RTP extension in mediasoup to receivers direction (for consumers). +- Enable MID RTP extension in mediasoup to receivers direction (for consumers). - This **requires** mediasoup-client 3.5.2 to work. - ### 3.5.1 -* `PlainTransport`: Fix event name: 'rtcpTuple' => 'rtcptuple'. - +- `PlainTransport`: Fix event name: 'rtcpTuple' => 'rtcptuple'. ### 3.5.0 -* `PipeTransport`: Add support for SRTP and RTP retransmission (RTX + NACK). Useful when connecting two mediasoup servers running in different hosts via pipe transports. -* `PlainTransport`: Add support for SRTP. -* Rename `PlainRtpTransport` to `PlainTransport` everywhere (classes, methods, TypeScript types, etc). Keep previous names and mark them as DEPRECATED. -* Fix vulnarability in IPv6 parser. - +- `PipeTransport`: Add support for SRTP and RTP retransmission (RTX + NACK). Useful when connecting two mediasoup servers running in different hosts via pipe transports. +- `PlainTransport`: Add support for SRTP. +- Rename `PlainRtpTransport` to `PlainTransport` everywhere (classes, methods, TypeScript types, etc). Keep previous names and mark them as DEPRECATED. +- Fix vulnarability in IPv6 parser. ### 3.4.13 -* Update `uuid` dep to 7.0.X (new API). -* Fix crash due wrong array index in `PipeConsumer::FillJson()`. +- Update `uuid` dep to 7.0.X (new API). +- Fix crash due wrong array index in `PipeConsumer::FillJson()`. - Fixes #364 - ### 3.4.12 -* TypeScript: generate `es2020` instead of `es6`. -* Update `usrsctp` library. +- TypeScript: generate `es2020` instead of `es6`. +- Update `usrsctp` library. - Fixes #362 (thanks @chvarlam for reporting it). - ### 3.4.11 -* `IceServer.cpp`: Reject received STUN Binding request with 487 if remote peer indicates ICE-CONTROLLED into it. - +- `IceServer.cpp`: Reject received STUN Binding request with 487 if remote peer indicates ICE-CONTROLLED into it. ### 3.4.10 -* `ProducerOptions`: Rename `keyFrameWaitTime` option to `keyFrameRequestDelay` and make it work as expected. - +- `ProducerOptions`: Rename `keyFrameWaitTime` option to `keyFrameRequestDelay` and make it work as expected. ### 3.4.9 -* Add `Utils::Json::IsPositiveInteger()` to not rely on `is_number_unsigned()` of json lib, which is unreliable due to its design. -* Avoid ES6 `export default` and always use named `export`. -* `router.pipeToRouter()`: Ensure a single `PipeTransport` pair is created between `router1` and `router2`. - - Since the operation is async, it may happen that two simultaneous calls to `router1.pipeToRouter({ producerId: xxx, router: router2 })` would end up generating two pairs of `PipeTranports`. To prevent that, let's use an async queue. -* Add `keyFrameWaitTime` option to `ProducerOptions`. -* Update Node and C++ deps. - +- Add `Utils::Json::IsPositiveInteger()` to not rely on `is_number_unsigned()` of json lib, which is unreliable due to its design. +- Avoid ES6 `export default` and always use named `export`. +- `router.pipeToRouter()`: Ensure a single `PipeTransport` pair is created between `router1` and `router2`. + - Since the operation is async, it may happen that two simultaneous calls to `router1.pipeToRouter({ producerId: xxx, router: router2 })` would end up generating two pairs of `PipeTranports`. To prevent that, let's use an async queue. +- Add `keyFrameWaitTime` option to `ProducerOptions`. +- Update Node and C++ deps. ### 3.4.8 -* `libsrtp.gyp`: Fix regression in mediasoup for Windows. +- `libsrtp.gyp`: Fix regression in mediasoup for Windows. - `libsrtp.gyp`: Modernize it based on the new `BUILD.gn` in Chromium. - `libsrtp.gyp`: Don't include "test" and other targets. - Assume `HAVE_INTTYPES_H`, `HAVE_INT8_T`, etc. in Windows. - Issue details: https://github.com/sctplab/usrsctp/issues/353 -* `gyp` dependency: Add support for Microsoft Visual Studio 2019. +- `gyp` dependency: Add support for Microsoft Visual Studio 2019. - Modify our own `gyp` sources to fix the issue. - CL uploaded to GYP project with the fix. - Issue details: https://github.com/sctplab/usrsctp/issues/347 - ### 3.4.7 -* `PortManager.cpp`: Do not limit the number of failed `bind()` attempts to 20 since it does not work well in scenarios that launch tons of `Workers` with same port range. Instead iterate all ports in the range given to the Worker. -* Do not copy `catch.hpp` into `test/include/` but make the GYP `mediasoup-worker-test` target include the corresponding folder in `deps/catch`. - +- `PortManager.cpp`: Do not limit the number of failed `bind()` attempts to 20 since it does not work well in scenarios that launch tons of `Workers` with same port range. Instead iterate all ports in the range given to the Worker. +- Do not copy `catch.hpp` into `test/include/` but make the GYP `mediasoup-worker-test` target include the corresponding folder in `deps/catch`. ### 3.4.6 -* Update libsrtp to 2.3.0. -* Update ESLint and TypeScript deps. - +- Update libsrtp to 2.3.0. +- Update ESLint and TypeScript deps. ### 3.4.5 -* Update deps. -* Fix text in `./github/Bug_Report.md` so it no longer references the deprecated mailing list. - +- Update deps. +- Fix text in `./github/Bug_Report.md` so it no longer references the deprecated mailing list. ### 3.4.4 -* `Transport.cpp`: Ignore RTCP SDES packets (we don't do anything with them anyway). -* `Producer` and `Consumer` stats: Always show `roundTripTime` (even if calculated value is 0) after a `roundTripTime` > 0 has been seen. - +- `Transport.cpp`: Ignore RTCP SDES packets (we don't do anything with them anyway). +- `Producer` and `Consumer` stats: Always show `roundTripTime` (even if calculated value is 0) after a `roundTripTime` > 0 has been seen. ### 3.4.3 -* `Transport.cpp`: Fix RTCP FIR processing: +- `Transport.cpp`: Fix RTCP FIR processing: - Instead of looking at the media ssrc in the common header, iterate FIR items and look for associated `Consumers` based on ssrcs in each FIR item. - Fixes #350 (thanks @j1elo for reporting and documenting the issue). - ### 3.4.2 -* `SctpAssociation.cpp`: Improve/fix logs. -* Improve Node `EventEmitter` events inline documentation. -* `test-node-sctp.js`: Wait for SCTP association to be open before sending data. - +- `SctpAssociation.cpp`: Improve/fix logs. +- Improve Node `EventEmitter` events inline documentation. +- `test-node-sctp.js`: Wait for SCTP association to be open before sending data. ### 3.4.1 -* Improve mediasoup-worker build system by using `sh` instead of `bash` and default to 4 cores (thanks @smoke, [PR #349](https://github.com/versatica/mediasoup/pull/349)). - +- Improve `mediasoup-worker` build system by using `sh` instead of `bash` and default to 4 cores (thanks @smoke, [PR #349](https://github.com/versatica/mediasoup/pull/349)). ### 3.4.0 -* Add `worker.getResourceUsage()` API. -* Update OpenSSL to 1.1.1d. -* Update `libuv` to 1.34.0. -* Update TypeScript and ESLint NPM dependencies. - +- Add `worker.getResourceUsage()` API. +- Update OpenSSL to 1.1.1d. +- Update `libuv` to 1.34.0. +- Update TypeScript version. ### 3.3.8 -* Update usrsctp dependency (it fixes a potential wrong memory access). +- Update usrsctp dependency (it fixes a potential wrong memory access). - More details in the reported issue: https://github.com/sctplab/usrsctp/issues/408 - ### 3.3.7 -* Fix `version` getter. - +- Fix `version` getter. ### 3.3.6 -* `SctpAssociation.cpp`: Initialize the `usrsctp` socket in the class constructor. Fixes #348. - +- `SctpAssociation.cpp`: Initialize the `usrsctp` socket in the class constructor. Fixes #348. ### 3.3.5 -* Fix usage of a deallocated `RTC::TcpConnection` instance under heavy CPU usage due to mediasoup deleting the instance in the middle of a receiving iteration. Fixes #333. +- Fix usage of a deallocated `RTC::TcpConnection` instance under heavy CPU usage due to mediasoup deleting the instance in the middle of a receiving iteration. Fixes #333. - More details in the commit: https://github.com/versatica/mediasoup/commit/49824baf102ab6d2b01e5bca565c29b8ac0fec22 - ### 3.3.4 -* IPv6 fix: Use `INET6_ADDRSTRLEN` instead of `INET_ADDRSTRLEN`. - +- IPv6 fix: Use `INET6_ADDRSTRLEN` instead of `INET_ADDRSTRLEN`. ### 3.3.3 -* Add `consumer.setPriority()` and `consumer.priority` API to prioritize how the estimated outgoing bitrate in a transport is distributed among all video consumers (in case there is not enough bitrate to satisfy them). -* Make video `SimpleConsumers` play the BWE game by helping in probation generation and bitrate distribution. -* Add `consumer.preferredLayers` getter. -* Rename `enablePacketEvent()` and "packet" event to `enableTraceEvent()` and "trace" event (sorry SEMVER). -* Transport: Add a new "trace" event of type "bwe" with detailed information about bitrates. - +- Add `consumer.setPriority()` and `consumer.priority` API to prioritize how the estimated outgoing bitrate in a transport is distributed among all video consumers (in case there is not enough bitrate to satisfy them). +- Make video `SimpleConsumers` play the BWE game by helping in probation generation and bitrate distribution. +- Add `consumer.preferredLayers` getter. +- Rename `enablePacketEvent()` and "packet" event to `enableTraceEvent()` and "trace" event (sorry SEMVER). +- Transport: Add a new "trace" event of type "bwe" with detailed information about bitrates. ### 3.3.2 -* Improve "packet" event by not firing both "keyframe" and "rtp" types for the same RTP packet. - +- Improve "packet" event by not firing both "keyframe" and "rtp" types for the same RTP packet. ### 3.3.1 - -* Add type "keyframe" as a valid type for "packet" event in `Producers` and `Consumers`. - +- Add type "keyframe" as a valid type for "packet" event in `Producers` and `Consumers`. ### 3.3.0 -* Add transport-cc bandwidth estimation and congestion control in sender and receiver side. -* Run in Windows. -* Rewrite to TypeScript. -* Tons of improvements. - +- Add transport-cc bandwidth estimation and congestion control in sender and receiver side. +- Run in Windows. +- Rewrite to TypeScript. +- Tons of improvements. ### 3.2.5 -* Fix TCP leak (#325). - +- Fix TCP leak (#325). ### 3.2.4 -* `PlainRtpTransport`: Fix comedia mode. - +- `PlainRtpTransport`: Fix comedia mode. ### 3.2.3 -* `RateCalculator`: improve efficiency in `GetRate()` method (#324). - +- `RateCalculator`: improve efficiency in `GetRate()` method (#324). ### 3.2.2 -* `RtpDataCounter`: use window size of 2500 ms instead of 1000 ms. +- `RtpDataCounter`: use window size of 2500 ms instead of 1000 ms. - Fixes false "lack of RTP" detection in some screen sharing usages with simulcast. - Fixes #312. - ### 3.2.1 -* Add RTCP Extended Reports for RTT calculation on receiver RTP stream (thanks @yangjinechofor for initial pull request #314). -* Make mediasoup-worker compile in Armbian Debian Buster (thanks @krishisola, fixes #321). - +- Add RTCP Extended Reports for RTT calculation on receiver RTP stream (thanks @yangjinechofor for initial pull request #314). +- Make `mediasoup-worker` compile in Armbian Debian Buster (thanks @krishisola, fixes #321). ### 3.2.0 -* Add DataChannel support via DataProducers and DataConsumers (#10). -* SRTP: Add support for AEAD GCM (#320). - +- Add DataChannel support via DataProducers and DataConsumers (#10). +- SRTP: Add support for AEAD GCM (#320). ### 3.1.7 -* `PipeConsumer.cpp`: Fix RTCP generation (thanks @vpalmisano). - +- `PipeConsumer.cpp`: Fix RTCP generation (thanks @vpalmisano). ### 3.1.6 -* VP8 and H264: Fix regression in 3.1.5 that produces lot of changes in current temporal layer detection. - +- VP8 and H264: Fix regression in 3.1.5 that produces lot of changes in current temporal layer detection. ### 3.1.5 -* VP8 and H264: Allow packets without temporal layer information even if N temporal layers were announced. - +- VP8 and H264: Allow packets without temporal layer information even if N temporal layers were announced. ### 3.1.4 -* Add `-fPIC` in `cflags` to compile in x86-64. Fixes #315. - +- Add `-fPIC` in `cflags` to compile in x86-64. Fixes #315. ### 3.1.3 -* Set the sender SSRC on PLI and FIR requests [related thread](https://mediasoup.discourse.group/t/broadcasting-a-vp8-rtp-stream-from-gstreamer/93). - +- Set the sender SSRC on PLI and FIR requests [related thread](https://mediasoup.discourse.group/t/broadcasting-a-vp8-rtp-stream-from-gstreamer/93). ### 3.1.2 -* Workaround to detect H264 key frames when Chrome uses external encoder (related [issue](https://bugs.chromium.org/p/webrtc/issues/detail?id=10746)). Fixes #313. - +- Workaround to detect H264 key frames when Chrome uses external encoder (related [issue](https://bugs.chromium.org/p/webrtc/issues/detail?id=10746)). Fixes #313. ### 3.1.1 -* Improve `GetBitratePriority()` method in `SimulcastConsumer` and `SvcConsumer` by checking the total bitrate of all temporal layers in a given producer stream or spatial layer. - +- Improve `GetBitratePriority()` method in `SimulcastConsumer` and `SvcConsumer` by checking the total bitrate of all temporal layers in a given producer stream or spatial layer. ### 3.1.0 -* Add SVC support. It includes VP9 full SVC and VP9 K-SVC as implemented by libwebrtc. -* Prefer Python 2 (if available) over Python 3. This is because there are yet pending issues with gyp + Python 3. - +- Add SVC support. It includes VP9 full SVC and VP9 K-SVC as implemented by libwebrtc. +- Prefer Python 2 (if available) over Python 3. This is because there are yet pending issues with gyp + Python 3. ### 3.0.12 -* Do not require Python 2 to compile mediasoup worker (#207). Both Python 2 and 3 can now be used. - +- Do not require Python 2 to compile mediasoup worker (#207). Both Python 2 and 3 can now be used. ### 3.0.11 -* Codecs: Improve temporal layer switching in VP8 and H264. -* Skip worker compilation if `MEDIASOUP_WORKER_BIN` environment variable is given (#309). This makes it possible to install mediasoup in platforms in which, somehow, gcc > 4.8 is not available during `npm install mediasoup` but it's available later. -* Fix `RtpStreamRecv::TransmissionCounter::GetBitrate()`. - +- Codecs: Improve temporal layer switching in VP8 and H264. +- Skip worker compilation if `MEDIASOUP_WORKER_BIN` environment variable is given (#309). This makes it possible to install mediasoup in platforms in which, somehow, gcc > 4.8 is not available during `npm install mediasoup` but it's available later. +- Fix `RtpStreamRecv::TransmissionCounter::GetBitrate()`. ### 3.0.10 -* `parseScalabilityMode()`: allow "S" as spatial layer (and not just "L"). "L" means "dependent spatial layer" while "S" means "independent spatial layer", which is used in K-SVC (VP9, AV1, etc). - +- `parseScalabilityMode()`: allow "S" as spatial layer (and not just "L"). "L" means "dependent spatial layer" while "S" means "independent spatial layer", which is used in K-SVC (VP9, AV1, etc). ### 3.0.9 -* `RtpStreamSend::ReceiveRtcpReceiverReport()`: improve `rtt` calculation if no Sender Report info is reported in received Received Report. -* Update `libuv` to version 1.29.1. - +- `RtpStreamSend::ReceiveRtcpReceiverReport()`: improve `rtt` calculation if no Sender Report info is reported in received Received Report. +- Update `libuv` to version 1.29.1. ### 3.0.8 -* VP8 & H264: Improve temporal layer switching. - +- VP8 & H264: Improve temporal layer switching. ### 3.0.7 -* RTP frame-marking: Add some missing checks. - +- RTP frame-marking: Add some missing checks. ### 3.0.6 -* Fix regression in proxied RTP header extensions. - +- Fix regression in proxied RTP header extensions. ### 3.0.5 -* Add support for frame-marking RTP extensions and use it to enable temporal layers switching in H264 codec (#305). - +- Add support for frame-marking RTP extensions and use it to enable temporal layers switching in H264 codec (#305). ### 3.0.4 -* Improve RTP probation for simulcast/svc consumers by using proper RTP retransmission with increasing sequence number. - +- Improve RTP probation for simulcast/svc consumers by using proper RTP retransmission with increasing sequence number. ### 3.0.3 -* Simulcast: Improve timestamps extra offset handling by having a map of extra offsets indexed by received timestamps. This helps in case of packet retransmission. - +- Simulcast: Improve timestamps extra offset handling by having a map of extra offsets indexed by received timestamps. This helps in case of packet retransmission. ### 3.0.2 -* Simulcast: proper RTP stream switching by rewriting packet timestamp with a new timestamp calculated from the SenderReports' NTP relationship. - +- Simulcast: proper RTP stream switching by rewriting packet timestamp with a new timestamp calculated from the SenderReports' NTP relationship. ### 3.0.1 -* Fix crash in `SimulcastConsumer::IncreaseLayer()` with Safari and H264 (#300). - +- Fix crash in `SimulcastConsumer::IncreaseLayer()` with Safari and H264 (#300). ### 3.0.0 -* v3 is here! - +- v3 is here! ### 2.6.19 -* `RtpStreamSend.cpp`: Fix a crash in `StorePacket()` when it receives an old packet and there is no space left in the storage buffer (thanks to zkfun for reporting it and providing us with the solution). -* Update deps. - +- `RtpStreamSend.cpp`: Fix a crash in `StorePacket()` when it receives an old packet and there is no space left in the storage buffer (thanks to zkfun for reporting it and providing us with the solution). +- Update deps. ### 2.6.18 -* Fix usage of a deallocated `RTC::TcpConnection` instance under heavy CPU usage due to mediasoup deleting the instance in the middle of a receiving iteration. - +- Fix usage of a deallocated `RTC::TcpConnection` instance under heavy CPU usage due to mediasoup deleting the instance in the middle of a receiving iteration. ### 2.6.17 -* Improve build system by using all available CPU cores in parallel. - +- Improve build system by using all available CPU cores in parallel. ### 2.6.16 -* Don't mandate server port range to be >= 99. - +- Don't mandate server port range to be >= 99. ### 2.6.15 -* Fix NACK retransmissions. - +- Fix NACK retransmissions. ### 2.6.14 -* Fix TCP leak (#325). - +- Fix TCP leak (#325). ### 2.6.13 -* Make mediasoup-worker compile in Armbian Debian Buster (thanks @krishisola, fixes #321). -* Update deps. - +- Make `mediasoup-worker` compile in Armbian Debian Buster (thanks @krishisola, fixes #321). +- Update deps. ### 2.6.12 -* Fix RTCP Receiver Report handling. - +- Fix RTCP Receiver Report handling. ### 2.6.11 -* Update deps. -* Simulcast: Increase profiles one by one unless explicitly forced (fixes #188). - +- Update deps. +- Simulcast: Increase profiles one by one unless explicitly forced (fixes #188). ### 2.6.10 -* `PlainRtpTransport.js`: Add missing methods and events. - +- `PlainRtpTransport.js`: Add missing methods and events. ### 2.6.9 -* Remove a potential crash if a single `encoding` is given in the Producer `rtpParameters` and it has a `profile` value. - +- Remove a potential crash if a single `encoding` is given in the Producer `rtpParameters` and it has a `profile` value. ### 2.6.8 -* C++: Verify in libuv static callbacks that the associated C++ instance has not been deallocated (thanks @artushin and @mariat-atg for reporting and providing valuable help in #258). - +- C++: Verify in libuv static callbacks that the associated C++ instance has not been deallocated (thanks @artushin and @mariat-atg for reporting and providing valuable help in #258). ### 2.6.7 -* Fix wrong destruction of Transports in Router.cpp that generates 100% CPU usage in mediasoup-worker processes. - +- Fix wrong destruction of Transports in Router.cpp that generates 100% CPU usage in `mediasoup-worker` processes. ### 2.6.6 -* Fix a port leak when a WebRtcTransport is remotely closed due to a DTLS close alert (thanks @artushin for reporting it in #259). - +- Fix a port leak when a WebRtcTransport is remotely closed due to a DTLS close alert (thanks @artushin for reporting it in #259). ### 2.6.5 -* RtpPacket: Fix Two-Byte header extensions parsing. - +- RtpPacket: Fix Two-Byte header extensions parsing. ### 2.6.4 -* Upgrade again to OpenSSL 1.1.0j (20 Nov 2018) after adding a workaround for issue [#257](https://github.com/versatica/mediasoup/issues/257). - +- Upgrade again to OpenSSL 1.1.0j (20 Nov 2018) after adding a workaround for issue [#257](https://github.com/versatica/mediasoup/issues/257). ### 2.6.3 -* Downgrade OpenSSL to version 1.1.0h (27 Mar 2018) until issue [#257](https://github.com/versatica/mediasoup/issues/257) is fixed. - +- Downgrade OpenSSL to version 1.1.0h (27 Mar 2018) until issue [#257](https://github.com/versatica/mediasoup/issues/257) is fixed. ### 2.6.2 -* C++: Remove all `Destroy()` class methods and no longer do `delete this`. -* Update libuv to 1.24.1. -* Update OpenSSL to 1.1.0g. - +- C++: Remove all `Destroy()` class methods and no longer do `delete this`. +- Update libuv to 1.24.1. +- Update OpenSSL to 1.1.0g. ### 2.6.1 -* worker: Internal refactor and code cleanup. -* Remove announced support for certain RTCP feedback types that mediasoup does nothing with (and avoid forwarding them to the remote RTP sender). -* fuzzer: fix some wrong memory access in `RtpPacket::Dump()` and `StunMessage::Dump()` (just used during development). +- worker: Internal refactor and code cleanup. +- Remove announced support for certain RTCP feedback types that mediasoup does nothing with (and avoid forwarding them to the remote RTP sender). +- fuzzer: fix some wrong memory access in `RtpPacket::Dump()` and `StunMessage::Dump()` (just used during development). ### 2.6.0 -* Integrate [libFuzzer](http://llvm.org/docs/LibFuzzer.html) into mediasoup (documentation in the `doc` folder). Extensive testing done. Several heap-buffer-overflow and memory leaks fixed. - +- Integrate [libFuzzer](http://llvm.org/docs/LibFuzzer.html) into mediasoup (documentation in the `doc` folder). Extensive testing done. Several heap-buffer-overflow and memory leaks fixed. ### 2.5.6 -* `Producer.cpp`: Remove `UpdateRtpParameters()`. It was broken since Consumers +- `Producer.cpp`: Remove `UpdateRtpParameters()`. It was broken since Consumers were not notified about profile removed and so on, so they may crash. -* `Producer.cpp: Remove some maps and simplify streams handling by having a - single `mapSsrcRtpStreamInfo`. Just keep `mapActiveProfiles` because - `GetActiveProfiles()` method needs it. -* `Producer::MayNeedNewStream()`: Ignore new media streams with new SSRC if +- `Producer.cpp: Remove some maps and simplify streams handling by having a +single `mapSsrcRtpStreamInfo`. Just keep `mapActiveProfiles`because`GetActiveProfiles()` method needs it. +- `Producer::MayNeedNewStream()`: Ignore new media streams with new SSRC if its RID is already in use by other media stream (fixes #235). -* Fix a bad memory access when using two byte RTP header extensions. - +- Fix a bad memory access when using two byte RTP header extensions. ### 2.5.5 -* `Server.js`: If a worker crashes make sure `_latestWorkerIdx` becomes 0. - +- `Server.js`: If a worker crashes make sure `_latestWorkerIdx` becomes 0. ### 2.5.4 -* `server.Room()`: Assign workers incrementally or explicitly via new `workerIdx` argument. -* Add `server.numWorkers` getter. - +- `server.Room()`: Assign workers incrementally or explicitly via new `workerIdx` argument. +- Add `server.numWorkers` getter. ### 2.5.3 -* Don't announce `muxId` nor RTP MID extension support in `Consumer` RTP parameters. - +- Don't announce `muxId` nor RTP MID extension support in `Consumer` RTP parameters. ### 2.5.2 -* Enable RTP MID extension again. - +- Enable RTP MID extension again. ### 2.5.1 -* Disable RTP MID extension until [#230](https://github.com/versatica/mediasoup/issues/230) is fixed. - +- Disable RTP MID extension until [#230](https://github.com/versatica/mediasoup/issues/230) is fixed. ### 2.5.0 -* Add RTP MID extension support. +- Add RTP MID extension support. ### 2.4.6 -* Do not close `Transport` on ICE disconnected (as it would prevent ICE restart on "recv" TCP transports). - +- Do not close `Transport` on ICE disconnected (as it would prevent ICE restart on "recv" TCP transports). ### 2.4.5 -* Improve codec matching. - +- Improve codec matching. ### 2.4.4 -* Fix audio codec matching when `channels` parameter is not given. - +- Fix audio codec matching when `channels` parameter is not given. ### 2.4.3 -* Make `PlainRtpTransport` not leak if port allocation fails (related issue [#224](https://github.com/versatica/mediasoup/issues/224)). - +- Make `PlainRtpTransport` not leak if port allocation fails (related issue [#224](https://github.com/versatica/mediasoup/issues/224)). ### 2.4.2 -* Fix a crash in when no more RTP ports were available (see related issue [#222](https://github.com/versatica/mediasoup/issues/222)). - +- Fix a crash in when no more RTP ports were available (see related issue [#222](https://github.com/versatica/mediasoup/issues/222)). ### 2.4.1 -* Update dependencies. - +- Update dependencies. ### 2.4.0 -* Allow non WebRTC peers to create plain RTP transports (no ICE/DTLS/SRTP but just plain RTP and RTCP) for sending and receiving media. - +- Allow non WebRTC peers to create plain RTP transports (no ICE/DTLS/SRTP but just plain RTP and RTCP) for sending and receiving media. ### 2.3.3 -* Fix C++ syntax to avoid an error when building the worker with clang 8.0.0 (OSX 10.11.6). - +- Fix C++ syntax to avoid an error when building the worker with clang 8.0.0 (OSX 10.11.6). ### 2.3.2 -* `Channel.js`: Upgrade `REQUEST_TIMEOUT` to 20 seconds to avoid timeout errors when the Node or worker thread usage is too high (related to this [issue](https://github.com/versatica/mediasoup-client/issues/48)). - +- `Channel.js`: Upgrade `REQUEST_TIMEOUT` to 20 seconds to avoid timeout errors when the Node or worker thread usage is too high (related to this [issue](https://github.com/versatica/mediasoup-client/issues/48)). ### 2.3.1 -* H264: Check if there is room for the indicated NAL unit size (thanks @ggarber). -* H264: Code cleanup. - +- H264: Check if there is room for the indicated NAL unit size (thanks @ggarber). +- H264: Code cleanup. ### 2.3.0 -* Add new "spy" feature. A "spy" peer cannot produce media and is invisible for other peers in the room. - +- Add new "spy" feature. A "spy" peer cannot produce media and is invisible for other peers in the room. ### 2.2.7 -* Fix H264 simulcast by properly detecting when the profile switching should be done. -* Fix a crash in `Consumer::GetStats()` (see related issue [#196](https://github.com/versatica/mediasoup/issues/196)). - +- Fix H264 simulcast by properly detecting when the profile switching should be done. +- Fix a crash in `Consumer::GetStats()` (see related issue [#196](https://github.com/versatica/mediasoup/issues/196)). ### 2.2.6 -* Add H264 simulcast capability. - +- Add H264 simulcast capability. ### 2.2.5 -* Avoid calling deprecated (NOOP) `SSL_CTX_set_ecdh_auto()` function in OpenSSL >= 1.1.0. - +- Avoid calling deprecated (NOOP) `SSL_CTX_set_ecdh_auto()` function in OpenSSL >= 1.1.0. ### 2.2.4 -* [Fix #4](https://github.com/versatica/mediasoup/issues/4): Avoid DTLS handshake fragmentation. - +- [Fix #4](https://github.com/versatica/mediasoup/issues/4): Avoid DTLS handshake fragmentation. ### 2.2.3 -* [Fix #196](https://github.com/versatica/mediasoup/issues/196): Crash in `Consumer::getStats()` due to wrong `targetProfile`. - +- [Fix #196](https://github.com/versatica/mediasoup/issues/196): Crash in `Consumer::getStats()` due to wrong `targetProfile`. ### 2.2.2 -* Improve [issue #209](https://github.com/versatica/mediasoup/issues/209). - +- Improve [issue #209](https://github.com/versatica/mediasoup/issues/209). ### 2.2.1 -* [Fix #209](https://github.com/versatica/mediasoup/issues/209): `DtlsTransport`: don't crash when signaled fingerprint and DTLS fingerprint do not match (thanks @yangjinecho for reporting it). - +- [Fix #209](https://github.com/versatica/mediasoup/issues/209): `DtlsTransport`: don't crash when signaled fingerprint and DTLS fingerprint do not match (thanks @yangjinecho for reporting it). ### 2.2.0 -* Update Node and C/C++ dependencies. - +- Update Node and C/C++ dependencies. ### 2.1.0 -* Add `localIP` option for `room.createRtpStreamer()` and `transport.startMirroring()` [[PR #199](https://github.com/versatica/mediasoup/pull/199)](https://github.com/versatica/mediasoup/pull/199). - +- Add `localIP` option for `room.createRtpStreamer()` and `transport.startMirroring()` [[PR #199](https://github.com/versatica/mediasoup/pull/199)](https://github.com/versatica/mediasoup/pull/199). ### 2.0.16 -* Improve C++ usage (remove "warning: missing initializer for member" [-Wmissing-field-initializers]). -* Update Travis-CI settings. - +- Improve C++ usage (remove "warning: missing initializer for member" [-Wmissing-field-initializers]). +- Update Travis-CI settings. ### 2.0.15 -* Make `PlainRtpTransport` also send RTCP SR/RR reports (thanks @artushin for reporting). - +- Make `PlainRtpTransport` also send RTCP SR/RR reports (thanks @artushin for reporting). ### 2.0.14 -* [Fix #193](https://github.com/versatica/mediasoup/issues/193): `preferTcp` not honored (thanks @artushin). - +- [Fix #193](https://github.com/versatica/mediasoup/issues/193): `preferTcp` not honored (thanks @artushin). ### 2.0.13 -* Avoid crash when no remote IP/port is given. - +- Avoid crash when no remote IP/port is given. ### 2.0.12 -* Add `handled` and `unhandled` events to `Consumer`. - +- Add `handled` and `unhandled` events to `Consumer`. ### 2.0.11 -* [Fix #185](https://github.com/versatica/mediasoup/issues/185): Consumer: initialize effective profile to 'NONE' (thanks @artushin). -* [Fix #186](https://github.com/versatica/mediasoup/issues/186): NackGenerator code being executed after instance deletion (thanks @baiyufei). - +- [Fix #185](https://github.com/versatica/mediasoup/issues/185): Consumer: initialize effective profile to 'NONE' (thanks @artushin). +- [Fix #186](https://github.com/versatica/mediasoup/issues/186): NackGenerator code being executed after instance deletion (thanks @baiyufei). ### 2.0.10 -* [Fix #183](https://github.com/versatica/mediasoup/issues/183): Always reset the effective `Consumer` profile when removed (thanks @thehappycoder). - +- [Fix #183](https://github.com/versatica/mediasoup/issues/183): Always reset the effective `Consumer` profile when removed (thanks @thehappycoder). ### 2.0.9 -* Make ICE+DTLS more flexible by allowing sending DTLS handshake when ICE is just connected. - +- Make ICE+DTLS more flexible by allowing sending DTLS handshake when ICE is just connected. ### 2.0.8 -* Disable stats periodic retrieval also on remote closure of `Producer` and `WebRtcTransport`. - +- Disable stats periodic retrieval also on remote closure of `Producer` and `WebRtcTransport`. ### 2.0.7 -* [Fix #180](https://github.com/versatica/mediasoup/issues/180): Added missing include `cmath` so that `std::round` can be used (thanks @jacobEAdamson). - +- [Fix #180](https://github.com/versatica/mediasoup/issues/180): Added missing include `cmath` so that `std::round` can be used (thanks @jacobEAdamson). ### 2.0.6 -* [Fix #173](https://github.com/versatica/mediasoup/issues/173): Avoid buffer overflow in `()` (thanks @lightmare). -* Improve stream layers management in `Consumer` by using the new `RtpMonitor` class. - +- [Fix #173](https://github.com/versatica/mediasoup/issues/173): Avoid buffer overflow in `()` (thanks @lightmare). +- Improve stream layers management in `Consumer` by using the new `RtpMonitor` class. ### 2.0.5 -* [Fix #164](https://github.com/versatica/mediasoup/issues/164): Sometimes video freezes forever (no RTP received in browser at all). -* [Fix #160](https://github.com/versatica/mediasoup/issues/160): Assert error in `RTC::Consumer::GetStats()`. - +- [Fix #164](https://github.com/versatica/mediasoup/issues/164): Sometimes video freezes forever (no RTP received in browser at all). +- [Fix #160](https://github.com/versatica/mediasoup/issues/160): Assert error in `RTC::Consumer::GetStats()`. ### 2.0.4 -* [Fix #159](https://github.com/versatica/mediasoup/issues/159): Donât rely on VP8 payload descriptor flags to assure the existence of data. -* [Fix #160](https://github.com/versatica/mediasoup/issues/160): Reset `targetProfile` when the corresponding profile is removed. - +- [Fix #159](https://github.com/versatica/mediasoup/issues/159): Donât rely on VP8 payload descriptor flags to assure the existence of data. +- [Fix #160](https://github.com/versatica/mediasoup/issues/160): Reset `targetProfile` when the corresponding profile is removed. ### 2.0.3 -* worker: Fix crash when VP8 payload has no `PictureId`. - +- worker: Fix crash when VP8 payload has no `PictureId`. ### 2.0.2 -* worker: Remove wrong `assert` on `Producer::DeactivateStreamProfiles()`. - +- worker: Remove wrong `assert` on `Producer::DeactivateStreamProfiles()`. ### 2.0.1 -* Update README file. - +- Update README file. ### 2.0.0 -* New design based on `Producers` and `Consumer` plus a mediasoup protocol and the **mediasoup-client** client side SDK. - +- New design based on `Producers` and `Consumer` plus a mediasoup protocol and the **mediasoup-client** client side SDK. ### 1.2.8 -* Fix a crash due to RTX packet processing while the associated `NackGenerator` is not yet created. - +- Fix a crash due to RTX packet processing while the associated `NackGenerator` is not yet created. ### 1.2.7 -* Habemus RTX ([RFC 4588](https://tools.ietf.org/html/rfc4588)) for proper RTP retransmission. - +- Habemus RTX ([RFC 4588](https://tools.ietf.org/html/rfc4588)) for proper RTP retransmission. ### 1.2.6 -* Fix an issue in `buffer.toString()` that makes mediasoup fail in Node 8. -* Update libuv to version 1.12.0. - +- Fix an issue in `buffer.toString()` that makes mediasoup fail in Node 8. +- Update libuv to version 1.12.0. ### 1.2.5 -* Add support for [ICE renomination](https://tools.ietf.org/html/draft-thatcher-ice-renomination). - +- Add support for [ICE renomination](https://tools.ietf.org/html/draft-thatcher-ice-renomination). ### 1.2.4 -* Fix a SDP negotiation issue when the remote peer does not have compatible codecs. - +- Fix a SDP negotiation issue when the remote peer does not have compatible codecs. ### 1.2.3 -* Add video codecs supported by Microsoft Edge. - +- Add video codecs supported by Microsoft Edge. ### 1.2.2 -* `RtpReceiver`: generate RTCP PLI when "rtpraw" or "rtpobject" event listener is set. - +- `RtpReceiver`: generate RTCP PLI when "rtpraw" or "rtpobject" event listener is set. ### 1.2.1 -* `RtpReceiver`: fix an error producing packets when "rtpobject" event is set. - +- `RtpReceiver`: fix an error producing packets when "rtpobject" event is set. ### 1.2.0 -* `RtpSender`: allow `disable()`/`enable()` without forcing SDP renegotiation (#114). - +- `RtpSender`: allow `disable()`/`enable()` without forcing SDP renegotiation (#114). ### 1.1.0 -* Add `Room.on('audiolevels')` event. - +- Add `Room.on('audiolevels')` event. ### 1.0.2 -* Set a maximum value of 1500 bytes for packet storage in `RtpStreamSend`. - +- Set a maximum value of 1500 bytes for packet storage in `RtpStreamSend`. ### 1.0.1 -* Avoid possible segfault if `RemoteBitrateEstimator` generates a bandwidth estimation with zero SSRCs. - +- Avoid possible segfault if `RemoteBitrateEstimator` generates a bandwidth estimation with zero SSRCs. ### 1.0.0 -* First stable release. +- First stable release. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000000..d6daf18d34 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,74 @@ +# Contributing to mediasoup + +Thanks for taking the time to contribute to mediasoup! đđ + +## License + +By contributing to mediasoup, you agree that your contributions will be licensed under its ISC License. + +## Reporting Bugs + +We primarily use GitHub as an issue tracker. Just open an issue in GitHub if you have encountered a bug in mediasoup. + +If you have questions or doubts about mediasoup or need support, please use the mediasoup Discourse Group instead: + +- https://mediasoup.discourse.group + +If you got a crash in mediasoup, please try to provide a core dump into the issue report: + +- https://mediasoup.org/support/#crashes-in-mediasoup-get-a-core-dump + +## Pull Request Process + +When creating a Pull Request for mediasoup: + +- Ensure that changes/additions done in TypeScript files (in `node/src` folder) are also applied to the Rust layer (in `rust` folder), and vice-versa. +- Test units must be added for both Node.js and Rust. +- Changes/additions in C++ code may need tests in `worker/test` folder. + +Once all changes are done, run the following commands to verify that the code in your PR conforms to the code syntax of the project, it does not break existing funtionality and tests pass: + +- `npm run lint`: Check TypeScript and C++ linting rules. Formating errors can be automatically fixed by running `npm run format`. +- `npm run typescript:build`: Compile TypeScript code (under `src` folder) into JavaScript code (under `lib` folder). +- `npm run test`: Run JavaScript and C++ test units. +- Instead, you can run `npm run release:check` which will run all those steps. +- `cargo fmt`, `cargo clippy` and `cargo test` to ensure that everything is good in Rust side. + +The full list of `npm` scripts, `invoke` tasks and `cargo` commands is available in the [doc/Building.md](/doc/Building.md) file. + +## Coding Style + +In adition to automatic checks performed by commands above, we also enforce other minor things related to coding style: + +### Comments in TypeScript and C++ + +We use `//` for inline comments in both JavaScript and C++ source files. + +- Comments must start with upercase letter. +- Comments must not exceed 80 columns (split into different lines if necessary). +- Comments must end with a dot. + +Example (good): + +```ts +// Calculate foo based on bar value. +const foo = bar / 2; +``` + +Example (bad): + +```ts +// calculate foo based on bar value +const foo = bar / 2; +``` + +When adding inline documentation for methods or functions, we use `/** */` syntax. Example: + +```ts +/** + * Calculates current score for foo and bar. + */ +function calculateScore(): number { + // [...] +} +``` diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000000..5cf4281fc2 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,2656 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "actix" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cba56612922b907719d4a01cf11c8d5b458e7d3dba946d0435f20f58d6795ed2" +dependencies = [ + "actix-macros", + "actix-rt", + "actix_derive", + "bitflags 2.4.0", + "bytes", + "crossbeam-channel", + "futures-core", + "futures-sink", + "futures-task", + "futures-util", + "log", + "once_cell", + "parking_lot 0.12.1", + "pin-project-lite", + "smallvec", + "tokio", + "tokio-util", +] + +[[package]] +name = "actix-codec" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "617a8268e3537fe1d8c9ead925fca49ef6400927ee7bc26750e90ecee14ce4b8" +dependencies = [ + "bitflags 1.3.2", + "bytes", + "futures-core", + "futures-sink", + "memchr", + "pin-project-lite", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "actix-http" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a92ef85799cba03f76e4f7c10f533e66d87c9a7e7055f3391f09000ad8351bc9" +dependencies = [ + "actix-codec", + "actix-rt", + "actix-service", + "actix-utils", + "ahash 0.8.3", + "base64", + "bitflags 2.4.0", + "bytes", + "bytestring", + "derive_more", + "encoding_rs", + "futures-core", + "http", + "httparse", + "httpdate", + "itoa", + "language-tags", + "local-channel", + "mime", + "percent-encoding", + "pin-project-lite", + "rand 0.8.5", + "sha1", + "smallvec", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "actix-macros" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01ed3140b2f8d422c68afa1ed2e85d996ea619c988ac834d255db32138655cb" +dependencies = [ + "quote", + "syn 2.0.32", +] + +[[package]] +name = "actix-router" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66ff4d247d2b160861fa2866457e85706833527840e4133f8f49aa423a38799" +dependencies = [ + "bytestring", + "http", + "regex", + "serde", + "tracing", +] + +[[package]] +name = "actix-rt" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28f32d40287d3f402ae0028a9d54bef51af15c8769492826a69d28f81893151d" +dependencies = [ + "futures-core", + "tokio", +] + +[[package]] +name = "actix-server" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eb13e7eef0423ea6eab0e59f6c72e7cb46d33691ad56a726b3cd07ddec2c2d4" +dependencies = [ + "actix-rt", + "actix-service", + "actix-utils", + "futures-core", + "futures-util", + "mio", + "socket2 0.5.3", + "tokio", + "tracing", +] + +[[package]] +name = "actix-service" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b894941f818cfdc7ccc4b9e60fa7e53b5042a2e8567270f9147d5591893373a" +dependencies = [ + "futures-core", + "paste 1.0.14", + "pin-project-lite", +] + +[[package]] +name = "actix-utils" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88a1dcdff1466e3c2488e1cb5c36a71822750ad43839937f85d2f4d9f8b705d8" +dependencies = [ + "local-waker", + "pin-project-lite", +] + +[[package]] +name = "actix-web" +version = "4.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e4a5b5e29603ca8c94a77c65cf874718ceb60292c5a5c3e5f4ace041af462b9" +dependencies = [ + "actix-codec", + "actix-http", + "actix-macros", + "actix-router", + "actix-rt", + "actix-server", + "actix-service", + "actix-utils", + "actix-web-codegen", + "ahash 0.8.3", + "bytes", + "bytestring", + "cfg-if", + "derive_more", + "encoding_rs", + "futures-core", + "futures-util", + "itoa", + "language-tags", + "log", + "mime", + "once_cell", + "pin-project-lite", + "regex", + "serde", + "serde_json", + "serde_urlencoded", + "smallvec", + "socket2 0.5.3", + "time", + "url", +] + +[[package]] +name = "actix-web-actors" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf6e9ccc371cfddbed7aa842256a4abc7a6dcac9f3fce392fe1d0f68cfd136b2" +dependencies = [ + "actix", + "actix-codec", + "actix-http", + "actix-web", + "bytes", + "bytestring", + "futures-core", + "pin-project-lite", + "tokio", + "tokio-util", +] + +[[package]] +name = "actix-web-codegen" +version = "4.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb1f50ebbb30eca122b188319a4398b3f7bb4a8cdf50ecfb73bfc6a3c3ce54f5" +dependencies = [ + "actix-router", + "proc-macro2", + "quote", + "syn 2.0.32", +] + +[[package]] +name = "actix_derive" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c7db3d5a9718568e4cf4a537cfd7070e6e6ff7481510d0237fb529ac850f6d3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.32", +] + +[[package]] +name = "addr2line" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "ahash" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a824f2aa7e75a0c98c5a504fceb80649e9c35265d44525b5f94de4771a395cd" +dependencies = [ + "getrandom 0.2.10", + "once_cell", + "version_check", +] + +[[package]] +name = "ahash" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" +dependencies = [ + "cfg-if", + "getrandom 0.2.10", + "once_cell", + "version_check", +] + +[[package]] +name = "aho-corasick" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c378d78423fdad8089616f827526ee33c19f2fddbd5de1629152c9593ba4783" +dependencies = [ + "memchr", +] + +[[package]] +name = "anes" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" + +[[package]] +name = "array-init-cursor" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf7d0a018de4f6aa429b9d33d69edf69072b1c5b1cb8d3e4a5f7ef898fc3eb76" + +[[package]] +name = "ascii-canvas" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8824ecca2e851cec16968d54a01dd372ef8f95b244fb84b84e70128be347c3c6" +dependencies = [ + "term", +] + +[[package]] +name = "askama" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb98f10f371286b177db5eeb9a6e5396609555686a35e1d4f7b9a9c6d8af0139" +dependencies = [ + "askama_derive", + "askama_escape", + "askama_shared", +] + +[[package]] +name = "askama_derive" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87bf87e6e8b47264efa9bde63d6225c6276a52e05e91bf37eaa8afd0032d6b71" +dependencies = [ + "askama_shared", + "proc-macro2", + "syn 1.0.109", +] + +[[package]] +name = "askama_escape" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "619743e34b5ba4e9703bba34deac3427c72507c7159f5fd030aea8cac0cfe341" + +[[package]] +name = "askama_shared" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf722b94118a07fcbc6640190f247334027685d4e218b794dbfe17c32bf38ed0" +dependencies = [ + "askama_escape", + "humansize", + "mime", + "mime_guess", + "nom", + "num-traits", + "percent-encoding", + "proc-macro2", + "quote", + "serde", + "syn 1.0.109", + "toml", +] + +[[package]] +name = "async-channel" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" +dependencies = [ + "concurrent-queue", + "event-listener", + "futures-core", +] + +[[package]] +name = "async-executor" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fa3dc5f2a8564f07759c008b9109dc0d39de92a88d5588b8a5036d286383afb" +dependencies = [ + "async-lock", + "async-task", + "concurrent-queue", + "fastrand", + "futures-lite", + "slab", +] + +[[package]] +name = "async-io" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af" +dependencies = [ + "async-lock", + "autocfg", + "cfg-if", + "concurrent-queue", + "futures-lite", + "log", + "parking", + "polling", + "rustix 0.37.27", + "slab", + "socket2 0.4.9", + "waker-fn", +] + +[[package]] +name = "async-lock" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "287272293e9d8c41773cec55e365490fe034813a2f172f502d6ddcf75b2f582b" +dependencies = [ + "event-listener", +] + +[[package]] +name = "async-oneshot" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ec7c75bcbcb0139e9177f30692fd617405ca4e0c27802e128d53171f7042e2c" +dependencies = [ + "futures-micro", +] + +[[package]] +name = "async-task" +version = "4.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc7ab41815b3c653ccd2978ec3255c81349336702dfdf62ee6f7069b12a3aae" + +[[package]] +name = "async-trait" +version = "0.1.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc00ceb34980c03614e35a3a4e218276a0a824e911d07651cd0d858a51e8c0f0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.32", +] + +[[package]] +name = "atomic-take" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8ab6b55fe97976e46f91ddbed8d147d966475dc29b2032757ba47e02376fbc3" + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi 0.1.19", + "libc", + "winapi", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "backtrace" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "base64" +version = "0.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ba43ea6f343b788c8764558649e08df62f86c6ef251fdaeb1ffd010a9ae50a2" + +[[package]] +name = "beef" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a8241f3ebb85c056b509d4327ad0358fbbba6ffb340bf388f26350aeda225b1" + +[[package]] +name = "bit-set" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" + +[[package]] +name = "bitpattern" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fc436c543b5fd522e5428d65c88e7bfb538ba267dad95cfb9d8fb8a465b0935" +dependencies = [ + "paste 0.1.18", + "proc-macro2", + "quote", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" + +[[package]] +name = "bytes" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" + +[[package]] +name = "bytestring" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "238e4886760d98c4f899360c834fa93e62cf7f721ac3c2da375cbdf4b8679aae" +dependencies = [ + "bytes", +] + +[[package]] +name = "cast" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" + +[[package]] +name = "cc" +version = "1.0.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +dependencies = [ + "libc", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "ciborium" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "effd91f6c78e5a4ace8a5d3c0b6bfaec9e2baaef55f3efc00e45fb2e477ee926" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdf919175532b369853f5d5e20b26b43112613fd6fe7aee757e35f7a44642656" + +[[package]] +name = "ciborium-ll" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "defaa24ecc093c77630e6c15e17c51f5e187bf35ee514f4e2d67baaa96dae22b" +dependencies = [ + "ciborium-io", + "half", +] + +[[package]] +name = "clap" +version = "3.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123" +dependencies = [ + "bitflags 1.3.2", + "clap_lex", + "indexmap 1.9.3", + "textwrap", +] + +[[package]] +name = "clap_lex" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" +dependencies = [ + "os_str_bytes", +] + +[[package]] +name = "codespan" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3362992a0d9f1dd7c3d0e89e0ab2bb540b7a95fea8cd798090e758fda2899b5e" +dependencies = [ + "codespan-reporting", +] + +[[package]] +name = "codespan-reporting" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" +dependencies = [ + "termcolor", + "unicode-width", +] + +[[package]] +name = "concurrent-queue" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62ec6771ecfa0762d24683ee5a32ad78487a3d3afdc0fb8cae19d2c5deb50b7c" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + +[[package]] +name = "cpufeatures" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1" +dependencies = [ + "libc", +] + +[[package]] +name = "criterion" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c76e09c1aae2bc52b3d2f29e13c6572553b30c4aa1b8a49fd70de6412654cb" +dependencies = [ + "anes", + "atty", + "cast", + "ciborium", + "clap", + "criterion-plot", + "itertools", + "lazy_static", + "num-traits", + "oorandom", + "plotters", + "rayon", + "regex", + "serde", + "serde_derive", + "serde_json", + "tinytemplate", + "walkdir", +] + +[[package]] +name = "criterion-plot" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" +dependencies = [ + "cast", + "itertools", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200" +dependencies = [ + "cfg-if", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef" +dependencies = [ + "cfg-if", + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7" +dependencies = [ + "autocfg", + "cfg-if", + "crossbeam-utils", + "memoffset", + "scopeguard", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "deranged" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2696e8a945f658fd14dc3b87242e6b80cd0f36ff04ea560fa39082368847946" + +[[package]] +name = "derive_more" +version = "0.99.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version", + "syn 1.0.109", +] + +[[package]] +name = "diff" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "dirs-next" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" +dependencies = [ + "cfg-if", + "dirs-sys-next", +] + +[[package]] +name = "dirs-sys-next" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + +[[package]] +name = "either" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" + +[[package]] +name = "ena" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c533630cf40e9caa44bd91aadc88a75d75a4c3a12b4cfde353cbed41daa1e1f1" +dependencies = [ + "log", +] + +[[package]] +name = "encoding_rs" +version = "0.8.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "env_logger" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a12e6657c4c97ebab115a42dcee77225f7f482cdd841cf7088c657a42e9e00e7" +dependencies = [ + "atty", + "humantime", + "log", + "regex", + "termcolor", +] + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "errno" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "136526188508e25c6fef639d7927dfb3e0e3084488bf202267829cf7fc23dbdd" +dependencies = [ + "errno-dragonfly", + "libc", + "windows-sys", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "event-listener" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" + +[[package]] +name = "event-listener-primitives" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5384093b4255393cc8c42f0fc3dd4f19977ad3b1025f968257854895a993028c" +dependencies = [ + "nohash-hasher", + "parking_lot 0.11.2", + "smallvec", +] + +[[package]] +name = "eyre" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c2b6b5a29c02cdc822728b7d7b8ae1bab3e3b05d44522770ddd49722eeac7eb" +dependencies = [ + "indenter", + "once_cell", +] + +[[package]] +name = "fastrand" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" +dependencies = [ + "instant", +] + +[[package]] +name = "fixedbitset" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "form_urlencoded" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures-core" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" + +[[package]] +name = "futures-io" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" + +[[package]] +name = "futures-lite" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "memchr", + "parking", + "pin-project-lite", + "waker-fn", +] + +[[package]] +name = "futures-micro" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b460264b3593d68b16a7bc35f7bc226ddfebdf9a1c8db1ed95d5cc6b7168c826" +dependencies = [ + "pin-project-lite", +] + +[[package]] +name = "futures-sink" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" + +[[package]] +name = "futures-task" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" + +[[package]] +name = "futures-util" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "pin-utils", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.9.0+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", +] + +[[package]] +name = "gimli" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" + +[[package]] +name = "h264-profile-level-id" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45d0dbf6a9847b64c9b06938660891f983434dcc009b5ae6a58f5e34af74a649" +dependencies = [ + "bitpattern", + "log", + "thiserror", +] + +[[package]] +name = "half" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" + +[[package]] +name = "hash_hasher" +version = "2.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74721d007512d0cb3338cd20f0654ac913920061a4c4d0d8708edb3f2a698c0c" + +[[package]] +name = "hashbrown" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" +dependencies = [ + "ahash 0.7.7", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hashbrown" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" +dependencies = [ + "ahash 0.8.3", +] + +[[package]] +name = "hashbrown" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f93e7192158dbcda357bdec5fb5788eebf8bbac027f3f33e719d29135ae84156" + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "hermit-abi" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" + +[[package]] +name = "http" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "httparse" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "humansize" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02296996cb8796d7c6e3bc2d9211b7802812d36999a51bb754123ead7d37d026" + +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + +[[package]] +name = "idna" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "indenter" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", +] + +[[package]] +name = "indexmap" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8adf3ddd720272c6ea8bf59463c04e0f93d0bbf7c5439b691bca2987e0270897" +dependencies = [ + "equivalent", + "hashbrown 0.14.2", +] + +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "io-lifetimes" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" +dependencies = [ + "hermit-abi 0.3.2", + "libc", + "windows-sys", +] + +[[package]] +name = "is-terminal" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" +dependencies = [ + "hermit-abi 0.3.2", + "rustix 0.38.13", + "windows-sys", +] + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" + +[[package]] +name = "js-sys" +version = "0.3.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "lalrpop" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da4081d44f4611b66c6dd725e6de3169f9f63905421e8626fcb86b6a898998b8" +dependencies = [ + "ascii-canvas", + "bit-set", + "diff", + "ena", + "is-terminal", + "itertools", + "lalrpop-util", + "petgraph", + "pico-args", + "regex", + "regex-syntax 0.7.5", + "string_cache", + "term", + "tiny-keccak", + "unicode-xid", +] + +[[package]] +name = "lalrpop-util" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f35c735096c0293d313e8f2a641627472b83d01b937177fe76e5e2708d31e0d" +dependencies = [ + "regex", +] + +[[package]] +name = "language-tags" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388" + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.153" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" + +[[package]] +name = "linux-raw-sys" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" + +[[package]] +name = "linux-raw-sys" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da2479e8c062e40bf0066ffa0bc823de0a9368974af99c9f6df941d2c231e03f" + +[[package]] +name = "local-channel" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f303ec0e94c6c54447f84f3b0ef7af769858a9c4ef56ef2a986d3dcd4c3fc9c" +dependencies = [ + "futures-core", + "futures-sink", + "futures-util", + "local-waker", +] + +[[package]] +name = "local-waker" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e34f76eb3611940e0e7d53a9aaa4e6a3151f69541a282fd0dad5571420c53ff1" + +[[package]] +name = "lock_api" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" +dependencies = [ + "autocfg", + "scopeguard", + "serde", +] + +[[package]] +name = "log" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" + +[[package]] +name = "logos" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf8b031682c67a8e3d5446840f9573eb7fe26efe7ec8d195c9ac4c0647c502f1" +dependencies = [ + "logos-derive", +] + +[[package]] +name = "logos-derive" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d849148dbaf9661a6151d1ca82b13bb4c4c128146a88d05253b38d4e2f496c" +dependencies = [ + "beef", + "fnv", + "proc-macro2", + "quote", + "regex-syntax 0.6.29", + "syn 1.0.109", +] + +[[package]] +name = "lru" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6e8aaa3f231bb4bd57b84b2d5dc3ae7f350265df8aa96492e0bc394a1571909" + +[[package]] +name = "mediasoup" +version = "0.17.1" +dependencies = [ + "actix", + "actix-web", + "actix-web-actors", + "async-channel", + "async-executor", + "async-io", + "async-lock", + "async-oneshot", + "async-trait", + "atomic-take", + "criterion", + "env_logger", + "event-listener-primitives", + "fastrand", + "futures-lite", + "h264-profile-level-id", + "hash_hasher", + "log", + "lru", + "mediasoup-sys", + "nohash-hasher", + "once_cell", + "parking_lot 0.12.1", + "planus", + "portpicker", + "regex", + "serde", + "serde_json", + "serde_repr", + "thiserror", + "uuid", +] + +[[package]] +name = "mediasoup-sys" +version = "0.9.1" +dependencies = [ + "planus", + "planus-codegen", + "planus-translation", + "serde", +] + +[[package]] +name = "memchr" +version = "2.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c" + +[[package]] +name = "memoffset" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" +dependencies = [ + "autocfg", +] + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "mime_guess" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef" +dependencies = [ + "mime", + "unicase", +] + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +dependencies = [ + "adler", +] + +[[package]] +name = "mio" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" +dependencies = [ + "libc", + "log", + "wasi 0.11.0+wasi-snapshot-preview1", + "windows-sys", +] + +[[package]] +name = "new_debug_unreachable" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54" + +[[package]] +name = "nohash-hasher" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "num-traits" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +dependencies = [ + "hermit-abi 0.3.2", + "libc", +] + +[[package]] +name = "object" +version = "0.32.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" + +[[package]] +name = "oorandom" +version = "11.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" + +[[package]] +name = "os_str_bytes" +version = "6.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d5d9eb14b174ee9aa2ef96dc2b94637a2d4b6e7cb873c7e171f0c20c6cf3eac" + +[[package]] +name = "parking" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14f2252c834a40ed9bb5422029649578e63aa341ac401f74e719dd1afda8394e" + +[[package]] +name = "parking_lot" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" +dependencies = [ + "instant", + "lock_api", + "parking_lot_core 0.8.6", +] + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core 0.9.8", +] + +[[package]] +name = "parking_lot_core" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc" +dependencies = [ + "cfg-if", + "instant", + "libc", + "redox_syscall 0.2.16", + "smallvec", + "winapi", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall 0.3.5", + "smallvec", + "windows-targets", +] + +[[package]] +name = "paste" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45ca20c77d80be666aef2b45486da86238fabe33e38306bd3118fe4af33fa880" +dependencies = [ + "paste-impl", + "proc-macro-hack", +] + +[[package]] +name = "paste" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" + +[[package]] +name = "paste-impl" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d95a7db200b97ef370c8e6de0088252f7e0dfff7d047a28528e47456c0fc98b6" +dependencies = [ + "proc-macro-hack", +] + +[[package]] +name = "percent-encoding" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" + +[[package]] +name = "petgraph" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9" +dependencies = [ + "fixedbitset", + "indexmap 2.0.2", +] + +[[package]] +name = "phf_shared" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pico-args" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" + +[[package]] +name = "pin-project-lite" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "planus" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cbec589fda965e8d0cf7dddaa87d37ae5ccaed0c43986e8faf5998f24ef82f5" +dependencies = [ + "array-init-cursor", + "hashbrown 0.13.2", +] + +[[package]] +name = "planus-codegen" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52d1bb11066c61d46d6d8f21d342d300f06d08bcce15f88399fdd40e1341c421" +dependencies = [ + "askama", + "eyre", + "heck", + "planus-types", + "random_color", + "thiserror", + "vec_map", +] + +[[package]] +name = "planus-lexer" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ecb00082b1324541f02f323585333e88ed3bf90cdbad3741e52eabf166fbcd6" +dependencies = [ + "codespan", + "derive_more", + "logos", +] + +[[package]] +name = "planus-translation" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7bd8ffea859844a23ea28d58354e9e466f3454981b368ba7d8226edac50398b" +dependencies = [ + "atty", + "bitflags 2.4.0", + "codespan", + "codespan-reporting", + "indexmap 1.9.3", + "lalrpop", + "lalrpop-util", + "num-traits", + "planus-lexer", + "planus-types", + "string-interner", +] + +[[package]] +name = "planus-types" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10306bda3579cc7a43537cb3f81212d93ce4fea32a219d9c0a7788d8c5d8e114" +dependencies = [ + "codespan", + "indexmap 1.9.3", + "lalrpop-util", + "planus-lexer", + "string-interner", +] + +[[package]] +name = "plotters" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2c224ba00d7cadd4d5c660deaf2098e5e80e07846537c51f9cfa4be50c1fd45" +dependencies = [ + "num-traits", + "plotters-backend", + "plotters-svg", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "plotters-backend" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e76628b4d3a7581389a35d5b6e2139607ad7c75b17aed325f210aa91f4a9609" + +[[package]] +name = "plotters-svg" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38f6d39893cca0701371e3c27294f09797214b86f1fb951b89ade8ec04e2abab" +dependencies = [ + "plotters-backend", +] + +[[package]] +name = "polling" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce" +dependencies = [ + "autocfg", + "bitflags 1.3.2", + "cfg-if", + "concurrent-queue", + "libc", + "log", + "pin-project-lite", + "windows-sys", +] + +[[package]] +name = "portpicker" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be97d76faf1bfab666e1375477b23fde79eccf0276e9b63b92a39d676a889ba9" +dependencies = [ + "rand 0.8.5", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "precomputed-hash" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" + +[[package]] +name = "proc-macro-hack" +version = "0.5.20+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" + +[[package]] +name = "proc-macro2" +version = "1.0.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +dependencies = [ + "getrandom 0.1.16", + "libc", + "rand_chacha 0.2.2", + "rand_core 0.5.1", + "rand_hc", + "rand_pcg", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +dependencies = [ + "ppv-lite86", + "rand_core 0.5.1", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom 0.1.16", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.10", +] + +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +dependencies = [ + "rand_core 0.5.1", +] + +[[package]] +name = "rand_pcg" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429" +dependencies = [ + "rand_core 0.5.1", +] + +[[package]] +name = "random_color" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5f34bd6526786b2ce5141fd37a4084b5da1ebae74595b5b0d05482a7cef7181" +dependencies = [ + "rand 0.7.3", +] + +[[package]] +name = "rayon" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d2df5196e37bcc87abebc0053e20787d73847bb33134a69841207dd0a47f03b" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b8f95bd6966f5c87776639160a66bd8ab9895d9d4ab01ddba9fc60661aebe8d" +dependencies = [ + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-utils", + "num_cpus", +] + +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "redox_syscall" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "redox_users" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" +dependencies = [ + "getrandom 0.2.10", + "redox_syscall 0.2.16", + "thiserror", +] + +[[package]] +name = "regex" +version = "1.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "697061221ea1b4a94a624f67d0ae2bfe4e22b8a17b6a192afb11046542cc8c47" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax 0.7.5", +] + +[[package]] +name = "regex-automata" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2f401f4955220693b56f8ec66ee9c78abffd8d1c4f23dc41a23839eb88f0795" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax 0.7.5", +] + +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + +[[package]] +name = "regex-syntax" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" + +[[package]] +name = "rustc-demangle" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" + +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "0.37.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fea8ca367a3a01fe35e6943c400addf443c0f57670e6ec51196f71a4b8762dd2" +dependencies = [ + "bitflags 1.3.2", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys 0.3.8", + "windows-sys", +] + +[[package]] +name = "rustix" +version = "0.38.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7db8590df6dfcd144d22afd1b83b36c21a18d7cbc1dc4bb5295a8712e9eb662" +dependencies = [ + "bitflags 2.4.0", + "errno", + "libc", + "linux-raw-sys 0.4.10", + "windows-sys", +] + +[[package]] +name = "rustversion" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" + +[[package]] +name = "ryu" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "semver" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918" + +[[package]] +name = "serde" +version = "1.0.190" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91d3c334ca1ee894a2c6f6ad698fe8c435b76d504b13d436f0685d648d6d96f7" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.190" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67c5609f394e5c2bd7fc51efda478004ea80ef42fee983d5c67a65e34f32c0e3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.32", +] + +[[package]] +name = "serde_json" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cc66a619ed80bf7a0f6b17dd063a84b88f6dea1813737cf469aef1d081142c2" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_repr" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8725e1dfadb3a50f7e5ce0b1a540466f6ed3fe7a0fca2ac2b8b831d31316bd00" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.32", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha1" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +dependencies = [ + "libc", +] + +[[package]] +name = "siphasher" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9" + +[[package]] +name = "socket2" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "socket2" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2538b18701741680e0322a2302176d3253a35388e2e62f172f64f4f16605f877" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "string-interner" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e2531d8525b29b514d25e275a43581320d587b86db302b9a7e464bac579648" +dependencies = [ + "cfg-if", + "hashbrown 0.11.2", + "serde", +] + +[[package]] +name = "string_cache" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f91138e76242f575eb1d3b38b4f1362f10d3a43f47d182a5b359af488a02293b" +dependencies = [ + "new_debug_unreachable", + "once_cell", + "parking_lot 0.12.1", + "phf_shared", + "precomputed-hash", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "239814284fd6f1a4ffe4ca893952cdd93c224b6a1571c9a9eadd670295c0c9e2" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "term" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f" +dependencies = [ + "dirs-next", + "rustversion", + "winapi", +] + +[[package]] +name = "termcolor" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "textwrap" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" + +[[package]] +name = "thiserror" +version = "1.0.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d6d7a740b8a666a7e828dd00da9c0dc290dff53154ea77ac109281de90589b7" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49922ecae66cc8a249b77e68d1d0623c1b2c514f0060c27cdc68bd62a1219d35" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.32", +] + +[[package]] +name = "time" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17f6bb557fd245c28e6411aa56b6403c689ad95061f50e4be16c274e70a17e48" +dependencies = [ + "deranged", + "itoa", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb" + +[[package]] +name = "time-macros" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a942f44339478ef67935ab2bbaec2fb0322496cf3cbe84b261e06ac3814c572" +dependencies = [ + "time-core", +] + +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + +[[package]] +name = "tinytemplate" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17ed6077ed6cd6c74735e21f37eb16dc3935f96878b1fe961074089cc80893f9" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "parking_lot 0.12.1", + "pin-project-lite", + "signal-hook-registry", + "socket2 0.5.3", + "windows-sys", +] + +[[package]] +name = "tokio-util" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "806fe8c2c87eccc8b3267cbae29ed3ab2d0bd37fca70ab622e46aaa9375ddb7d" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", + "tracing", +] + +[[package]] +name = "toml" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +dependencies = [ + "serde", +] + +[[package]] +name = "tracing" +version = "0.1.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" +dependencies = [ + "cfg-if", + "log", + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a" +dependencies = [ + "once_cell", +] + +[[package]] +name = "typenum" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" + +[[package]] +name = "unicase" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" +dependencies = [ + "version_check", +] + +[[package]] +name = "unicode-bidi" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" + +[[package]] +name = "unicode-ident" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" + +[[package]] +name = "unicode-normalization" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-width" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" + +[[package]] +name = "unicode-xid" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" + +[[package]] +name = "url" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "uuid" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79daa5ed5740825c40b389c5e50312b9c86df53fccd33f281df655642b43869d" +dependencies = [ + "getrandom 0.2.10", + "serde", +] + +[[package]] +name = "vec_map" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "waker-fn" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" + +[[package]] +name = "walkdir" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.32", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.32", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" + +[[package]] +name = "web-sys" +version = "0.3.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" diff --git a/Cargo.toml b/Cargo.toml index efb0f433ee..fac71da63a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,4 +1,5 @@ [workspace] +resolver = "2" members = [ "rust", "worker" diff --git a/README.md b/README.md index aa709787ab..28b1204556 100644 --- a/README.md +++ b/README.md @@ -10,58 +10,51 @@ [![][codeql-shield-mediasoup]][codeql-mediasoup] [![][opencollective-shield-mediasoup]][opencollective-mediasoup] - ## Website and Documentation -* [mediasoup.org][mediasoup-website] - +- [mediasoup.org][mediasoup-website] ## Support Forum -* [mediasoup.discourse.group][mediasoup-discourse] - +- [mediasoup.discourse.group][mediasoup-discourse] ## Design Goals mediasoup and its client side libraries are designed to accomplish with the following goals: -* Be a [SFU](https://webrtcglossary.com/sfu/) (Selective Forwarding Unit). -* Support both WebRTC and plain RTP input and output. -* Be a Node.js module/Rust crate in server side. -* Be a tiny JavaScript and C++ libraries in client side. -* Be minimalist: just handle the media layer. -* Be signaling agnostic: do not mandate any signaling protocol. -* Be super low level API. -* Support all existing WebRTC endpoints. -* Enable integration with well known multimedia libraries/tools. - +- Be a [SFU](https://webrtcglossary.com/sfu) (Selective Forwarding Unit). +- Support both WebRTC and plain RTP input and output. +- Be a Node.js module or Rust crate in server side. +- Be a tiny TypeScript and C++ libraries in client side. +- Be minimalist: just handle the media layer. +- Be signaling agnostic: do not mandate any signaling protocol. +- Be super low level API. +- Support all existing WebRTC endpoints. +- Enable integration with well known multimedia libraries/tools. ## Architecture ![][mediasoup-architecture] - ## Use Cases mediasoup and its client side libraries provide a super low level API. They are intended to enable different use cases and scenarios, without any constraint or assumption. Some of these use cases are: -* Group video chat applications. -* One-to-many (or few-to-many) broadcasting applications in real-time. -* RTP streaming. - +- Group video chat applications. +- One-to-many (or few-to-many) broadcasting applications in real-time. +- RTP streaming. ## Features -* ECMAScript 6/Idiomatic Rust low level API. -* Multi-stream: multiple audio/video streams over a single ICE + DTLS transport. -* IPv6 ready. -* ICE / DTLS / RTP / RTCP over UDP and TCP. -* Simulcast and SVC support. -* Congestion control. -* Sender and receiver bandwidth estimation with spatial/temporal layers distribution algorithm. -* Data message exchange (via WebRTC DataChannels, SCTP over plain UDP, and direct termination in Node.js/Rust). -* Extremely powerful (media worker thread/subprocess coded in C++ on top of [libuv](https://libuv.org)). - +- ECMAScript 6/Idiomatic Rust low level API. +- Multi-stream: multiple audio/video streams over a single ICE + DTLS transport. +- IPv6 ready. +- ICE / DTLS / RTP / RTCP over UDP and TCP. +- Simulcast and SVC support. +- Congestion control. +- Sender and receiver bandwidth estimation with spatial/temporal layers distribution algorithm. +- Data message exchange (via WebRTC DataChannels, SCTP over plain UDP, and direct termination in Node.js/Rust). +- Extremely powerful (media worker thread/subprocess coded in C++ on top of [libuv](https://libuv.org)). ## Demo Online @@ -69,31 +62,24 @@ mediasoup and its client side libraries provide a super low level API. They are Try it at [v3demo.mediasoup.org](https://v3demo.mediasoup.org) ([source code](https://github.com/versatica/mediasoup-demo)). - ## Authors -* IÃąaki Baz Castillo [[website](https://inakibaz.me)|[github](https://github.com/ibc/)] -* JosÊ Luis MillÃĄn [[github](https://github.com/jmillan/)] -* Nazar Mokynskyi [[github](https://github.com/nazar-pc/)] - +- IÃąaki Baz Castillo [[website](https://inakibaz.me)|[github](https://github.com/ibc/)] +- JosÊ Luis MillÃĄn [[github](https://github.com/jmillan/)] +- Nazar Mokynskyi [[github](https://github.com/nazar-pc/)] ## Social -* Twitter: [@mediasoup_sfu](https://twitter.com/mediasoup_sfu) - +- Twitter: [@mediasoup_sfu](https://twitter.com/mediasoup_sfu) ## Sponsor You can support mediasoup by [sponsoring][sponsor] it. Thanks! - ## License [ISC](./LICENSE) - - - [mediasoup-banner]: /art/mediasoup-banner.png [mediasoup-website]: https://mediasoup.org [mediasoup-discourse]: https://mediasoup.discourse.group diff --git a/art/mediasoup-v3-architecture-01.svg b/art/mediasoup-v3-architecture-01.svg index 9613ddb17f..89966d0f70 100644 --- a/art/mediasoup-v3-architecture-01.svg +++ b/art/mediasoup-v3-architecture-01.svg @@ -1,2 +1,2 @@ -HostHostWorker 1Worker 1WebRtcTransportWebRtcTransportAudio ProducerAudio ProducerVideo ProducerVideo ProducerPlainRtpTransportPlainRtpTransportVideo ProducerVideo ProducerPipeTransportPipeTransportVideo ConsumerVideo ConsumerSRTPSRTPParticipant(mic/webcam on)Participant<br>(mic/webcam on)<br>Participant(viewer)[Not supported by viewer]RTPFFmpeg(recording)[Not supported by viewer]GStreamer(mp4 broadcaster)GStreamer<br>(mp4 broadcaster)<br>RTPRouter 2Router 2Router 1Router 1Worker 2Worker 2PipeTransportPipeTransportVideo ConsumerVideo ConsumerRouter 3Router 3PipeTransportPipeTransportVideo ProducerVideo ProducerWorker 3Worker 3PipeTransportPipeTransportVideo ProducerVideo ProducerRouter 4Router 4RTPRTPParticipant(viewer)[Not supported by viewer]Participant(viewer)[Not supported by viewer]SRTPSRTPSRTPParticipant(viewer)[Not supported by viewer]WebRtcTransportWebRtcTransportVideo ConsumerVideo ConsumerWebRtcTransportWebRtcTransportVideo ConsumerVideo ConsumerWebRtcTransportWebRtcTransportVideo ConsumerVideo ConsumerWebRtcTransportWebRtcTransportVideo ConsumerVideo ConsumerParticipant(viewer)[Not supported by viewer]SRTPWebRtcTransportWebRtcTransportAudio ConsumerAudio ConsumerVideo ConsumerVideo ConsumerPlainRtpTransportPlainRtpTransportAudio ConsumerAudio Consumer \ No newline at end of file +HostHostWorker 1Worker 1WebRtcTransportWebRtcTransportAudio ProducerAudio ProducerVideo ProducerVideo ProducerPlainTransportPlainTransportVideo ProducerVideo ProducerPipeTransportPipeTransportVideo ConsumerVideo ConsumerSRTPSRTPParticipant(mic/webcam on)Participant<br>(mic/webcam on)<br>Participant(viewer)[Not supported by viewer]RTPFFmpeg(recording)[Not supported by viewer]GStreamer(mp4 broadcaster)GStreamer<br>(mp4 broadcaster)<br>RTPRouter 2Router 2Router 1Router 1Worker 2Worker 2PipeTransportPipeTransportVideo ConsumerVideo ConsumerRouter 3Router 3PipeTransportPipeTransportVideo ProducerVideo ProducerWorker 3Worker 3PipeTransportPipeTransportVideo ProducerVideo ProducerRouter 4Router 4RTPRTPParticipant(viewer)[Not supported by viewer]Participant(viewer)[Not supported by viewer]SRTPSRTPSRTPParticipant(viewer)[Not supported by viewer]WebRtcTransportWebRtcTransportVideo ConsumerVideo ConsumerWebRtcTransportWebRtcTransportVideo ConsumerVideo ConsumerWebRtcTransportWebRtcTransportVideo ConsumerVideo ConsumerWebRtcTransportWebRtcTransportVideo ConsumerVideo ConsumerParticipant(viewer)[Not supported by viewer]SRTPWebRtcTransportWebRtcTransportAudio ConsumerAudio ConsumerVideo ConsumerVideo ConsumerPlainTransportPlainTransportAudio ConsumerAudio Consumer diff --git a/doc/Building.md b/doc/Building.md index 456c312724..aaf183591a 100644 --- a/doc/Building.md +++ b/doc/Building.md @@ -2,227 +2,278 @@ This document is intended for mediasoup developers. - ## NPM scripts The `package.json` file in the main folder includes the following scripts: - ### `npm run typescript:build` -Compiles mediasoup TypeScript code (`lib` folder) JavaScript and places it into the `lib` directory. - +Compiles mediasoup TypeScript code (`node/src` folder) JavaScript and places it into the `node/lib` directory. ### `npm run typescript:watch` -Compiles mediasoup TypeScript code (`lib` folder) JavaScript, places it into the `lib` directory an watches for changes in the TypeScript files. - +Compiles mediasoup TypeScript code (`node/src` folder) JavaScript, places it into the `node/lib` directory an watches for changes in the TypeScript files. ### `npm run worker:build` -Builds the `mediasoup-worker` binary. It invokes `make`below. +Builds the `mediasoup-worker` binary. It invokes `invoke`below. +### `npm run worker:prebuild` + +Creates a prebuilt of `mediasoup-worker` binary in the `worker/prebuild` folder. ### `npm run lint` Runs both `npm run lint:node` and `npm run lint:worker`. - ### `npm run lint:node` Validates mediasoup JavaScript files using [ESLint](https://eslint.org). - ### `npm run lint:worker` -Validates mediasoup-worker C++ files using [clang-format](https://clang.llvm.org/docs/ClangFormat.html). It invokes `make lint` below. +Validates mediasoup worker C++ files using [clang-format](https://clang.llvm.org/docs/ClangFormat.html). It invokes `invoke lint` below. +### `npm run format` -### `npm run format:worker` +Runs both `npm run format:node` and `npm run format:worker`. -Rewrites mediasoup-worker C++ files using [clang-format](https://clang.llvm.org/docs/ClangFormat.html). It invokes `make format` below. +### `npm run format:node` +Format TypeScript and JavaScript code using [Prettier](https://prettier.io). -### `npm run test` +### `npm run format:worker` -Runs both `npm run test:node` and `npm run test:worker`. +Rewrites mediasoup worker C++ files using [clang-format](https://clang.llvm.org/docs/ClangFormat.html). It invokes `invoke format` below. +### `npm run flatc` -### `npm run test:node` +Runs both `npm run flatc:node` and `npm run flatc:worker`. -Runs [Jest](https://jestjs.io) test units located at `test/` folder. +### `npm run flatc:node` +Compiles [FlatBuffers](https://github.com/google/flatbuffers) `.fbs` files in `worker/fbs` to TypeScript code. -### `npm run test:worker` +### `npm run flatc:worker` -Runs [Catch2](https://github.com/catchorg/Catch2) test units located at `worker/test/` folder. It invokes `make test` below. +Compiles [FlatBuffers](https://github.com/google/flatbuffers) `.fbs` files in `worker/fbs` to C++ code. +### `npm run test` -### `npm run coverage:node` +Runs both `npm run test:node` and `npm run test:worker`. -Same as `npm run test:node` but it also opens a browser window with JavaScript coverage results. +### `npm run test:node` +Runs [Jest](https://jestjs.io) test units located at `node/test` folder. -### `npm run install-deps:node` +Jest command arguments can be given using `--` as follows: -Installs NPM dependencies and updates `package-lock.json`. +```bash +npm run test:node -- --testPathPattern "test-Worker.ts" --testNamePattern "createWorker" +``` +### `npm run test:worker` -### `npm run install-clang-tools` +Runs [Catch2](https://github.com/catchorg/Catch2) test units located at `worker/test` folder. It invokes `invoke test` below. -Installs clang tools needed for local development. +### `npm run coverage:node` + +Same as `npm run test:node` but it also opens a browser window with JavaScript coverage results. +### `npm run release:check` + +Runs linters and tests in Node and C++ code. ## Rust -The only special feature in Rust case is special environment variable `KEEP_BUILD_ARTIFACTS`, that when set to `1` will allow incremental recompilation of changed C++ sources during hacking on mediasoup. +The only special feature in Rust case is special environment variable "KEEP_BUILD_ARTIFACTS", that when set to "1" will allow incremental recompilation of changed C++ sources during hacking on mediasoup. + It is not necessary for normal usage of mediasoup as a dependency. +## Python Invoke and `tasks.py` file -## Makefile +mediasoup uses Python [Invoke](https://www.pyinvoke.org) library for managing and organizing tasks in the `worker` folder (mediasoup worker C++ subproject). `Invoke` is basically a replacemente of `make` + `Makefile` written in Python. mediasoup automatically installs `Invoke` in a local custom path during the installation process (in both Node and Rust) so the user doesn't need to worry about it. -The `worker` folder contains a `Makefile` for the mediasoup-worker C++ subproject. It includes the following tasks: +Tasks are defined in `worker/tasks.py`. For development purposes, developers or contributors can install `Invoke` using `pip3 install invoke` and run tasks below within the `worker` folder. +See all the tasks by running `invoke --list` within the `worker` folder. -### `make` or `make mediasoup-worker` +_NOTE:_ For some of these tasks to work, npm dependencies of `worker/scripts/package.json` must be installed: -Alias of ``make mediasoup-worker` below. +```bash +npm ci --prefix worker/scripts +``` +### `invoke` (default task) -### `make meson-ninja` +Alias of `invoke mediasoup-worker` task below. -Installs `meson` and `ninja`. +### `invoke meson-ninja` +Installs `meson` and `ninja` into a local custom path. -### `make clean` +### `invoke clean` Cleans built objects and binaries. - -### `make clean-build` +### `invoke clean-build` Cleans built objects and other artifacts, but keeps `mediasoup-worker` binary in place. - -### `make clean-pip` +### `invoke clean-pip` Cleans `meson` and `ninja` installed in local prefix with pip. - -### `make clean-subprojects` +### `invoke clean-subprojects` Cleans subprojects downloaded with Meson. - -### `make clean-all` +### `invoke clean-all` Cleans built objects and binaries, `meson` and `ninja` installed in local prefix with pip and all subprojects downloaded with Meson. +### `invoke update-wrap-file [subproject]` -### `make update-wrap-file` - -Update the wrap file of a subproject with Meson. Usage example: +Updates the wrap file of a subproject (those in `worker/subprojects` folder) with Meson. After updating it, `invoke setup` must be called by passing `MESON_ARGS="--reconfigure"` environment variable. Usage example: ```bash -$ cd worker -$ make update-wrap-file SUBPROJECT=openssl +cd worker +invoke update-wrap-file openssl +MESON_ARGS="--reconfigure" invoke setup ``` +### `invoke mediasoup-worker` -### `make mediasoup-worker` - -Builds the `mediasoup-worker` binary at `worker/out/Release/`. +Builds the `mediasoup-worker` binary at `worker/out/Release`. If the "MEDIASOUP_MAX_CORES" environment variable is set, the build process will use that number of CPU cores. Otherwise it will auto-detect the number of cores in the machine. -"MEDIASOUP_BUILDTYPE" environment variable controls build types, `Release` and `Debug` are presets optimized for those use cases. -Other build types are possible too, but they are not presets and will require "MESON_ARGS" use to customize build configuration. +"MEDIASOUP_BUILDTYPE" environment variable controls build types, "Release" and "Debug" are presets optimized for those use cases. Other build types are possible too, but they are not presets and will require "MESON_ARGS" use to customize build configuration. + Check the meaning of useful macros in the `worker/include/Logger.hpp` header file if you want to enable tracing or other debug information. -Binary is built at `worker/out/MEDIASOUP_BUILDTYPE/build`. +Binary is built at `worker/out/MEDIASOUP_BUILDTYPE/build`. -In order to instruct the mediasoup Node.js module to use the `Debug` mediasoup-worker binary, an environment variable must be set before running the Node.js application: +In order to instruct the mediasoup Node.js module to use the "Debug" mediasoup-worker` binary, an environment variable must be set before running the Node.js application: ```bash -$ MEDIASOUP_BUILDTYPE=Debug node myapp.js +MEDIASOUP_BUILDTYPE=Debug node myapp.js ``` -If the "MEDIASOUP_WORKER_BIN" environment variable is set, mediasoup will use the it as mediasoup-worker binary and **won't** compile the binary: +If the "MEDIASOUP_WORKER_BIN" environment variable is set (it must be an absolute file path), mediasoup will use the it as `mediasoup-worker` binary and **won't** compile the binary: ```bash -$ MEDIASOUP_WORKER_BIN="/home/xxx/src/foo/mediasoup-worker" node myapp.js +MEDIASOUP_WORKER_BIN="/home/xxx/src/foo/mediasoup-worker" node myapp.js ``` +### `invoke libmediasoup-worker` +Builds the `libmediasoup-worker` static library at `worker/out/Release`. -### `make libmediasoup-worker` +"MEDIASOUP_MAX_CORES"` and "MEDIASOUP_BUILDTYPE" environment variables from above still apply for static library build. -Builds the `libmediasoup-worker` static library at `worker/out/Release/`. +### `invoke xcode` -`MEDIASOUP_MAX_CORES` and `MEDIASOUP_BUILDTYPE` environment variables from above still apply for static library build. +Builds a Xcode project for the mediasoup worker subproject. +### `invoke lint` -### `make xcode` +Validates mediasoup worker C++ files using [clang-format](https://clang.llvm.org/docs/ClangFormat.html) and rules in `worker/.clang-format`. -Builds a Xcode project for the mediasoup-worker subproject. +### `invoke format` +Rewrites mediasoup worker C++ files using [clang-format](https://clang.llvm.org/docs/ClangFormat.html). -### `make lint` +### `invoke test` -Validates mediasoup-worker C++ files using [clang-format](https://clang.llvm.org/docs/ClangFormat.html) and rules in `worker/.clang-format`. +Builds and runs the `mediasoup-worker-test` binary at `worker/out/Release` (or at `worker/out/Debug` if the "MEDIASOUP_BUILDTYPE" environment variable is set to "Debug"), which uses [Catch2](https://github.com/catchorg/Catch2) to run test units located at `worker/test` folder. +### `invoke test-asan-address` -### `make format` +Run test with Address Sanitizer with `-fsanitize=address`. -Rewrites mediasoup-worker C++ files using [clang-format](https://clang.llvm.org/docs/ClangFormat.html). +### `invoke test-asan-undefined` +Run test with Address Sanitizer with `-fsanitize=undefined`. -### `make test` +### `invoke test-asan-thread` -Builds and runs the `mediasoup-worker-test` binary at `worker/out/Release/` (or at `worker/out/Debug/` if the "MEDIASOUP_BUILDTYPE" environment variable is set to "Debug"), which uses [Catch2](https://github.com/catchorg/Catch2) to run test units located at `worker/test/` folder. +Run test with Address Sanitizer with `-fsanitize=thread`. +### `invoke tidy` -### `make tidy` - -Runs [clang-tidy](http://clang.llvm.org/extra/clang-tidy/) and performs C++ code checks following `worker/.clang-tidy` rules. +Runs [clang-tidy](http://clang.llvm.org/extra/clang-tidy) and performs C++ code checks following `worker/.clang-tidy` rules. **Requirements:** -* `make clean` and `make` must have been called first. -* [PyYAML](https://pyyaml.org/) is required. - - In OSX install it with `brew install libyaml` and `sudo easy_install-X.Y pyyaml`. +- `invoke clean` and `invoke mediasoup-worker` must have been called first. +- [clang-tools-extra](https://clang.llvm.org/extra) is required. + - In OSX install it with `brew install llvm`. + - In linux the package name is `clang-tools-extra`. + +**Environment variables:** +- "MEDIASOUP_TIDY_CHECKS": Comma separated list of checks. Overrides the checks defined in `worker/.clang-tidy` file. +- "MEDIASOUP_TIDY_FILES": Space separated source files to process, including their path. All `.cpp` files will be processes by default. +- "MEDIASOUP_CLANG_TIDY_DIR": Path to directory containing clang tools (`run-clang-tidy`, `clang-tidy`, `clang-apply-replacements`). -### `make fuzzer` +**Usage example in macOS:** + +```bash +MEDIASOUP_CLANG_TIDY_DIR=/usr/local/opt/llvm/bin invoke tidy +``` -Builds the `mediasoup-worker-fuzzer` binary (which uses [libFuzzer](http://llvm.org/docs/LibFuzzer.html)) at `worker/out/Release/` (or at `worker/out/Debug/` if the "MEDIASOUP_BUILDTYPE" environment variable is set to "Debug"). +### `invoke fuzzer` + +Builds the `mediasoup-worker-fuzzer` binary (which uses [libFuzzer](http://llvm.org/docs/LibFuzzer.html)) at `worker/out/Release` (or at `worker/out/Debug/` if the "MEDIASOUP_BUILDTYPE" environment variable is set to "Debug"). **Requirements:** -* Linux with fuzzer capable clang++. -* `CC` environment variable must point to "clang". -* `CXX` environment variable must point to "clang++". +- Linux with fuzzer capable clang++. +- "CC" environment variable must point to `clang`. +- "CXX" environment variable must point to `clang++`. Read the [Fuzzer](Fuzzer.md) documentation for detailed information. - -### `make fuzzer-run-all` +### `invoke fuzzer-run-all` Runs all fuzzer cases. +### `invoke docker` -### `make docker` - -Builds a Linux image with fuzzer capable clang++. +Builds a Linux Ubuntu Docker image with fuzzer capable clang++ and all dependencies to run mediasoup. **NOTE:** Before running this command, a specific version of Linux clang must be downloaded. To get it, run: ```bash -$ cd worker -$ ./scripts/get-dep.sh clang-fuzzer +cd worker +scripts/get-dep.sh clang-fuzzer ``` +### `invoke docker-run` + +Runs a container of the Ubuntu Docker image created with `invoke docker`. It automatically executes a `bash` session in the `/mediasoup` directory, which is a Docker volume that points to the mediasoup root folder. + +**NOTE:** To install and run mediasoup in the container, previous installation (if any) must be properly cleaned by entering the `worker` directory and running `invoke clean-all`. + +### `invoke docker-alpine` + +Builds a Linux Alpine Docker image with all dependencies to run mediasoup. + +### `invoke docker-alpine-run` + +Runs a container of the Alpine Docker image created with `invoke docker-alpine`. It automatically executes an `ash` session in the `/mediasoup` directory, which is a Docker volume that points to the mediasoup root folder. + +**NOTE:** To install and run mediasoup in the container, previous installation (if any) must be properly cleaned by entering the `worker` directory and running `invoke clean-all`. + +## Makefile + +The `worker` folder contains a `Makefile` file for the mediasoup worker C++ subproject. It acts as a proxy to the `Invoke` tasks defined in `tasks.py`. The `Makefile` file exists to help developers or contributors that prefer keep using `make` commands. -### `make docker-run` +All tasks defined in `tasks.py` (see above) are available in `Makefile`. There is only one exception: -Runs a container of the Docker image created with `make docker`. It automatically executes a `bash` session in the `/mediasoup` directory, which is a Docker volume that points to the real `mediasoup` directory. +- The `update-wrap-file` needs a "SUBPROJECT" environment variable indicating the subproject to update. Usage example: + ```bash + cd worker + make update-wrap-file SUBPROJECT=openssl + ``` diff --git a/doc/Charts.md b/doc/Charts.md index 6be4f00efc..266700b464 100644 --- a/doc/Charts.md +++ b/doc/Charts.md @@ -1,6 +1,5 @@ # Charts - ## Broadcasting mediasoup **v2** (a room uses a single media worker subprocess by design, so a single CPU). @@ -9,9 +8,8 @@ Charts provided by [CoSMo](https://www.cosmosoftware.io) team. Scenario: -* 1 peer producing audio and video tracks. -* N spy peers receiving them. - +- 1 peer producing audio and video tracks. +- N spy peers receiving them. #### Bandwidth out (Mbps) / number of viewers diff --git a/doc/Consumer.md b/doc/Consumer.md index 6c246e8d2d..824ac03a53 100644 --- a/doc/Consumer.md +++ b/doc/Consumer.md @@ -1,92 +1,109 @@ # Consumer - ## RTP sequence number handling ------------------------------------------------- +--- INIT: -* rtpLastSeq: 1000 -* rtpPreviousBaseSeq: 0 -* rtpBaseSeq: 0 +- rtpLastSeq: 1000 +- rtpPreviousBaseSeq: 0 +- rtpBaseSeq: 0 ------------------------------------------------- +--- packet (SYNC): -* seq: 3000 -* rtpPreviousBaseSeq: 1000 -* rtpBaseSeq: 3000 + +- seq: 3000 +- rtpPreviousBaseSeq: 1000 +- rtpBaseSeq: 3000 out: -* rtpLastSeq: 3000 - 3000 + 1000 + 1 = 1001 ------------------------------------------------- +- rtpLastSeq: 3000 - 3000 + 1000 + 1 = 1001 + +--- packet: -* seq: 3001 + +- seq: 3001 out: -* rtpLastSeq: 3001 - 3000 + 1000 + 1 = 1002 ------------------------------------------------- +- rtpLastSeq: 3001 - 3000 + 1000 + 1 = 1002 + +--- packet: -* seq: 3003 + +- seq: 3003 out: -* rtpLastSeq: 3003 - 3000 + 1000 + 1 = 1004 ------------------------------------------------- +- rtpLastSeq: 3003 - 3000 + 1000 + 1 = 1004 + +--- packet (SYNC): -* seq: 4050 -- rtpPreviousBaseSeq: 1004 -- rtpBaseSeq: 4050 + +- seq: 4050 + +* rtpPreviousBaseSeq: 1004 +* rtpBaseSeq: 4050 out: -* rtpLastSeq: 4050 - 4050 + 1004 + 1 = 1005 ------------------------------------------------- +- rtpLastSeq: 4050 - 4050 + 1004 + 1 = 1005 + +--- packet: -* seq: 4051 + +- seq: 4051 out: -* rtpLastSeq: 4051 - 4050 + 1004 + 1 = 1006 ------------------------------------------------- +- rtpLastSeq: 4051 - 4050 + 1004 + 1 = 1006 + +--- packet (DROP): -* seq: 4052 + +- seq: 4052 if (seq > rtpLastSeq) -* rtpBaseSeq++: 4051 ------------------------------------------------- +- rtpBaseSeq++: 4051 + +--- packet: -* seq: 4053 + +- seq: 4053 out: -* rtpLastSeq: 4053 - 4051 + 1004 + 1 = 1007 ------------------------------------------------- +- rtpLastSeq: 4053 - 4051 + 1004 + 1 = 1007 + +--- probation packet: -* rtpLastSeq++ = 1008 + +- rtpLastSeq++ = 1008 out: -* rtpLastSeq: 1008 -* rtpBaseSeq--: 4050 + +- rtpLastSeq: 1008 +- rtpBaseSeq--: 4050 NOTE: probation packets should just be sent (assuming same RTP timestamp) **after** a video packet with `marker` bit set to 1. ------------------------------------------------- +--- packet: -* seq: 4054 + +- seq: 4054 out: -* rtpLastSeq: 4054 - 4050 + 1004 + 1 = 1009 +- rtpLastSeq: 4054 - 4050 + 1004 + 1 = 1009 diff --git a/doc/Fuzzer.md b/doc/Fuzzer.md index 8e39c13406..9514743f42 100644 --- a/doc/Fuzzer.md +++ b/doc/Fuzzer.md @@ -4,14 +4,12 @@ Once we have built the `mediasoup-worker-fuzzer` target in a Linux environment w **NOTE:** From now on, we assume we are in the mediasoup `worker` directory. - ## Related documentation -* [libFuzzer documentation](http://llvm.org/docs/LibFuzzer.html) -* [libFuzzer Tutorial](https://github.com/google/fuzzer-test-suite/blob/master/tutorial/libFuzzerTutorial.md) -* [webrtcH4cKS ~ Lets get better at fuzzing in 2019](https://webrtchacks.com/lets-get-better-at-fuzzing-in-2019-heres-how/) -* [OSS-Fuzz](https://github.com/google/oss-fuzz) - Continuous fuzzing of open source software ("fuzz for me") - +- [libFuzzer documentation](http://llvm.org/docs/LibFuzzer.html) +- [libFuzzer Tutorial](https://github.com/google/fuzzer-test-suite/blob/master/tutorial/libFuzzerTutorial.md) +- [webrtcH4cKS ~ Lets get better at fuzzing in 2019](https://webrtchacks.com/lets-get-better-at-fuzzing-in-2019-heres-how/) +- [OSS-Fuzz](https://github.com/google/oss-fuzz) - Continuous fuzzing of open source software ("fuzz for me") ## Corpus files @@ -19,7 +17,6 @@ The `deps/webrtc-fuzzer-corpora/corpora` directory has corpus directories taken However, given how `libFuzzer` [works](http://llvm.org/docs/LibFuzzer.html#options), the first directory given as command line parameter is not just used for reading corpus files, but also to store newly generated ones. So, it's recommended to pass `fuzzer/new-corpus` as first directory. Such a directory is gitignored. - ## Crash reports When the fuzzer detects an issue it generates a crash report file which contains the bytes given as input. Those files can be individually used later (instead of passing corpus directories) to the fuzzer to verify that the issue has been fixed. @@ -34,57 +31,70 @@ The `fuzzer/reports` directory should be used to store those new crash reports. It's recommended to (also) pass the following options to the fuzzer: -* `-max_len=1400`: We don't need much more input size. +- `-max_len=1400`: We don't need much more input size. For memory leak detection enable the following environment variable: -* `LSAN_OPTIONS=verbosity=1:log_threads=1` +- `LSAN_OPTIONS=verbosity=1:log_threads=1` The mediasoup-worker fuzzer reads some custom environment variables to decide which kind of fuzzing perform: -* `MS_FUZZ_STUN=1`: Do STUN fuzzing. -* `MS_FUZZ_RTP=1`: Do RTP fuzzing. -* `MS_FUZZ_RTCP=1`: Do RTCP fuzzing. -* `MS_FUZZ_UTILS=1`: Do C++ utils fuzzing. -* If none of them is given, then **all** fuzzers are enabled. +- `MS_FUZZ_STUN=1`: Enable STUN fuzzer. +- `MS_FUZZ_DTLS=1`: Enable DTLS fuzzer. +- `MS_FUZZ_RTP=1`: Enable RTP fuzzer. +- `MS_FUZZ_RTCP=1`: Enable RTCP fuzzer. +- `MS_FUZZ_CODECS=1`: Enable audio/video codecs fuzzer. +- `MS_FUZZ_UTILS=1`: Enable C++ utils fuzzer. +- If none of them is given, then **all** fuzzers are enabled. The log level can also be set by setting the `MS_FUZZ_LOG_LEVEL` environment variable to "debug", "warn" or "error" (it is "none" if unset). - ## Usage examples -* Detect memory leaks and just fuzz STUN: +- Detect memory leaks and just fuzz STUN: + +```bash +MS_FUZZ_STUN=1 LSAN_OPTIONS=verbosity=1:log_threads=1 ./out/Release/mediasoup-worker-fuzzer -artifact_prefix=fuzzer/reports/ -max_len=1400 fuzzer/new-corpus deps/webrtc-fuzzer-corpora/corpora/stun-corpus +``` + +- Detect memory leaks and just fuzz DTLS: + +```bash +MS_FUZZ_DTLS=1 LSAN_OPTIONS=verbosity=1:log_threads=1 ./out/Release/mediasoup-worker-fuzzer -artifact_prefix=fuzzer/reports/ -max_len=1400 fuzzer/new-corpus +``` + +- Detect memory leaks and just fuzz RTP: ```bash -$ MS_FUZZ_STUN=1 LSAN_OPTIONS=verbosity=1:log_threads=1 ./out/Release/mediasoup-worker-fuzzer -artifact_prefix=fuzzer/reports/ -max_len=1400 fuzzer/new-corpus deps/webrtc-fuzzer-corpora/corpora/stun-corpus +MS_FUZZ_RTP=1 LSAN_OPTIONS=verbosity=1:log_threads=1 ./out/Release/mediasoup-worker-fuzzer -artifact_prefix=fuzzer/reports/ -max_len=1400 fuzzer/new-corpus deps/webrtc-fuzzer-corpora/corpora/rtp-corpus ``` -* Detect memory leaks and just fuzz RTP: +- Detect memory leaks and just fuzz RTCP: ```bash -$ MS_FUZZ_RTP=1 LSAN_OPTIONS=verbosity=1:log_threads=1 ./out/Release/mediasoup-worker-fuzzer -artifact_prefix=fuzzer/reports/ -max_len=1400 fuzzer/new-corpus deps/webrtc-fuzzer-corpora/corpora/rtp-corpus +MS_FUZZ_RTCP=1 LSAN_OPTIONS=verbosity=1:log_threads=1 ./out/Release/mediasoup-worker-fuzzer -artifact_prefix=fuzzer/reports/ -max_len=1400 fuzzer/new-corpus deps/webrtc-fuzzer-corpora/corpora/rtcp-corpus ``` -* Detect memory leaks and just fuzz RTCP: +- Detect memory leaks and just fuzz audio/video codecs: ```bash -$ MS_FUZZ_RTCP=1 LSAN_OPTIONS=verbosity=1:log_threads=1 ./out/Release/mediasoup-worker-fuzzer -artifact_prefix=fuzzer/reports/ -max_len=1400 fuzzer/new-corpus deps/webrtc-fuzzer-corpora/corpora/rtcp-corpus +MS_FUZZ_CODECS=1 LSAN_OPTIONS=verbosity=1:log_threads=1 ./out/Release/mediasoup-worker-fuzzer -artifact_prefix=fuzzer/reports/ -max_len=1400 fuzzer/new-corpus ``` -* Detect memory leaks and just fuzz mediasoup-worker C++ utils: +- Detect memory leaks and just fuzz mediasoup-worker C++ utils: ```bash -$ MS_FUZZ_UTILS=1 LSAN_OPTIONS=verbosity=1:log_threads=1 ./out/Release/mediasoup-worker-fuzzer -artifact_prefix=fuzzer/reports/ -max_len=2000 fuzzer/new-corpus +MS_FUZZ_UTILS=1 LSAN_OPTIONS=verbosity=1:log_threads=1 ./out/Release/mediasoup-worker-fuzzer -artifact_prefix=fuzzer/reports/ -max_len=2000 fuzzer/new-corpus ``` -* Detect memory leaks and fuzz everything with log level "warn": +- Detect memory leaks and fuzz everything with log level "warn": ```bash -$ MS_FUZZ_LOG_LEVEL=warn LSAN_OPTIONS=verbosity=1:log_threads=1 ./out/Release/mediasoup-worker-fuzzer -artifact_prefix=fuzzer/reports/ -max_len=1400 fuzzer/new-corpus deps/webrtc-fuzzer-corpora/corpora/stun-corpus deps/webrtc-fuzzer-corpora/corpora/rtp-corpus deps/webrtc-fuzzer-corpora/corpora/rtcp-corpus +MS_FUZZ_LOG_LEVEL=warn LSAN_OPTIONS=verbosity=1:log_threads=1 ./out/Release/mediasoup-worker-fuzzer -artifact_prefix=fuzzer/reports/ -max_len=1400 fuzzer/new-corpus deps/webrtc-fuzzer-corpora/corpora/stun-corpus deps/webrtc-fuzzer-corpora/corpora/rtp-corpus deps/webrtc-fuzzer-corpora/corpora/rtcp-corpus ``` -* Verify that a specific crash is fixed: +- Verify that a specific crash is fixed: ```bash -$ LSAN_OPTIONS=verbosity=1:log_threads=1 ./out/Release/mediasoup-worker-fuzzer fuzzer/reports/crash-f39771f7a03c0e7e539d4e52f48f7adad8976404 +LSAN_OPTIONS=verbosity=1:log_threads=1 ./out/Release/mediasoup-worker-fuzzer fuzzer/reports/crash-f39771f7a03c0e7e539d4e52f48f7adad8976404 ``` diff --git a/doc/README.md b/doc/README.md index f17d5a4944..3efef33aa6 100644 --- a/doc/README.md +++ b/doc/README.md @@ -2,9 +2,8 @@ **NOTE:** Internal documentation for developing purposes. Get the mediasoup public documentation at [mediasoup.org](https://mediasoup.org). -* [Building](Building.md) -* [Fuzzer](Fuzzer.md) -* [RTCP](RTCP.md) -* [Consumer](Consumer.md) -* [Charts](Charts.md) - +- [Building](Building.md) +- [Fuzzer](Fuzzer.md) +- [RTCP](RTCP.md) +- [Consumer](Consumer.md) +- [Charts](Charts.md) diff --git a/doc/RTCP.md b/doc/RTCP.md index dac8e9287a..13fde78342 100644 --- a/doc/RTCP.md +++ b/doc/RTCP.md @@ -8,7 +8,6 @@ This document also describes which RTCP packets are locally generated by mediaso mediasoup does not forward feedback information from a remote RTP receiver, which could incur in the remote sender modifying the transmition rate in a way that it would affect the reception quality for the overall participants in a router (i.e. limiting the remote RTP sender transmition rate). - ### Sender Reports mediasoup locally generates the Sender Reports of the streams it sends and processes the Sender Reports is receives from producer endpoints. @@ -150,31 +149,29 @@ This information is locally consumed to perform sender side bandwidth estimation REMB RTCP is generated locally based on the remote bitrate estimation. - ## Mediasoup internal behaviour for each type of RTCP ### Generic RTCP -| | SR | RR | SDES | BYE | APP | -| --------- | -- | -- | ---- | --- | --- | -| Consumer | G | C | | | | -| Producer | C | G | C | I | I | +| | SR | RR | SDES | BYE | APP | +| -------- | --- | --- | ---- | --- | --- | +| Consumer | G | C | | | | +| Producer | C | G | C | I | I | ### RTP Feedback RTCP -| | NACK | TMMBR | TMMBN | TLLEI | ECN-FB | PAUSE-RESUME | TCC | -| --------- | ---- | ----- | ----- | ----- | ------ | ------------ | --- | -| Consumer | C | I | I | I | I | I | | -| Producer | G | | I | I | | | | +| | NACK | TMMBR | TMMBN | TLLEI | ECN-FB | PAUSE-RESUME | TCC | +| -------- | ---- | ----- | ----- | ----- | ------ | ------------ | --- | +| Consumer | C | I | I | I | I | I | | +| Producer | G | | I | I | | | | ## PS Feedback RTCP | | PLI | SLI | RPSI | FIR | TSTR | TSTN | VBCM | PSLI | AFB | REMB | | --------- | --- | --- | ---- | --- | ---- | ---- | ---- | ---- | --- | ---- | -| Consumer | C | I | I | C | I | | I | | I | C | -| Producer | G | | | | | I | | I | | | -| Transport | | | | | | | | | | G | - +| Consumer | C | I | I | C | I | | I | | I | C | +| Producer | G | | | | | I | | I | | | +| Transport | | | | | | | | | | G | ( ): Does not apply. diff --git a/doc/closures.md b/doc/closures.md index 11086ae6c9..bb3dca3150 100644 --- a/doc/closures.md +++ b/doc/closures.md @@ -2,147 +2,147 @@ Some considerations: -* Any JS `xxxxx.yyyyyClosed()` method is equivalent to the corresponding C++ `~Xxxxx()` destructor. They both silently destroy things without generating any internal notifications/events. - - *NOTE:* Yes, the JS `xxxxx.yyyyyClosed()` produces **public** JS event `xxxxx.on('yyyyyclose')`, but that's not internal stuff. - +- Any JS `xxxxx.yyyyyClosed()` method is equivalent to the corresponding C++ `~Xxxxx()` destructor. They both silently destroy things without generating any internal notifications/events. + - _NOTE:_ Yes, the JS `xxxxx.yyyyyClosed()` produces **public** JS event `xxxxx.on('yyyyyclose')`, but that's not internal stuff. ## JS worker.close() -* Public API. -* Kills the mediasoup-worker process (if not already died) via signal. -* Iterates all JS Routers and calls `router.workerClosed()`. +- Public API. +- Kills the mediasoup-worker process (if not already died) via signal. +- Iterates all JS Routers and calls `router.workerClosed()`. ## mediasoup-worker process dies unexpectely -* The JS Worker emits public JS `worker.on('died')`. -* Iterates all JS Routers and calls `router.workerClosed()`. +- The JS Worker emits public JS `worker.on('died')`. +- Iterates all JS Routers and calls `router.workerClosed()`. ## C++ Worker::Close() -* Called when the mediasoup-worker process is killed. -* Iterates all C++ Routers and calls `delete router`. +- Called when the mediasoup-worker process is killed. +- Iterates all C++ Routers and calls `delete router`. ## JS router.workerClosed() -* Private API. -* Emits public JS `router.on('workerclose')`. +- Private API. +- Emits public JS `router.on('workerclose')`. ## JS router.close() -* Public API. -* Sends channel request `WORKER_CLOSE_ROUTER`: +- Public API. +- Sends channel request `WORKER_CLOSE_ROUTER`: - Processed by the C++ Worker. - It removes the C++ Router from its map. - It calls C++ `delete router`. -* Iterates all JS Transports and calls `transport.routerClosed()`. -* Emits private JS `router.on('@close')` (so the JS Worker cleans its map). +- Iterates all JS Transports and calls `transport.routerClosed()`. +- Emits private JS `router.on('@close')` (so the JS Worker cleans its map). ## C++ ~Router() destructor -* Iterates all C++ Transports and calls `delete transport`. +- Iterates all C++ Transports and calls `delete transport`. ## JS transport.routerClosed() -* Private API. -* Iterates all JS Producers and calls `producer.transportClosed()`. -* Iterates all JS Consumers and calls `consumer.transportClosed()`. -* Emits public JS `transport.on('routerclose')`. +- Private API. +- Iterates all JS Producers and calls `producer.transportClosed()`. +- Iterates all JS Consumers and calls `consumer.transportClosed()`. +- Emits public JS `transport.on('routerclose')`. ## JS transport.close() -* Public API. -* Sends channel request `ROUTER_CLOSE_TRANSPORT`. +- Public API. +- Sends channel request `ROUTER_CLOSE_TRANSPORT`. - Processed by the C++ Router. - It calls C++ `transport->Close()` (so the C++ Transport will notify the C++ Router about closed Producers and Consumers in that Transport). - It removes the C++ Transport from its map. - It calls C++ `delete transport`. -* Iterates all JS Producers and calls `producer.transportClosed()`. -* For each JS Producer, the JS Transport emits private JS `transport.on('@producerclose')` (so the JS Router cleans its maps). -* Iterates all JS Consumers and calls `consumer.transportClosed()`. -* Emits private JS `transport.on('@close')` (so the JS Router cleans its map). +- Iterates all JS Producers and calls `producer.transportClosed()`. +- For each JS Producer, the JS Transport emits private JS `transport.on('@producerclose')` (so the JS Router cleans its maps). +- Iterates all JS Consumers and calls `consumer.transportClosed()`. +- Emits private JS `transport.on('@close')` (so the JS Router cleans its map). ## C++ ~Transport() destructor -* Iterates all C++ Producers and calls `delete producer`. -* Iterates all C++ Consumer and calls `delete consumer`. +- Iterates all C++ Producers and calls `delete producer`. +- Iterates all C++ Consumer and calls `delete consumer`. ## C++ Transport::Close() -* Iterates all C++ Producers. For each Producer: +- Iterates all C++ Producers. For each Producer: - Removes it from its map of Producers. - Calls its `listener->OnTransportProducerClosed(this, producer)` (so the C++ Router cleans its maps and calls `consumer->ProducerClosed()` on its associated Consumers). - Calls `delete producer`. -* It clears its map of C++ Producers. -* Iterates all C++ Consumer. For each Consumer: +- It clears its map of C++ Producers. +- Iterates all C++ Consumer. For each Consumer: - Removes it from its map of Consumers. - Call its `listener->OnTransportConsumerClosed(this, consumer)` (so the C++ Router cleans its maps). - Calls `delete consumer`. -* It clears its map of C++ Consumers. +- It clears its map of C++ Consumers. + +_NOTE:_ If a Transport holds a Producer and a Consumer associated to that Producer, ugly things may happen when calling `Transport::Close()`: -*NOTE:* If a Transport holds a Producer and a Consumer associated to that Producer, ugly things may happen when calling `Transport::Close()`: - - While iterating the C++ Producers as above, the C++ Consumer would be deleted (via `Consumer::ProducerClosed()`). - + As far as it's properly removed from the `Transport::mapConsumers` everything would be ok when later iterating the map of Consumers. - - Must ensure that, in this scenario, the JS event `consumer.on('producerclose')` is not called since `consumer.on('transportclose')` is supposed to happen before. - + This won't happen since the JS `Consumer` has removed its channel notifications within its `transportClosed()` method. +- While iterating the C++ Producers as above, the C++ Consumer would be deleted (via `Consumer::ProducerClosed()`). + - As far as it's properly removed from the `Transport::mapConsumers` everything would be ok when later iterating the map of Consumers. +- Must ensure that, in this scenario, the JS event `consumer.on('producerclose')` is not called since `consumer.on('transportclose')` is supposed to happen before. + - This won't happen since the JS `Consumer` has removed its channel notifications within its `transportClosed()` method. ## C++ Router::OnTransportProducerClosed(transport, producer) -* Gets the set of C++ Consumers associated to the closed Producer in its `mapProducerConsumers`. For each Consumer: +- Gets the set of C++ Consumers associated to the closed Producer in its `mapProducerConsumers`. For each Consumer: - Calls `consumer->ProducerClosed()`. -* Deletes the entry in `mapProducerConsumers` with key `producer`. -* Deletes the entry in `mapProducers` with key `producer->id`. +- Deletes the entry in `mapProducerConsumers` with key `producer`. +- Deletes the entry in `mapProducers` with key `producer->id`. ## C++ Router::OnTransportConsumerClosed(transport, consumer) -* Get the associated C++ Producer from `mapConsumerProducer`. -* Remove the closed C++ Consumer from the set of Consumers in the corresponding `mapProducerConsumers` entry for the given Producer. -* Deletes the entry in `mapConsumerProducer` with key `consumer`. +- Get the associated C++ Producer from `mapConsumerProducer`. +- Remove the closed C++ Consumer from the set of Consumers in the corresponding `mapProducerConsumers` entry for the given Producer. +- Deletes the entry in `mapConsumerProducer` with key `consumer`. ## JS producer.transportClosed() -* Private API. -* Emits public JS `producer.on('transportclose')`. +- Private API. +- Emits public JS `producer.on('transportclose')`. ## JS producer.close() -* Public API. -* Sends channel request `TRANSPORT_CLOSE_PRODUCER`. +- Public API. +- Sends channel request `TRANSPORT_CLOSE_PRODUCER`. - Processed by the C++ Transport. - Removes it from its map of Producers. - Calls its `listener->OnTransportProducerClosed(this, producer)` (so the C++ Router cleans its maps and calls `consumer->ProducerClose()` on its associated Consumers). - Calls `delete producer`. -* Emits private JS `producer.on('@close')` (so the JS Transport cleans its map and will also emit private JS `transport.on('@producerclose')` so the JS Router cleans its map). +- Emits private JS `producer.on('@close')` (so the JS Transport cleans its map and will also emit private JS `transport.on('@producerclose')` so the JS Router cleans its map). ## C++ ~Producer() destructor -* Destroys its stuff. +- Destroys its stuff. ## JS consumer.transportClosed() -* Private API. -* Emits public JS `consumer.on('transportclose')`. +- Private API. +- Emits public JS `consumer.on('transportclose')`. ## JS consumer.close() -* Public API. -* Sends channel request `TRANSPORT_CLOSE_CONSUMER`. +- Public API. +- Sends channel request `TRANSPORT_CLOSE_CONSUMER`. - Processed by the C++ Transport. - Removes it from its map of Consumers. - Calls its `listener->OnTransportConsumerClosed(this, consumer)` (so the C++ Router cleans its maps). - Calls `delete consumer`. -* Emits private JS `consumer.on('@close')` (so the JS Transport cleans its map). +- Emits private JS `consumer.on('@close')` (so the JS Transport cleans its map). ## C++ ~Consumer() destructor -* Destroys its stuff. +- Destroys its stuff. ## C++ Consumer::ProducerClosed() -* Called from the C++ Router within the `Router::OnTransportProducerClosed()` listener. -* Send a channel notification `producerclose` to the JS Consumer. +- Called from the C++ Router within the `Router::OnTransportProducerClosed()` listener. +- Send a channel notification `producerclose` to the JS Consumer. - The JS Consumer emits private JS `consumer.on('@produceclose')` (so the JS Transport cleans its map). - The JS Consumer emits public JS `consumer.on('produceclose')`. -* Notifies its C++ Transport via `listener->onConsumerProducerClosed()` which: +- Notifies its C++ Transport via `listener->onConsumerProducerClosed()` which: - cleans its map of Consumers, - notifies the Router via `listener->OnTransportConsumerClosed()`), and - deletes the Consumer. diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 0000000000..1e596554d8 --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,179 @@ +import eslint from '@eslint/js'; +import tsEslint from 'typescript-eslint'; +import jestEslint from 'eslint-plugin-jest'; +import prettierRecommendedEslint from 'eslint-plugin-prettier/recommended'; +import globals from 'globals'; + +const config = tsEslint.config( + { + languageOptions: { + sourceType: 'module', + globals: { ...globals.nodeBuiltin }, + }, + linterOptions: { + noInlineConfig: false, + reportUnusedDisableDirectives: 'error', + }, + }, + eslint.configs.recommended, + { + rules: { + 'constructor-super': 2, + curly: [2, 'all'], + // Unfortunatelly `curly` does not apply to blocks in `switch` cases so + // this is needed. + 'no-restricted-syntax': [ + 2, + { + selector: 'SwitchCase > *.consequent[type!="BlockStatement"]', + message: 'Switch cases without blocks are disallowed', + }, + ], + 'guard-for-in': 2, + 'newline-after-var': 2, + 'newline-before-return': 2, + 'no-alert': 2, + 'no-caller': 2, + 'no-case-declarations': 2, + 'no-catch-shadow': 2, + 'no-class-assign': 2, + 'no-console': 2, + 'no-const-assign': 2, + 'no-debugger': 2, + 'no-dupe-args': 2, + 'no-dupe-keys': 2, + 'no-duplicate-case': 2, + 'no-div-regex': 2, + 'no-empty': [2, { allowEmptyCatch: true }], + 'no-empty-pattern': 2, + 'no-eval': 2, + 'no-extend-native': 2, + 'no-ex-assign': 2, + 'no-extra-bind': 2, + 'no-extra-boolean-cast': 2, + 'no-extra-label': 2, + 'no-fallthrough': 2, + 'no-func-assign': 2, + 'no-global-assign': 2, + 'no-implicit-coercion': 2, + 'no-implicit-globals': 2, + 'no-inner-declarations': 2, + 'no-invalid-regexp': 2, + 'no-invalid-this': 2, + 'no-irregular-whitespace': 2, + 'no-lonely-if': 2, + 'no-multi-str': 2, + 'no-native-reassign': 2, + 'no-negated-in-lhs': 2, + 'no-new': 2, + 'no-new-func': 2, + 'no-new-wrappers': 2, + 'no-obj-calls': 2, + 'no-proto': 2, + 'no-prototype-builtins': 0, + 'no-redeclare': 2, + 'no-regex-spaces': 2, + 'no-restricted-imports': 2, + 'no-return-assign': 2, + 'no-self-assign': 2, + 'no-self-compare': 2, + 'no-sequences': 2, + 'no-shadow': 2, + 'no-shadow-restricted-names': 2, + 'no-sparse-arrays': 2, + 'no-this-before-super': 2, + 'no-throw-literal': 2, + 'no-undef': 2, + 'no-unmodified-loop-condition': 2, + 'no-unreachable': 2, + 'no-unused-vars': [ + 2, + { vars: 'all', args: 'after-used', caughtErrors: 'none' }, + ], + 'no-unused-private-class-members': 2, + 'no-use-before-define': 0, + 'no-useless-call': 2, + 'no-useless-computed-key': 2, + 'no-useless-concat': 2, + 'no-useless-rename': 2, + 'no-var': 2, + 'object-curly-newline': 0, + 'prefer-const': 2, + 'prefer-rest-params': 2, + 'prefer-spread': 2, + 'prefer-template': 2, + 'spaced-comment': [2, 'always'], + strict: 2, + 'valid-typeof': 2, + yoda: 2, + }, + }, + // NOTE: We need to apply this only to .ts files (and not to .mjs files). + ...tsEslint.configs.recommendedTypeChecked.map(item => ({ + ...item, + files: ['node/src/**/*.ts'], + })), + // NOTE: We need to apply this only to .ts files (and not to .mjs files). + ...tsEslint.configs.stylisticTypeChecked.map(item => ({ + ...item, + files: ['node/src/**/*.ts'], + })), + { + name: 'mediasoup .ts files', + files: ['node/src/**/*.ts'], + languageOptions: { + parserOptions: { + projectService: true, + project: 'tsconfig.json', + }, + }, + rules: { + '@typescript-eslint/class-literal-property-style': [2, 'getters'], + '@typescript-eslint/consistent-generic-constructors': [ + 2, + 'type-annotation', + ], + '@typescript-eslint/dot-notation': 0, + '@typescript-eslint/no-unused-vars': [ + 2, + { + vars: 'all', + args: 'after-used', + caughtErrors: 'none', + ignoreRestSiblings: false, + }, + ], + // We want to use `type` instead of `interface`. + '@typescript-eslint/consistent-type-definitions': 0, + // Sorry, we need many `any` usage. + '@typescript-eslint/no-explicit-any': 0, + '@typescript-eslint/explicit-function-return-type': 2, + '@typescript-eslint/no-unsafe-member-access': 0, + '@typescript-eslint/no-unsafe-assignment': 0, + '@typescript-eslint/no-unsafe-call': 0, + '@typescript-eslint/no-unsafe-return': 0, + '@typescript-eslint/no-unsafe-argument': 0, + '@typescript-eslint/consistent-indexed-object-style': 0, + '@typescript-eslint/no-empty-function': 0, + '@typescript-eslint/restrict-template-expressions': 0, + '@typescript-eslint/no-duplicate-type-constituents': [ + 2, + { ignoreUnions: true }, + ], + }, + }, + { + name: 'mediasoup .ts test files', + ...jestEslint.configs['flat/recommended'], + files: ['node/src/test/**/*.ts'], + rules: { + ...jestEslint.configs['flat/recommended'].rules, + 'jest/no-disabled-tests': 2, + 'jest/prefer-expect-assertions': 0, + '@typescript-eslint/no-unnecessary-type-assertion': 0, + }, + }, + prettierRecommendedEslint +); + +export default config; diff --git a/node/.eslintrc.js b/node/.eslintrc.js deleted file mode 100644 index 1fd2b5775c..0000000000 --- a/node/.eslintrc.js +++ /dev/null @@ -1,242 +0,0 @@ -const os = require('os'); - -const isWindows = os.platform() === 'win32'; - -const eslintConfig = -{ - env : - { - es6 : true, - node : true - }, - plugins : [], - settings : {}, - parserOptions : - { - ecmaVersion : 2018, - sourceType : 'module', - ecmaFeatures : - { - impliedStrict : true - }, - lib : [ 'es2018' ], - project : 'node/tsconfig.json' - }, - globals : - { - NodeJS : 'readonly' - }, - rules : - { - 'array-bracket-spacing' : [ 2, 'always', - { - objectsInArrays : true, - arraysInArrays : true - } - ], - 'arrow-parens' : [ 2, 'always' ], - 'arrow-spacing' : 2, - 'block-spacing' : [ 2, 'always' ], - 'brace-style' : [ 2, 'allman', { allowSingleLine: true } ], - 'camelcase' : 2, - 'comma-dangle' : 2, - 'comma-spacing' : [ 2, { before: false, after: true } ], - 'comma-style' : 2, - 'computed-property-spacing' : 2, - 'constructor-super' : 2, - 'func-call-spacing' : 2, - 'generator-star-spacing' : 2, - 'guard-for-in' : 2, - 'indent' : [ 2, 'tab', { 'SwitchCase': 1 } ], - 'key-spacing' : [ 2, - { - singleLine : - { - beforeColon : false, - afterColon : true - }, - multiLine : - { - beforeColon : true, - afterColon : true, - mode : 'minimum', - align : 'colon' - } - } - ], - 'keyword-spacing' : 2, - 'linebreak-style' : [ 2, isWindows ? 'windows' : 'unix' ], - 'lines-around-comment' : [ 2, - { - allowBlockStart : true, - allowObjectStart : true, - beforeBlockComment : false, - beforeLineComment : false - } - ], - 'max-len' : [ 2, 90, - { - tabWidth : 2, - comments : 90, - ignoreUrls : true, - ignoreStrings : true, - ignoreTemplateLiterals : true, - ignoreRegExpLiterals : true - } - ], - 'newline-after-var' : 2, - 'newline-before-return' : 2, - 'newline-per-chained-call' : 2, - 'no-alert' : 2, - 'no-caller' : 2, - 'no-case-declarations' : 2, - 'no-catch-shadow' : 2, - 'no-class-assign' : 2, - 'no-confusing-arrow' : 2, - 'no-console' : 2, - 'no-const-assign' : 2, - 'no-debugger' : 2, - 'no-dupe-args' : 2, - 'no-dupe-keys' : 2, - 'no-duplicate-case' : 2, - 'no-div-regex' : 2, - 'no-empty' : [ 2, { allowEmptyCatch: true } ], - 'no-empty-pattern' : 2, - 'no-else-return' : 0, - 'no-eval' : 2, - 'no-extend-native' : 2, - 'no-ex-assign' : 2, - 'no-extra-bind' : 2, - 'no-extra-boolean-cast' : 2, - 'no-extra-label' : 2, - 'no-extra-semi' : 2, - 'no-fallthrough' : 2, - 'no-func-assign' : 2, - 'no-global-assign' : 2, - 'no-implicit-coercion' : 2, - 'no-implicit-globals' : 2, - 'no-inner-declarations' : 2, - 'no-invalid-regexp' : 2, - 'no-invalid-this' : 2, - 'no-irregular-whitespace' : 2, - 'no-lonely-if' : 2, - 'no-mixed-operators' : 2, - 'no-mixed-spaces-and-tabs' : 2, - 'no-multi-spaces' : 2, - 'no-multi-str' : 2, - 'no-multiple-empty-lines' : [ 1, { max: 1, maxEOF: 0, maxBOF: 0 } ], - 'no-native-reassign' : 2, - 'no-negated-in-lhs' : 2, - 'no-new' : 2, - 'no-new-func' : 2, - 'no-new-wrappers' : 2, - 'no-obj-calls' : 2, - 'no-proto' : 2, - 'no-prototype-builtins' : 0, - 'no-redeclare' : 2, - 'no-regex-spaces' : 2, - 'no-restricted-imports' : 2, - 'no-return-assign' : 2, - 'no-self-assign' : 2, - 'no-self-compare' : 2, - 'no-sequences' : 2, - 'no-shadow' : 2, - 'no-shadow-restricted-names' : 2, - 'no-spaced-func' : 2, - 'no-sparse-arrays' : 2, - 'no-this-before-super' : 2, - 'no-throw-literal' : 2, - 'no-undef' : 2, - 'no-unexpected-multiline' : 2, - 'no-unmodified-loop-condition' : 2, - 'no-unreachable' : 2, - 'no-unused-vars' : [ 1, { vars: 'all', args: 'after-used' } ], - 'no-use-before-define' : 0, - 'no-useless-call' : 2, - 'no-useless-computed-key' : 2, - 'no-useless-concat' : 2, - 'no-useless-rename' : 2, - 'no-var' : 2, - 'no-whitespace-before-property' : 2, - 'object-curly-newline' : 0, - 'object-curly-spacing' : [ 2, 'always' ], - 'object-property-newline' : [ 2, { allowMultiplePropertiesPerLine: true } ], - 'prefer-const' : 2, - 'prefer-rest-params' : 2, - 'prefer-spread' : 2, - 'prefer-template' : 2, - 'quotes' : [ 2, 'single', { avoidEscape: true } ], - 'semi' : [ 2, 'always' ], - 'semi-spacing' : 2, - 'space-before-blocks' : 2, - 'space-before-function-paren' : [ 2, - { - anonymous : 'never', - named : 'never', - asyncArrow : 'always' - } - ], - 'space-in-parens' : [ 2, 'never' ], - 'spaced-comment' : [ 2, 'always' ], - 'strict' : 2, - 'valid-typeof' : 2, - 'yoda' : 2 - }, - overrides : [] -}; - -eslintConfig.overrides.push( - { - files : [ '*.ts' ], - parser : '@typescript-eslint/parser', - plugins : [ - ...eslintConfig.plugins, - '@typescript-eslint' - ], - extends : [ - 'eslint:recommended', - 'plugin:@typescript-eslint/eslint-recommended', - 'plugin:@typescript-eslint/recommended' - ], - rules : { - ...eslintConfig.rules, - 'no-unused-vars' : 0, - '@typescript-eslint/ban-types' : 0, - '@typescript-eslint/ban-ts-comment' : 0, - '@typescript-eslint/ban-ts-ignore' : 0, - '@typescript-eslint/explicit-module-boundary-types' : 0, - '@typescript-eslint/semi' : 2, - '@typescript-eslint/member-delimiter-style' : [ 2, - { - multiline : { delimiter: 'semi', requireLast: true }, - singleline : { delimiter: 'semi', requireLast: false } - } - ], - '@typescript-eslint/no-explicit-any' : 0, - '@typescript-eslint/no-unused-vars' : [ 2, - { - vars : 'all', - args : 'after-used', - ignoreRestSiblings : false - } - ], - '@typescript-eslint/no-use-before-define' : [ 2, { functions: false } ], - '@typescript-eslint/no-empty-function' : 0, - '@typescript-eslint/no-non-null-assertion' : 0 - } - }); - -eslintConfig.overrides.push( - { - files : [ '*.ts' ], - env : { - ...eslintConfig.env, - 'jest/globals' : true - }, - plugins : [ - ...eslintConfig.plugins, - 'jest' - ] - }); - -module.exports = eslintConfig; diff --git a/node/src/ActiveSpeakerObserver.ts b/node/src/ActiveSpeakerObserver.ts index 063853c882..a46ac16ab4 100644 --- a/node/src/ActiveSpeakerObserver.ts +++ b/node/src/ActiveSpeakerObserver.ts @@ -1,94 +1,94 @@ import { Logger } from './Logger'; -import { EnhancedEventEmitter } from './EnhancedEventEmitter'; -import { - RtpObserver, - RtpObserverEvents, - RtpObserverObserverEvents, - RtpObserverConstructorOptions -} from './RtpObserver'; -import { Producer } from './Producer'; - -export type ActiveSpeakerObserverOptions = -{ - interval?: number; - - /** - * Custom application data. - */ - appData?: Record; -}; - -export type ActiveSpeakerObserverDominantSpeaker = -{ - /** - * The audio Producer instance. - */ - producer: Producer; -}; - -export type ActiveSpeakerObserverEvents = RtpObserverEvents & -{ - dominantspeaker: [ActiveSpeakerObserverDominantSpeaker]; -}; - -export type ActiveSpeakerObserverObserverEvents = RtpObserverObserverEvents & -{ - dominantspeaker: [ActiveSpeakerObserverDominantSpeaker]; -}; - -type RtpObserverObserverConstructorOptions = RtpObserverConstructorOptions; +import { EnhancedEventEmitter } from './enhancedEvents'; +import type { + ActiveSpeakerObserver, + ActiveSpeakerObserverDominantSpeaker, + ActiveSpeakerObserverEvents, + ActiveSpeakerObserverObserver, + ActiveSpeakerObserverObserverEvents, +} from './ActiveSpeakerObserverTypes'; +import type { RtpObserver } from './RtpObserverTypes'; +import { RtpObserverImpl, RtpObserverConstructorOptions } from './RtpObserver'; +import type { AppData } from './types'; +import { Event, Notification } from './fbs/notification'; +import * as FbsActiveSpeakerObserver from './fbs/active-speaker-observer'; + +type RtpObserverObserverConstructorOptions = + RtpObserverConstructorOptions; const logger = new Logger('ActiveSpeakerObserver'); -export class ActiveSpeakerObserver extends RtpObserver +export class ActiveSpeakerObserverImpl< + ActiveSpeakerObserverAppData extends AppData = AppData, + > + extends RtpObserverImpl< + ActiveSpeakerObserverAppData, + ActiveSpeakerObserverEvents, + ActiveSpeakerObserverObserver + > + implements RtpObserver, ActiveSpeakerObserver { - /** - * @private - */ - constructor(options: RtpObserverObserverConstructorOptions) - { - super(options); + constructor( + options: RtpObserverObserverConstructorOptions + ) { + const observer: ActiveSpeakerObserverObserver = + new EnhancedEventEmitter(); + + super(options, observer); this.handleWorkerNotifications(); + this.handleListenerError(); } - /** - * Observer. - */ - get observer(): EnhancedEventEmitter - { + get type(): 'activespeaker' { + return 'activespeaker'; + } + + get observer(): ActiveSpeakerObserverObserver { return super.observer; } - private handleWorkerNotifications(): void - { - this.channel.on(this.internal.rtpObserverId, (event: string, data?: any) => - { - switch (event) - { - case 'dominantspeaker': - { - const producer = this.getProducerById(data.producerId); - - if (!producer) - break; + private handleWorkerNotifications(): void { + this.channel.on( + this.internal.rtpObserverId, + (event: Event, data?: Notification) => { + switch (event) { + case Event.ACTIVESPEAKEROBSERVER_DOMINANT_SPEAKER: { + const notification = + new FbsActiveSpeakerObserver.DominantSpeakerNotification(); - const dominantSpeaker: ActiveSpeakerObserverDominantSpeaker = - { - producer - }; + data!.body(notification); - this.safeEmit('dominantspeaker', dominantSpeaker); - this.observer.safeEmit('dominantspeaker', dominantSpeaker); + const producer = this.getProducerById(notification.producerId()!); - break; - } + if (!producer) { + break; + } + + const dominantSpeaker: ActiveSpeakerObserverDominantSpeaker = { + producer, + }; - default: - { - logger.error('ignoring unknown event "%s"', event); + this.safeEmit('dominantspeaker', dominantSpeaker); + this.observer.safeEmit('dominantspeaker', dominantSpeaker); + + break; + } + + default: { + logger.error(`ignoring unknown event "${event}"`); + } } } + ); + } + + private handleListenerError(): void { + this.on('listenererror', (eventName, error) => { + logger.error( + `event listener threw an error [eventName:${eventName}]:`, + error + ); }); } } diff --git a/node/src/ActiveSpeakerObserverTypes.ts b/node/src/ActiveSpeakerObserverTypes.ts new file mode 100644 index 0000000000..9d00005697 --- /dev/null +++ b/node/src/ActiveSpeakerObserverTypes.ts @@ -0,0 +1,59 @@ +import type { EnhancedEventEmitter } from './enhancedEvents'; +import type { + RtpObserver, + RtpObserverEvents, + RtpObserverObserverEvents, +} from './RtpObserverTypes'; +import type { Producer } from './ProducerTypes'; +import type { AppData } from './types'; + +export type ActiveSpeakerObserverOptions< + ActiveSpeakerObserverAppData extends AppData = AppData, +> = { + interval?: number; + + /** + * Custom application data. + */ + appData?: ActiveSpeakerObserverAppData; +}; + +export type ActiveSpeakerObserverDominantSpeaker = { + /** + * The audio Producer instance. + */ + producer: Producer; +}; + +export type ActiveSpeakerObserverEvents = RtpObserverEvents & { + dominantspeaker: [ActiveSpeakerObserverDominantSpeaker]; +}; + +export type ActiveSpeakerObserverObserver = + EnhancedEventEmitter; + +export type ActiveSpeakerObserverObserverEvents = RtpObserverObserverEvents & { + dominantspeaker: [ActiveSpeakerObserverDominantSpeaker]; +}; + +export interface ActiveSpeakerObserver< + ActiveSpeakerObserverAppData extends AppData = AppData, +> extends RtpObserver< + ActiveSpeakerObserverAppData, + ActiveSpeakerObserverEvents, + ActiveSpeakerObserverObserver + > { + /** + * RtpObserver type. + * + * @override + */ + get type(): 'activespeaker'; + + /** + * Observer. + * + * @override + */ + get observer(): ActiveSpeakerObserverObserver; +} diff --git a/node/src/AudioLevelObserver.ts b/node/src/AudioLevelObserver.ts index 0b0f8cf6c3..5890956fdb 100644 --- a/node/src/AudioLevelObserver.ts +++ b/node/src/AudioLevelObserver.ts @@ -1,132 +1,127 @@ import { Logger } from './Logger'; -import { EnhancedEventEmitter } from './EnhancedEventEmitter'; -import { - RtpObserver, - RtpObserverEvents, - RtpObserverObserverEvents, - RtpObserverConstructorOptions -} from './RtpObserver'; -import { Producer } from './Producer'; - -export type AudioLevelObserverOptions = -{ - /** - * Maximum number of entries in the 'volumesâ' event. Default 1. - */ - maxEntries?: number; - - /** - * Minimum average volume (in dBvo from -127 to 0) for entries in the - * 'volumes' event. Default -80. - */ - threshold?: number; - - /** - * Interval in ms for checking audio volumes. Default 1000. - */ - interval?: number; - - /** - * Custom application data. - */ - appData?: Record; -}; - -export type AudioLevelObserverVolume = -{ - /** - * The audio Producer instance. - */ - producer: Producer; - - /** - * The average volume (in dBvo from -127 to 0) of the audio Producer in the - * last interval. - */ - volume: number; -}; - -export type AudioLevelObserverEvents = RtpObserverEvents & -{ - volumes: [AudioLevelObserverVolume[]]; - silence: []; -}; - -export type AudioLevelObserverObserverEvents = RtpObserverObserverEvents & -{ - volumes: [AudioLevelObserverVolume[]]; - silence: []; -}; - -type AudioLevelObserverConstructorOptions = RtpObserverConstructorOptions; +import { EnhancedEventEmitter } from './enhancedEvents'; +import type { + AudioLevelObserver, + AudioLevelObserverVolume, + AudioLevelObserverEvents, + AudioLevelObserverObserver, + AudioLevelObserverObserverEvents, +} from './AudioLevelObserverTypes'; +import type { RtpObserver } from './RtpObserverTypes'; +import { RtpObserverImpl, RtpObserverConstructorOptions } from './RtpObserver'; +import type { Producer } from './ProducerTypes'; +import type { AppData } from './types'; +import * as fbsUtils from './fbsUtils'; +import { Event, Notification } from './fbs/notification'; +import * as FbsAudioLevelObserver from './fbs/audio-level-observer'; + +type AudioLevelObserverConstructorOptions = + RtpObserverConstructorOptions; const logger = new Logger('AudioLevelObserver'); -export class AudioLevelObserver extends RtpObserver +export class AudioLevelObserverImpl< + AudioLevelObserverAppData extends AppData = AppData, + > + extends RtpObserverImpl< + AudioLevelObserverAppData, + AudioLevelObserverEvents, + AudioLevelObserverObserver + > + implements RtpObserver, AudioLevelObserver { - /** - * @private - */ - constructor(options: AudioLevelObserverConstructorOptions) - { - super(options); + constructor( + options: AudioLevelObserverConstructorOptions + ) { + const observer: AudioLevelObserverObserver = + new EnhancedEventEmitter(); + + super(options, observer); this.handleWorkerNotifications(); + this.handleListenerError(); } - /** - * Observer. - */ - get observer(): EnhancedEventEmitter - { - return super.observer; + get type(): 'audiolevel' { + return 'audiolevel'; } - private handleWorkerNotifications(): void - { - this.channel.on(this.internal.rtpObserverId, (event: string, data?: any) => - { - switch (event) - { - case 'volumes': - { - // Get the corresponding Producer instance and remove entries with - // no Producer (it may have been closed in the meanwhile). - const volumes: AudioLevelObserverVolume[] = data - .map(({ producerId, volume }: { producerId: string; volume: number }) => ( - { - producer : this.getProducerById(producerId), - volume - } - )) - .filter(({ producer }: { producer: Producer }) => producer); - - if (volumes.length > 0) - { - this.safeEmit('volumes', volumes); + get observer(): AudioLevelObserverObserver { + return super.observer; + } - // Emit observer event. - this.observer.safeEmit('volumes', volumes); + private handleWorkerNotifications(): void { + this.channel.on( + this.internal.rtpObserverId, + (event: Event, data?: Notification) => { + switch (event) { + case Event.AUDIOLEVELOBSERVER_VOLUMES: { + const notification = + new FbsAudioLevelObserver.VolumesNotification(); + + data!.body(notification); + + // Get the corresponding Producer instance and remove entries with + // no Producer (it may have been closed in the meanwhile). + const volumes: AudioLevelObserverVolume[] = fbsUtils + .parseVector(notification, 'volumes', parseVolume) + .map( + ({ + producerId, + volume, + }: { + producerId: string; + volume: number; + }) => ({ + producer: this.getProducerById(producerId)!, + volume, + }) + ) + .filter(({ producer }: { producer: Producer }) => producer); + + if (volumes.length > 0) { + this.safeEmit('volumes', volumes); + + // Emit observer event. + this.observer.safeEmit('volumes', volumes); + } + + break; } - break; - } - - case 'silence': - { - this.safeEmit('silence'); + case Event.AUDIOLEVELOBSERVER_SILENCE: { + this.safeEmit('silence'); - // Emit observer event. - this.observer.safeEmit('silence'); + // Emit observer event. + this.observer.safeEmit('silence'); - break; - } + break; + } - default: - { - logger.error('ignoring unknown event "%s"', event); + default: { + logger.error(`ignoring unknown event "${event}"`); + } } } + ); + } + + private handleListenerError(): void { + this.on('listenererror', (eventName, error) => { + logger.error( + `event listener threw an error [eventName:${eventName}]:`, + error + ); }); } } + +function parseVolume(binary: FbsAudioLevelObserver.Volume): { + producerId: string; + volume: number; +} { + return { + producerId: binary.producerId()!, + volume: binary.volume(), + }; +} diff --git a/node/src/AudioLevelObserverTypes.ts b/node/src/AudioLevelObserverTypes.ts new file mode 100644 index 0000000000..dca5b0e568 --- /dev/null +++ b/node/src/AudioLevelObserverTypes.ts @@ -0,0 +1,81 @@ +import type { EnhancedEventEmitter } from './enhancedEvents'; +import type { + RtpObserver, + RtpObserverEvents, + RtpObserverObserverEvents, +} from './RtpObserverTypes'; +import type { Producer } from './ProducerTypes'; +import type { AppData } from './types'; + +export type AudioLevelObserverOptions< + AudioLevelObserverAppData extends AppData = AppData, +> = { + /** + * Maximum number of entries in the 'volumesâ' event. Default 1. + */ + maxEntries?: number; + + /** + * Minimum average volume (in dBvo from -127 to 0) for entries in the + * 'volumes' event. Default -80. + */ + threshold?: number; + + /** + * Interval in ms for checking audio volumes. Default 1000. + */ + interval?: number; + + /** + * Custom application data. + */ + appData?: AudioLevelObserverAppData; +}; + +export type AudioLevelObserverVolume = { + /** + * The audio Producer instance. + */ + producer: Producer; + + /** + * The average volume (in dBvo from -127 to 0) of the audio Producer in the + * last interval. + */ + volume: number; +}; + +export type AudioLevelObserverEvents = RtpObserverEvents & { + volumes: [AudioLevelObserverVolume[]]; + silence: []; +}; + +export type AudioLevelObserverObserver = + EnhancedEventEmitter; + +export type AudioLevelObserverObserverEvents = RtpObserverObserverEvents & { + volumes: [AudioLevelObserverVolume[]]; + silence: []; +}; + +export interface AudioLevelObserver< + AudioLevelObserverAppData extends AppData = AppData, +> extends RtpObserver< + AudioLevelObserverAppData, + AudioLevelObserverEvents, + AudioLevelObserverObserver + > { + /** + * RtpObserver type. + * + * @override + */ + get type(): 'audiolevel'; + + /** + * Observer. + * + * @override + */ + get observer(): AudioLevelObserverObserver; +} diff --git a/node/src/Channel.ts b/node/src/Channel.ts index bfa5e677e1..6621d1e188 100644 --- a/node/src/Channel.ts +++ b/node/src/Channel.ts @@ -1,14 +1,25 @@ -import * as os from 'os'; -import { Duplex } from 'stream'; +import * as os from 'node:os'; +import { Duplex } from 'node:stream'; +import { info, warn } from 'node:console'; +import * as flatbuffers from 'flatbuffers'; import { Logger } from './Logger'; -import { EnhancedEventEmitter } from './EnhancedEventEmitter'; +import { EnhancedEventEmitter } from './enhancedEvents'; import { InvalidStateError } from './errors'; +import { Body as RequestBody, Method, Request } from './fbs/request'; +import { Response } from './fbs/response'; +import { Message, Body as MessageBody } from './fbs/message'; +import { + Notification, + Body as NotificationBody, + Event, +} from './fbs/notification'; +import { Log } from './fbs/log'; + +const IS_LITTLE_ENDIAN = os.endianness() === 'LE'; -const littleEndian = os.endianness() == 'LE'; const logger = new Logger('Channel'); -type Sent = -{ +type Sent = { id: number; method: string; resolve: (data?: any) => void; @@ -20,8 +31,7 @@ type Sent = const MESSAGE_MAX_LEN = 4194308; const PAYLOAD_MAX_LEN = 4194304; -export class Channel extends EnhancedEventEmitter -{ +export class Channel extends EnhancedEventEmitter { // Closed flag. #closed = false; @@ -40,21 +50,18 @@ export class Channel extends EnhancedEventEmitter // Buffer for reading messages from the worker. #recvBuffer = Buffer.alloc(0); - /** - * @private - */ - constructor( - { - producerSocket, - consumerSocket, - pid - }: - { - producerSocket: any; - consumerSocket: any; - pid: number; - }) - { + // flatbuffers builder. + #bufferBuilder: flatbuffers.Builder = new flatbuffers.Builder(1024); + + constructor({ + producerSocket, + consumerSocket, + pid, + }: { + producerSocket: any; + consumerSocket: any; + pid: number; + }) { super(); logger.debug('constructor()'); @@ -63,21 +70,17 @@ export class Channel extends EnhancedEventEmitter this.#consumerSocket = consumerSocket as Duplex; // Read Channel responses/notifications from the worker. - this.#consumerSocket.on('data', (buffer: Buffer) => - { - if (!this.#recvBuffer.length) - { + this.#consumerSocket.on('data', (buffer: Buffer) => { + if (!this.#recvBuffer.length) { this.#recvBuffer = buffer; - } - else - { + } else { this.#recvBuffer = Buffer.concat( - [ this.#recvBuffer, buffer ], - this.#recvBuffer.length + buffer.length); + [this.#recvBuffer, buffer], + this.#recvBuffer.length + buffer.length + ); } - if (this.#recvBuffer.length > PAYLOAD_MAX_LEN) - { + if (this.#recvBuffer.length > PAYLOAD_MAX_LEN) { logger.error('receiving buffer is full, discarding all data in it'); // Reset the buffer and exit. @@ -88,115 +91,123 @@ export class Channel extends EnhancedEventEmitter let msgStart = 0; - while (true) // eslint-disable-line no-constant-condition - { + while (true) { const readLen = this.#recvBuffer.length - msgStart; - if (readLen < 4) - { + if (readLen < 4) { // Incomplete data. break; } const dataView = new DataView( this.#recvBuffer.buffer, - this.#recvBuffer.byteOffset + msgStart); - const msgLen = dataView.getUint32(0, littleEndian); + this.#recvBuffer.byteOffset + msgStart + ); + const msgLen = dataView.getUint32(0, IS_LITTLE_ENDIAN); - if (readLen < 4 + msgLen) - { + if (readLen < 4 + msgLen) { // Incomplete data. break; } - const payload = this.#recvBuffer.subarray(msgStart + 4, msgStart + 4 + msgLen); + const payload = this.#recvBuffer.subarray( + msgStart + 4, + msgStart + 4 + msgLen + ); msgStart += 4 + msgLen; - try - { - // We can receive JSON messages (Channel messages) or log strings. - switch (payload[0]) - { - // 123 = '{' (a Channel JSON message). - case 123: - this.processMessage(JSON.parse(payload.toString('utf8'))); - break; + const buf = new flatbuffers.ByteBuffer(new Uint8Array(payload)); + const message = Message.getRootAsMessage(buf); - // 68 = 'D' (a debug log). - case 68: - logger.debug(`[pid:${pid}] ${payload.toString('utf8', 1)}`); - break; + try { + switch (message.dataType()) { + case MessageBody.Response: { + const response = new Response(); - // 87 = 'W' (a warn log). - case 87: - logger.warn(`[pid:${pid}] ${payload.toString('utf8', 1)}`); - break; + message.data(response); + + this.processResponse(response); - // 69 = 'E' (an error log). - case 69: - logger.error(`[pid:${pid} ${payload.toString('utf8', 1)}`); break; + } + + case MessageBody.Notification: { + const notification = new Notification(); + + message.data(notification); + + this.processNotification(notification); - // 88 = 'X' (a dump log). - case 88: - // eslint-disable-next-line no-console - console.log(payload.toString('utf8', 1)); break; + } - default: - // eslint-disable-next-line no-console - console.warn( - `worker[pid:${pid}] unexpected data: %s`, - payload.toString('utf8', 1)); + case MessageBody.Log: { + const log = new Log(); + + message.data(log); + + this.processLog(pid, log); + + break; + } + + default: { + warn( + `worker[pid:${pid}] unexpected data: ${payload.toString( + 'utf8', + 1 + )}` + ); + } } - } - catch (error) - { + } catch (error) { logger.error( - 'received invalid message from the worker process: %s', - String(error)); + `received invalid message from the worker process: ${error}` + ); } } - if (msgStart != 0) - { + if (msgStart != 0) { this.#recvBuffer = this.#recvBuffer.slice(msgStart); } }); - this.#consumerSocket.on('end', () => ( + this.#consumerSocket.on('end', () => logger.debug('Consumer Channel ended by the worker process') - )); + ); - this.#consumerSocket.on('error', (error) => ( - logger.error('Consumer Channel error: %s', String(error)) - )); + this.#consumerSocket.on('error', error => + logger.error(`Consumer Channel error: ${error}`) + ); - this.#producerSocket.on('end', () => ( + this.#producerSocket.on('end', () => logger.debug('Producer Channel ended by the worker process') - )); + ); - this.#producerSocket.on('error', (error) => ( - logger.error('Producer Channel error: %s', String(error)) - )); + this.#producerSocket.on('error', error => + logger.error(`Producer Channel error: ${error}`) + ); } /** - * @private + * flatbuffer builder. */ - close(): void - { - if (this.#closed) + get bufferBuilder(): flatbuffers.Builder { + return this.#bufferBuilder; + } + + close(): void { + if (this.#closed) { return; + } logger.debug('close()'); this.#closed = true; // Close every pending sent. - for (const sent of this.#sents.values()) - { + for (const sent of this.#sents.values()) { sent.close(); } @@ -210,64 +221,175 @@ export class Channel extends EnhancedEventEmitter this.#producerSocket.removeAllListeners('error'); this.#producerSocket.on('error', () => {}); - // Destroy the socket after a while to allow pending incoming messages. - setTimeout(() => - { - try { this.#producerSocket.destroy(); } - catch (error) {} - try { this.#consumerSocket.destroy(); } - catch (error) {} - }, 200); + // Destroy the sockets. + try { + this.#producerSocket.destroy(); + } catch (error) {} + try { + this.#consumerSocket.destroy(); + } catch (error) {} } - /** - * @private - */ - async request(method: string, handlerId?: string, data?: any): Promise - { - this.#nextId < 4294967295 ? ++this.#nextId : (this.#nextId = 1); + notify( + event: Event, + bodyType?: NotificationBody, + bodyOffset?: number, + handlerId?: string + ): void { + logger.debug(`notify() [event:${Event[event]}]`); + + if (this.#closed) { + throw new InvalidStateError( + `Channel closed, cannot send notification [event:${Event[event]}]` + ); + } + + const handlerIdOffset = this.#bufferBuilder.createString(handlerId); + + let notificationOffset: number; + + if (bodyType && bodyOffset) { + notificationOffset = Notification.createNotification( + this.#bufferBuilder, + handlerIdOffset, + event, + bodyType, + bodyOffset + ); + } else { + notificationOffset = Notification.createNotification( + this.#bufferBuilder, + handlerIdOffset, + event, + NotificationBody.NONE, + 0 + ); + } + + const messageOffset = Message.createMessage( + this.#bufferBuilder, + MessageBody.Notification, + notificationOffset + ); + + // Finalizes the buffer and adds a 4 byte prefix with the size of the buffer. + this.#bufferBuilder.finishSizePrefixed(messageOffset); + + // Create a new buffer with this data so multiple contiguous flatbuffers + // do not point to the builder buffer overriding others info. + const buffer = new Uint8Array(this.#bufferBuilder.asUint8Array()); + + // Clear the buffer builder so it's reused for the next request. + this.#bufferBuilder.clear(); + + if (buffer.byteLength > MESSAGE_MAX_LEN) { + throw new Error(`notification too big [event:${Event[event]}]`); + } + + try { + // This may throw if closed or remote side ended. + this.#producerSocket.write(buffer, 'binary'); + } catch (error) { + logger.warn(`notify() | sending notification failed: ${error}`); + + return; + } + } + + async request( + method: Method, + bodyType?: RequestBody, + bodyOffset?: number, + handlerId?: string + ): Promise { + logger.debug(`request() [method:${Method[method]}]`); + + if (this.#closed) { + throw new InvalidStateError( + `Channel closed, cannot send request [method:${Method[method]}]` + ); + } + + if (this.#nextId < 4294967295) { + ++this.#nextId; + } else { + this.#nextId = 1; + } const id = this.#nextId; - logger.debug('request() [method:%s, id:%s]', method, id); + const handlerIdOffset = this.#bufferBuilder.createString(handlerId ?? ''); + + let requestOffset: number; + + if (bodyType && bodyOffset) { + requestOffset = Request.createRequest( + this.#bufferBuilder, + id, + method, + handlerIdOffset, + bodyType, + bodyOffset + ); + } else { + requestOffset = Request.createRequest( + this.#bufferBuilder, + id, + method, + handlerIdOffset, + RequestBody.NONE, + 0 + ); + } + + const messageOffset = Message.createMessage( + this.#bufferBuilder, + MessageBody.Request, + requestOffset + ); - if (this.#closed) - throw new InvalidStateError('Channel closed'); + // Finalizes the buffer and adds a 4 byte prefix with the size of the buffer. + this.#bufferBuilder.finishSizePrefixed(messageOffset); - const request = `${id}:${method}:${handlerId}:${JSON.stringify(data)}`; + // Create a new buffer with this data so multiple contiguous flatbuffers + // do not point to the builder buffer overriding others info. + const buffer = new Uint8Array(this.#bufferBuilder.asUint8Array()); - if (Buffer.byteLength(request) > MESSAGE_MAX_LEN) - throw new Error('Channel request too big'); + // Clear the buffer builder so it's reused for the next request. + this.#bufferBuilder.clear(); + + if (buffer.byteLength > MESSAGE_MAX_LEN) { + throw new Error(`request too big [method:${Method[method]}]`); + } // This may throw if closed or remote side ended. - this.#producerSocket.write( - Buffer.from(Uint32Array.of(Buffer.byteLength(request)).buffer)); - this.#producerSocket.write(request); - - return new Promise((pResolve, pReject) => - { - const sent: Sent = - { - id : id, - method : method, - resolve : (data2) => - { - if (!this.#sents.delete(id)) + this.#producerSocket.write(buffer, 'binary'); + + return new Promise((pResolve, pReject) => { + const sent: Sent = { + id: id, + method: Method[method], + resolve: data2 => { + if (!this.#sents.delete(id)) { return; + } pResolve(data2); }, - reject : (error) => - { - if (!this.#sents.delete(id)) + reject: error => { + if (!this.#sents.delete(id)) { return; + } pReject(error); }, - close : () => - { - pReject(new InvalidStateError('Channel closed')); - } + close: () => { + pReject( + new InvalidStateError( + `Channel closed, pending request aborted [method:${Method[method]}, id:${id}]` + ) + ); + }, }; // Add sent stuff to the map. @@ -275,67 +397,89 @@ export class Channel extends EnhancedEventEmitter }); } - private processMessage(msg: any): void - { - // If a response, retrieve its associated request. - if (msg.id) - { - const sent = this.#sents.get(msg.id); + private processResponse(response: Response): void { + const sent = this.#sents.get(response.id()); - if (!sent) - { - logger.error( - 'received response does not match any sent request [id:%s]', msg.id); + if (!sent) { + logger.error( + `received response does not match any sent request [id:${response.id()}]` + ); - return; + return; + } + + if (response.accepted()) { + logger.debug(`request succeeded [method:${sent.method}, id:${sent.id}]`); + + sent.resolve(response); + } else if (response.error()) { + logger.warn( + `request failed [method:${sent.method}, id:${ + sent.id + }]: ${response.reason()}` + ); + + switch (response.error()!) { + case 'TypeError': { + sent.reject(new TypeError(response.reason()!)); + + break; + } + + default: { + sent.reject(new Error(response.reason()!)); + } } + } else { + logger.error( + `received response is not accepted nor rejected [method:${sent.method}, id:${sent.id}]` + ); + } + } - if (msg.accepted) - { - logger.debug( - 'request succeeded [method:%s, id:%s]', sent.method, sent.id); + private processNotification(notification: Notification): void { + // Due to how Promises work, it may happen that we receive a response + // from the worker followed by a notification from the worker. If we + // emit the notification immediately it may reach its target **before** + // the response, destroying the ordered delivery. So we must wait a bit + // here. + // See https://github.com/versatica/mediasoup/issues/510 + setImmediate(() => + this.emit(notification.handlerId()!, notification.event(), notification) + ); + } - sent.resolve(msg.data); + private processLog(pid: number, log: Log): void { + const logData = log.data()!; + + switch (logData[0]) { + // 'D' (a debug log). + case 'D': { + logger.debug(`[pid:${pid}] ${logData.slice(1)}`); + + break; } - else if (msg.error) - { - logger.warn( - 'request failed [method:%s, id:%s]: %s', - sent.method, sent.id, msg.reason); - - switch (msg.error) - { - case 'TypeError': - sent.reject(new TypeError(msg.reason)); - break; - - default: - sent.reject(new Error(msg.reason)); - } + + // 'W' (a warn log). + case 'W': { + logger.warn(`[pid:${pid}] ${logData.slice(1)}`); + + break; } - else - { - logger.error( - 'received response is not accepted nor rejected [method:%s, id:%s]', - sent.method, sent.id); + + // 'E' (a error log). + case 'E': { + logger.error(`[pid:${pid}] ${logData.slice(1)}`); + + break; + } + + // 'X' (a dump log). + case 'X': { + info(logData.slice(1)); + + break; } - } - // If a notification emit it to the corresponding entity. - else if (msg.targetId && msg.event) - { - // Due to how Promises work, it may happen that we receive a response - // from the worker followed by a notification from the worker. If we - // emit the notification immediately it may reach its target **before** - // the response, destroying the ordered delivery. So we must wait a bit - // here. - // See https://github.com/versatica/mediasoup/issues/510 - setImmediate(() => this.emit(String(msg.targetId), msg.event, msg.data)); - } - // Otherwise unexpected message. - else - { - logger.error( - 'received message is not a response nor a notification'); } } } diff --git a/node/src/Consumer.ts b/node/src/Consumer.ts index 23c5d47a07..ac092a5d2f 100644 --- a/node/src/Consumer.ts +++ b/node/src/Consumer.ts @@ -1,198 +1,54 @@ import { Logger } from './Logger'; -import { EnhancedEventEmitter } from './EnhancedEventEmitter'; +import { EnhancedEventEmitter } from './enhancedEvents'; +import type { + Consumer, + ConsumerType, + ConsumerScore, + ConsumerLayers, + ConsumerDump, + SimpleConsumerDump, + SimulcastConsumerDump, + SvcConsumerDump, + PipeConsumerDump, + BaseConsumerDump, + RtpStreamDump, + RtpStreamParametersDump, + RtxStreamDump, + RtxStreamParameters, + ConsumerStat, + ConsumerTraceEventType, + ConsumerTraceEventData, + ConsumerEvents, + ConsumerObserver, + ConsumerObserverEvents, +} from './ConsumerTypes'; import { Channel } from './Channel'; -import { PayloadChannel } from './PayloadChannel'; -import { TransportInternal } from './Transport'; -import { ProducerStat } from './Producer'; +import type { TransportInternal } from './Transport'; +import type { ProducerStat } from './ProducerTypes'; +import type { MediaKind, RtpParameters } from './rtpParametersTypes'; import { - MediaKind, - RtpCapabilities, - RtpParameters -} from './RtpParameters'; - -export type ConsumerOptions = -{ - /** - * The id of the Producer to consume. - */ - producerId: string; - - /** - * RTP capabilities of the consuming endpoint. - */ - rtpCapabilities: RtpCapabilities; - - /** - * Whether the Consumer must start in paused mode. Default false. - * - * When creating a video Consumer, it's recommended to set paused to true, - * then transmit the Consumer parameters to the consuming endpoint and, once - * the consuming endpoint has created its local side Consumer, unpause the - * server side Consumer using the resume() method. This is an optimization - * to make it possible for the consuming endpoint to render the video as far - * as possible. If the server side Consumer was created with paused: false, - * mediasoup will immediately request a key frame to the remote Producer and - * suych a key frame may reach the consuming endpoint even before it's ready - * to consume it, generating âblackâ video until the device requests a keyframe - * by itself. - */ - paused?: boolean; - - /** - * The MID for the Consumer. If not specified, a sequentially growing - * number will be assigned. - */ - mid?: string; - - /** - * Preferred spatial and temporal layer for simulcast or SVC media sources. - * If unset, the highest ones are selected. - */ - preferredLayers?: ConsumerLayers; - - /** - * Whether this Consumer should ignore DTX packets (only valid for Opus codec). - * If set, DTX packets are not forwarded to the remote Consumer. - */ - ignoreDtx?: Boolean; - - /** - * Whether this Consumer should consume all RTP streams generated by the - * Producer. - */ - pipe?: boolean; - - /** - * Custom application data. - */ - appData?: Record; -}; - -/** - * Valid types for 'trace' event. - */ -export type ConsumerTraceEventType = 'rtp' | 'keyframe' | 'nack' | 'pli' | 'fir'; - -/** - * 'trace' event data. - */ -export type ConsumerTraceEventData = -{ - /** - * Trace type. - */ - type: ConsumerTraceEventType; - - /** - * Event timestamp. - */ - timestamp: number; - - /** - * Event direction. - */ - direction: 'in' | 'out'; - - /** - * Per type information. - */ - info: any; -}; - -export type ConsumerScore = -{ - /** - * The score of the RTP stream of the consumer. - */ - score: number; - - /** - * The score of the currently selected RTP stream of the producer. - */ - producerScore: number; - - /** - * The scores of all RTP streams in the producer ordered by encoding (just - * useful when the producer uses simulcast). - */ - producerScores: number[]; -}; - -export type ConsumerLayers = -{ - /** - * The spatial layer index (from 0 to N). - */ - spatialLayer: number; - - /** - * The temporal layer index (from 0 to N). - */ - temporalLayer?: number; -}; - -export type ConsumerStat = -{ - // Common to all RtpStreams. - type: string; - timestamp: number; - ssrc: number; - rtxSsrc?: number; - kind: string; - mimeType: string; - packetsLost: number; - fractionLost: number; - packetsDiscarded: number; - packetsRetransmitted: number; - packetsRepaired: number; - nackCount: number; - nackPacketCount: number; - pliCount: number; - firCount: number; - score: number; - packetCount: number; - byteCount: number; - bitrate: number; - roundTripTime?: number; -}; - -/** - * Consumer type. - */ -export type ConsumerType = 'simple' | 'simulcast' | 'svc' | 'pipe'; - -export type ConsumerEvents = -{ - transportclose: []; - producerclose: []; - producerpause: []; - producerresume: []; - score: [ConsumerScore]; - layerschange: [ConsumerLayers?]; - trace: [ConsumerTraceEventData]; - rtp: [Buffer]; - // Private events. - '@close': []; - '@producerclose': []; -}; - -export type ConsumerObserverEvents = -{ - close: []; - pause: []; - resume: []; - score: [ConsumerScore]; - layerschange: [ConsumerLayers?]; - trace: [ConsumerTraceEventData]; -}; - -type ConsumerInternal = TransportInternal & -{ + parseRtpEncodingParameters, + parseRtpParameters, +} from './rtpParametersFbsUtils'; +import { parseRtpStreamStats } from './rtpStreamStatsFbsUtils'; +import type { AppData } from './types'; +import * as fbsUtils from './fbsUtils'; +import { Event, Notification } from './fbs/notification'; +import { TraceDirection as FbsTraceDirection } from './fbs/common'; +import * as FbsRequest from './fbs/request'; +import * as FbsTransport from './fbs/transport'; +import * as FbsConsumer from './fbs/consumer'; +import * as FbsConsumerTraceInfo from './fbs/consumer/trace-info'; +import * as FbsRtpStream from './fbs/rtp-stream'; +import * as FbsRtxStream from './fbs/rtx-stream'; +import { Type as FbsRtpParametersType } from './fbs/rtp-parameters'; +import * as FbsRtpParameters from './fbs/rtp-parameters'; + +type ConsumerInternal = TransportInternal & { consumerId: string; }; -type ConsumerData = -{ +type ConsumerData = { producerId: string; kind: MediaKind; rtpParameters: RtpParameters; @@ -201,7 +57,9 @@ type ConsumerData = const logger = new Logger('Consumer'); -export class Consumer extends EnhancedEventEmitter +export class ConsumerImpl + extends EnhancedEventEmitter + implements Consumer { // Internal data. readonly #internal: ConsumerInternal; @@ -212,14 +70,11 @@ export class Consumer extends EnhancedEventEmitter // Channel instance. readonly #channel: Channel; - // PayloadChannel instance. - #payloadChannel: PayloadChannel; - // Closed flag. #closed = false; // Custom app data. - readonly #appData: Record; + #appData: ConsumerAppData; // Paused flag. #paused = false; @@ -240,35 +95,28 @@ export class Consumer extends EnhancedEventEmitter #currentLayers?: ConsumerLayers; // Observer instance. - readonly #observer = new EnhancedEventEmitter(); - - /** - * @private - */ - constructor( - { - internal, - data, - channel, - payloadChannel, - appData, - paused, - producerPaused, - score = { score: 10, producerScore: 10, producerScores: [] }, - preferredLayers - }: - { - internal: ConsumerInternal; - data: ConsumerData; - channel: Channel; - payloadChannel: PayloadChannel; - appData?: Record; - paused: boolean; - producerPaused: boolean; - score?: ConsumerScore; - preferredLayers?: ConsumerLayers; - }) - { + readonly #observer: ConsumerObserver = + new EnhancedEventEmitter(); + + constructor({ + internal, + data, + channel, + appData, + paused, + producerPaused, + score = { score: 10, producerScore: 10, producerScores: [] }, + preferredLayers, + }: { + internal: ConsumerInternal; + data: ConsumerData; + channel: Channel; + appData?: ConsumerAppData; + paused: boolean; + producerPaused: boolean; + score?: ConsumerScore; + preferredLayers?: ConsumerLayers; + }) { super(); logger.debug('constructor()'); @@ -276,152 +124,89 @@ export class Consumer extends EnhancedEventEmitter this.#internal = internal; this.#data = data; this.#channel = channel; - this.#payloadChannel = payloadChannel; - this.#appData = appData || {}; this.#paused = paused; this.#producerPaused = producerPaused; this.#score = score; this.#preferredLayers = preferredLayers; + this.#appData = appData ?? ({} as ConsumerAppData); this.handleWorkerNotifications(); + this.handleListenerError(); } - /** - * Consumer id. - */ - get id(): string - { + get id(): string { return this.#internal.consumerId; } - /** - * Associated Producer id. - */ - get producerId(): string - { + get producerId(): string { return this.#data.producerId; } - /** - * Whether the Consumer is closed. - */ - get closed(): boolean - { + get closed(): boolean { return this.#closed; } - /** - * Media kind. - */ - get kind(): MediaKind - { + get kind(): MediaKind { return this.#data.kind; } - /** - * RTP parameters. - */ - get rtpParameters(): RtpParameters - { + get rtpParameters(): RtpParameters { return this.#data.rtpParameters; } - /** - * Consumer type. - */ - get type(): ConsumerType - { + get type(): ConsumerType { return this.#data.type; } - /** - * Whether the Consumer is paused. - */ - get paused(): boolean - { + get paused(): boolean { return this.#paused; } - /** - * Whether the associate Producer is paused. - */ - get producerPaused(): boolean - { + get producerPaused(): boolean { return this.#producerPaused; } - /** - * Current priority. - */ - get priority(): number - { + get priority(): number { return this.#priority; } - /** - * Consumer score. - */ - get score(): ConsumerScore - { + get score(): ConsumerScore { return this.#score; } - /** - * Preferred video layers. - */ - get preferredLayers(): ConsumerLayers | undefined - { + get preferredLayers(): ConsumerLayers | undefined { return this.#preferredLayers; } - /** - * Current video layers. - */ - get currentLayers(): ConsumerLayers | undefined - { + get currentLayers(): ConsumerLayers | undefined { return this.#currentLayers; } - /** - * App custom data. - */ - get appData(): Record - { + get appData(): ConsumerAppData { return this.#appData; } - /** - * Invalid setter. - */ - set appData(appData: Record) // eslint-disable-line no-unused-vars - { - throw new Error('cannot override appData object'); + set appData(appData: ConsumerAppData) { + this.#appData = appData; } - /** - * Observer. - */ - get observer(): EnhancedEventEmitter - { + get observer(): ConsumerObserver { return this.#observer; } /** - * @private * Just for testing purposes. + * + * @private */ - get channelForTesting(): Channel - { + get channelForTesting(): Channel { return this.#channel; } - /** - * Close the Consumer. - */ - close(): void - { - if (this.#closed) + close(): void { + if (this.#closed) { return; + } logger.debug('close()'); @@ -429,11 +214,19 @@ export class Consumer extends EnhancedEventEmitter // Remove notification subscriptions. this.#channel.removeAllListeners(this.#internal.consumerId); - this.#payloadChannel.removeAllListeners(this.#internal.consumerId); - const reqData = { consumerId: this.#internal.consumerId }; - - this.#channel.request('transport.closeConsumer', this.#internal.transportId, reqData) + /* Build Request. */ + const requestOffset = new FbsTransport.CloseConsumerRequestT( + this.#internal.consumerId + ).pack(this.#channel.bufferBuilder); + + this.#channel + .request( + FbsRequest.Method.TRANSPORT_CLOSE_CONSUMER, + FbsRequest.Body.Transport_CloseConsumerRequest, + requestOffset, + this.#internal.transportId + ) .catch(() => {}); this.emit('@close'); @@ -442,15 +235,10 @@ export class Consumer extends EnhancedEventEmitter this.#observer.safeEmit('close'); } - /** - * Transport was closed. - * - * @private - */ - transportClosed(): void - { - if (this.#closed) + transportClosed(): void { + if (this.#closed) { return; + } logger.debug('transportClosed()'); @@ -458,7 +246,6 @@ export class Consumer extends EnhancedEventEmitter // Remove notification subscriptions. this.#channel.removeAllListeners(this.#internal.consumerId); - this.#payloadChannel.removeAllListeners(this.#internal.consumerId); this.safeEmit('transportclose'); @@ -466,267 +253,647 @@ export class Consumer extends EnhancedEventEmitter this.#observer.safeEmit('close'); } - /** - * Dump Consumer. - */ - async dump(): Promise - { + async dump(): Promise { logger.debug('dump()'); - return this.#channel.request('consumer.dump', this.#internal.consumerId); + const response = await this.#channel.request( + FbsRequest.Method.CONSUMER_DUMP, + undefined, + undefined, + this.#internal.consumerId + ); + + /* Decode Response. */ + const data = new FbsConsumer.DumpResponse(); + + response.body(data); + + return parseConsumerDumpResponse(data); } - /** - * Get Consumer stats. - */ - async getStats(): Promise> - { + async getStats(): Promise<(ConsumerStat | ProducerStat)[]> { logger.debug('getStats()'); - return this.#channel.request('consumer.getStats', this.#internal.consumerId); + const response = await this.#channel.request( + FbsRequest.Method.CONSUMER_GET_STATS, + undefined, + undefined, + this.#internal.consumerId + ); + + /* Decode Response. */ + const data = new FbsConsumer.GetStatsResponse(); + + response.body(data); + + return parseConsumerStats(data); } - /** - * Pause the Consumer. - */ - async pause(): Promise - { + async pause(): Promise { logger.debug('pause()'); - const wasPaused = this.#paused || this.#producerPaused; + await this.#channel.request( + FbsRequest.Method.CONSUMER_PAUSE, + undefined, + undefined, + this.#internal.consumerId + ); - await this.#channel.request('consumer.pause', this.#internal.consumerId); + const wasPaused = this.#paused; this.#paused = true; // Emit observer event. - if (!wasPaused) + if (!wasPaused && !this.#producerPaused) { this.#observer.safeEmit('pause'); + } } - /** - * Resume the Consumer. - */ - async resume(): Promise - { + async resume(): Promise { logger.debug('resume()'); - const wasPaused = this.#paused || this.#producerPaused; + await this.#channel.request( + FbsRequest.Method.CONSUMER_RESUME, + undefined, + undefined, + this.#internal.consumerId + ); - await this.#channel.request('consumer.resume', this.#internal.consumerId); + const wasPaused = this.#paused; this.#paused = false; // Emit observer event. - if (wasPaused && !this.#producerPaused) + if (wasPaused && !this.#producerPaused) { this.#observer.safeEmit('resume'); + } } - /** - * Set preferred video layers. - */ - async setPreferredLayers( - { - spatialLayer, - temporalLayer - }: ConsumerLayers - ): Promise - { + async setPreferredLayers({ + spatialLayer, + temporalLayer, + }: ConsumerLayers): Promise { logger.debug('setPreferredLayers()'); - const reqData = { spatialLayer, temporalLayer }; - - const data = await this.#channel.request( - 'consumer.setPreferredLayers', this.#internal.consumerId, reqData); + if (typeof spatialLayer !== 'number') { + throw new TypeError('spatialLayer must be a number'); + } + if (temporalLayer && typeof temporalLayer !== 'number') { + throw new TypeError('if given, temporalLayer must be a number'); + } + + const builder = this.#channel.bufferBuilder; + + const preferredLayersOffset = + FbsConsumer.ConsumerLayers.createConsumerLayers( + builder, + spatialLayer, + temporalLayer ?? null + ); + const requestOffset = + FbsConsumer.SetPreferredLayersRequest.createSetPreferredLayersRequest( + builder, + preferredLayersOffset + ); + + const response = await this.#channel.request( + FbsRequest.Method.CONSUMER_SET_PREFERRED_LAYERS, + FbsRequest.Body.Consumer_SetPreferredLayersRequest, + requestOffset, + this.#internal.consumerId + ); + + /* Decode Response. */ + const data = new FbsConsumer.SetPreferredLayersResponse(); + + let preferredLayers: ConsumerLayers | undefined; + + // Response is empty for non Simulcast Consumers. + if (response.body(data)) { + const status = data.unpack(); + + if (status.preferredLayers) { + preferredLayers = { + spatialLayer: status.preferredLayers.spatialLayer, + temporalLayer: status.preferredLayers.temporalLayer ?? undefined, + }; + } + } - this.#preferredLayers = data || undefined; + this.#preferredLayers = preferredLayers; } - /** - * Set priority. - */ - async setPriority(priority: number): Promise - { + async setPriority(priority: number): Promise { logger.debug('setPriority()'); - const reqData = { priority }; + if (typeof priority !== 'number' || priority < 0) { + throw new TypeError('priority must be a positive number'); + } - const data = await this.#channel.request( - 'consumer.setPriority', this.#internal.consumerId, reqData); + const requestOffset = + FbsConsumer.SetPriorityRequest.createSetPriorityRequest( + this.#channel.bufferBuilder, + priority + ); - this.#priority = data.priority; - } + const response = await this.#channel.request( + FbsRequest.Method.CONSUMER_SET_PRIORITY, + FbsRequest.Body.Consumer_SetPriorityRequest, + requestOffset, + this.#internal.consumerId + ); - /** - * Unset priority. - */ - async unsetPriority(): Promise - { - logger.debug('unsetPriority()'); + const data = new FbsConsumer.SetPriorityResponse(); - const reqData = { priority: 1 }; + response.body(data); - const data = await this.#channel.request( - 'consumer.setPriority', this.#internal.consumerId, reqData); + const status = data.unpack(); - this.#priority = data.priority; + this.#priority = status.priority; } - /** - * Request a key frame to the Producer. - */ - async requestKeyFrame(): Promise - { + async unsetPriority(): Promise { + logger.debug('unsetPriority()'); + + await this.setPriority(1); + } + + async requestKeyFrame(): Promise { logger.debug('requestKeyFrame()'); - await this.#channel.request('consumer.requestKeyFrame', this.#internal.consumerId); + await this.#channel.request( + FbsRequest.Method.CONSUMER_REQUEST_KEY_FRAME, + undefined, + undefined, + this.#internal.consumerId + ); } - /** - * Enable 'trace' event. - */ - async enableTraceEvent(types: ConsumerTraceEventType[] = []): Promise - { + async enableTraceEvent(types: ConsumerTraceEventType[] = []): Promise { logger.debug('enableTraceEvent()'); - const reqData = { types }; + if (!Array.isArray(types)) { + throw new TypeError('types must be an array'); + } + if (types.find(type => typeof type !== 'string')) { + throw new TypeError('every type must be a string'); + } + + // Convert event types. + const fbsEventTypes: FbsConsumer.TraceEventType[] = []; + + for (const eventType of types) { + try { + fbsEventTypes.push(consumerTraceEventTypeToFbs(eventType)); + } catch (error) { + logger.warn('enableTraceEvent() | [error:${error}]'); + } + } + + /* Build Request. */ + const requestOffset = new FbsConsumer.EnableTraceEventRequestT( + fbsEventTypes + ).pack(this.#channel.bufferBuilder); await this.#channel.request( - 'consumer.enableTraceEvent', this.#internal.consumerId, reqData); - } - - private handleWorkerNotifications(): void - { - this.#channel.on(this.#internal.consumerId, (event: string, data?: any) => - { - switch (event) - { - case 'producerclose': - { - if (this.#closed) - break; + FbsRequest.Method.CONSUMER_ENABLE_TRACE_EVENT, + FbsRequest.Body.Consumer_EnableTraceEventRequest, + requestOffset, + this.#internal.consumerId + ); + } - this.#closed = true; + private handleWorkerNotifications(): void { + this.#channel.on( + this.#internal.consumerId, + (event: Event, data?: Notification) => { + switch (event) { + case Event.CONSUMER_PRODUCER_CLOSE: { + if (this.#closed) { + break; + } - // Remove notification subscriptions. - this.#channel.removeAllListeners(this.#internal.consumerId); - this.#payloadChannel.removeAllListeners(this.#internal.consumerId); + this.#closed = true; - this.emit('@producerclose'); - this.safeEmit('producerclose'); + // Remove notification subscriptions. + this.#channel.removeAllListeners(this.#internal.consumerId); - // Emit observer event. - this.#observer.safeEmit('close'); + this.emit('@producerclose'); + this.safeEmit('producerclose'); - break; - } + // Emit observer event. + this.#observer.safeEmit('close'); - case 'producerpause': - { - if (this.#producerPaused) break; + } + + case Event.CONSUMER_PRODUCER_PAUSE: { + if (this.#producerPaused) { + break; + } - const wasPaused = this.#paused || this.#producerPaused; + this.#producerPaused = true; - this.#producerPaused = true; + this.safeEmit('producerpause'); - this.safeEmit('producerpause'); + // Emit observer event. + if (!this.#paused) { + this.#observer.safeEmit('pause'); + } - // Emit observer event. - if (!wasPaused) - this.#observer.safeEmit('pause'); + break; + } - break; - } + case Event.CONSUMER_PRODUCER_RESUME: { + if (!this.#producerPaused) { + break; + } + + this.#producerPaused = false; + + this.safeEmit('producerresume'); + + // Emit observer event. + if (!this.#paused) { + this.#observer.safeEmit('resume'); + } - case 'producerresume': - { - if (!this.#producerPaused) break; + } - const wasPaused = this.#paused || this.#producerPaused; + case Event.CONSUMER_SCORE: { + const notification = new FbsConsumer.ScoreNotification(); - this.#producerPaused = false; + data!.body(notification); - this.safeEmit('producerresume'); + const score: ConsumerScore = notification.score()!.unpack(); - // Emit observer event. - if (wasPaused && !this.#paused) - this.#observer.safeEmit('resume'); + this.#score = score; - break; - } + this.safeEmit('score', score); + + // Emit observer event. + this.#observer.safeEmit('score', score); + + break; + } - case 'score': - { - const score = data as ConsumerScore; + case Event.CONSUMER_LAYERS_CHANGE: { + const notification = new FbsConsumer.LayersChangeNotification(); - this.#score = score; + data!.body(notification); - this.safeEmit('score', score); + const layers: ConsumerLayers | undefined = notification.layers() + ? parseConsumerLayers(notification.layers()!) + : undefined; - // Emit observer event. - this.#observer.safeEmit('score', score); + this.#currentLayers = layers; - break; - } + this.safeEmit('layerschange', layers); - case 'layerschange': - { - const layers = data as ConsumerLayers | undefined; + // Emit observer event. + this.#observer.safeEmit('layerschange', layers); - this.#currentLayers = layers; + break; + } - this.safeEmit('layerschange', layers); + case Event.CONSUMER_TRACE: { + const notification = new FbsConsumer.TraceNotification(); - // Emit observer event. - this.#observer.safeEmit('layerschange', layers); + data!.body(notification); - break; - } + const trace: ConsumerTraceEventData = + parseTraceEventData(notification); - case 'trace': - { - const trace = data as ConsumerTraceEventData; + this.safeEmit('trace', trace); - this.safeEmit('trace', trace); + // Emit observer event. + this.observer.safeEmit('trace', trace); - // Emit observer event. - this.#observer.safeEmit('trace', trace); + this.safeEmit('trace', trace); - break; - } + // Emit observer event. + this.#observer.safeEmit('trace', trace); - default: - { - logger.error('ignoring unknown event "%s"', event); - } - } - }); + break; + } - this.#payloadChannel.on( - this.#internal.consumerId, - (event: string, data: any | undefined, payload: Buffer) => - { - switch (event) - { - case 'rtp': - { - if (this.#closed) + case Event.CONSUMER_RTP: { + if (this.#closed) { break; + } + + const notification = new FbsConsumer.RtpNotification(); - const packet = payload; + data!.body(notification); - this.safeEmit('rtp', packet); + this.safeEmit('rtp', Buffer.from(notification.dataArray()!)); break; } - default: - { - logger.error('ignoring unknown event "%s"', event); + default: { + logger.error(`ignoring unknown event "${event}"`); } } - }); + } + ); } + + private handleListenerError(): void { + this.on('listenererror', (eventName, error) => { + logger.error( + `event listener threw an error [eventName:${eventName}]:`, + error + ); + }); + } +} + +export function parseTraceEventData( + trace: FbsConsumer.TraceNotification +): ConsumerTraceEventData { + let info: any; + + if (trace.infoType() !== FbsConsumer.TraceInfo.NONE) { + const accessor = trace.info.bind(trace); + + info = FbsConsumerTraceInfo.unionToTraceInfo(trace.infoType(), accessor); + + trace.info(info); + } + + return { + type: consumerTraceEventTypeFromFbs(trace.type()), + timestamp: Number(trace.timestamp()), + direction: + trace.direction() === FbsTraceDirection.DIRECTION_IN ? 'in' : 'out', + info: info ? info.unpack() : undefined, + }; +} + +function consumerTraceEventTypeToFbs( + eventType: ConsumerTraceEventType +): FbsConsumer.TraceEventType { + switch (eventType) { + case 'keyframe': { + return FbsConsumer.TraceEventType.KEYFRAME; + } + + case 'fir': { + return FbsConsumer.TraceEventType.FIR; + } + + case 'nack': { + return FbsConsumer.TraceEventType.NACK; + } + + case 'pli': { + return FbsConsumer.TraceEventType.PLI; + } + + case 'rtp': { + return FbsConsumer.TraceEventType.RTP; + } + + default: { + throw new TypeError(`invalid ConsumerTraceEventType: ${eventType}`); + } + } +} + +function consumerTraceEventTypeFromFbs( + traceType: FbsConsumer.TraceEventType +): ConsumerTraceEventType { + switch (traceType) { + case FbsConsumer.TraceEventType.KEYFRAME: { + return 'keyframe'; + } + + case FbsConsumer.TraceEventType.FIR: { + return 'fir'; + } + + case FbsConsumer.TraceEventType.NACK: { + return 'nack'; + } + + case FbsConsumer.TraceEventType.PLI: { + return 'pli'; + } + + case FbsConsumer.TraceEventType.RTP: { + return 'rtp'; + } + + default: { + throw new TypeError(`invalid FbsConsumer.TraceEventType: ${traceType}`); + } + } +} + +function parseConsumerLayers(data: FbsConsumer.ConsumerLayers): ConsumerLayers { + const spatialLayer = data.spatialLayer(); + const temporalLayer = + data.temporalLayer() !== null ? data.temporalLayer()! : undefined; + + return { + spatialLayer, + temporalLayer, + }; +} + +function parseRtpStream(data: FbsRtpStream.Dump): RtpStreamDump { + const params = parseRtpStreamParameters(data.params()!); + + let rtxStream: RtxStreamDump | undefined; + + if (data.rtxStream()) { + rtxStream = parseRtxStream(data.rtxStream()!); + } + + return { + params, + score: data.score(), + rtxStream, + }; +} + +function parseRtpStreamParameters( + data: FbsRtpStream.Params +): RtpStreamParametersDump { + return { + encodingIdx: data.encodingIdx(), + ssrc: data.ssrc(), + payloadType: data.payloadType(), + mimeType: data.mimeType()!, + clockRate: data.clockRate(), + rid: data.rid()!.length > 0 ? data.rid()! : undefined, + cname: data.cname()!, + rtxSsrc: data.rtxSsrc() !== null ? data.rtxSsrc()! : undefined, + rtxPayloadType: + data.rtxPayloadType() !== null ? data.rtxPayloadType()! : undefined, + useNack: data.useNack(), + usePli: data.usePli(), + useFir: data.useFir(), + useInBandFec: data.useInBandFec(), + useDtx: data.useDtx(), + spatialLayers: data.spatialLayers(), + temporalLayers: data.temporalLayers(), + }; +} + +function parseRtxStream(data: FbsRtxStream.RtxDump): RtxStreamDump { + const params = parseRtxStreamParameters(data.params()!); + + return { + params, + }; +} + +function parseRtxStreamParameters( + data: FbsRtxStream.Params +): RtxStreamParameters { + return { + ssrc: data.ssrc(), + payloadType: data.payloadType(), + mimeType: data.mimeType()!, + clockRate: data.clockRate(), + rrid: data.rrid()!.length > 0 ? data.rrid()! : undefined, + cname: data.cname()!, + }; +} + +function parseBaseConsumerDump( + data: FbsConsumer.BaseConsumerDump +): BaseConsumerDump { + return { + id: data.id()!, + producerId: data.producerId()!, + kind: data.kind() === FbsRtpParameters.MediaKind.AUDIO ? 'audio' : 'video', + rtpParameters: parseRtpParameters(data.rtpParameters()!), + consumableRtpEncodings: + data.consumableRtpEncodingsLength() > 0 + ? fbsUtils.parseVector( + data, + 'consumableRtpEncodings', + parseRtpEncodingParameters + ) + : undefined, + traceEventTypes: fbsUtils.parseVector( + data, + 'traceEventTypes', + consumerTraceEventTypeFromFbs + ), + supportedCodecPayloadTypes: fbsUtils.parseVector( + data, + 'supportedCodecPayloadTypes' + ), + paused: data.paused(), + producerPaused: data.producerPaused(), + priority: data.priority(), + }; +} + +function parseSimpleConsumerDump( + data: FbsConsumer.ConsumerDump +): SimpleConsumerDump { + const base = parseBaseConsumerDump(data.base()!); + const rtpStream = parseRtpStream(data.rtpStreams(0)!); + + return { + ...base, + type: 'simple', + rtpStream, + }; +} + +function parseSimulcastConsumerDump( + data: FbsConsumer.ConsumerDump +): SimulcastConsumerDump { + const base = parseBaseConsumerDump(data.base()!); + const rtpStream = parseRtpStream(data.rtpStreams(0)!); + + return { + ...base, + type: 'simulcast', + rtpStream, + preferredSpatialLayer: data.preferredSpatialLayer()!, + targetSpatialLayer: data.targetSpatialLayer()!, + currentSpatialLayer: data.currentSpatialLayer()!, + preferredTemporalLayer: data.preferredTemporalLayer()!, + targetTemporalLayer: data.targetTemporalLayer()!, + currentTemporalLayer: data.currentTemporalLayer()!, + }; +} + +function parseSvcConsumerDump(data: FbsConsumer.ConsumerDump): SvcConsumerDump { + const dump = parseSimulcastConsumerDump(data); + + dump.type = 'svc'; + + return dump; +} + +function parsePipeConsumerDump( + data: FbsConsumer.ConsumerDump +): PipeConsumerDump { + const base = parseBaseConsumerDump(data.base()!); + const rtpStreams = fbsUtils.parseVector(data, 'rtpStreams', parseRtpStream); + + return { + ...base, + type: 'pipe', + rtpStreams, + }; +} + +function parseConsumerDumpResponse( + data: FbsConsumer.DumpResponse +): ConsumerDump { + const type = data.data()!.base()!.type(); + + switch (type) { + case FbsRtpParametersType.SIMPLE: { + const dump = new FbsConsumer.ConsumerDump(); + + data.data(dump); + + return parseSimpleConsumerDump(dump); + } + + case FbsRtpParametersType.SIMULCAST: { + const dump = new FbsConsumer.ConsumerDump(); + + data.data(dump); + + return parseSimulcastConsumerDump(dump); + } + + case FbsRtpParametersType.SVC: { + const dump = new FbsConsumer.ConsumerDump(); + + data.data(dump); + + return parseSvcConsumerDump(dump); + } + + case FbsRtpParametersType.PIPE: { + const dump = new FbsConsumer.ConsumerDump(); + + data.data(dump); + + return parsePipeConsumerDump(dump); + } + + default: { + throw new TypeError(`invalid Consumer type: ${type}`); + } + } +} + +function parseConsumerStats( + binary: FbsConsumer.GetStatsResponse +): (ConsumerStat | ProducerStat)[] { + return fbsUtils.parseVector(binary, 'stats', parseRtpStreamStats); } diff --git a/node/src/ConsumerTypes.ts b/node/src/ConsumerTypes.ts new file mode 100644 index 0000000000..2b1fbf7fd0 --- /dev/null +++ b/node/src/ConsumerTypes.ts @@ -0,0 +1,393 @@ +import type { EnhancedEventEmitter } from './enhancedEvents'; +import type { ProducerStat } from './ProducerTypes'; +import type { + MediaKind, + RtpCapabilities, + RtpEncodingParameters, + RtpParameters, +} from './rtpParametersTypes'; +import type { RtpStreamSendStats } from './rtpStreamStatsTypes'; +import type { AppData } from './types'; + +export type ConsumerOptions = { + /** + * The id of the Producer to consume. + */ + producerId: string; + + /** + * RTP capabilities of the consuming endpoint. + */ + rtpCapabilities: RtpCapabilities; + + /** + * Whether the consumer must start in paused mode. Default false. + * + * When creating a video Consumer, it's recommended to set paused to true, + * then transmit the Consumer parameters to the consuming endpoint and, once + * the consuming endpoint has created its local side Consumer, unpause the + * server side Consumer using the resume() method. This is an optimization + * to make it possible for the consuming endpoint to render the video as far + * as possible. If the server side Consumer was created with paused: false, + * mediasoup will immediately request a key frame to the remote Producer and + * suych a key frame may reach the consuming endpoint even before it's ready + * to consume it, generating âblackâ video until the device requests a keyframe + * by itself. + */ + paused?: boolean; + + /** + * The MID for the Consumer. If not specified, a sequentially growing + * number will be assigned. + */ + mid?: string; + + /** + * Preferred spatial and temporal layer for simulcast or SVC media sources. + * If unset, the highest ones are selected. + */ + preferredLayers?: ConsumerLayers; + + /** + * Whether this Consumer should enable RTP retransmissions, storing sent RTP + * and processing the incoming RTCP NACK from the remote Consumer. If not set + * it's true by default for video codecs and false for audio codecs. If set + * to true, NACK will be enabled if both endpoints (mediasoup and the remote + * Consumer) support NACK for this codec. When it comes to audio codecs, just + * OPUS supports NACK. + */ + enableRtx?: boolean; + + /** + * Whether this Consumer should ignore DTX packets (only valid for Opus codec). + * If set, DTX packets are not forwarded to the remote Consumer. + */ + ignoreDtx?: boolean; + + /** + * Whether this Consumer should consume all RTP streams generated by the + * Producer. + */ + pipe?: boolean; + + /** + * Custom application data. + */ + appData?: ConsumerAppData; +}; + +/** + * Consumer type. + */ +export type ConsumerType = 'simple' | 'simulcast' | 'svc' | 'pipe'; + +export type ConsumerScore = { + /** + * The score of the RTP stream of the consumer. + */ + score: number; + + /** + * The score of the currently selected RTP stream of the producer. + */ + producerScore: number; + + /** + * The scores of all RTP streams in the producer ordered by encoding (just + * useful when the producer uses simulcast). + */ + producerScores: number[]; +}; + +export type ConsumerLayers = { + /** + * The spatial layer index (from 0 to N). + */ + spatialLayer: number; + + /** + * The temporal layer index (from 0 to N). + */ + temporalLayer?: number; +}; + +export type ConsumerDump = + | SimpleConsumerDump + | SimulcastConsumerDump + | SvcConsumerDump + | PipeConsumerDump; + +export type SimpleConsumerDump = BaseConsumerDump & { + type: string; + rtpStream: RtpStreamDump; +}; + +export type SimulcastConsumerDump = BaseConsumerDump & { + type: string; + rtpStream: RtpStreamDump; + preferredSpatialLayer: number; + targetSpatialLayer: number; + currentSpatialLayer: number; + preferredTemporalLayer: number; + targetTemporalLayer: number; + currentTemporalLayer: number; +}; + +export type SvcConsumerDump = SimulcastConsumerDump; + +export type PipeConsumerDump = BaseConsumerDump & { + type: string; + rtpStreams: RtpStreamDump[]; +}; + +export type BaseConsumerDump = { + id: string; + producerId: string; + kind: MediaKind; + rtpParameters: RtpParameters; + consumableRtpEncodings?: RtpEncodingParameters[]; + supportedCodecPayloadTypes: number[]; + traceEventTypes: string[]; + paused: boolean; + producerPaused: boolean; + priority: number; +}; + +export type RtpStreamDump = { + params: RtpStreamParametersDump; + score: number; + rtxStream?: RtxStreamDump; +}; + +export type RtpStreamParametersDump = { + encodingIdx: number; + ssrc: number; + payloadType: number; + mimeType: string; + clockRate: number; + rid?: string; + cname: string; + rtxSsrc?: number; + rtxPayloadType?: number; + useNack: boolean; + usePli: boolean; + useFir: boolean; + useInBandFec: boolean; + useDtx: boolean; + spatialLayers: number; + temporalLayers: number; +}; + +export type RtxStreamDump = { + params: RtxStreamParameters; +}; + +export type RtxStreamParameters = { + ssrc: number; + payloadType: number; + mimeType: string; + clockRate: number; + rrid?: string; + cname: string; +}; + +export type ConsumerStat = RtpStreamSendStats; + +/** + * Valid types for 'trace' event. + */ +export type ConsumerTraceEventType = + | 'rtp' + | 'keyframe' + | 'nack' + | 'pli' + | 'fir'; + +/** + * 'trace' event data. + */ +export type ConsumerTraceEventData = { + /** + * Trace type. + */ + type: ConsumerTraceEventType; + + /** + * Event timestamp. + */ + timestamp: number; + + /** + * Event direction. + */ + direction: 'in' | 'out'; + + /** + * Per type information. + */ + info: any; +}; + +export type ConsumerEvents = { + transportclose: []; + producerclose: []; + producerpause: []; + producerresume: []; + score: [ConsumerScore]; + layerschange: [ConsumerLayers?]; + trace: [ConsumerTraceEventData]; + rtp: [Buffer]; + listenererror: [string, Error]; + // Private events. + '@close': []; + '@producerclose': []; +}; + +export type ConsumerObserver = EnhancedEventEmitter; + +export type ConsumerObserverEvents = { + close: []; + pause: []; + resume: []; + score: [ConsumerScore]; + layerschange: [ConsumerLayers?]; + trace: [ConsumerTraceEventData]; +}; + +export interface Consumer + extends EnhancedEventEmitter { + /** + * Consumer id. + */ + get id(): string; + + /** + * Associated Producer id. + */ + get producerId(): string; + + /** + * Whether the Consumer is closed. + */ + get closed(): boolean; + + /** + * Media kind. + */ + get kind(): MediaKind; + + /** + * RTP parameters. + */ + get rtpParameters(): RtpParameters; + + /** + * Consumer type. + */ + get type(): ConsumerType; + + /** + * Whether the Consumer is paused. + */ + get paused(): boolean; + + /** + * Whether the associate Producer is paused. + */ + get producerPaused(): boolean; + + /** + * Current priority. + */ + get priority(): number; + + /** + * Consumer score. + */ + get score(): ConsumerScore; + + /** + * Preferred video layers. + */ + get preferredLayers(): ConsumerLayers | undefined; + + /** + * Current video layers. + */ + get currentLayers(): ConsumerLayers | undefined; + + /** + * App custom data. + */ + get appData(): ConsumerAppData; + + /** + * App custom data setter. + */ + set appData(appData: ConsumerAppData); + + /** + * Observer. + */ + get observer(): ConsumerObserver; + + /** + * Close the Consumer. + */ + close(): void; + + /** + * Transport was closed. + * + * @private + */ + transportClosed(): void; + + /** + * Dump Consumer. + */ + dump(): Promise; + + /** + * Get Consumer stats. + */ + getStats(): Promise<(ConsumerStat | ProducerStat)[]>; + + /** + * Pause the Consumer. + */ + pause(): Promise; + + /** + * Resume the Consumer. + */ + resume(): Promise; + + /** + * Set preferred video layers. + */ + setPreferredLayers({ + spatialLayer, + temporalLayer, + }: ConsumerLayers): Promise; + + /** + * Set priority. + */ + setPriority(priority: number): Promise; + + /** + * Unset priority. + */ + unsetPriority(): Promise; + + /** + * Request a key frame to the Producer. + */ + requestKeyFrame(): Promise; + + /** + * Enable 'trace' event. + */ + enableTraceEvent(types?: ConsumerTraceEventType[]): Promise; +} diff --git a/node/src/DataConsumer.ts b/node/src/DataConsumer.ts index bad2dd11ad..3bcc932069 100644 --- a/node/src/DataConsumer.ts +++ b/node/src/DataConsumer.ts @@ -1,97 +1,44 @@ import { Logger } from './Logger'; -import { EnhancedEventEmitter } from './EnhancedEventEmitter'; +import { EnhancedEventEmitter } from './enhancedEvents'; +import type { + DataConsumer, + DataConsumerType, + DataConsumerDump, + DataConsumerStat, + DataConsumerEvents, + DataConsumerObserver, + DataConsumerObserverEvents, +} from './DataConsumerTypes'; import { Channel } from './Channel'; -import { PayloadChannel } from './PayloadChannel'; -import { TransportInternal } from './Transport'; -import { SctpStreamParameters } from './SctpParameters'; - -export type DataConsumerOptions = -{ - /** - * The id of the DataProducer to consume. - */ - dataProducerId: string; - - /** - * Just if consuming over SCTP. - * Whether data messages must be received in order. If true the messages will - * be sent reliably. Defaults to the value in the DataProducer if it has type - * 'sctp' or to true if it has type 'direct'. - */ - ordered?: boolean; - - /** - * Just if consuming over SCTP. - * When ordered is false indicates the time (in milliseconds) after which a - * SCTP packet will stop being retransmitted. Defaults to the value in the - * DataProducer if it has type 'sctp' or unset if it has type 'direct'. - */ - maxPacketLifeTime?: number; - - /** - * Just if consuming over SCTP. - * When ordered is false indicates the maximum number of times a packet will - * be retransmitted. Defaults to the value in the DataProducer if it has type - * 'sctp' or unset if it has type 'direct'. - */ - maxRetransmits?: number; - - /** - * Custom application data. - */ - appData?: Record; -}; - -export type DataConsumerStat = -{ - type: string; - timestamp: number; - label: string; - protocol: string; - messagesSent: number; - bytesSent: number; - bufferedAmount: number; -}; - -/** - * DataConsumer type. - */ -export type DataConsumerType = 'sctp' | 'direct'; - -export type DataConsumerEvents = -{ - transportclose: []; - dataproducerclose: []; - message: [Buffer, number]; - sctpsendbufferfull: []; - bufferedamountlow: [number]; - // Private events. - '@close': []; - '@dataproducerclose': []; -}; - -export type DataConsumerObserverEvents = -{ - close: []; -}; - -type DataConsumerInternal = TransportInternal & -{ +import type { TransportInternal } from './Transport'; +import type { SctpStreamParameters } from './sctpParametersTypes'; +import { parseSctpStreamParameters } from './sctpParametersFbsUtils'; +import type { AppData } from './types'; +import * as fbsUtils from './fbsUtils'; +import { Event, Notification } from './fbs/notification'; +import * as FbsTransport from './fbs/transport'; +import * as FbsRequest from './fbs/request'; +import * as FbsDataConsumer from './fbs/data-consumer'; +import * as FbsDataProducer from './fbs/data-producer'; + +type DataConsumerInternal = TransportInternal & { dataConsumerId: string; }; -type DataConsumerData = -{ +type DataConsumerData = { dataProducerId: string; type: DataConsumerType; sctpStreamParameters?: SctpStreamParameters; label: string; protocol: string; + bufferedAmountLowThreshold: number; }; const logger = new Logger('DataConsumer'); -export class DataConsumer extends EnhancedEventEmitter +export class DataConsumerImpl + extends EnhancedEventEmitter + implements DataConsumer { // Internal data. readonly #internal: DataConsumerInternal; @@ -102,38 +49,42 @@ export class DataConsumer extends EnhancedEventEmitter // Channel instance. readonly #channel: Channel; - // PayloadChannel instance. - readonly #payloadChannel: PayloadChannel; - // Closed flag. #closed = false; + // Paused flag. + #paused = false; + + // Associated DataProducer paused flag. + #dataProducerPaused = false; + + // Subchannels subscribed to. + #subchannels: number[]; + // Custom app data. - readonly #appData: Record; + #appData: DataConsumerAppData; // Observer instance. - readonly #observer = new EnhancedEventEmitter(); - - /** - * @private - */ - constructor( - { - internal, - data, - channel, - payloadChannel, - appData - }: - { - internal: DataConsumerInternal; - data: DataConsumerData; - channel: Channel; - payloadChannel: PayloadChannel; - appData?: Record; - } - ) - { + readonly #observer: DataConsumerObserver = + new EnhancedEventEmitter(); + + constructor({ + internal, + data, + channel, + paused, + dataProducerPaused, + subchannels, + appData, + }: { + internal: DataConsumerInternal; + data: DataConsumerData; + channel: Channel; + paused: boolean; + dataProducerPaused: boolean; + subchannels: number[]; + appData?: DataConsumerAppData; + }) { super(); logger.debug('constructor()'); @@ -141,99 +92,71 @@ export class DataConsumer extends EnhancedEventEmitter this.#internal = internal; this.#data = data; this.#channel = channel; - this.#payloadChannel = payloadChannel; - this.#appData = appData || {}; + this.#paused = paused; + this.#dataProducerPaused = dataProducerPaused; + this.#subchannels = subchannels; + this.#appData = appData ?? ({} as DataConsumerAppData); this.handleWorkerNotifications(); + this.handleListenerError(); } - /** - * DataConsumer id. - */ - get id(): string - { + get id(): string { return this.#internal.dataConsumerId; } - /** - * Associated DataProducer id. - */ - get dataProducerId(): string - { + get dataProducerId(): string { return this.#data.dataProducerId; } - /** - * Whether the DataConsumer is closed. - */ - get closed(): boolean - { + get closed(): boolean { return this.#closed; } - /** - * DataConsumer type. - */ - get type(): DataConsumerType - { + get type(): DataConsumerType { return this.#data.type; } - /** - * SCTP stream parameters. - */ - get sctpStreamParameters(): SctpStreamParameters | undefined - { + get sctpStreamParameters(): SctpStreamParameters | undefined { return this.#data.sctpStreamParameters; } - /** - * DataChannel label. - */ - get label(): string - { + get label(): string { return this.#data.label; } - /** - * DataChannel protocol. - */ - get protocol(): string - { + get protocol(): string { return this.#data.protocol; } - /** - * App custom data. - */ - get appData(): Record - { + get paused(): boolean { + return this.#paused; + } + + get dataProducerPaused(): boolean { + return this.#dataProducerPaused; + } + + get subchannels(): number[] { + return Array.from(this.#subchannels); + } + + get appData(): DataConsumerAppData { return this.#appData; } - /** - * Invalid setter. - */ - set appData(appData: Record) // eslint-disable-line no-unused-vars - { - throw new Error('cannot override appData object'); + set appData(appData: DataConsumerAppData) { + this.#appData = appData; } - /** - * Observer. - */ - get observer(): EnhancedEventEmitter - { + get observer(): DataConsumerObserver { return this.#observer; } - /** - * Close the DataConsumer. - */ - close(): void - { - if (this.#closed) + close(): void { + if (this.#closed) { return; + } logger.debug('close()'); @@ -241,11 +164,19 @@ export class DataConsumer extends EnhancedEventEmitter // Remove notification subscriptions. this.#channel.removeAllListeners(this.#internal.dataConsumerId); - this.#payloadChannel.removeAllListeners(this.#internal.dataConsumerId); - const reqData = { dataConsumerId: this.#internal.dataConsumerId }; - - this.#channel.request('transport.closeDataConsumer', this.#internal.transportId, reqData) + /* Build Request. */ + const requestOffset = new FbsTransport.CloseDataConsumerRequestT( + this.#internal.dataConsumerId + ).pack(this.#channel.bufferBuilder); + + this.#channel + .request( + FbsRequest.Method.TRANSPORT_CLOSE_DATACONSUMER, + FbsRequest.Body.Transport_CloseDataConsumerRequest, + requestOffset, + this.#internal.transportId + ) .catch(() => {}); this.emit('@close'); @@ -254,15 +185,10 @@ export class DataConsumer extends EnhancedEventEmitter this.#observer.safeEmit('close'); } - /** - * Transport was closed. - * - * @private - */ - transportClosed(): void - { - if (this.#closed) + transportClosed(): void { + if (this.#closed) { return; + } logger.debug('transportClosed()'); @@ -270,7 +196,6 @@ export class DataConsumer extends EnhancedEventEmitter // Remove notification subscriptions. this.#channel.removeAllListeners(this.#internal.dataConsumerId); - this.#payloadChannel.removeAllListeners(this.#internal.dataConsumerId); this.safeEmit('transportclose'); @@ -278,46 +203,119 @@ export class DataConsumer extends EnhancedEventEmitter this.#observer.safeEmit('close'); } - /** - * Dump DataConsumer. - */ - async dump(): Promise - { + async dump(): Promise { logger.debug('dump()'); - return this.#channel.request('dataConsumer.dump', this.#internal.dataConsumerId); + const response = await this.#channel.request( + FbsRequest.Method.DATACONSUMER_DUMP, + undefined, + undefined, + this.#internal.dataConsumerId + ); + + /* Decode Response. */ + const dumpResponse = new FbsDataConsumer.DumpResponse(); + + response.body(dumpResponse); + + return parseDataConsumerDumpResponse(dumpResponse); } - /** - * Get DataConsumer stats. - */ - async getStats(): Promise - { + async getStats(): Promise { logger.debug('getStats()'); - return this.#channel.request('dataConsumer.getStats', this.#internal.dataConsumerId); + const response = await this.#channel.request( + FbsRequest.Method.DATACONSUMER_GET_STATS, + undefined, + undefined, + this.#internal.dataConsumerId + ); + + /* Decode Response. */ + const data = new FbsDataConsumer.GetStatsResponse(); + + response.body(data); + + return [parseDataConsumerStats(data)]; + } + + async pause(): Promise { + logger.debug('pause()'); + + await this.#channel.request( + FbsRequest.Method.DATACONSUMER_PAUSE, + undefined, + undefined, + this.#internal.dataConsumerId + ); + + const wasPaused = this.#paused; + + this.#paused = true; + + // Emit observer event. + if (!wasPaused && !this.#dataProducerPaused) { + this.#observer.safeEmit('pause'); + } + } + + async resume(): Promise { + logger.debug('resume()'); + + await this.#channel.request( + FbsRequest.Method.DATACONSUMER_RESUME, + undefined, + undefined, + this.#internal.dataConsumerId + ); + + const wasPaused = this.#paused; + + this.#paused = false; + + // Emit observer event. + if (wasPaused && !this.#dataProducerPaused) { + this.#observer.safeEmit('resume'); + } } - /** - * Set buffered amount low threshold. - */ - async setBufferedAmountLowThreshold(threshold: number): Promise - { - logger.debug('setBufferedAmountLowThreshold() [threshold:%s]', threshold); + async setBufferedAmountLowThreshold(threshold: number): Promise { + logger.debug(`setBufferedAmountLowThreshold() [threshold:${threshold}]`); - const reqData = { threshold }; + /* Build Request. */ + const requestOffset = + FbsDataConsumer.SetBufferedAmountLowThresholdRequest.createSetBufferedAmountLowThresholdRequest( + this.#channel.bufferBuilder, + threshold + ); await this.#channel.request( - 'dataConsumer.setBufferedAmountLowThreshold', this.#internal.dataConsumerId, reqData); + FbsRequest.Method.DATACONSUMER_SET_BUFFERED_AMOUNT_LOW_THRESHOLD, + FbsRequest.Body.DataConsumer_SetBufferedAmountLowThresholdRequest, + requestOffset, + this.#internal.dataConsumerId + ); + } + + async getBufferedAmount(): Promise { + logger.debug('getBufferedAmount()'); + + const response = await this.#channel.request( + FbsRequest.Method.DATACONSUMER_GET_BUFFERED_AMOUNT, + undefined, + undefined, + this.#internal.dataConsumerId + ); + + const data = new FbsDataConsumer.GetBufferedAmountResponse(); + + response.body(data); + + return data.bufferedAmount(); } - /** - * Send data. - */ - async send(message: string | Buffer, ppid?: number): Promise - { - if (typeof message !== 'string' && !Buffer.isBuffer(message)) - { + async send(message: string | Buffer, ppid?: number): Promise { + if (typeof message !== 'string' && !Buffer.isBuffer(message)) { throw new TypeError('message must be a string or a Buffer'); } @@ -337,111 +335,299 @@ export class DataConsumer extends EnhancedEventEmitter * +-------------------------------+----------+ */ - if (typeof ppid !== 'number') - { - ppid = (typeof message === 'string') - ? message.length > 0 ? 51 : 56 - : message.length > 0 ? 53 : 57; + if (typeof ppid !== 'number') { + ppid = + typeof message === 'string' + ? message.length > 0 + ? 51 + : 56 + : message.length > 0 + ? 53 + : 57; } // Ensure we honor PPIDs. - if (ppid === 56) + if (ppid === 56) { message = ' '; - else if (ppid === 57) + } else if (ppid === 57) { message = Buffer.alloc(1); + } + + const builder = this.#channel.bufferBuilder; + + let dataOffset = 0; + + if (typeof message === 'string') { + message = Buffer.from(message); + } + + dataOffset = FbsDataConsumer.SendRequest.createDataVector(builder, message); + + const requestOffset = FbsDataConsumer.SendRequest.createSendRequest( + builder, + ppid, + dataOffset + ); - const requestData = String(ppid); + await this.#channel.request( + FbsRequest.Method.DATACONSUMER_SEND, + FbsRequest.Body.DataConsumer_SendRequest, + requestOffset, + this.#internal.dataConsumerId + ); + } + + async setSubchannels(subchannels: number[]): Promise { + logger.debug('setSubchannels()'); + + /* Build Request. */ + const requestOffset = new FbsDataConsumer.SetSubchannelsRequestT( + subchannels + ).pack(this.#channel.bufferBuilder); + + const response = await this.#channel.request( + FbsRequest.Method.DATACONSUMER_SET_SUBCHANNELS, + FbsRequest.Body.DataConsumer_SetSubchannelsRequest, + requestOffset, + this.#internal.dataConsumerId + ); - await this.#payloadChannel.request( - 'dataConsumer.send', this.#internal.dataConsumerId, requestData, message); + /* Decode Response. */ + const data = new FbsDataConsumer.SetSubchannelsResponse(); + + response.body(data); + + // Update subchannels. + this.#subchannels = fbsUtils.parseVector(data, 'subchannels'); } - /** - * Get buffered amount size. - */ - async getBufferedAmount(): Promise - { - logger.debug('getBufferedAmount()'); + async addSubchannel(subchannel: number): Promise { + logger.debug('addSubchannel()'); + + /* Build Request. */ + const requestOffset = + FbsDataConsumer.AddSubchannelRequest.createAddSubchannelRequest( + this.#channel.bufferBuilder, + subchannel + ); + + const response = await this.#channel.request( + FbsRequest.Method.DATACONSUMER_ADD_SUBCHANNEL, + FbsRequest.Body.DataConsumer_AddSubchannelRequest, + requestOffset, + this.#internal.dataConsumerId + ); + + /* Decode Response. */ + const data = new FbsDataConsumer.AddSubchannelResponse(); + + response.body(data); + + // Update subchannels. + this.#subchannels = fbsUtils.parseVector(data, 'subchannels'); + } + + async removeSubchannel(subchannel: number): Promise { + logger.debug('removeSubchannel()'); + + /* Build Request. */ + const requestOffset = + FbsDataConsumer.RemoveSubchannelRequest.createRemoveSubchannelRequest( + this.#channel.bufferBuilder, + subchannel + ); + + const response = await this.#channel.request( + FbsRequest.Method.DATACONSUMER_REMOVE_SUBCHANNEL, + FbsRequest.Body.DataConsumer_RemoveSubchannelRequest, + requestOffset, + this.#internal.dataConsumerId + ); + + /* Decode Response. */ + const data = new FbsDataConsumer.RemoveSubchannelResponse(); - const { bufferedAmount } = - await this.#channel.request('dataConsumer.getBufferedAmount', this.#internal.dataConsumerId); + response.body(data); - return bufferedAmount; + // Update subchannels. + this.#subchannels = fbsUtils.parseVector(data, 'subchannels'); } - private handleWorkerNotifications(): void - { - this.#channel.on(this.#internal.dataConsumerId, (event: string, data: any) => - { - switch (event) - { - case 'dataproducerclose': - { - if (this.#closed) + private handleWorkerNotifications(): void { + this.#channel.on( + this.#internal.dataConsumerId, + (event: Event, data?: Notification) => { + switch (event) { + case Event.DATACONSUMER_DATAPRODUCER_CLOSE: { + if (this.#closed) { + break; + } + + this.#closed = true; + + // Remove notification subscriptions. + this.#channel.removeAllListeners(this.#internal.dataConsumerId); + + this.emit('@dataproducerclose'); + this.safeEmit('dataproducerclose'); + + // Emit observer event. + this.#observer.safeEmit('close'); + break; + } - this.#closed = true; + case Event.DATACONSUMER_DATAPRODUCER_PAUSE: { + if (this.#dataProducerPaused) { + break; + } - // Remove notification subscriptions. - this.#channel.removeAllListeners(this.#internal.dataConsumerId); - this.#payloadChannel.removeAllListeners(this.#internal.dataConsumerId); + this.#dataProducerPaused = true; - this.emit('@dataproducerclose'); - this.safeEmit('dataproducerclose'); + this.safeEmit('dataproducerpause'); - // Emit observer event. - this.#observer.safeEmit('close'); + // Emit observer event. + if (!this.#paused) { + this.#observer.safeEmit('pause'); + } - break; - } + break; + } - case 'sctpsendbufferfull': - { - this.safeEmit('sctpsendbufferfull'); + case Event.DATACONSUMER_DATAPRODUCER_RESUME: { + if (!this.#dataProducerPaused) { + break; + } - break; - } + this.#dataProducerPaused = false; - case 'bufferedamountlow': - { - const { bufferedAmount } = data as { bufferedAmount: number }; + this.safeEmit('dataproducerresume'); - this.safeEmit('bufferedamountlow', bufferedAmount); + // Emit observer event. + if (!this.#paused) { + this.#observer.safeEmit('resume'); + } - break; - } + break; + } - default: - { - logger.error('ignoring unknown event "%s" in channel listener', event); - } - } - }); + case Event.DATACONSUMER_SCTP_SENDBUFFER_FULL: { + this.safeEmit('sctpsendbufferfull'); - this.#payloadChannel.on( - this.#internal.dataConsumerId, - (event: string, data: any | undefined, payload: Buffer) => - { - switch (event) - { - case 'message': - { - if (this.#closed) + break; + } + + case Event.DATACONSUMER_BUFFERED_AMOUNT_LOW: { + const notification = + new FbsDataConsumer.BufferedAmountLowNotification(); + + data!.body(notification); + + const bufferedAmount = notification.bufferedAmount(); + + this.safeEmit('bufferedamountlow', bufferedAmount); + + break; + } + + case Event.DATACONSUMER_MESSAGE: { + if (this.#closed) { break; + } + + const notification = new FbsDataConsumer.MessageNotification(); - const ppid = data.ppid as number; - const message = payload; + data!.body(notification); - this.safeEmit('message', message, ppid); + this.safeEmit( + 'message', + Buffer.from(notification.dataArray()!), + notification.ppid() + ); break; } - default: - { - logger.error('ignoring unknown event "%s" in payload channel listener', event); + default: { + logger.error(`ignoring unknown event "${event}"`); } } - }); + } + ); + } + + private handleListenerError(): void { + this.on('listenererror', (eventName, error) => { + logger.error( + `event listener threw an error [eventName:${eventName}]:`, + error + ); + }); } } + +export function dataConsumerTypeToFbs( + type: DataConsumerType +): FbsDataProducer.Type { + switch (type) { + case 'sctp': { + return FbsDataProducer.Type.SCTP; + } + + case 'direct': { + return FbsDataProducer.Type.DIRECT; + } + + default: { + throw new TypeError('invalid DataConsumerType: ${type}'); + } + } +} + +export function dataConsumerTypeFromFbs( + type: FbsDataProducer.Type +): DataConsumerType { + switch (type) { + case FbsDataProducer.Type.SCTP: { + return 'sctp'; + } + + case FbsDataProducer.Type.DIRECT: { + return 'direct'; + } + } +} + +export function parseDataConsumerDumpResponse( + data: FbsDataConsumer.DumpResponse +): DataConsumerDump { + return { + id: data.id()!, + dataProducerId: data.dataProducerId()!, + type: dataConsumerTypeFromFbs(data.type()), + sctpStreamParameters: + data.sctpStreamParameters() !== null + ? parseSctpStreamParameters(data.sctpStreamParameters()!) + : undefined, + label: data.label()!, + protocol: data.protocol()!, + bufferedAmountLowThreshold: data.bufferedAmountLowThreshold(), + paused: data.paused(), + dataProducerPaused: data.dataProducerPaused(), + subchannels: fbsUtils.parseVector(data, 'subchannels'), + }; +} + +function parseDataConsumerStats( + binary: FbsDataConsumer.GetStatsResponse +): DataConsumerStat { + return { + type: 'data-consumer', + timestamp: Number(binary.timestamp()), + label: binary.label()!, + protocol: binary.protocol()!, + messagesSent: Number(binary.messagesSent()), + bytesSent: Number(binary.bytesSent()), + bufferedAmount: binary.bufferedAmount(), + }; +} diff --git a/node/src/DataConsumerTypes.ts b/node/src/DataConsumerTypes.ts new file mode 100644 index 0000000000..5f0d06815c --- /dev/null +++ b/node/src/DataConsumerTypes.ts @@ -0,0 +1,233 @@ +import type { EnhancedEventEmitter } from './enhancedEvents'; +import type { SctpStreamParameters } from './sctpParametersTypes'; +import type { AppData } from './types'; + +export type DataConsumerOptions = + { + /** + * The id of the DataProducer to consume. + */ + dataProducerId: string; + + /** + * Just if consuming over SCTP. + * Whether data messages must be received in order. If true the messages will + * be sent reliably. Defaults to the value in the DataProducer if it has type + * 'sctp' or to true if it has type 'direct'. + */ + ordered?: boolean; + + /** + * Just if consuming over SCTP. + * When ordered is false indicates the time (in milliseconds) after which a + * SCTP packet will stop being retransmitted. Defaults to the value in the + * DataProducer if it has type 'sctp' or unset if it has type 'direct'. + */ + maxPacketLifeTime?: number; + + /** + * Just if consuming over SCTP. + * When ordered is false indicates the maximum number of times a packet will + * be retransmitted. Defaults to the value in the DataProducer if it has type + * 'sctp' or unset if it has type 'direct'. + */ + maxRetransmits?: number; + + /** + * Whether the data consumer must start in paused mode. Default false. + */ + paused?: boolean; + + /** + * Subchannels this data consumer initially subscribes to. + * Only used in case this data consumer receives messages from a local data + * producer that specifies subchannel(s) when calling send(). + */ + subchannels?: number[]; + + /** + * Custom application data. + */ + appData?: DataConsumerAppData; + }; + +/** + * DataConsumer type. + */ +export type DataConsumerType = 'sctp' | 'direct'; + +export type DataConsumerDump = { + id: string; + paused: boolean; + dataProducerPaused: boolean; + subchannels: number[]; + dataProducerId: string; + type: DataConsumerType; + sctpStreamParameters?: SctpStreamParameters; + label: string; + protocol: string; + bufferedAmountLowThreshold: number; +}; + +export type DataConsumerStat = { + type: string; + timestamp: number; + label: string; + protocol: string; + messagesSent: number; + bytesSent: number; + bufferedAmount: number; +}; + +export type DataConsumerEvents = { + transportclose: []; + dataproducerclose: []; + dataproducerpause: []; + dataproducerresume: []; + message: [Buffer, number]; + sctpsendbufferfull: []; + bufferedamountlow: [number]; + listenererror: [string, Error]; + // Private events. + '@close': []; + '@dataproducerclose': []; +}; + +export type DataConsumerObserver = + EnhancedEventEmitter; + +export type DataConsumerObserverEvents = { + close: []; + pause: []; + resume: []; +}; + +export interface DataConsumer + extends EnhancedEventEmitter { + /** + * DataConsumer id. + */ + get id(): string; + + /** + * Associated DataProducer id. + */ + get dataProducerId(): string; + + /** + * Whether the DataConsumer is closed. + */ + get closed(): boolean; + + /** + * DataConsumer type. + */ + get type(): DataConsumerType; + + /** + * SCTP stream parameters. + */ + get sctpStreamParameters(): SctpStreamParameters | undefined; + + /** + * DataChannel label. + */ + get label(): string; + + /** + * DataChannel protocol. + */ + get protocol(): string; + + /** + * Whether the DataConsumer is paused. + */ + get paused(): boolean; + + /** + * Whether the associate DataProducer is paused. + */ + get dataProducerPaused(): boolean; + + /** + * Get current subchannels this data consumer is subscribed to. + */ + get subchannels(): number[]; + + /** + * App custom data. + */ + get appData(): DataConsumerAppData; + + /** + * App custom data setter. + */ + set appData(appData: DataConsumerAppData); + + /** + * Observer. + */ + get observer(): DataConsumerObserver; + + /** + * Close the DataConsumer. + */ + close(): void; + + /** + * Transport was closed. + * + * @private + */ + transportClosed(): void; + + /** + * Dump DataConsumer. + */ + dump(): Promise; + + /** + * Get DataConsumer stats. + */ + getStats(): Promise; + + /** + * Pause the DataConsumer. + */ + pause(): Promise; + + /** + * Resume the DataConsumer. + */ + resume(): Promise; + + /** + * Set buffered amount low threshold. + */ + setBufferedAmountLowThreshold(threshold: number): Promise; + + /** + * Get buffered amount size. + */ + getBufferedAmount(): Promise; + + /** + * Send a message. + */ + send(message: string | Buffer, ppid?: number): Promise; + + /** + * Set subchannels. + */ + setSubchannels(subchannels: number[]): Promise; + + /** + * Add a subchannel. + */ + addSubchannel(subchannel: number): Promise; + + /** + * Remove a subchannel. + */ + removeSubchannel(subchannel: number): Promise; +} diff --git a/node/src/DataProducer.ts b/node/src/DataProducer.ts index 6a99288348..e1c2c0f6e1 100644 --- a/node/src/DataProducer.ts +++ b/node/src/DataProducer.ts @@ -1,73 +1,29 @@ import { Logger } from './Logger'; -import { EnhancedEventEmitter } from './EnhancedEventEmitter'; +import { EnhancedEventEmitter } from './enhancedEvents'; +import type { + DataProducer, + DataProducerType, + DataProducerDump, + DataProducerStat, + DataProducerEvents, + DataProducerObserver, + DataProducerObserverEvents, +} from './DataProducerTypes'; import { Channel } from './Channel'; -import { PayloadChannel } from './PayloadChannel'; -import { TransportInternal } from './Transport'; -import { SctpStreamParameters } from './SctpParameters'; - -export type DataProducerOptions = -{ - /** - * DataProducer id (just for Router.pipeToRouter() method). - */ - id?: string; - - /** - * SCTP parameters defining how the endpoint is sending the data. - * Just if messages are sent over SCTP. - */ - sctpStreamParameters?: SctpStreamParameters; - - /** - * A label which can be used to distinguish this DataChannel from others. - */ - label?: string; - - /** - * Name of the sub-protocol used by this DataChannel. - */ - protocol?: string; - - /** - * Custom application data. - */ - appData?: Record; -}; - -export type DataProducerStat = -{ - type: string; - timestamp: number; - label: string; - protocol: string; - messagesReceived: number; - bytesReceived: number; -}; - -/** - * DataProducer type. - */ -export type DataProducerType = 'sctp' | 'direct'; - -export type DataProducerEvents = -{ - transportclose: []; - // Private events. - '@close': []; -}; - -export type DataProducerObserverEvents = -{ - close: []; -}; - -type DataProducerInternal = TransportInternal & -{ +import type { TransportInternal } from './Transport'; +import type { SctpStreamParameters } from './sctpParametersTypes'; +import { parseSctpStreamParameters } from './sctpParametersFbsUtils'; +import type { AppData } from './types'; +import * as FbsTransport from './fbs/transport'; +import * as FbsNotification from './fbs/notification'; +import * as FbsRequest from './fbs/request'; +import * as FbsDataProducer from './fbs/data-producer'; + +type DataProducerInternal = TransportInternal & { dataProducerId: string; }; -type DataProducerData = -{ +type DataProducerData = { type: DataProducerType; sctpStreamParameters?: SctpStreamParameters; label: string; @@ -76,7 +32,9 @@ type DataProducerData = const logger = new Logger('DataProducer'); -export class DataProducer extends EnhancedEventEmitter +export class DataProducerImpl + extends EnhancedEventEmitter + implements DataProducer { // Internal data. readonly #internal: DataProducerInternal; @@ -87,38 +45,32 @@ export class DataProducer extends EnhancedEventEmitter // Channel instance. readonly #channel: Channel; - // PayloadChannel instance. - readonly #payloadChannel: PayloadChannel; - // Closed flag. #closed = false; + // Paused flag. + #paused = false; + // Custom app data. - readonly #appData: Record; + #appData: DataProducerAppData; // Observer instance. - readonly #observer = new EnhancedEventEmitter(); - - /** - * @private - */ - constructor( - { - internal, - data, - channel, - payloadChannel, - appData - }: - { - internal: DataProducerInternal; - data: DataProducerData; - channel: Channel; - payloadChannel: PayloadChannel; - appData?: Record; - } - ) - { + readonly #observer: DataProducerObserver = + new EnhancedEventEmitter(); + + constructor({ + internal, + data, + channel, + paused, + appData, + }: { + internal: DataProducerInternal; + data: DataProducerData; + channel: Channel; + paused: boolean; + appData?: DataProducerAppData; + }) { super(); logger.debug('constructor()'); @@ -126,91 +78,57 @@ export class DataProducer extends EnhancedEventEmitter this.#internal = internal; this.#data = data; this.#channel = channel; - this.#payloadChannel = payloadChannel; - this.#appData = appData || {}; + this.#paused = paused; + this.#appData = appData ?? ({} as DataProducerAppData); this.handleWorkerNotifications(); + this.handleListenerError(); } - /** - * DataProducer id. - */ - get id(): string - { + get id(): string { return this.#internal.dataProducerId; } - /** - * Whether the DataProducer is closed. - */ - get closed(): boolean - { + get closed(): boolean { return this.#closed; } - /** - * DataProducer type. - */ - get type(): DataProducerType - { + get type(): DataProducerType { return this.#data.type; } - /** - * SCTP stream parameters. - */ - get sctpStreamParameters(): SctpStreamParameters | undefined - { + get sctpStreamParameters(): SctpStreamParameters | undefined { return this.#data.sctpStreamParameters; } - /** - * DataChannel label. - */ - get label(): string - { + get label(): string { return this.#data.label; } - /** - * DataChannel protocol. - */ - get protocol(): string - { + get protocol(): string { return this.#data.protocol; } - /** - * App custom data. - */ - get appData(): Record - { + get paused(): boolean { + return this.#paused; + } + + get appData(): DataProducerAppData { return this.#appData; } - /** - * Invalid setter. - */ - set appData(appData: Record) // eslint-disable-line no-unused-vars - { - throw new Error('cannot override appData object'); + set appData(appData: DataProducerAppData) { + this.#appData = appData; } - /** - * Observer. - */ - get observer(): EnhancedEventEmitter - { + get observer(): DataProducerObserver { return this.#observer; } - /** - * Close the DataProducer. - */ - close(): void - { - if (this.#closed) + close(): void { + if (this.#closed) { return; + } logger.debug('close()'); @@ -218,11 +136,19 @@ export class DataProducer extends EnhancedEventEmitter // Remove notification subscriptions. this.#channel.removeAllListeners(this.#internal.dataProducerId); - this.#payloadChannel.removeAllListeners(this.#internal.dataProducerId); - const reqData = { dataProducerId: this.#internal.dataProducerId }; - - this.#channel.request('transport.closeDataProducer', this.#internal.transportId, reqData) + /* Build Request. */ + const requestOffset = new FbsTransport.CloseDataProducerRequestT( + this.#internal.dataProducerId + ).pack(this.#channel.bufferBuilder); + + this.#channel + .request( + FbsRequest.Method.TRANSPORT_CLOSE_DATAPRODUCER, + FbsRequest.Body.Transport_CloseDataProducerRequest, + requestOffset, + this.#internal.transportId + ) .catch(() => {}); this.emit('@close'); @@ -231,15 +157,10 @@ export class DataProducer extends EnhancedEventEmitter this.#observer.safeEmit('close'); } - /** - * Transport was closed. - * - * @private - */ - transportClosed(): void - { - if (this.#closed) + transportClosed(): void { + if (this.#closed) { return; + } logger.debug('transportClosed()'); @@ -247,7 +168,6 @@ export class DataProducer extends EnhancedEventEmitter // Remove notification subscriptions. this.#channel.removeAllListeners(this.#internal.dataProducerId); - this.#payloadChannel.removeAllListeners(this.#internal.dataProducerId); this.safeEmit('transportclose'); @@ -255,33 +175,89 @@ export class DataProducer extends EnhancedEventEmitter this.#observer.safeEmit('close'); } - /** - * Dump DataProducer. - */ - async dump(): Promise - { + async dump(): Promise { logger.debug('dump()'); - return this.#channel.request('dataProducer.dump', this.#internal.dataProducerId); + const response = await this.#channel.request( + FbsRequest.Method.DATAPRODUCER_DUMP, + undefined, + undefined, + this.#internal.dataProducerId + ); + + /* Decode Response. */ + const produceResponse = new FbsDataProducer.DumpResponse(); + + response.body(produceResponse); + + return parseDataProducerDumpResponse(produceResponse); } - /** - * Get DataProducer stats. - */ - async getStats(): Promise - { + async getStats(): Promise { logger.debug('getStats()'); - return this.#channel.request('dataProducer.getStats', this.#internal.dataProducerId); + const response = await this.#channel.request( + FbsRequest.Method.DATAPRODUCER_GET_STATS, + undefined, + undefined, + this.#internal.dataProducerId + ); + + /* Decode Response. */ + const data = new FbsDataProducer.GetStatsResponse(); + + response.body(data); + + return [parseDataProducerStats(data)]; } - /** - * Send data (just valid for DataProducers created on a DirectTransport). - */ - send(message: string | Buffer, ppid?: number): void - { - if (typeof message !== 'string' && !Buffer.isBuffer(message)) - { + async pause(): Promise { + logger.debug('pause()'); + + await this.#channel.request( + FbsRequest.Method.DATAPRODUCER_PAUSE, + undefined, + undefined, + this.#internal.dataProducerId + ); + + const wasPaused = this.#paused; + + this.#paused = true; + + // Emit observer event. + if (!wasPaused) { + this.#observer.safeEmit('pause'); + } + } + + async resume(): Promise { + logger.debug('resume()'); + + await this.#channel.request( + FbsRequest.Method.DATAPRODUCER_RESUME, + undefined, + undefined, + this.#internal.dataProducerId + ); + + const wasPaused = this.#paused; + + this.#paused = false; + + // Emit observer event. + if (wasPaused) { + this.#observer.safeEmit('resume'); + } + } + + send( + message: string | Buffer, + ppid?: number, + subchannels?: number[], + requiredSubchannel?: number + ): void { + if (typeof message !== 'string' && !Buffer.isBuffer(message)) { throw new TypeError('message must be a string or a Buffer'); } @@ -301,27 +277,131 @@ export class DataProducer extends EnhancedEventEmitter * +-------------------------------+----------+ */ - if (typeof ppid !== 'number') - { - ppid = (typeof message === 'string') - ? message.length > 0 ? 51 : 56 - : message.length > 0 ? 53 : 57; + if (typeof ppid !== 'number') { + ppid = + typeof message === 'string' + ? message.length > 0 + ? 51 + : 56 + : message.length > 0 + ? 53 + : 57; } // Ensure we honor PPIDs. - if (ppid === 56) + if (ppid === 56) { message = ' '; - else if (ppid === 57) + } else if (ppid === 57) { message = Buffer.alloc(1); + } + + const builder = this.#channel.bufferBuilder; - const notifData = String(ppid); + let dataOffset = 0; - this.#payloadChannel.notify( - 'dataProducer.send', this.#internal.dataProducerId, notifData, message); + const subchannelsOffset = + FbsDataProducer.SendNotification.createSubchannelsVector( + builder, + subchannels ?? [] + ); + + if (typeof message === 'string') { + message = Buffer.from(message); + } + + dataOffset = FbsDataProducer.SendNotification.createDataVector( + builder, + message + ); + + const notificationOffset = + FbsDataProducer.SendNotification.createSendNotification( + builder, + ppid, + dataOffset, + subchannelsOffset, + requiredSubchannel ?? null + ); + + this.#channel.notify( + FbsNotification.Event.DATAPRODUCER_SEND, + FbsNotification.Body.DataProducer_SendNotification, + notificationOffset, + this.#internal.dataProducerId + ); } - private handleWorkerNotifications(): void - { + private handleWorkerNotifications(): void { // No need to subscribe to any event. } + + private handleListenerError(): void { + this.on('listenererror', (eventName, error) => { + logger.error( + `event listener threw an error [eventName:${eventName}]:`, + error + ); + }); + } +} + +export function dataProducerTypeToFbs( + type: DataProducerType +): FbsDataProducer.Type { + switch (type) { + case 'sctp': { + return FbsDataProducer.Type.SCTP; + } + + case 'direct': { + return FbsDataProducer.Type.DIRECT; + } + + default: { + throw new TypeError('invalid DataConsumerType: ${type}'); + } + } +} + +export function dataProducerTypeFromFbs( + type: FbsDataProducer.Type +): DataProducerType { + switch (type) { + case FbsDataProducer.Type.SCTP: { + return 'sctp'; + } + + case FbsDataProducer.Type.DIRECT: { + return 'direct'; + } + } +} + +export function parseDataProducerDumpResponse( + data: FbsDataProducer.DumpResponse +): DataProducerDump { + return { + id: data.id()!, + type: dataProducerTypeFromFbs(data.type()), + sctpStreamParameters: + data.sctpStreamParameters() !== null + ? parseSctpStreamParameters(data.sctpStreamParameters()!) + : undefined, + label: data.label()!, + protocol: data.protocol()!, + paused: data.paused(), + }; +} + +function parseDataProducerStats( + binary: FbsDataProducer.GetStatsResponse +): DataProducerStat { + return { + type: 'data-producer', + timestamp: Number(binary.timestamp()), + label: binary.label()!, + protocol: binary.protocol()!, + messagesReceived: Number(binary.messagesReceived()), + bytesReceived: Number(binary.bytesReceived()), + }; } diff --git a/node/src/DataProducerTypes.ts b/node/src/DataProducerTypes.ts new file mode 100644 index 0000000000..efb64d35bf --- /dev/null +++ b/node/src/DataProducerTypes.ts @@ -0,0 +1,171 @@ +import type { EnhancedEventEmitter } from './enhancedEvents'; +import type { SctpStreamParameters } from './sctpParametersTypes'; +import type { AppData } from './types'; + +export type DataProducerOptions = + { + /** + * DataProducer id (just for Router.pipeToRouter() method). + */ + id?: string; + + /** + * SCTP parameters defining how the endpoint is sending the data. + * Just if messages are sent over SCTP. + */ + sctpStreamParameters?: SctpStreamParameters; + + /** + * A label which can be used to distinguish this DataChannel from others. + */ + label?: string; + + /** + * Name of the sub-protocol used by this DataChannel. + */ + protocol?: string; + + /** + * Whether the data producer must start in paused mode. Default false. + */ + paused?: boolean; + + /** + * Custom application data. + */ + appData?: DataProducerAppData; + }; + +/** + * DataProducer type. + */ +export type DataProducerType = 'sctp' | 'direct'; + +export type DataProducerDump = { + id: string; + paused: boolean; + type: DataProducerType; + sctpStreamParameters?: SctpStreamParameters; + label: string; + protocol: string; +}; + +export type DataProducerStat = { + type: string; + timestamp: number; + label: string; + protocol: string; + messagesReceived: number; + bytesReceived: number; +}; + +export type DataProducerEvents = { + transportclose: []; + listenererror: [string, Error]; + // Private events. + '@close': []; +}; + +export type DataProducerObserver = + EnhancedEventEmitter; + +export type DataProducerObserverEvents = { + close: []; + pause: []; + resume: []; +}; + +export interface DataProducer + extends EnhancedEventEmitter { + /** + * DataProducer id. + */ + get id(): string; + + /** + * Whether the DataProducer is closed. + */ + get closed(): boolean; + + /** + * DataProducer type. + */ + get type(): DataProducerType; + + /** + * SCTP stream parameters. + */ + get sctpStreamParameters(): SctpStreamParameters | undefined; + + /** + * DataChannel label. + */ + get label(): string; + + /** + * DataChannel protocol. + */ + get protocol(): string; + + /** + * Whether the DataProducer is paused. + */ + get paused(): boolean; + + /** + * App custom data. + */ + get appData(): DataProducerAppData; + + /** + * App custom data setter. + */ + set appData(appData: DataProducerAppData); + + /** + * Observer. + */ + get observer(): DataProducerObserver; + + /** + * Close the DataProducer. + */ + close(): void; + + /** + * Transport was closed. + * + * @private + */ + transportClosed(): void; + + /** + * Dump DataProducer. + */ + dump(): Promise; + + /** + * Get DataProducer stats. + */ + getStats(): Promise; + + /** + * Pause the DataProducer. + */ + pause(): Promise; + + /** + * Resume the DataProducer. + */ + resume(): Promise; + + /** + * Send data (just valid for DataProducers created on a DirectTransport). + */ + send( + message: string | Buffer, + ppid?: number, + subchannels?: number[], + requiredSubchannel?: number + ): void; +} diff --git a/node/src/DirectTransport.ts b/node/src/DirectTransport.ts index 0b7f7ea865..a75d4b18bb 100644 --- a/node/src/DirectTransport.ts +++ b/node/src/DirectTransport.ts @@ -1,229 +1,248 @@ import { Logger } from './Logger'; -import { UnsupportedError } from './errors'; +import { EnhancedEventEmitter } from './enhancedEvents'; +import type { + DirectTransport, + DirectTransportDump, + DirectTransportStat, + DirectTransportEvents, + DirectTransportObserver, + DirectTransportObserverEvents, +} from './DirectTransportTypes'; +import type { Transport, BaseTransportDump } from './TransportTypes'; import { - Transport, - TransportTraceEventData, - TransportEvents, - TransportObserverEvents, - TransportConstructorOptions + TransportImpl, + TransportConstructorOptions, + parseBaseTransportDump, + parseBaseTransportStats, + parseTransportTraceEventData, } from './Transport'; -import { SctpParameters } from './SctpParameters'; - -export type DirectTransportOptions = -{ - /** - * Maximum allowed size for direct messages sent from DataProducers. - * Default 262144. - */ - maxMessageSize: number; - - /** - * Custom application data. - */ - appData?: Record; -}; - -export type DirectTransportStat = -{ - // Common to all Transports. - type: string; - transportId: string; - timestamp: number; - bytesReceived: number; - recvBitrate: number; - bytesSent: number; - sendBitrate: number; - rtpBytesReceived: number; - rtpRecvBitrate: number; - rtpBytesSent: number; - rtpSendBitrate: number; - rtxBytesReceived: number; - rtxRecvBitrate: number; - rtxBytesSent: number; - rtxSendBitrate: number; - probationBytesSent: number; - probationSendBitrate: number; - availableOutgoingBitrate?: number; - availableIncomingBitrate?: number; - maxIncomingBitrate?: number; -}; - -export type DirectTransportEvents = TransportEvents & -{ - rtcp: [Buffer]; -}; - -export type DirectTransportObserverEvents = TransportObserverEvents & -{ - rtcp: [Buffer]; -}; - -type DirectTransportConstructorOptions = TransportConstructorOptions & -{ - data: DirectTransportData; -}; - -export type DirectTransportData = -{ +import type { SctpParameters } from './sctpParametersTypes'; +import type { AppData } from './types'; +import { UnsupportedError } from './errors'; +import { Event, Notification } from './fbs/notification'; +import * as FbsDirectTransport from './fbs/direct-transport'; +import * as FbsTransport from './fbs/transport'; +import * as FbsNotification from './fbs/notification'; +import * as FbsRequest from './fbs/request'; + +type DirectTransportConstructorOptions = + TransportConstructorOptions & { + data: DirectTransportData; + }; + +export type DirectTransportData = { sctpParameters?: SctpParameters; }; const logger = new Logger('DirectTransport'); -export class DirectTransport extends - Transport +export class DirectTransportImpl< + DirectTransportAppData extends AppData = AppData, + > + extends TransportImpl< + DirectTransportAppData, + DirectTransportEvents, + DirectTransportObserver + > + implements Transport, DirectTransport { // DirectTransport data. + // eslint-disable-next-line no-unused-private-class-members readonly #data: DirectTransportData; - /** - * @private - */ - constructor(options: DirectTransportConstructorOptions) - { - super(options); + constructor( + options: DirectTransportConstructorOptions + ) { + const observer: DirectTransportObserver = + new EnhancedEventEmitter(); + + super(options, observer); logger.debug('constructor()'); - this.#data = - { + this.#data = { // Nothing. }; this.handleWorkerNotifications(); + this.handleListenerError(); + } + + get type(): 'direct' { + return 'direct'; } - /** - * Close the DirectTransport. - * - * @override - */ - close(): void - { - if (this.closed) + get observer(): DirectTransportObserver { + return super.observer; + } + + close(): void { + if (this.closed) { return; + } super.close(); } - /** - * Router was closed. - * - * @private - * @override - */ - routerClosed(): void - { - if (this.closed) + routerClosed(): void { + if (this.closed) { return; + } super.routerClosed(); } - /** - * Get DirectTransport stats. - * - * @override - */ - async getStats(): Promise - { + async dump(): Promise { + logger.debug('dump()'); + + const response = await this.channel.request( + FbsRequest.Method.TRANSPORT_DUMP, + undefined, + undefined, + this.internal.transportId + ); + + /* Decode Response. */ + const data = new FbsDirectTransport.DumpResponse(); + + response.body(data); + + return parseDirectTransportDumpResponse(data); + } + + async getStats(): Promise { logger.debug('getStats()'); - return this.channel.request('transport.getStats', this.internal.transportId); + const response = await this.channel.request( + FbsRequest.Method.TRANSPORT_GET_STATS, + undefined, + undefined, + this.internal.transportId + ); + + /* Decode Response. */ + const data = new FbsDirectTransport.GetStatsResponse(); + + response.body(data); + + return [parseGetStatsResponse(data)]; } - /** - * NO-OP method in DirectTransport. - * - * @override - */ - async connect(): Promise - { + // eslint-disable-next-line @typescript-eslint/require-await + async connect(): Promise { logger.debug('connect()'); } - /** - * @override - */ - // eslint-disable-next-line @typescript-eslint/no-unused-vars - async setMaxIncomingBitrate(bitrate: number): Promise - { + // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/require-await + async setMaxIncomingBitrate(bitrate: number): Promise { + throw new UnsupportedError( + 'setMaxIncomingBitrate() not implemented in DirectTransport' + ); + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/require-await + async setMaxOutgoingBitrate(bitrate: number): Promise { throw new UnsupportedError( - 'setMaxIncomingBitrate() not implemented in DirectTransport'); + 'setMaxOutgoingBitrate() not implemented in DirectTransport' + ); } - /** - * @override - */ - // eslint-disable-next-line @typescript-eslint/no-unused-vars - async setMaxOutgoingBitrate(bitrate: number): Promise - { + // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/require-await + async setMinOutgoingBitrate(bitrate: number): Promise { throw new UnsupportedError( - 'setMaxOutgoingBitrate() not implemented in DirectTransport'); + 'setMinOutgoingBitrate() not implemented in DirectTransport' + ); } - /** - * Send RTCP packet. - */ - sendRtcp(rtcpPacket: Buffer) - { - if (!Buffer.isBuffer(rtcpPacket)) - { + sendRtcp(rtcpPacket: Buffer): void { + if (!Buffer.isBuffer(rtcpPacket)) { throw new TypeError('rtcpPacket must be a Buffer'); } - this.payloadChannel.notify( - 'transport.sendRtcp', this.internal.transportId, undefined, rtcpPacket); + const builder = this.channel.bufferBuilder; + const dataOffset = FbsTransport.SendRtcpNotification.createDataVector( + builder, + rtcpPacket + ); + const notificationOffset = + FbsTransport.SendRtcpNotification.createSendRtcpNotification( + builder, + dataOffset + ); + + this.channel.notify( + FbsNotification.Event.TRANSPORT_SEND_RTCP, + FbsNotification.Body.Transport_SendRtcpNotification, + notificationOffset, + this.internal.transportId + ); } - private handleWorkerNotifications(): void - { - this.channel.on(this.internal.transportId, (event: string, data?: any) => - { - switch (event) - { - case 'trace': - { - const trace = data as TransportTraceEventData; + private handleWorkerNotifications(): void { + this.channel.on( + this.internal.transportId, + (event: Event, data?: Notification) => { + switch (event) { + case Event.TRANSPORT_TRACE: { + const notification = new FbsTransport.TraceNotification(); - this.safeEmit('trace', trace); + data!.body(notification); - // Emit observer event. - this.observer.safeEmit('trace', trace); + const trace = parseTransportTraceEventData(notification); - break; - } + this.safeEmit('trace', trace); - default: - { - logger.error('ignoring unknown event "%s"', event); - } - } - }); + // Emit observer event. + this.observer.safeEmit('trace', trace); - this.payloadChannel.on( - this.internal.transportId, - (event: string, data: any | undefined, payload: Buffer) => - { - switch (event) - { - case 'rtcp': - { - if (this.closed) + break; + } + + case Event.DIRECTTRANSPORT_RTCP: { + if (this.closed) { break; + } - const packet = payload; + const notification = new FbsDirectTransport.RtcpNotification(); - this.safeEmit('rtcp', packet); + data!.body(notification); + + this.safeEmit('rtcp', Buffer.from(notification.dataArray()!)); break; } - default: - { - logger.error('ignoring unknown event "%s"', event); + default: { + logger.error(`ignoring unknown event "${event}"`); } } - }); + } + ); + } + + private handleListenerError(): void { + this.on('listenererror', (eventName, error) => { + logger.error( + `event listener threw an error [eventName:${eventName}]:`, + error + ); + }); } } + +export function parseDirectTransportDumpResponse( + binary: FbsDirectTransport.DumpResponse +): BaseTransportDump { + return parseBaseTransportDump(binary.base()!); +} + +function parseGetStatsResponse( + binary: FbsDirectTransport.GetStatsResponse +): DirectTransportStat { + const base = parseBaseTransportStats(binary.base()!); + + return { + ...base, + type: 'direct-transport', + }; +} diff --git a/node/src/DirectTransportTypes.ts b/node/src/DirectTransportTypes.ts new file mode 100644 index 0000000000..85bb824341 --- /dev/null +++ b/node/src/DirectTransportTypes.ts @@ -0,0 +1,89 @@ +import type { EnhancedEventEmitter } from './enhancedEvents'; +import type { + Transport, + BaseTransportDump, + BaseTransportStats, + TransportEvents, + TransportObserverEvents, +} from './TransportTypes'; +import type { AppData } from './types'; + +export type DirectTransportOptions< + DirectTransportAppData extends AppData = AppData, +> = { + /** + * Maximum allowed size for direct messages sent from DataProducers. + * Default 262144. + */ + maxMessageSize: number; + + /** + * Custom application data. + */ + appData?: DirectTransportAppData; +}; + +export type DirectTransportDump = BaseTransportDump; + +export type DirectTransportStat = BaseTransportStats & { + type: string; +}; + +export type DirectTransportEvents = TransportEvents & { + rtcp: [Buffer]; +}; + +export type DirectTransportObserver = + EnhancedEventEmitter; + +export type DirectTransportObserverEvents = TransportObserverEvents & { + rtcp: [Buffer]; +}; + +export interface DirectTransport< + DirectTransportAppData extends AppData = AppData, +> extends Transport< + DirectTransportAppData, + DirectTransportEvents, + DirectTransportObserver + > { + /** + * Transport type. + * + * @override + */ + get type(): 'direct'; + + /** + * Observer. + * + * @override + */ + get observer(): DirectTransportObserver; + + /** + * Dump DirectTransport. + * + * @override + */ + dump(): Promise; + + /** + * Get DirectTransport stats. + * + * @override + */ + getStats(): Promise; + + /** + * NO-OP method in DirectTransport. + * + * @override + */ + connect(): Promise; + + /** + * Send RTCP packet. + */ + sendRtcp(rtcpPacket: Buffer): void; +} diff --git a/node/src/Logger.ts b/node/src/Logger.ts index b5a199d989..1dd0b30a75 100644 --- a/node/src/Logger.ts +++ b/node/src/Logger.ts @@ -1,23 +1,41 @@ import debug from 'debug'; +import type { EnhancedEventEmitter } from './enhancedEvents'; const APP_NAME = 'mediasoup'; -export class Logger -{ +export type LoggerEmitterEvents = { + debuglog: [string, string]; + warnlog: [string, string]; + errorlog: [string, string, Error?]; +}; + +export type LoggerEmitter = EnhancedEventEmitter; + +export class Logger { + private static debugLogEmitter?: LoggerEmitter; + private static warnLogEmitter?: LoggerEmitter; + private static errorLogEmitter?: LoggerEmitter; + readonly #debug: debug.Debugger; readonly #warn: debug.Debugger; readonly #error: debug.Debugger; - constructor(prefix?: string) - { - if (prefix) - { + static setEmitters( + debugLogEmitter?: LoggerEmitter, + warnLogEmitter?: LoggerEmitter, + errorLogEmitter?: LoggerEmitter + ): void { + Logger.debugLogEmitter = debugLogEmitter; + Logger.warnLogEmitter = warnLogEmitter; + Logger.errorLogEmitter = errorLogEmitter; + } + + constructor(prefix?: string) { + if (prefix) { this.#debug = debug(`${APP_NAME}:${prefix}`); this.#warn = debug(`${APP_NAME}:WARN:${prefix}`); this.#error = debug(`${APP_NAME}:ERROR:${prefix}`); - } - else - { + } else { this.#debug = debug(APP_NAME); this.#warn = debug(`${APP_NAME}:WARN`); this.#error = debug(`${APP_NAME}:ERROR`); @@ -30,18 +48,26 @@ export class Logger /* eslint-enable no-console */ } - get debug(): debug.Debugger - { - return this.#debug; + debug(log: string): void { + this.#debug(log); + + Logger.debugLogEmitter?.safeEmit('debuglog', this.#debug.namespace, log); } - get warn(): debug.Debugger - { - return this.#warn; + warn(log: string): void { + this.#warn(log); + + Logger.warnLogEmitter?.safeEmit('warnlog', this.#warn.namespace, log); } - get error(): debug.Debugger - { - return this.#error; + error(log: string, error?: Error): void { + this.#error(log, error); + + Logger.errorLogEmitter?.safeEmit( + 'errorlog', + this.#error.namespace, + log, + error + ); } } diff --git a/node/src/PayloadChannel.ts b/node/src/PayloadChannel.ts deleted file mode 100644 index 2b5d83b412..0000000000 --- a/node/src/PayloadChannel.ts +++ /dev/null @@ -1,384 +0,0 @@ -import * as os from 'os'; -import { Duplex } from 'stream'; -import { Logger } from './Logger'; -import { EnhancedEventEmitter } from './EnhancedEventEmitter'; -import { InvalidStateError } from './errors'; - -const littleEndian = os.endianness() == 'LE'; -const logger = new Logger('PayloadChannel'); - -type Sent = -{ - id: number; - method: string; - resolve: (data?: any) => void; - reject: (error: Error) => void; - close: () => void; -}; - -// Binary length for a 4194304 bytes payload. -const MESSAGE_MAX_LEN = 4194308; -const PAYLOAD_MAX_LEN = 4194304; - -export class PayloadChannel extends EnhancedEventEmitter -{ - // Closed flag. - #closed = false; - - // Unix Socket instance for sending messages to the worker process. - readonly #producerSocket: Duplex; - - // Unix Socket instance for receiving messages to the worker process. - readonly #consumerSocket: Duplex; - - // Next id for messages sent to the worker process. - #nextId = 0; - - // Map of pending sent requests. - readonly #sents: Map = new Map(); - - // Buffer for reading messages from the worker. - #recvBuffer = Buffer.alloc(0); - - // Ongoing notification (waiting for its payload). - #ongoingNotification?: { targetId: string; event: string; data?: any }; - - /** - * @private - */ - constructor( - { - producerSocket, - consumerSocket - }: - { - producerSocket: any; - consumerSocket: any; - }) - { - super(); - - logger.debug('constructor()'); - - this.#producerSocket = producerSocket as Duplex; - this.#consumerSocket = consumerSocket as Duplex; - - // Read PayloadChannel notifications from the worker. - this.#consumerSocket.on('data', (buffer: Buffer) => - { - if (!this.#recvBuffer.length) - { - this.#recvBuffer = buffer; - } - else - { - this.#recvBuffer = Buffer.concat( - [ this.#recvBuffer, buffer ], - this.#recvBuffer.length + buffer.length); - } - - if (this.#recvBuffer.length > PAYLOAD_MAX_LEN) - { - logger.error('receiving buffer is full, discarding all data in it'); - - // Reset the buffer and exit. - this.#recvBuffer = Buffer.alloc(0); - - return; - } - - let msgStart = 0; - - while (true) // eslint-disable-line no-constant-condition - { - const readLen = this.#recvBuffer.length - msgStart; - - if (readLen < 4) - { - // Incomplete data. - break; - } - - const dataView = new DataView( - this.#recvBuffer.buffer, - this.#recvBuffer.byteOffset + msgStart); - const msgLen = dataView.getUint32(0, littleEndian); - - if (readLen < 4 + msgLen) - { - // Incomplete data. - break; - } - - const payload = this.#recvBuffer.subarray(msgStart + 4, msgStart + 4 + msgLen); - - msgStart += 4 + msgLen; - - this.processData(payload); - } - - if (msgStart != 0) - { - this.#recvBuffer = this.#recvBuffer.slice(msgStart); - } - }); - - this.#consumerSocket.on('end', () => ( - logger.debug('Consumer PayloadChannel ended by the worker process') - )); - - this.#consumerSocket.on('error', (error) => ( - logger.error('Consumer PayloadChannel error: %s', String(error)) - )); - - this.#producerSocket.on('end', () => ( - logger.debug('Producer PayloadChannel ended by the worker process') - )); - - this.#producerSocket.on('error', (error) => ( - logger.error('Producer PayloadChannel error: %s', String(error)) - )); - } - - /** - * @private - */ - close(): void - { - if (this.#closed) - return; - - logger.debug('close()'); - - this.#closed = true; - - // Remove event listeners but leave a fake 'error' handler to avoid - // propagation. - this.#consumerSocket.removeAllListeners('end'); - this.#consumerSocket.removeAllListeners('error'); - this.#consumerSocket.on('error', () => {}); - - this.#producerSocket.removeAllListeners('end'); - this.#producerSocket.removeAllListeners('error'); - this.#producerSocket.on('error', () => {}); - - // Destroy the socket after a while to allow pending incoming messages. - setTimeout(() => - { - try { this.#producerSocket.destroy(); } - catch (error) {} - try { this.#consumerSocket.destroy(); } - catch (error) {} - }, 200); - } - - /** - * @private - */ - notify( - event: string, - handlerId: string, - data: string | undefined, - payload: string | Buffer - ): void - { - logger.debug('notify() [event:%s]', event); - - if (this.#closed) - throw new InvalidStateError('PayloadChannel closed'); - - const notification = `n:${event}:${handlerId}:${data}`; - - if (Buffer.byteLength(notification) > MESSAGE_MAX_LEN) - throw new Error('PayloadChannel notification too big'); - else if (Buffer.byteLength(payload) > MESSAGE_MAX_LEN) - throw new Error('PayloadChannel payload too big'); - - try - { - // This may throw if closed or remote side ended. - this.#producerSocket.write( - Buffer.from(Uint32Array.of(Buffer.byteLength(notification)).buffer)); - this.#producerSocket.write(notification); - } - catch (error) - { - logger.warn('notify() | sending notification failed: %s', String(error)); - - return; - } - - try - { - // This may throw if closed or remote side ended. - this.#producerSocket.write( - Buffer.from(Uint32Array.of(Buffer.byteLength(payload)).buffer)); - this.#producerSocket.write(payload); - } - catch (error) - { - logger.warn('notify() | sending payload failed: %s', String(error)); - - return; - } - } - - /** - * @private - */ - async request( - method: string, - handlerId: string, - data: string, - payload: string | Buffer): Promise - { - this.#nextId < 4294967295 ? ++this.#nextId : (this.#nextId = 1); - - const id = this.#nextId; - - logger.debug('request() [method:%s, id:%s]', method, id); - - if (this.#closed) - throw new InvalidStateError('PayloadChannel closed'); - - const request = `r:${id}:${method}:${handlerId}:${data}`; - - if (Buffer.byteLength(request) > MESSAGE_MAX_LEN) - throw new Error('PayloadChannel request too big'); - else if (Buffer.byteLength(payload) > MESSAGE_MAX_LEN) - throw new Error('PayloadChannel payload too big'); - - // This may throw if closed or remote side ended. - this.#producerSocket.write( - Buffer.from(Uint32Array.of(Buffer.byteLength(request)).buffer)); - this.#producerSocket.write(request); - this.#producerSocket.write( - Buffer.from(Uint32Array.of(Buffer.byteLength(payload)).buffer)); - this.#producerSocket.write(payload); - - return new Promise((pResolve, pReject) => - { - const sent: Sent = - { - id : id, - method : method, - resolve : (data2) => - { - if (!this.#sents.delete(id)) - return; - - pResolve(data2); - }, - reject : (error) => - { - if (!this.#sents.delete(id)) - return; - - pReject(error); - }, - close : () => - { - pReject(new InvalidStateError('PayloadChannel closed')); - } - }; - - // Add sent stuff to the map. - this.#sents.set(id, sent); - }); - } - - private processData(data: Buffer): void - { - if (!this.#ongoingNotification) - { - let msg; - - try - { - msg = JSON.parse(data.toString('utf8')); - } - catch (error) - { - logger.error( - 'received invalid data from the worker process: %s', - String(error)); - - return; - } - - // If a response, retrieve its associated request. - if (msg.id) - { - const sent = this.#sents.get(msg.id); - - if (!sent) - { - logger.error( - 'received response does not match any sent request [id:%s]', msg.id); - - return; - } - - if (msg.accepted) - { - logger.debug( - 'request succeeded [method:%s, id:%s]', sent.method, sent.id); - - sent.resolve(msg.data); - } - else if (msg.error) - { - logger.warn( - 'request failed [method:%s, id:%s]: %s', - sent.method, sent.id, msg.reason); - - switch (msg.error) - { - case 'TypeError': - sent.reject(new TypeError(msg.reason)); - break; - - default: - sent.reject(new Error(msg.reason)); - } - } - else - { - logger.error( - 'received response is not accepted nor rejected [method:%s, id:%s]', - sent.method, sent.id); - } - } - // If a notification, create the ongoing notification instance. - else if (msg.targetId && msg.event) - { - this.#ongoingNotification = - { - targetId : String(msg.targetId), - event : msg.event, - data : msg.data - }; - } - else - { - logger.error('received data is not a notification nor a response'); - - return; - } - } - else - { - const payload = data as Buffer; - - // Emit the corresponding event. - this.emit( - this.#ongoingNotification.targetId, - this.#ongoingNotification.event, - this.#ongoingNotification.data, - payload); - - // Unset ongoing notification. - this.#ongoingNotification = undefined; - } - } -} diff --git a/node/src/PipeTransport.ts b/node/src/PipeTransport.ts index e9fadbd6e5..266c0d50d4 100644 --- a/node/src/PipeTransport.ts +++ b/node/src/PipeTransport.ts @@ -1,133 +1,55 @@ -import { v4 as uuidv4 } from 'uuid'; +import * as flatbuffers from 'flatbuffers'; import { Logger } from './Logger'; +import { EnhancedEventEmitter } from './enhancedEvents'; import * as ortc from './ortc'; +import type { + PipeTransport, + PipeConsumerOptions, + PipeTransportDump, + PipeTransportStat, + PipeTransportEvents, + PipeTransportObserver, + PipeTransportObserverEvents, +} from './PipeTransportTypes'; +import type { Transport, TransportTuple, SctpState } from './TransportTypes'; import { - Transport, - TransportListenIp, - TransportTuple, - TransportTraceEventData, - TransportEvents, - TransportObserverEvents, + TransportImpl, TransportConstructorOptions, - SctpState + parseBaseTransportDump, + parseBaseTransportStats, + parseSctpState, + parseTuple, + parseTransportTraceEventData, } from './Transport'; -import { Consumer, ConsumerType } from './Consumer'; -import { SctpParameters, NumSctpStreams } from './SctpParameters'; -import { SrtpParameters } from './SrtpParameters'; - -export type PipeTransportOptions = -{ - /** - * Listening IP address. - */ - listenIp: TransportListenIp | string; - - /** - * Fixed port to listen on instead of selecting automatically from Worker's port - * range. - */ - port?: number; - - /** - * Create a SCTP association. Default false. - */ - enableSctp?: boolean; - - /** - * SCTP streams number. - */ - numSctpStreams?: NumSctpStreams; - - /** - * Maximum allowed size for SCTP messages sent by DataProducers. - * Default 268435456. - */ - maxSctpMessageSize?: number; - - /** - * Maximum SCTP send buffer used by DataConsumers. - * Default 268435456. - */ - sctpSendBufferSize?: number; - - /** - * Enable RTX and NACK for RTP retransmission. Useful if both Routers are - * located in different hosts and there is packet lost in the link. For this - * to work, both PipeTransports must enable this setting. Default false. - */ - enableRtx?: boolean; - - /** - * Enable SRTP. Useful to protect the RTP and RTCP traffic if both Routers - * are located in different hosts. For this to work, connect() must be called - * with remote SRTP parameters. Default false. - */ - enableSrtp?: boolean; - - /** - * Custom application data. - */ - appData?: Record; -}; - -export type PipeTransportStat = -{ - // Common to all Transports. - type: string; - transportId: string; - timestamp: number; - sctpState?: SctpState; - bytesReceived: number; - recvBitrate: number; - bytesSent: number; - sendBitrate: number; - rtpBytesReceived: number; - rtpRecvBitrate: number; - rtpBytesSent: number; - rtpSendBitrate: number; - rtxBytesReceived: number; - rtxRecvBitrate: number; - rtxBytesSent: number; - rtxSendBitrate: number; - probationBytesSent: number; - probationSendBitrate: number; - availableOutgoingBitrate?: number; - availableIncomingBitrate?: number; - maxIncomingBitrate?: number; - // PipeTransport specific. - tuple: TransportTuple; -}; - -export type PipeConsumerOptions = -{ - /** - * The id of the Producer to consume. - */ - producerId: string; - - /** - * Custom application data. - */ - appData?: Record; -}; - -export type PipeTransportEvents = TransportEvents & -{ - sctpstatechange: [SctpState]; -}; - -export type PipeTransportObserverEvents = TransportObserverEvents & -{ - sctpstatechange: [SctpState]; -}; - -type PipeTransportConstructorOptions = TransportConstructorOptions & -{ - data: PipeTransportData; -}; - -export type PipeTransportData = -{ +import type { Producer } from './ProducerTypes'; +import type { Consumer, ConsumerType } from './ConsumerTypes'; +import { ConsumerImpl } from './Consumer'; +import type { RtpParameters } from './rtpParametersTypes'; +import { + serializeRtpEncodingParameters, + serializeRtpParameters, +} from './rtpParametersFbsUtils'; +import type { SctpParameters } from './sctpParametersTypes'; +import type { SrtpParameters } from './srtpParametersTypes'; +import { + parseSrtpParameters, + serializeSrtpParameters, +} from './srtpParametersFbsUtils'; +import type { AppData } from './types'; +import { generateUUIDv4 } from './utils'; +import { MediaKind as FbsMediaKind } from './fbs/rtp-parameters/media-kind'; +import * as FbsRtpParameters from './fbs/rtp-parameters'; +import { Event, Notification } from './fbs/notification'; +import * as FbsRequest from './fbs/request'; +import * as FbsTransport from './fbs/transport'; +import * as FbsPipeTransport from './fbs/pipe-transport'; + +type PipeTransportConstructorOptions = + TransportConstructorOptions & { + data: PipeTransportData; + }; + +export type PipeTransportData = { tuple: TransportTuple; sctpParameters?: SctpParameters; sctpState?: SctpState; @@ -137,199 +59,225 @@ export type PipeTransportData = const logger = new Logger('PipeTransport'); -export class PipeTransport - extends Transport +export class PipeTransportImpl + extends TransportImpl< + PipeTransportAppData, + PipeTransportEvents, + PipeTransportObserver + > + implements Transport, PipeTransport { // PipeTransport data. readonly #data: PipeTransportData; - /** - * @private - */ - constructor(options: PipeTransportConstructorOptions) - { - super(options); + constructor(options: PipeTransportConstructorOptions) { + const observer: PipeTransportObserver = + new EnhancedEventEmitter(); + + super(options, observer); logger.debug('constructor()'); const { data } = options; - this.#data = - { - tuple : data.tuple, - sctpParameters : data.sctpParameters, - sctpState : data.sctpState, - rtx : data.rtx, - srtpParameters : data.srtpParameters + this.#data = { + tuple: data.tuple, + sctpParameters: data.sctpParameters, + sctpState: data.sctpState, + rtx: data.rtx, + srtpParameters: data.srtpParameters, }; this.handleWorkerNotifications(); + this.handleListenerError(); + } + + get type(): 'pipe' { + return 'pipe'; } - /** - * Transport tuple. - */ - get tuple(): TransportTuple - { + get observer(): PipeTransportObserver { + return super.observer; + } + + get tuple(): TransportTuple { return this.#data.tuple; } - /** - * SCTP parameters. - */ - get sctpParameters(): SctpParameters | undefined - { + get sctpParameters(): SctpParameters | undefined { return this.#data.sctpParameters; } - /** - * SCTP state. - */ - get sctpState(): SctpState | undefined - { + get sctpState(): SctpState | undefined { return this.#data.sctpState; } - /** - * SRTP parameters. - */ - get srtpParameters(): SrtpParameters | undefined - { + get srtpParameters(): SrtpParameters | undefined { return this.#data.srtpParameters; } - /** - * Close the PipeTransport. - * - * @override - */ - close(): void - { - if (this.closed) + close(): void { + if (this.closed) { return; + } - if (this.#data.sctpState) + if (this.#data.sctpState) { this.#data.sctpState = 'closed'; + } super.close(); } - /** - * Router was closed. - * - * @private - * @override - */ - routerClosed(): void - { - if (this.closed) + routerClosed(): void { + if (this.closed) { return; + } - if (this.#data.sctpState) + if (this.#data.sctpState) { this.#data.sctpState = 'closed'; + } super.routerClosed(); } - /** - * Get PipeTransport stats. - * - * @override - */ - async getStats(): Promise - { + async dump(): Promise { + logger.debug('dump()'); + + const response = await this.channel.request( + FbsRequest.Method.TRANSPORT_DUMP, + undefined, + undefined, + this.internal.transportId + ); + + /* Decode Response. */ + const data = new FbsPipeTransport.DumpResponse(); + + response.body(data); + + return parsePipeTransportDumpResponse(data); + } + + async getStats(): Promise { logger.debug('getStats()'); - return this.channel.request('transport.getStats', this.internal.transportId); + const response = await this.channel.request( + FbsRequest.Method.TRANSPORT_GET_STATS, + undefined, + undefined, + this.internal.transportId + ); + + /* Decode Response. */ + const data = new FbsPipeTransport.GetStatsResponse(); + + response.body(data); + + return [parseGetStatsResponse(data)]; } - /** - * Provide the PipeTransport remote parameters. - * - * @override - */ - async connect( - { + async connect({ + ip, + port, + srtpParameters, + }: { + ip: string; + port: number; + srtpParameters?: SrtpParameters; + }): Promise { + logger.debug('connect()'); + + const requestOffset = createConnectRequest({ + builder: this.channel.bufferBuilder, ip, port, - srtpParameters - }: - { - ip: string; - port: number; - srtpParameters?: SrtpParameters; - } - ): Promise - { - logger.debug('connect()'); + srtpParameters, + }); + + // Wait for response. + const response = await this.channel.request( + FbsRequest.Method.PIPETRANSPORT_CONNECT, + FbsRequest.Body.PipeTransport_ConnectRequest, + requestOffset, + this.internal.transportId + ); - const reqData = { ip, port, srtpParameters }; + /* Decode Response. */ + const data = new FbsPipeTransport.ConnectResponse(); - const data = - await this.channel.request('transport.connect', this.internal.transportId, reqData); + response.body(data); // Update data. - this.#data.tuple = data.tuple; + if (data.tuple()) { + this.#data.tuple = parseTuple(data.tuple()!); + } } - /** - * Create a pipe Consumer. - * - * @override - */ - async consume({ producerId, appData }: PipeConsumerOptions): Promise - { + async consume({ + producerId, + appData, + }: PipeConsumerOptions): Promise> { logger.debug('consume()'); - if (!producerId || typeof producerId !== 'string') + if (!producerId || typeof producerId !== 'string') { throw new TypeError('missing producerId'); - else if (appData && typeof appData !== 'object') + } else if (appData && typeof appData !== 'object') { throw new TypeError('if given, appData must be an object'); + } const producer = this.getProducerById(producerId); - if (!producer) + if (!producer) { throw Error(`Producer with id "${producerId}" not found`); + } // This may throw. - const rtpParameters = ortc.getPipeConsumerRtpParameters( - producer.consumableRtpParameters, this.#data.rtx); + const rtpParameters = ortc.getPipeConsumerRtpParameters({ + consumableRtpParameters: producer.consumableRtpParameters, + enableRtx: this.#data.rtx, + }); - const reqData = - { - consumerId : uuidv4(), - producerId, - kind : producer.kind, + const consumerId = generateUUIDv4(); + + const consumeRequestOffset = createConsumeRequest({ + builder: this.channel.bufferBuilder, + consumerId, + producer, rtpParameters, - type : 'pipe', - consumableRtpEncodings : producer.consumableRtpParameters.encodings - }; + }); + + const response = await this.channel.request( + FbsRequest.Method.TRANSPORT_CONSUME, + FbsRequest.Body.Transport_ConsumeRequest, + consumeRequestOffset, + this.internal.transportId + ); + + /* Decode Response. */ + const consumeResponse = new FbsTransport.ConsumeResponse(); - const status = - await this.channel.request('transport.consume', this.internal.transportId, reqData); + response.body(consumeResponse); - const data = - { + const status = consumeResponse.unpack(); + + const data = { producerId, - kind : producer.kind, + kind: producer.kind, rtpParameters, - type : 'pipe' as ConsumerType + type: 'pipe' as ConsumerType, }; - const consumer = new Consumer( - { - internal : - { - ...this.internal, - consumerId : reqData.consumerId - }, - data, - channel : this.channel, - payloadChannel : this.payloadChannel, - appData, - paused : status.paused, - producerPaused : status.producerPaused - }); + const consumer: Consumer = new ConsumerImpl({ + internal: { + ...this.internal, + consumerId, + }, + data, + channel: this.channel, + appData, + paused: status.paused, + producerPaused: status.producerPaused, + }); this.consumers.set(consumer.id, consumer); consumer.on('@close', () => this.consumers.delete(consumer.id)); @@ -341,43 +289,183 @@ export class PipeTransport return consumer; } - private handleWorkerNotifications(): void - { - this.channel.on(this.internal.transportId, (event: string, data?: any) => - { - switch (event) - { - case 'sctpstatechange': - { - const sctpState = data.sctpState as SctpState; + private handleWorkerNotifications(): void { + this.channel.on( + this.internal.transportId, + (event: Event, data?: Notification) => { + switch (event) { + case Event.TRANSPORT_SCTP_STATE_CHANGE: { + const notification = new FbsTransport.SctpStateChangeNotification(); - this.#data.sctpState = sctpState; + data!.body(notification); - this.safeEmit('sctpstatechange', sctpState); + const sctpState = parseSctpState(notification.sctpState()); - // Emit observer event. - this.observer.safeEmit('sctpstatechange', sctpState); + this.#data.sctpState = sctpState; - break; - } + this.safeEmit('sctpstatechange', sctpState); - case 'trace': - { - const trace = data as TransportTraceEventData; + // Emit observer event. + this.observer.safeEmit('sctpstatechange', sctpState); - this.safeEmit('trace', trace); + break; + } - // Emit observer event. - this.observer.safeEmit('trace', trace); + case Event.TRANSPORT_TRACE: { + const notification = new FbsTransport.TraceNotification(); - break; - } + data!.body(notification); + + const trace = parseTransportTraceEventData(notification); + + this.safeEmit('trace', trace); - default: - { - logger.error('ignoring unknown event "%s"', event); + // Emit observer event. + this.observer.safeEmit('trace', trace); + + break; + } + + default: { + logger.error(`ignoring unknown event "${event}"`); + } } } + ); + } + + private handleListenerError(): void { + this.on('listenererror', (eventName, error) => { + logger.error( + `event listener threw an error [eventName:${eventName}]:`, + error + ); }); } } + +/* + * flatbuffers helpers. + */ + +export function parsePipeTransportDumpResponse( + binary: FbsPipeTransport.DumpResponse +): PipeTransportDump { + // Retrieve BaseTransportDump. + const baseTransportDump = parseBaseTransportDump(binary.base()!); + // Retrieve RTP Tuple. + const tuple = parseTuple(binary.tuple()!); + + // Retrieve SRTP Parameters. + let srtpParameters: SrtpParameters | undefined; + + if (binary.srtpParameters()) { + srtpParameters = parseSrtpParameters(binary.srtpParameters()!); + } + + return { + ...baseTransportDump, + tuple: tuple, + rtx: binary.rtx(), + srtpParameters: srtpParameters, + }; +} + +function parseGetStatsResponse( + binary: FbsPipeTransport.GetStatsResponse +): PipeTransportStat { + const base = parseBaseTransportStats(binary.base()!); + + return { + ...base, + type: 'pipe-transport', + tuple: parseTuple(binary.tuple()!), + }; +} + +function createConsumeRequest({ + builder, + consumerId, + producer, + rtpParameters, +}: { + builder: flatbuffers.Builder; + consumerId: string; + producer: Producer; + rtpParameters: RtpParameters; +}): number { + // Build the request. + const producerIdOffset = builder.createString(producer.id); + const consumerIdOffset = builder.createString(consumerId); + const rtpParametersOffset = serializeRtpParameters(builder, rtpParameters); + let consumableRtpEncodingsOffset: number | undefined; + + if (producer.consumableRtpParameters.encodings) { + consumableRtpEncodingsOffset = serializeRtpEncodingParameters( + builder, + producer.consumableRtpParameters.encodings + ); + } + + const ConsumeRequest = FbsTransport.ConsumeRequest; + + // Create Consume Request. + ConsumeRequest.startConsumeRequest(builder); + ConsumeRequest.addConsumerId(builder, consumerIdOffset); + ConsumeRequest.addProducerId(builder, producerIdOffset); + ConsumeRequest.addKind( + builder, + producer.kind === 'audio' ? FbsMediaKind.AUDIO : FbsMediaKind.VIDEO + ); + ConsumeRequest.addRtpParameters(builder, rtpParametersOffset); + ConsumeRequest.addType(builder, FbsRtpParameters.Type.PIPE); + + if (consumableRtpEncodingsOffset) { + ConsumeRequest.addConsumableRtpEncodings( + builder, + consumableRtpEncodingsOffset + ); + } + + return ConsumeRequest.endConsumeRequest(builder); +} + +function createConnectRequest({ + builder, + ip, + port, + srtpParameters, +}: { + builder: flatbuffers.Builder; + ip?: string; + port?: number; + srtpParameters?: SrtpParameters; +}): number { + let ipOffset = 0; + let srtpParametersOffset = 0; + + if (ip) { + ipOffset = builder.createString(ip); + } + + // Serialize SrtpParameters. + if (srtpParameters) { + srtpParametersOffset = serializeSrtpParameters(builder, srtpParameters); + } + + // Create PlainTransportConnectData. + FbsPipeTransport.ConnectRequest.startConnectRequest(builder); + FbsPipeTransport.ConnectRequest.addIp(builder, ipOffset); + + if (typeof port === 'number') { + FbsPipeTransport.ConnectRequest.addPort(builder, port); + } + if (srtpParameters) { + FbsPipeTransport.ConnectRequest.addSrtpParameters( + builder, + srtpParametersOffset + ); + } + + return FbsPipeTransport.ConnectRequest.endConnectRequest(builder); +} diff --git a/node/src/PipeTransportTypes.ts b/node/src/PipeTransportTypes.ts new file mode 100644 index 0000000000..4bc9734eb8 --- /dev/null +++ b/node/src/PipeTransportTypes.ts @@ -0,0 +1,200 @@ +import type { EnhancedEventEmitter } from './enhancedEvents'; +import type { + Transport, + TransportListenInfo, + TransportListenIp, + TransportTuple, + SctpState, + BaseTransportDump, + BaseTransportStats, + TransportEvents, + TransportObserverEvents, +} from './TransportTypes'; +import type { Consumer } from './ConsumerTypes'; +import type { SrtpParameters } from './srtpParametersTypes'; +import type { SctpParameters, NumSctpStreams } from './sctpParametersTypes'; +import type { Either, AppData } from './types'; + +export type PipeTransportOptions< + PipeTransportAppData extends AppData = AppData, +> = { + /** + * Create a SCTP association. Default false. + */ + enableSctp?: boolean; + + /** + * SCTP streams number. + */ + numSctpStreams?: NumSctpStreams; + + /** + * Maximum allowed size for SCTP messages sent by DataProducers. + * Default 268435456. + */ + maxSctpMessageSize?: number; + + /** + * Maximum SCTP send buffer used by DataConsumers. + * Default 268435456. + */ + sctpSendBufferSize?: number; + + /** + * Enable RTX and NACK for RTP retransmission. Useful if both Routers are + * located in different hosts and there is packet lost in the link. For this + * to work, both PipeTransports must enable this setting. Default false. + */ + enableRtx?: boolean; + + /** + * Enable SRTP. Useful to protect the RTP and RTCP traffic if both Routers + * are located in different hosts. For this to work, connect() must be called + * with remote SRTP parameters. Default false. + */ + enableSrtp?: boolean; + + /** + * Custom application data. + */ + appData?: PipeTransportAppData; +} & PipeTransportListen; + +type PipeTransportListen = Either< + PipeTransportListenInfo, + PipeTransportListenIp +>; + +type PipeTransportListenInfo = { + /** + * Listening info. + */ + listenInfo: TransportListenInfo; +}; + +type PipeTransportListenIp = { + /** + * Listening IP address. + */ + listenIp: TransportListenIp | string; + + /** + * Fixed port to listen on instead of selecting automatically from Worker's + * port range. + */ + port?: number; +}; + +export type PipeConsumerOptions = { + /** + * The id of the Producer to consume. + */ + producerId: string; + + /** + * Custom application data. + */ + appData?: ConsumerAppData; +}; + +export type PipeTransportDump = BaseTransportDump & { + tuple: TransportTuple; + rtx: boolean; + srtpParameters?: SrtpParameters; +}; + +export type PipeTransportStat = BaseTransportStats & { + type: string; + tuple: TransportTuple; +}; + +export type PipeTransportEvents = TransportEvents & { + sctpstatechange: [SctpState]; +}; + +export type PipeTransportObserver = + EnhancedEventEmitter; + +export type PipeTransportObserverEvents = TransportObserverEvents & { + sctpstatechange: [SctpState]; +}; + +export interface PipeTransport + extends Transport< + PipeTransportAppData, + PipeTransportEvents, + PipeTransportObserver + > { + /** + * Transport type. + * + * @override + */ + get type(): 'pipe'; + + /** + * Observer. + * + * @override + */ + get observer(): PipeTransportObserver; + + /** + * PipeTransport tuple. + */ + get tuple(): TransportTuple; + + /** + * SCTP parameters. + */ + get sctpParameters(): SctpParameters | undefined; + + /** + * SCTP state. + */ + get sctpState(): SctpState | undefined; + + /** + * SRTP parameters. + */ + get srtpParameters(): SrtpParameters | undefined; + + /** + * Dump PipeTransport. + * + * @override + */ + dump(): Promise; + + /** + * Get PipeTransport stats. + * + * @override + */ + getStats(): Promise; + + /** + * Provide the PipeTransport remote parameters. + * + * @override + */ + connect({ + ip, + port, + srtpParameters, + }: { + ip: string; + port: number; + srtpParameters?: SrtpParameters; + }): Promise; + + /** + * Create a pipe Consumer. + * + * @override + */ + consume({ + producerId, + appData, + }: PipeConsumerOptions): Promise>; +} diff --git a/node/src/PlainTransport.ts b/node/src/PlainTransport.ts index 4286d34c95..5e595152c1 100644 --- a/node/src/PlainTransport.ts +++ b/node/src/PlainTransport.ts @@ -1,135 +1,42 @@ +import * as flatbuffers from 'flatbuffers'; import { Logger } from './Logger'; +import { EnhancedEventEmitter } from './enhancedEvents'; +import type { + PlainTransport, + PlainTransportDump, + PlainTransportStat, + PlainTransportEvents, + PlainTransportObserver, + PlainTransportObserverEvents, +} from './PlainTransportTypes'; +import type { Transport, TransportTuple, SctpState } from './TransportTypes'; import { - Transport, - TransportListenIp, - TransportTuple, - TransportTraceEventData, - TransportEvents, - TransportObserverEvents, + TransportImpl, TransportConstructorOptions, - SctpState + parseSctpState, + parseTuple, + parseBaseTransportDump, + parseBaseTransportStats, + parseTransportTraceEventData, } from './Transport'; -import { SctpParameters, NumSctpStreams } from './SctpParameters'; -import { SrtpParameters, SrtpCryptoSuite } from './SrtpParameters'; - -export type PlainTransportOptions = -{ - /** - * Listening IP address. - */ - listenIp: TransportListenIp | string; - - /** - * Fixed port to listen on instead of selecting automatically from Worker's port - * range. - */ - port?: number; - - /** - * Use RTCP-mux (RTP and RTCP in the same port). Default true. - */ - rtcpMux?: boolean; - - /** - * Whether remote IP:port should be auto-detected based on first RTP/RTCP - * packet received. If enabled, connect() method must not be called unless - * SRTP is enabled. If so, it must be called with just remote SRTP parameters. - * Default false. - */ - comedia?: boolean; - - /** - * Create a SCTP association. Default false. - */ - enableSctp?: boolean; - - /** - * SCTP streams number. - */ - numSctpStreams?: NumSctpStreams; - - /** - * Maximum allowed size for SCTP messages sent by DataProducers. - * Default 262144. - */ - maxSctpMessageSize?: number; - - /** - * Maximum SCTP send buffer used by DataConsumers. - * Default 262144. - */ - sctpSendBufferSize?: number; - - /** - * Enable SRTP. For this to work, connect() must be called - * with remote SRTP parameters. Default false. - */ - enableSrtp?: boolean; - - /** - * The SRTP crypto suite to be used if enableSrtp is set. Default - * 'AES_CM_128_HMAC_SHA1_80'. - */ - srtpCryptoSuite?: SrtpCryptoSuite; - - /** - * Custom application data. - */ - appData?: Record; -}; - -export type PlainTransportStat = -{ - // Common to all Transports. - type: string; - transportId: string; - timestamp: number; - sctpState?: SctpState; - bytesReceived: number; - recvBitrate: number; - bytesSent: number; - sendBitrate: number; - rtpBytesReceived: number; - rtpRecvBitrate: number; - rtpBytesSent: number; - rtpSendBitrate: number; - rtxBytesReceived: number; - rtxRecvBitrate: number; - rtxBytesSent: number; - rtxSendBitrate: number; - probationBytesSent: number; - probationSendBitrate: number; - availableOutgoingBitrate?: number; - availableIncomingBitrate?: number; - maxIncomingBitrate?: number; - // PlainTransport specific. - rtcpMux: boolean; - comedia: boolean; - tuple: TransportTuple; - rtcpTuple?: TransportTuple; -}; - -export type PlainTransportEvents = TransportEvents & -{ - tuple: [TransportTuple]; - rtcptuple: [TransportTuple]; - sctpstatechange: [SctpState]; -}; - -export type PlainTransportObserverEvents = TransportObserverEvents & -{ - tuple: [TransportTuple]; - rtcptuple: [TransportTuple]; - sctpstatechange: [SctpState]; -}; - -type PlainTransportConstructorOptions = TransportConstructorOptions & -{ - data: PlainTransportData; -}; - -export type PlainTransportData = -{ +import type { SctpParameters } from './sctpParametersTypes'; +import type { SrtpParameters } from './srtpParametersTypes'; +import { + parseSrtpParameters, + serializeSrtpParameters, +} from './srtpParametersFbsUtils'; +import type { AppData } from './types'; +import { Event, Notification } from './fbs/notification'; +import * as FbsRequest from './fbs/request'; +import * as FbsTransport from './fbs/transport'; +import * as FbsPlainTransport from './fbs/plain-transport'; + +type PlainTransportConstructorOptions = + TransportConstructorOptions & { + data: PlainTransportData; + }; + +export type PlainTransportData = { rtcpMux?: boolean; comedia?: boolean; tuple: TransportTuple; @@ -141,224 +48,356 @@ export type PlainTransportData = const logger = new Logger('PlainTransport'); -export class PlainTransport extends - Transport +export class PlainTransportImpl + extends TransportImpl< + PlainTransportAppData, + PlainTransportEvents, + PlainTransportObserver + > + implements Transport, PlainTransport { // PlainTransport data. readonly #data: PlainTransportData; - /** - * @private - */ - constructor(options: PlainTransportConstructorOptions) - { - super(options); + constructor( + options: PlainTransportConstructorOptions + ) { + const observer: PlainTransportObserver = + new EnhancedEventEmitter(); + + super(options, observer); logger.debug('constructor()'); const { data } = options; - this.#data = - { - rtcpMux : data.rtcpMux, - comedia : data.comedia, - tuple : data.tuple, - rtcpTuple : data.rtcpTuple, - sctpParameters : data.sctpParameters, - sctpState : data.sctpState, - srtpParameters : data.srtpParameters + this.#data = { + rtcpMux: data.rtcpMux, + comedia: data.comedia, + tuple: data.tuple, + rtcpTuple: data.rtcpTuple, + sctpParameters: data.sctpParameters, + sctpState: data.sctpState, + srtpParameters: data.srtpParameters, }; this.handleWorkerNotifications(); + this.handleListenerError(); + } + + get type(): 'plain' { + return 'plain'; } - /** - * Transport tuple. - */ - get tuple(): TransportTuple - { + get observer(): PlainTransportObserver { + return super.observer; + } + + get tuple(): TransportTuple { return this.#data.tuple; } - /** - * Transport RTCP tuple. - */ - get rtcpTuple(): TransportTuple | undefined - { + get rtcpTuple(): TransportTuple | undefined { return this.#data.rtcpTuple; } - /** - * SCTP parameters. - */ - get sctpParameters(): SctpParameters | undefined - { + get sctpParameters(): SctpParameters | undefined { return this.#data.sctpParameters; } - /** - * SCTP state. - */ - get sctpState(): SctpState | undefined - { + get sctpState(): SctpState | undefined { return this.#data.sctpState; } - /** - * SRTP parameters. - */ - get srtpParameters(): SrtpParameters | undefined - { + get srtpParameters(): SrtpParameters | undefined { return this.#data.srtpParameters; } - /** - * Close the PlainTransport. - * - * @override - */ - close(): void - { - if (this.closed) + close(): void { + if (this.closed) { return; + } - if (this.#data.sctpState) + if (this.#data.sctpState) { this.#data.sctpState = 'closed'; + } super.close(); } - /** - * Router was closed. - * - * @private - * @override - */ - routerClosed(): void - { - if (this.closed) + routerClosed(): void { + if (this.closed) { return; + } - if (this.#data.sctpState) + if (this.#data.sctpState) { this.#data.sctpState = 'closed'; + } super.routerClosed(); } - /** - * Get PlainTransport stats. - * - * @override - */ - async getStats(): Promise - { + async dump(): Promise { + logger.debug('dump()'); + + const response = await this.channel.request( + FbsRequest.Method.TRANSPORT_DUMP, + undefined, + undefined, + this.internal.transportId + ); + + /* Decode Response. */ + const data = new FbsPlainTransport.DumpResponse(); + + response.body(data); + + return parsePlainTransportDumpResponse(data); + } + + async getStats(): Promise { logger.debug('getStats()'); - return this.channel.request('transport.getStats', this.internal.transportId); + const response = await this.channel.request( + FbsRequest.Method.TRANSPORT_GET_STATS, + undefined, + undefined, + this.internal.transportId + ); + + /* Decode Response. */ + const data = new FbsPlainTransport.GetStatsResponse(); + + response.body(data); + + return [parseGetStatsResponse(data)]; } - /** - * Provide the PlainTransport remote parameters. - * - * @override - */ - async connect( - { + async connect({ + ip, + port, + rtcpPort, + srtpParameters, + }: { + ip?: string; + port?: number; + rtcpPort?: number; + srtpParameters?: SrtpParameters; + }): Promise { + logger.debug('connect()'); + + const requestOffset = createConnectRequest({ + builder: this.channel.bufferBuilder, ip, port, rtcpPort, - srtpParameters - }: - { - ip?: string; - port?: number; - rtcpPort?: number; - srtpParameters?: SrtpParameters; - } - ): Promise - { - logger.debug('connect()'); + srtpParameters, + }); - const reqData = { ip, port, rtcpPort, srtpParameters }; + // Wait for response. + const response = await this.channel.request( + FbsRequest.Method.PLAINTRANSPORT_CONNECT, + FbsRequest.Body.PlainTransport_ConnectRequest, + requestOffset, + this.internal.transportId + ); - const data = - await this.channel.request('transport.connect', this.internal.transportId, reqData); + /* Decode Response. */ + const data = new FbsPlainTransport.ConnectResponse(); + + response.body(data); // Update data. - if (data.tuple) - this.#data.tuple = data.tuple; + if (data.tuple()) { + this.#data.tuple = parseTuple(data.tuple()!); + } - if (data.rtcpTuple) - this.#data.rtcpTuple = data.rtcpTuple; + if (data.rtcpTuple()) { + this.#data.rtcpTuple = parseTuple(data.rtcpTuple()!); + } - this.#data.srtpParameters = data.srtpParameters; + if (data.srtpParameters()) { + this.#data.srtpParameters = parseSrtpParameters(data.srtpParameters()!); + } } - private handleWorkerNotifications(): void - { - this.channel.on(this.internal.transportId, (event: string, data?: any) => - { - switch (event) - { - case 'tuple': - { - const tuple = data.tuple as TransportTuple; + private handleWorkerNotifications(): void { + this.channel.on( + this.internal.transportId, + (event: Event, data?: Notification) => { + switch (event) { + case Event.PLAINTRANSPORT_TUPLE: { + const notification = new FbsPlainTransport.TupleNotification(); - this.#data.tuple = tuple; + data!.body(notification); - this.safeEmit('tuple', tuple); + const tuple = parseTuple(notification.tuple()!); - // Emit observer event. - this.observer.safeEmit('tuple', tuple); + this.#data.tuple = tuple; - break; - } + this.safeEmit('tuple', tuple); - case 'rtcptuple': - { - const rtcpTuple = data.rtcpTuple as TransportTuple; + // Emit observer event. + this.observer.safeEmit('tuple', tuple); - this.#data.rtcpTuple = rtcpTuple; + break; + } - this.safeEmit('rtcptuple', rtcpTuple); + case Event.PLAINTRANSPORT_RTCP_TUPLE: { + const notification = new FbsPlainTransport.RtcpTupleNotification(); - // Emit observer event. - this.observer.safeEmit('rtcptuple', rtcpTuple); + data!.body(notification); - break; - } + const rtcpTuple = parseTuple(notification.tuple()!); - case 'sctpstatechange': - { - const sctpState = data.sctpState as SctpState; + this.#data.rtcpTuple = rtcpTuple; - this.#data.sctpState = sctpState; + this.safeEmit('rtcptuple', rtcpTuple); - this.safeEmit('sctpstatechange', sctpState); + // Emit observer event. + this.observer.safeEmit('rtcptuple', rtcpTuple); - // Emit observer event. - this.observer.safeEmit('sctpstatechange', sctpState); + break; + } - break; - } + case Event.TRANSPORT_SCTP_STATE_CHANGE: { + const notification = new FbsTransport.SctpStateChangeNotification(); - case 'trace': - { - const trace = data as TransportTraceEventData; + data!.body(notification); - this.safeEmit('trace', trace); + const sctpState = parseSctpState(notification.sctpState()); - // Emit observer event. - this.observer.safeEmit('trace', trace); + this.#data.sctpState = sctpState; - break; - } + this.safeEmit('sctpstatechange', sctpState); + + // Emit observer event. + this.observer.safeEmit('sctpstatechange', sctpState); - default: - { - logger.error('ignoring unknown event "%s"', event); + break; + } + + case Event.TRANSPORT_TRACE: { + const notification = new FbsTransport.TraceNotification(); + + data!.body(notification); + + const trace = parseTransportTraceEventData(notification); + + this.safeEmit('trace', trace); + + // Emit observer event. + this.observer.safeEmit('trace', trace); + + break; + } + + default: { + logger.error(`ignoring unknown event "${event}"`); + } } } + ); + } + + private handleListenerError(): void { + this.on('listenererror', (eventName, error) => { + logger.error( + `event listener threw an error [eventName:${eventName}]:`, + error + ); }); } } + +export function parsePlainTransportDumpResponse( + binary: FbsPlainTransport.DumpResponse +): PlainTransportDump { + // Retrieve BaseTransportDump. + const baseTransportDump = parseBaseTransportDump(binary.base()!); + // Retrieve RTP Tuple. + const tuple = parseTuple(binary.tuple()!); + + // Retrieve RTCP Tuple. + let rtcpTuple: TransportTuple | undefined; + + if (binary.rtcpTuple()) { + rtcpTuple = parseTuple(binary.rtcpTuple()!); + } + + // Retrieve SRTP Parameters. + let srtpParameters: SrtpParameters | undefined; + + if (binary.srtpParameters()) { + srtpParameters = parseSrtpParameters(binary.srtpParameters()!); + } + + return { + ...baseTransportDump, + rtcpMux: binary.rtcpMux(), + comedia: binary.comedia(), + tuple: tuple, + rtcpTuple: rtcpTuple, + srtpParameters: srtpParameters, + }; +} + +function parseGetStatsResponse( + binary: FbsPlainTransport.GetStatsResponse +): PlainTransportStat { + const base = parseBaseTransportStats(binary.base()!); + + return { + ...base, + type: 'plain-rtp-transport', + rtcpMux: binary.rtcpMux(), + comedia: binary.comedia(), + tuple: parseTuple(binary.tuple()!), + rtcpTuple: binary.rtcpTuple() ? parseTuple(binary.rtcpTuple()!) : undefined, + }; +} + +function createConnectRequest({ + builder, + ip, + port, + rtcpPort, + srtpParameters, +}: { + builder: flatbuffers.Builder; + ip?: string; + port?: number; + rtcpPort?: number; + srtpParameters?: SrtpParameters; +}): number { + let ipOffset = 0; + let srtpParametersOffset = 0; + + if (ip) { + ipOffset = builder.createString(ip); + } + + // Serialize SrtpParameters. + if (srtpParameters) { + srtpParametersOffset = serializeSrtpParameters(builder, srtpParameters); + } + + // Create PlainTransportConnectData. + FbsPlainTransport.ConnectRequest.startConnectRequest(builder); + FbsPlainTransport.ConnectRequest.addIp(builder, ipOffset); + + if (typeof port === 'number') { + FbsPlainTransport.ConnectRequest.addPort(builder, port); + } + if (typeof rtcpPort === 'number') { + FbsPlainTransport.ConnectRequest.addRtcpPort(builder, rtcpPort); + } + if (srtpParameters) { + FbsPlainTransport.ConnectRequest.addSrtpParameters( + builder, + srtpParametersOffset + ); + } + + return FbsPlainTransport.ConnectRequest.endConnectRequest(builder); +} diff --git a/node/src/PlainTransportTypes.ts b/node/src/PlainTransportTypes.ts new file mode 100644 index 0000000000..383a29c04a --- /dev/null +++ b/node/src/PlainTransportTypes.ts @@ -0,0 +1,209 @@ +import type { EnhancedEventEmitter } from './enhancedEvents'; +import type { + Transport, + TransportListenInfo, + TransportListenIp, + TransportTuple, + SctpState, + BaseTransportDump, + BaseTransportStats, + TransportEvents, + TransportObserverEvents, +} from './TransportTypes'; +import type { SrtpParameters, SrtpCryptoSuite } from './srtpParametersTypes'; +import type { SctpParameters, NumSctpStreams } from './sctpParametersTypes'; +import type { Either, AppData } from './types'; + +export type PlainTransportOptions< + PlainTransportAppData extends AppData = AppData, +> = { + /** + * Use RTCP-mux (RTP and RTCP in the same port). Default true. + */ + rtcpMux?: boolean; + + /** + * Whether remote IP:port should be auto-detected based on first RTP/RTCP + * packet received. If enabled, connect() method must not be called unless + * SRTP is enabled. If so, it must be called with just remote SRTP parameters. + * Default false. + */ + comedia?: boolean; + + /** + * Create a SCTP association. Default false. + */ + enableSctp?: boolean; + + /** + * SCTP streams number. + */ + numSctpStreams?: NumSctpStreams; + + /** + * Maximum allowed size for SCTP messages sent by DataProducers. + * Default 262144. + */ + maxSctpMessageSize?: number; + + /** + * Maximum SCTP send buffer used by DataConsumers. + * Default 262144. + */ + sctpSendBufferSize?: number; + + /** + * Enable SRTP. For this to work, connect() must be called + * with remote SRTP parameters. Default false. + */ + enableSrtp?: boolean; + + /** + * The SRTP crypto suite to be used if enableSrtp is set. Default + * 'AES_CM_128_HMAC_SHA1_80'. + */ + srtpCryptoSuite?: SrtpCryptoSuite; + + /** + * Custom application data. + */ + appData?: PlainTransportAppData; +} & PlainTransportListen; + +type PlainTransportListen = Either< + PlainTransportListenInfo, + PlainTransportListenIp +>; + +type PlainTransportListenInfo = { + /** + * Listening info. + */ + listenInfo: TransportListenInfo; + + /** + * Optional listening info for RTCP. + */ + rtcpListenInfo?: TransportListenInfo; +}; + +type PlainTransportListenIp = { + /** + * Listening IP address. + */ + listenIp: TransportListenIp | string; + + /** + * Fixed port to listen on instead of selecting automatically from Worker's + * port range. + */ + port?: number; +}; + +export type PlainTransportDump = BaseTransportDump & { + rtcpMux: boolean; + comedia: boolean; + tuple: TransportTuple; + rtcpTuple?: TransportTuple; + srtpParameters?: SrtpParameters; +}; + +export type PlainTransportStat = BaseTransportStats & { + type: string; + rtcpMux: boolean; + comedia: boolean; + tuple: TransportTuple; + rtcpTuple?: TransportTuple; +}; + +export type PlainTransportEvents = TransportEvents & { + tuple: [TransportTuple]; + rtcptuple: [TransportTuple]; + sctpstatechange: [SctpState]; +}; + +export type PlainTransportObserver = + EnhancedEventEmitter; + +export type PlainTransportObserverEvents = TransportObserverEvents & { + tuple: [TransportTuple]; + rtcptuple: [TransportTuple]; + sctpstatechange: [SctpState]; +}; + +export interface PlainTransport + extends Transport< + PlainTransportAppData, + PlainTransportEvents, + PlainTransportObserver + > { + /** + * Transport type. + * + * @override + */ + get type(): 'plain'; + + /** + * Observer. + * + * @override + */ + get observer(): PlainTransportObserver; + + /** + * PlainTransport tuple. + */ + get tuple(): TransportTuple; + + /** + * PlainTransport RTCP tuple. + */ + get rtcpTuple(): TransportTuple | undefined; + + /** + * SCTP parameters. + */ + get sctpParameters(): SctpParameters | undefined; + + /** + * SCTP state. + */ + get sctpState(): SctpState | undefined; + + /** + * SRTP parameters. + */ + get srtpParameters(): SrtpParameters | undefined; + + /** + * Dump PlainTransport. + * + * @override + */ + dump(): Promise; + + /** + * Get PlainTransport stats. + * + * @override + */ + getStats(): Promise; + + /** + * Provide the PlainTransport remote parameters. + * + * @override + */ + connect({ + ip, + port, + rtcpPort, + srtpParameters, + }: { + ip?: string; + port?: number; + rtcpPort?: number; + srtpParameters?: SrtpParameters; + }): Promise; +} diff --git a/node/src/Producer.ts b/node/src/Producer.ts index 95ec93d265..9420b8f8eb 100644 --- a/node/src/Producer.ts +++ b/node/src/Producer.ts @@ -1,182 +1,50 @@ import { Logger } from './Logger'; -import { EnhancedEventEmitter } from './EnhancedEventEmitter'; +import { EnhancedEventEmitter } from './enhancedEvents'; +import type { + Producer, + ProducerType, + ProducerScore, + ProducerVideoOrientation, + ProducerDump, + ProducerStat, + ProducerTraceEventType, + ProducerTraceEventData, + ProducerEvents, + ProducerObserver, + ProducerObserverEvents, +} from './ProducerTypes'; import { Channel } from './Channel'; -import { PayloadChannel } from './PayloadChannel'; -import { TransportInternal } from './Transport'; -import { MediaKind, RtpParameters } from './RtpParameters'; - -export type ProducerOptions = -{ - /** - * Producer id (just for Router.pipeToRouter() method). - */ - id?: string; - - /** - * Media kind ('audio' or 'video'). - */ - kind: MediaKind; - - /** - * RTP parameters defining what the endpoint is sending. - */ - rtpParameters: RtpParameters; - - /** - * Whether the producer must start in paused mode. Default false. - */ - paused?: boolean; - - /** - * Just for video. Time (in ms) before asking the sender for a new key frame - * after having asked a previous one. Default 0. - */ - keyFrameRequestDelay?: number; - - /** - * Custom application data. - */ - appData?: Record; -}; - -/** - * Valid types for 'trace' event. - */ -export type ProducerTraceEventType = 'rtp' | 'keyframe' | 'nack' | 'pli' | 'fir'; - -/** - * 'trace' event data. - */ -export type ProducerTraceEventData = -{ - /** - * Trace type. - */ - type: ProducerTraceEventType; - - /** - * Event timestamp. - */ - timestamp: number; - - /** - * Event direction. - */ - direction: 'in' | 'out'; - - /** - * Per type information. - */ - info: any; -}; - -export type ProducerScore = -{ - /** - * SSRC of the RTP stream. - */ - ssrc: number; - - /** - * RID of the RTP stream. - */ - rid?: string; - - /** - * The score of the RTP stream. - */ - score: number; -}; - -export type ProducerVideoOrientation = -{ - /** - * Whether the source is a video camera. - */ - camera: boolean; - - /** - * Whether the video source is flipped. - */ - flip: boolean; - - /** - * Rotation degrees (0, 90, 180 or 270). - */ - rotation: number; -}; - -export type ProducerStat = -{ - // Common to all RtpStreams. - type: string; - timestamp: number; - ssrc: number; - rtxSsrc?: number; - rid?: string; - kind: string; - mimeType: string; - packetsLost: number; - fractionLost: number; - packetsDiscarded: number; - packetsRetransmitted: number; - packetsRepaired: number; - nackCount: number; - nackPacketCount: number; - pliCount: number; - firCount: number; - score: number; - packetCount: number; - byteCount: number; - bitrate: number; - roundTripTime?: number; - rtxPacketsDiscarded?: number; - // RtpStreamRecv specific. - jitter: number; - bitrateByLayer?: any; -}; - -/** - * Producer type. - */ -export type ProducerType = 'simple' | 'simulcast' | 'svc'; - -export type ProducerEvents = -{ - transportclose: []; - score: [ProducerScore[]]; - videoorientationchange: [ProducerVideoOrientation]; - trace: [ProducerTraceEventData]; - // Private events. - '@close': []; -}; - -export type ProducerObserverEvents = -{ - close: []; - pause: []; - resume: []; - score: [ProducerScore[]]; - videoorientationchange: [ProducerVideoOrientation]; - trace: [ProducerTraceEventData]; -}; - -type ProducerInternal = TransportInternal & -{ +import type { TransportInternal } from './Transport'; +import type { MediaKind, RtpParameters } from './rtpParametersTypes'; +import { parseRtpParameters } from './rtpParametersFbsUtils'; +import { parseRtpStreamRecvStats } from './rtpStreamStatsFbsUtils'; +import type { AppData } from './types'; +import * as fbsUtils from './fbsUtils'; +import { Event, Notification } from './fbs/notification'; +import { TraceDirection as FbsTraceDirection } from './fbs/common'; +import * as FbsNotification from './fbs/notification'; +import * as FbsRequest from './fbs/request'; +import * as FbsTransport from './fbs/transport'; +import * as FbsProducer from './fbs/producer'; +import * as FbsProducerTraceInfo from './fbs/producer/trace-info'; +import * as FbsRtpParameters from './fbs/rtp-parameters'; + +type ProducerInternal = TransportInternal & { producerId: string; }; -type ProducerData = -{ +const logger = new Logger('Producer'); + +type ProducerData = { kind: MediaKind; rtpParameters: RtpParameters; type: ProducerType; consumableRtpParameters: RtpParameters; }; -const logger = new Logger('Producer'); - -export class Producer extends EnhancedEventEmitter +export class ProducerImpl + extends EnhancedEventEmitter + implements Producer { // Internal data. readonly #internal: ProducerInternal; @@ -187,46 +55,35 @@ export class Producer extends EnhancedEventEmitter // Channel instance. readonly #channel: Channel; - // PayloadChannel instance. - readonly #payloadChannel: PayloadChannel; - // Closed flag. #closed = false; - // Custom app data. - readonly #appData: Record; - // Paused flag. #paused = false; + // Custom app data. + #appData: ProducerAppData; + // Current score. #score: ProducerScore[] = []; // Observer instance. - readonly #observer = new EnhancedEventEmitter(); - - /** - * @private - */ - constructor( - { - internal, - data, - channel, - payloadChannel, - appData, - paused - }: - { - internal: ProducerInternal; - data: ProducerData; - channel: Channel; - payloadChannel: PayloadChannel; - appData?: Record; - paused: boolean; - } - ) - { + readonly #observer: ProducerObserver = + new EnhancedEventEmitter(); + + constructor({ + internal, + data, + channel, + appData, + paused, + }: { + internal: ProducerInternal; + data: ProducerData; + channel: Channel; + appData?: ProducerAppData; + paused: boolean; + }) { super(); logger.debug('constructor()'); @@ -234,119 +91,70 @@ export class Producer extends EnhancedEventEmitter this.#internal = internal; this.#data = data; this.#channel = channel; - this.#payloadChannel = payloadChannel; - this.#appData = appData || {}; this.#paused = paused; + this.#appData = appData ?? ({} as ProducerAppData); this.handleWorkerNotifications(); + this.handleListenerError(); } - /** - * Producer id. - */ - get id(): string - { + get id(): string { return this.#internal.producerId; } - /** - * Whether the Producer is closed. - */ - get closed(): boolean - { + get closed(): boolean { return this.#closed; } - /** - * Media kind. - */ - get kind(): MediaKind - { + get kind(): MediaKind { return this.#data.kind; } - /** - * RTP parameters. - */ - get rtpParameters(): RtpParameters - { + get rtpParameters(): RtpParameters { return this.#data.rtpParameters; } - /** - * Producer type. - */ - get type(): ProducerType - { + get type(): ProducerType { return this.#data.type; } - /** - * Consumable RTP parameters. - * - * @private - */ - get consumableRtpParameters(): RtpParameters - { + get consumableRtpParameters(): RtpParameters { return this.#data.consumableRtpParameters; } - /** - * Whether the Producer is paused. - */ - get paused(): boolean - { + get paused(): boolean { return this.#paused; } - /** - * Producer score list. - */ - get score(): ProducerScore[] - { + get score(): ProducerScore[] { return this.#score; } - /** - * App custom data. - */ - get appData(): Record - { + get appData(): ProducerAppData { return this.#appData; } - /** - * Invalid setter. - */ - set appData(appData: Record) // eslint-disable-line no-unused-vars - { - throw new Error('cannot override appData object'); + set appData(appData: ProducerAppData) { + this.#appData = appData; } - /** - * Observer. - */ - get observer(): EnhancedEventEmitter - { + get observer(): ProducerObserver { return this.#observer; } /** - * @private * Just for testing purposes. + * + * @private */ - get channelForTesting(): Channel - { + get channelForTesting(): Channel { return this.#channel; } - /** - * Close the Producer. - */ - close(): void - { - if (this.#closed) + close(): void { + if (this.#closed) { return; + } logger.debug('close()'); @@ -354,11 +162,19 @@ export class Producer extends EnhancedEventEmitter // Remove notification subscriptions. this.#channel.removeAllListeners(this.#internal.producerId); - this.#payloadChannel.removeAllListeners(this.#internal.producerId); - - const reqData = { producerId: this.#internal.producerId }; - this.#channel.request('transport.closeProducer', this.#internal.transportId, reqData) + /* Build Request. */ + const requestOffset = new FbsTransport.CloseProducerRequestT( + this.#internal.producerId + ).pack(this.#channel.bufferBuilder); + + this.#channel + .request( + FbsRequest.Method.TRANSPORT_CLOSE_PRODUCER, + FbsRequest.Body.Transport_CloseProducerRequest, + requestOffset, + this.#internal.transportId + ) .catch(() => {}); this.emit('@close'); @@ -367,15 +183,10 @@ export class Producer extends EnhancedEventEmitter this.#observer.safeEmit('close'); } - /** - * Transport was closed. - * - * @private - */ - transportClosed(): void - { - if (this.#closed) + transportClosed(): void { + if (this.#closed) { return; + } logger.debug('transportClosed()'); @@ -383,7 +194,6 @@ export class Producer extends EnhancedEventEmitter // Remove notification subscriptions. this.#channel.removeAllListeners(this.#internal.producerId); - this.#payloadChannel.removeAllListeners(this.#internal.producerId); this.safeEmit('transportclose'); @@ -391,138 +201,379 @@ export class Producer extends EnhancedEventEmitter this.#observer.safeEmit('close'); } - /** - * Dump Producer. - */ - async dump(): Promise - { + async dump(): Promise { logger.debug('dump()'); - return this.#channel.request('producer.dump', this.#internal.producerId); + const response = await this.#channel.request( + FbsRequest.Method.PRODUCER_DUMP, + undefined, + undefined, + this.#internal.producerId + ); + + /* Decode Response. */ + const dumpResponse = new FbsProducer.DumpResponse(); + + response.body(dumpResponse); + + return parseProducerDump(dumpResponse); } - /** - * Get Producer stats. - */ - async getStats(): Promise - { + async getStats(): Promise { logger.debug('getStats()'); - return this.#channel.request('producer.getStats', this.#internal.producerId); + const response = await this.#channel.request( + FbsRequest.Method.PRODUCER_GET_STATS, + undefined, + undefined, + this.#internal.producerId + ); + + /* Decode Response. */ + const data = new FbsProducer.GetStatsResponse(); + + response.body(data); + + return parseProducerStats(data); } - /** - * Pause the Producer. - */ - async pause(): Promise - { + async pause(): Promise { logger.debug('pause()'); - const wasPaused = this.#paused; + await this.#channel.request( + FbsRequest.Method.PRODUCER_PAUSE, + undefined, + undefined, + this.#internal.producerId + ); - await this.#channel.request('producer.pause', this.#internal.producerId); + const wasPaused = this.#paused; this.#paused = true; // Emit observer event. - if (!wasPaused) + if (!wasPaused) { this.#observer.safeEmit('pause'); + } } - /** - * Resume the Producer. - */ - async resume(): Promise - { + async resume(): Promise { logger.debug('resume()'); - const wasPaused = this.#paused; + await this.#channel.request( + FbsRequest.Method.PRODUCER_RESUME, + undefined, + undefined, + this.#internal.producerId + ); - await this.#channel.request('producer.resume', this.#internal.producerId); + const wasPaused = this.#paused; this.#paused = false; // Emit observer event. - if (wasPaused) + if (wasPaused) { this.#observer.safeEmit('resume'); + } } - /** - * Enable 'trace' event. - */ - async enableTraceEvent(types: ProducerTraceEventType[] = []): Promise - { + async enableTraceEvent(types: ProducerTraceEventType[] = []): Promise { logger.debug('enableTraceEvent()'); - const reqData = { types }; + if (!Array.isArray(types)) { + throw new TypeError('types must be an array'); + } + if (types.find(type => typeof type !== 'string')) { + throw new TypeError('every type must be a string'); + } + + // Convert event types. + const fbsEventTypes: FbsProducer.TraceEventType[] = []; + + for (const eventType of types) { + try { + fbsEventTypes.push(producerTraceEventTypeToFbs(eventType)); + } catch (error) { + logger.warn('enableTraceEvent() | [error:${error}]'); + } + } + + /* Build Request. */ + const requestOffset = new FbsProducer.EnableTraceEventRequestT( + fbsEventTypes + ).pack(this.#channel.bufferBuilder); await this.#channel.request( - 'producer.enableTraceEvent', this.#internal.producerId, reqData); + FbsRequest.Method.PRODUCER_ENABLE_TRACE_EVENT, + FbsRequest.Body.Producer_EnableTraceEventRequest, + requestOffset, + this.#internal.producerId + ); } - /** - * Send RTP packet (just valid for Producers created on a DirectTransport). - */ - send(rtpPacket: Buffer) - { - if (!Buffer.isBuffer(rtpPacket)) - { + send(rtpPacket: Buffer): void { + if (!Buffer.isBuffer(rtpPacket)) { throw new TypeError('rtpPacket must be a Buffer'); } - this.#payloadChannel.notify( - 'producer.send', this.#internal.producerId, undefined, rtpPacket); + const builder = this.#channel.bufferBuilder; + const dataOffset = FbsProducer.SendNotification.createDataVector( + builder, + rtpPacket + ); + const notificationOffset = + FbsProducer.SendNotification.createSendNotification(builder, dataOffset); + + this.#channel.notify( + FbsNotification.Event.PRODUCER_SEND, + FbsNotification.Body.Producer_SendNotification, + notificationOffset, + this.#internal.producerId + ); } - private handleWorkerNotifications(): void - { - this.#channel.on(this.#internal.producerId, (event: string, data?: any) => - { - switch (event) - { - case 'score': - { - const score = data as ProducerScore[]; + private handleWorkerNotifications(): void { + this.#channel.on( + this.#internal.producerId, + (event: Event, data?: Notification) => { + switch (event) { + case Event.PRODUCER_SCORE: { + const notification = new FbsProducer.ScoreNotification(); - this.#score = score; + data!.body(notification); - this.safeEmit('score', score); + const score: ProducerScore[] = fbsUtils.parseVector( + notification, + 'scores', + parseProducerScore + ); - // Emit observer event. - this.#observer.safeEmit('score', score); + this.#score = score; - break; - } + this.safeEmit('score', score); - case 'videoorientationchange': - { - const videoOrientation = data as ProducerVideoOrientation; + // Emit observer event. + this.#observer.safeEmit('score', score); - this.safeEmit('videoorientationchange', videoOrientation); + break; + } - // Emit observer event. - this.#observer.safeEmit('videoorientationchange', videoOrientation); + case Event.PRODUCER_VIDEO_ORIENTATION_CHANGE: { + const notification = + new FbsProducer.VideoOrientationChangeNotification(); - break; - } + data!.body(notification); - case 'trace': - { - const trace = data as ProducerTraceEventData; + const videoOrientation: ProducerVideoOrientation = + notification.unpack(); - this.safeEmit('trace', trace); + this.safeEmit('videoorientationchange', videoOrientation); - // Emit observer event. - this.#observer.safeEmit('trace', trace); + // Emit observer event. + this.#observer.safeEmit('videoorientationchange', videoOrientation); - break; - } + break; + } + + case Event.PRODUCER_TRACE: { + const notification = new FbsProducer.TraceNotification(); - default: - { - logger.error('ignoring unknown event "%s"', event); + data!.body(notification); + + const trace: ProducerTraceEventData = + parseTraceEventData(notification); + + this.safeEmit('trace', trace); + + // Emit observer event. + this.#observer.safeEmit('trace', trace); + + break; + } + + default: { + logger.error(`ignoring unknown event "${event}"`); + } } } + ); + } + + private handleListenerError(): void { + this.on('listenererror', (eventName, error) => { + logger.error( + `event listener threw an error [eventName:${eventName}]:`, + error + ); }); } } + +export function producerTypeFromFbs(type: FbsRtpParameters.Type): ProducerType { + switch (type) { + case FbsRtpParameters.Type.SIMPLE: { + return 'simple'; + } + + case FbsRtpParameters.Type.SIMULCAST: { + return 'simulcast'; + } + + case FbsRtpParameters.Type.SVC: { + return 'svc'; + } + + default: { + throw new TypeError(`invalid FbsRtpParameters.Type: ${type}`); + } + } +} + +export function producerTypeToFbs(type: ProducerType): FbsRtpParameters.Type { + switch (type) { + case 'simple': { + return FbsRtpParameters.Type.SIMPLE; + } + + case 'simulcast': { + return FbsRtpParameters.Type.SIMULCAST; + } + + case 'svc': { + return FbsRtpParameters.Type.SVC; + } + + default: { + throw new TypeError(`invalid ProducerType: ${type}`); + } + } +} + +function producerTraceEventTypeToFbs( + eventType: ProducerTraceEventType +): FbsProducer.TraceEventType { + switch (eventType) { + case 'keyframe': { + return FbsProducer.TraceEventType.KEYFRAME; + } + + case 'fir': { + return FbsProducer.TraceEventType.FIR; + } + + case 'nack': { + return FbsProducer.TraceEventType.NACK; + } + + case 'pli': { + return FbsProducer.TraceEventType.PLI; + } + + case 'rtp': { + return FbsProducer.TraceEventType.RTP; + } + + case 'sr': { + return FbsProducer.TraceEventType.SR; + } + + default: { + throw new TypeError(`invalid ProducerTraceEventType: ${eventType}`); + } + } +} + +function producerTraceEventTypeFromFbs( + eventType: FbsProducer.TraceEventType +): ProducerTraceEventType { + switch (eventType) { + case FbsProducer.TraceEventType.KEYFRAME: { + return 'keyframe'; + } + + case FbsProducer.TraceEventType.FIR: { + return 'fir'; + } + + case FbsProducer.TraceEventType.NACK: { + return 'nack'; + } + + case FbsProducer.TraceEventType.PLI: { + return 'pli'; + } + + case FbsProducer.TraceEventType.RTP: { + return 'rtp'; + } + + case FbsProducer.TraceEventType.SR: { + return 'sr'; + } + } +} + +export function parseProducerDump( + data: FbsProducer.DumpResponse +): ProducerDump { + return { + id: data.id()!, + kind: data.kind() === FbsRtpParameters.MediaKind.AUDIO ? 'audio' : 'video', + type: producerTypeFromFbs(data.type()), + rtpParameters: parseRtpParameters(data.rtpParameters()!), + // NOTE: optional values are represented with null instead of undefined. + // TODO: Make flatbuffers TS return undefined instead of null. + rtpMapping: data.rtpMapping() ? data.rtpMapping()!.unpack() : undefined, + // NOTE: optional values are represented with null instead of undefined. + // TODO: Make flatbuffers TS return undefined instead of null. + rtpStreams: + data.rtpStreamsLength() > 0 + ? fbsUtils.parseVector(data, 'rtpStreams', (rtpStream: any) => + rtpStream.unpack() + ) + : undefined, + traceEventTypes: fbsUtils.parseVector( + data, + 'traceEventTypes', + producerTraceEventTypeFromFbs + ), + paused: data.paused(), + }; +} + +function parseProducerStats( + binary: FbsProducer.GetStatsResponse +): ProducerStat[] { + return fbsUtils.parseVector(binary, 'stats', parseRtpStreamRecvStats); +} + +function parseProducerScore(binary: FbsProducer.Score): ProducerScore { + return { + encodingIdx: binary.encodingIdx(), + ssrc: binary.ssrc(), + rid: binary.rid() ?? undefined, + score: binary.score(), + }; +} + +function parseTraceEventData( + trace: FbsProducer.TraceNotification +): ProducerTraceEventData { + let info: any; + + if (trace.infoType() !== FbsProducer.TraceInfo.NONE) { + const accessor = trace.info.bind(trace); + + info = FbsProducerTraceInfo.unionToTraceInfo(trace.infoType(), accessor); + + trace.info(info); + } + + return { + type: producerTraceEventTypeFromFbs(trace.type()), + timestamp: Number(trace.timestamp()), + direction: + trace.direction() === FbsTraceDirection.DIRECTION_IN ? 'in' : 'out', + info: info ? info.unpack() : undefined, + }; +} diff --git a/node/src/ProducerTypes.ts b/node/src/ProducerTypes.ts new file mode 100644 index 0000000000..989acf72c0 --- /dev/null +++ b/node/src/ProducerTypes.ts @@ -0,0 +1,253 @@ +import type { EnhancedEventEmitter } from './enhancedEvents'; +import type { MediaKind, RtpParameters } from './rtpParametersTypes'; +import type { RtpStreamRecvStats } from './rtpStreamStatsTypes'; +import type { AppData } from './types'; + +export type ProducerOptions = { + /** + * Producer id (just for Router.pipeToRouter() method). + */ + id?: string; + + /** + * Media kind ('audio' or 'video'). + */ + kind: MediaKind; + + /** + * RTP parameters defining what the endpoint is sending. + */ + rtpParameters: RtpParameters; + + /** + * Whether the producer must start in paused mode. Default false. + */ + paused?: boolean; + + /** + * Just for video. Time (in ms) before asking the sender for a new key frame + * after having asked a previous one. Default 0. + */ + keyFrameRequestDelay?: number; + + /** + * Custom application data. + */ + appData?: ProducerAppData; +}; + +/** + * Producer type. + */ +export type ProducerType = 'simple' | 'simulcast' | 'svc'; + +export type ProducerScore = { + /** + * Index of the RTP stream in the rtpParameters.encodings array. + */ + encodingIdx: number; + + /** + * SSRC of the RTP stream. + */ + ssrc: number; + + /** + * RID of the RTP stream. + */ + rid?: string; + + /** + * The score of the RTP stream. + */ + score: number; +}; + +export type ProducerVideoOrientation = { + /** + * Whether the source is a video camera. + */ + camera: boolean; + + /** + * Whether the video source is flipped. + */ + flip: boolean; + + /** + * Rotation degrees (0, 90, 180 or 270). + */ + rotation: number; +}; + +export type ProducerDump = { + id: string; + kind: string; + type: ProducerType; + rtpParameters: RtpParameters; + rtpMapping: any; + rtpStreams: any; + traceEventTypes: string[]; + paused: boolean; +}; + +export type ProducerStat = RtpStreamRecvStats; + +/** + * Valid types for 'trace' event. + */ +export type ProducerTraceEventType = + | 'rtp' + | 'keyframe' + | 'nack' + | 'pli' + | 'fir' + | 'sr'; + +/** + * 'trace' event data. + */ +export type ProducerTraceEventData = { + /** + * Trace type. + */ + type: ProducerTraceEventType; + + /** + * Event timestamp. + */ + timestamp: number; + + /** + * Event direction. + */ + direction: 'in' | 'out'; + + /** + * Per type information. + */ + info: any; +}; + +export type ProducerEvents = { + transportclose: []; + score: [ProducerScore[]]; + videoorientationchange: [ProducerVideoOrientation]; + trace: [ProducerTraceEventData]; + listenererror: [string, Error]; + // Private events. + '@close': []; +}; + +export type ProducerObserver = EnhancedEventEmitter; + +export type ProducerObserverEvents = { + close: []; + pause: []; + resume: []; + score: [ProducerScore[]]; + videoorientationchange: [ProducerVideoOrientation]; + trace: [ProducerTraceEventData]; +}; + +export interface Producer + extends EnhancedEventEmitter { + /** + * Producer id. + */ + get id(): string; + + /** + * Whether the Producer is closed. + */ + get closed(): boolean; + + /** + * Media kind. + */ + get kind(): MediaKind; + + /** + * RTP parameters. + */ + get rtpParameters(): RtpParameters; + + /** + * Producer type. + */ + get type(): ProducerType; + + /** + * Consumable RTP parameters. + * + * @private + */ + get consumableRtpParameters(): RtpParameters; + + /** + * Whether the Producer is paused. + */ + get paused(): boolean; + + /** + * Producer score list. + */ + get score(): ProducerScore[]; + + /** + * App custom data. + */ + get appData(): ProducerAppData; + + /** + * App custom data setter. + */ + set appData(appData: ProducerAppData); + + /** + * Observer. + */ + get observer(): ProducerObserver; + + /** + * Close the Producer. + */ + close(): void; + + /** + * Transport was closed. + * + * @private + */ + transportClosed(): void; + + /** + * Dump Producer. + */ + dump(): Promise; + + /** + * Get Producer stats. + */ + getStats(): Promise; + + /** + * Pause the Producer. + */ + pause(): Promise; + + /** + * Resume the Producer. + */ + resume(): Promise; + + /** + * Enable 'trace' event. + */ + enableTraceEvent(types?: ProducerTraceEventType[]): Promise; + + /** + * Send RTP packet (just valid for Producers created on a DirectTransport). + */ + send(rtpPacket: Buffer): void; +} diff --git a/node/src/Router.ts b/node/src/Router.ts index 0c5bff2ddd..ebe6a3f1a7 100644 --- a/node/src/Router.ts +++ b/node/src/Router.ts @@ -1,152 +1,114 @@ -import { v4 as uuidv4 } from 'uuid'; import { Logger } from './Logger'; -import { EnhancedEventEmitter } from './EnhancedEventEmitter'; +import { EnhancedEventEmitter } from './enhancedEvents'; import * as ortc from './ortc'; import { InvalidStateError } from './errors'; -import { Channel } from './Channel'; -import { PayloadChannel } from './PayloadChannel'; -import { Transport, TransportListenIp } from './Transport'; -import { WebRtcTransport, WebRtcTransportOptions } from './WebRtcTransport'; -import { PlainTransport, PlainTransportOptions } from './PlainTransport'; -import { PipeTransport, PipeTransportOptions } from './PipeTransport'; -import { DirectTransport, DirectTransportOptions } from './DirectTransport'; -import { Producer } from './Producer'; -import { Consumer } from './Consumer'; -import { DataProducer } from './DataProducer'; -import { DataConsumer } from './DataConsumer'; -import { RtpObserver } from './RtpObserver'; -import { ActiveSpeakerObserver, ActiveSpeakerObserverOptions } from './ActiveSpeakerObserver'; -import { AudioLevelObserver, AudioLevelObserverOptions } from './AudioLevelObserver'; -import { RtpCapabilities, RtpCodecCapability } from './RtpParameters'; -import { NumSctpStreams } from './SctpParameters'; - -export type RouterOptions = -{ - /** - * Router media codecs. - */ - mediaCodecs?: RtpCodecCapability[]; - - /** - * Custom application data. - */ - appData?: Record; -}; - -export type PipeToRouterOptions = -{ - /** - * The id of the Producer to consume. - */ - producerId?: string; - - /** - * The id of the DataProducer to consume. - */ - dataProducerId?: string; - - /** - * Target Router instance. - */ - router: Router; - - /** - * IP used in the PipeTransport pair. Default '127.0.0.1'. - */ - listenIp?: TransportListenIp | string; - - /** - * Create a SCTP association. Default true. - */ - enableSctp?: boolean; - - /** - * SCTP streams number. - */ - numSctpStreams?: NumSctpStreams; - - /** - * Enable RTX and NACK for RTP retransmission. - */ - enableRtx?: boolean; - - /** - * Enable SRTP. - */ - enableSrtp?: boolean; -}; - -export type PipeToRouterResult = -{ - /** - * The Consumer created in the current Router. - */ - pipeConsumer?: Consumer; - - /** - * The Producer created in the target Router. - */ - pipeProducer?: Producer; - - /** - * The DataConsumer created in the current Router. - */ - pipeDataConsumer?: DataConsumer; - - /** - * The DataProducer created in the target Router. - */ - pipeDataProducer?: DataProducer; -}; - -type PipeTransportPair = -{ - [key: string]: PipeTransport; -}; - -export type RouterEvents = -{ - workerclose: []; - // Private events. - '@close': []; -}; - -export type RouterObserverEvents = -{ - close: []; - newtransport: [Transport]; - newrtpobserver: [RtpObserver]; +import type { Channel } from './Channel'; +import type { + Router, + PipeToRouterOptions, + PipeToRouterResult, + PipeTransportPair, + RouterDump, + RouterEvents, + RouterObserver, + RouterObserverEvents, +} from './RouterTypes'; +import type { + Transport, + TransportListenIp, + TransportProtocol, +} from './TransportTypes'; +import { portRangeToFbs, socketFlagsToFbs } from './Transport'; +import type { + WebRtcTransport, + WebRtcTransportOptions, +} from './WebRtcTransportTypes'; +import { + WebRtcTransportImpl, + parseWebRtcTransportDumpResponse, +} from './WebRtcTransport'; +import type { + PlainTransport, + PlainTransportOptions, +} from './PlainTransportTypes'; +import { + PlainTransportImpl, + parsePlainTransportDumpResponse, +} from './PlainTransport'; +import type { PipeTransport, PipeTransportOptions } from './PipeTransportTypes'; +import { + PipeTransportImpl, + parsePipeTransportDumpResponse, +} from './PipeTransport'; +import type { + DirectTransport, + DirectTransportOptions, +} from './DirectTransportTypes'; +import { + DirectTransportImpl, + parseDirectTransportDumpResponse, +} from './DirectTransport'; +import type { Producer } from './ProducerTypes'; +import type { Consumer } from './ConsumerTypes'; +import type { DataProducer } from './DataProducerTypes'; +import type { DataConsumer } from './DataConsumerTypes'; +import type { RtpObserver } from './RtpObserverTypes'; +import type { + ActiveSpeakerObserver, + ActiveSpeakerObserverOptions, +} from './ActiveSpeakerObserverTypes'; +import { ActiveSpeakerObserverImpl } from './ActiveSpeakerObserver'; +import type { + AudioLevelObserver, + AudioLevelObserverOptions, +} from './AudioLevelObserverTypes'; +import { AudioLevelObserverImpl } from './AudioLevelObserver'; +import type { RtpCapabilities } from './rtpParametersTypes'; +import { cryptoSuiteToFbs } from './srtpParametersFbsUtils'; +import type { AppData } from './types'; +import * as utils from './utils'; +import * as fbsUtils from './fbsUtils'; +import * as FbsActiveSpeakerObserver from './fbs/active-speaker-observer'; +import * as FbsAudioLevelObserver from './fbs/audio-level-observer'; +import * as FbsRequest from './fbs/request'; +import * as FbsWorker from './fbs/worker'; +import * as FbsRouter from './fbs/router'; +import * as FbsTransport from './fbs/transport'; +import { Protocol as FbsTransportProtocol } from './fbs/transport/protocol'; +import * as FbsWebRtcTransport from './fbs/web-rtc-transport'; +import * as FbsPlainTransport from './fbs/plain-transport'; +import * as FbsPipeTransport from './fbs/pipe-transport'; +import * as FbsDirectTransport from './fbs/direct-transport'; +import * as FbsSctpParameters from './fbs/sctp-parameters'; + +export type RouterInternal = { + routerId: string; }; -export type RouterInternal = -{ - routerId: string; +type RouterData = { + rtpCapabilities: RtpCapabilities; }; const logger = new Logger('Router'); -export class Router extends EnhancedEventEmitter +export class RouterImpl + extends EnhancedEventEmitter + implements Router { // Internal data. readonly #internal: RouterInternal; // Router data. - readonly #data: - { - rtpCapabilities: RtpCapabilities; - }; + readonly #data: RouterData; // Channel instance. readonly #channel: Channel; - // PayloadChannel instance. - readonly #payloadChannel: PayloadChannel; - // Closed flag. #closed = false; // Custom app data. - readonly #appData: Record; + #appData: RouterAppData; // Transports map. readonly #transports: Map = new Map(); @@ -162,32 +124,26 @@ export class Router extends EnhancedEventEmitter