diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index 80068c65..00000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,381 +0,0 @@ -version: 2.1 - -orbs: - trivy: fifteen5/trivy-orb@1.0.0 - docker: circleci/docker@2.2.0 - terraform: circleci/terraform@3.2.1 - codecov: codecov/codecov@3.2.5 - -commands: - setup-docker: - steps: - - setup_remote_docker - - - run: - name: Setup docker buildx - command: | - set -eux - - docker context create buildx-build - docker buildx create --use buildx-build - - build-scan-docker: - steps: - - run: - name: Install Dependencies - command: apk add --no-cache curl jq wget tar gzip ca-certificates - - - run: - name: Build Platform API Image - command: docker build -f ./docker/platform/api.Dockerfile -t ghcr.io/scuffletv/platform/api:$(git rev-parse HEAD) . - - - trivy/scan: - args: image ghcr.io/scuffletv/platform/api:$(git rev-parse HEAD) - - - run: - name: Build Platform Website Image - command: docker build -f ./docker/platform/website.Dockerfile -t ghcr.io/scuffletv/platform/website:$(git rev-parse HEAD) . - - - trivy/scan: - args: image ghcr.io/scuffletv/platform/website:$(git rev-parse HEAD) - - - run: - name: Build Video Edge Image - command: docker build -f ./docker/video/edge.Dockerfile -t ghcr.io/scuffletv/video/edge:$(git rev-parse HEAD) . - - - trivy/scan: - args: image ghcr.io/scuffletv/video/edge:$(git rev-parse HEAD) - - - run: - name: Build Video Ingest Image - command: docker build -f ./docker/video/ingest.Dockerfile -t ghcr.io/scuffletv/video/ingest:$(git rev-parse HEAD) . - - - trivy/scan: - args: image ghcr.io/scuffletv/video/ingest:$(git rev-parse HEAD) - - - run: - name: Build Video Transcoder Image - command: docker build -f ./docker/video/transcoder.Dockerfile -t ghcr.io/scuffletv/video/transcoder:$(git rev-parse HEAD) . - - - trivy/scan: - args: image ghcr.io/scuffletv/video/transcoder:$(git rev-parse HEAD) - -jobs: - lint-test: - resource_class: large - - docker: - - image: ghcr.io/scuffletv/build:c8a76333b9cd45d77aceff358322a0e6a5e4023f - environment: - DATABASE_URL: postgres://root@localhost:26257/scuffle - RMQ_URL: amqp://rabbitmq:rabbitmq@localhost:5672/scuffle - REDIS_URL: redis://localhost:6379 - CARGO_INCREMENTAL: 1 - - - image: ghcr.io/scuffletv/cockroach:latest - - - image: bitnami/rabbitmq:latest - environment: - RABBITMQ_USERNAME: rabbitmq - RABBITMQ_PASSWORD: rabbitmq - RABBITMQ_VHOSTS: scuffle - RABBITMQ_DISK_FREE_ABSOLUTE_LIMIT: 50MB - - - image: redis:latest - - steps: - - docker/install-dockerize - - - terraform/install - - - checkout - - - restore_cache: - name: Restore Rust Lint/Test Cache - keys: - - &lint-test-cache lint-test-cache-{{ .Environment.CACHE_VERSION }}-{{ checksum "Cargo.lock" }} - - lint-test-cache-{{ .Environment.CACHE_VERSION }}- - - - run: - name: Install dependencies - command: mask bootstrap --no-db --no-docker --no-env --no-rust - - - run: - name: Wait for services to start - command: | - dockerize -wait tcp://localhost:26257 -timeout 15s - dockerize -wait tcp://localhost:5672 -timeout 15s - dockerize -wait tcp://localhost:6379 -timeout 15s - - - run: - name: Migrate Database - command: mask db migrate - - - run: - name: Cargo Sweep Start - command: | - cargo sweep -s - - - run: - name: Run Lint - command: mask lint - - - run: - name: Run Rust Tests - command: | - cargo llvm-cov nextest \ - --workspace \ - --lcov \ - --output-path lcov.info \ - --ignore-filename-regex "(main\.rs|tests|.*\.nocov\.rs)" \ - --no-fail-fast \ - -E "not test(_v6)" \ - --status-level all \ - --profile ci \ - --tests \ - --exclude video-player \ - --config "profile.dev.debug = 0" - - - codecov/upload: - file: lcov.info - - - run: - name: Install Playwright Dependencies - command: pnpm --filter website exec playwright install-deps - - - run: - name: Run JavaScript Tests - command: mask test --no-rust - - - run: - name: Cargo Sweep Finish - command: | - cargo sweep -f - - - save_cache: - name: Save Test Cache - key: *lint-test-cache - paths: - - target - - build: - resource_class: large - - docker: - - image: ghcr.io/scuffletv/build:c8a76333b9cd45d77aceff358322a0e6a5e4023f - environment: - CARGO_INCREMENTAL: 1 - - steps: - - checkout - - - run: - name: Install dependencies - command: mask bootstrap --no-db --no-docker --no-env --no-js-tests --no-rust - - - restore_cache: - name: Restore Rust Build Cache - keys: - - &build-cache build-cache-{{ .Environment.CACHE_VERSION }}-{{ checksum "Cargo.lock" }} - - build-cache-{{ .Environment.CACHE_VERSION }}- - - - run: - name: Cargo Sweep Start - command: | - cargo sweep -s - - - run: - name: Build Rust - command: mask build rust - - - run: - name: Build JavaScript - command: mask build website --no-gql-prepare - - - run: - name: Cargo Sweep Finish - command: | - cargo sweep -f - - - save_cache: - name: Save Build Cache - key: *build-cache - paths: - - target - - - store_artifacts: - path: target/x86_64-unknown-linux-gnu/release/platform-api - destination: platform-api - - - store_artifacts: - path: target/x86_64-unknown-linux-gnu/release/video-edge - destination: video-edge - - - store_artifacts: - path: target/x86_64-unknown-linux-gnu/release/video-ingest - destination: video-ingest - - - store_artifacts: - path: target/x86_64-unknown-linux-gnu/release/video-transcoder - destination: video-transcoder - - - run: - name: Compress Website Build - command: tar -czf website.tar.gz -C platform/website build --transform s/build/website/ - - - store_artifacts: - path: website.tar.gz - - - run: - name: Compress Player Build - command: tar -czf player.tar.gz -C video/player dist --transform s/dist/player/ - - - store_artifacts: - path: player.tar.gz - - - run: - name: Compress Player Package - command: tar -czf player-pkg.tar.gz -C video/player pkg --transform s/pkg/player-pkg/ - - - store_artifacts: - path: player-pkg.tar.gz - - - run: - name: Compress Player Demo - command: tar -czf player-demo.tar.gz -C video/player demo-dist --transform s/demo-dist/player-demo/ - - - store_artifacts: - path: player-demo.tar.gz - - - persist_to_workspace: - root: . - paths: - - target/x86_64-unknown-linux-gnu/release/platform-api - - target/x86_64-unknown-linux-gnu/release/video-edge - - target/x86_64-unknown-linux-gnu/release/video-ingest - - target/x86_64-unknown-linux-gnu/release/video-transcoder - - platform/website/build - - docker: - resource_class: medium - - docker: - - image: docker:24.0.4-cli-alpine3.18 - - steps: - - run: - name: Install Git - command: apk add --no-cache git - - - checkout - - - setup-docker - - - attach_workspace: - at: . - - - build-scan-docker - - docker-push: - resource_class: medium - - docker: - - image: docker:24.0.4-cli-alpine3.18 - - steps: - - run: - name: Install Git - command: apk add --no-cache git bash - - - checkout - - - setup-docker - - - attach_workspace: - at: . - - - build-scan-docker - - - run: - name: Login to GitHub Container Registry - command: echo $SCUFFLE_GHCR_TOKEN | docker login ghcr.io -u scuffletv --password-stdin - - - run: - name: Push Images - shell: bash -eo pipefail - command: | - if [[ -n $CIRCLE_PR_USERNAME ]]; then - TAG="$CIRCLE_PR_USERNAME-$CIRCLE_PR_NUMBER" - elif [[ $CIRCLE_BRANCH == "main" ]]; then - TAG="latest" - else - TAG="branch-$CIRCLE_BRANCH" - fi - - # Convert to lowercase & replace all invalid characters - TAG=$(echo $TAG | tr '[:upper:]' '[:lower:]') - TAG=$(echo $TAG | tr '/' '-') - TAG=$(echo $TAG | tr -cd '[[:alnum:]]._-') - - echo "Tagging images with $TAG" - - docker tag ghcr.io/scuffletv/platform/api:$(git rev-parse HEAD) ghcr.io/scuffletv/platform/api:$TAG - docker tag ghcr.io/scuffletv/platform/website:$(git rev-parse HEAD) ghcr.io/scuffletv/platform/website:$TAG - docker tag ghcr.io/scuffletv/video/edge:$(git rev-parse HEAD) ghcr.io/scuffletv/video/edge:$TAG - docker tag ghcr.io/scuffletv/video/ingest:$(git rev-parse HEAD) ghcr.io/scuffletv/video/ingest:$TAG - docker tag ghcr.io/scuffletv/video/transcoder:$(git rev-parse HEAD) ghcr.io/scuffletv/video/transcoder:$TAG - - docker push ghcr.io/scuffletv/platform/api:$(git rev-parse HEAD) - docker push ghcr.io/scuffletv/platform/api:$TAG - - docker push ghcr.io/scuffletv/platform/website:$(git rev-parse HEAD) - docker push ghcr.io/scuffletv/platform/website:$TAG - - docker push ghcr.io/scuffletv/video/edge:$(git rev-parse HEAD) - docker push ghcr.io/scuffletv/video/edge:$TAG - - docker push ghcr.io/scuffletv/video/ingest:$(git rev-parse HEAD) - docker push ghcr.io/scuffletv/video/ingest:$TAG - - docker push ghcr.io/scuffletv/video/transcoder:$(git rev-parse HEAD) - docker push ghcr.io/scuffletv/video/transcoder:$TAG - -workflows: - lint-test-build: - jobs: - - hold: - type: approval - - - lint-test: - requires: - - hold - - - build: - requires: - - lint-test - - - docker: - requires: - - build - filters: - branches: - ignore: - - main - - /^feature\/.*$/ - - - docker-push-approve: - type: approval - requires: - - build - filters: - branches: - only: - - main - - /^feature\/.*$/ - - - docker-push: - requires: - - docker-push-approve - - context: - - ghcr diff --git a/.github/workflows/audit.yml b/.github/workflows/audit.yml index c44edac9..538f41d9 100644 --- a/.github/workflows/audit.yml +++ b/.github/workflows/audit.yml @@ -25,7 +25,7 @@ jobs: checks: write steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Install Mask run: | diff --git a/.github/workflows/commitlint.yml b/.github/workflows/commitlint.yml index 8fc88fff..549b3c21 100644 --- a/.github/workflows/commitlint.yml +++ b/.github/workflows/commitlint.yml @@ -8,7 +8,7 @@ jobs: name: Run Commitlint on PR runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 diff --git a/.github/workflows/lint-test-build.yml b/.github/workflows/lint-test-build.yml new file mode 100644 index 00000000..cfdf3925 --- /dev/null +++ b/.github/workflows/lint-test-build.yml @@ -0,0 +1,221 @@ +name: Lint Test Build + +on: + pull_request: + branches: + - main + - feature/** + push: + branches: + - main + - feature/** + workflow_dispatch: + schedule: + - cron: "0 0 * * *" + + +concurrency: + group: lint-test-build-{{ github.head_ref || github.ref }} + +jobs: + pre_job: + name: Pre Job + runs-on: ubuntu-latest + steps: + - id: skip_check + uses: fkirc/skip-duplicate-actions@v3.4.0 + with: + skip_after_successful_duplicate: 'true' + do_not_skip: '["workflow_dispatch", "schedule"]' + + - name: Set output + run: echo "should_skip=${{ steps.skip_check.outputs.should_skip }}" >> $GITHUB_OUTPUT + + lint: + needs: pre_job + if: needs.pre_job.outputs.should_skip != 'true' + runs-on: scuffle-ci-i5-13500 + container: ghcr.io/scuffletv/ci/build:latest + steps: + - uses: actions/checkout@v4 + + - name: Install dependencies + run: pnpm install -r + + - name: Lint + run: mask lint + + test: + needs: pre_job + if: needs.pre_job.outputs.should_skip != 'true' + runs-on: scuffle-ci-i5-13500 + container: ghcr.io/scuffletv/ci/build:latest + services: + cockroach: + image: ghcr.io/scuffletv/ci/cockroach:latest + env: + ARGS: start-single-node --insecure --advertise-addr=0.0.0.0 + ports: + - 26257:26257 + - 8080:8080 + + nats: + image: ghcr.io/scuffletv/ci/nats:latest + env: + ARGS: -js + ports: + - 4222:4222 + - 8222:8222 + - 6222:6222 + + redis: + image: ghcr.io/scuffletv/ci/redis:latest + ports: + - 6379:6379 + + minio: + image: ghcr.io/scuffletv/ci/minio:latest + env: + ARGS: server /export --console-address ":9001" + MINIO_ACCESS_KEY: root + MINIO_SECRET_KEY: scuffle123 + ports: + - 9000:9000 + - 9001:9001 + + env: + PLATFORM_DATABASE_URL: postgres://root@cockroach:26257/scuffle_platform + VIDEO_DATABASE_URL: postgres://root@cockroach:26257/scuffle_video + PLATFORM_DATABASE_URL_TEST: postgres://root@cockroach:26257/scuffle_platform_test + VIDEO_DATABASE_URL_TEST: postgres://root@cockroach:26257/scuffle_video_test + NATS_ADDR: nats:4222 + REDIS_ADDR: redis:6379 + + steps: + - uses: actions/checkout@v4 + + - name: Install dependencies + run: pnpm install -r + + - name: Migrate database + run: mask dev migrate + + - name: Install MinIO client + run: curl -sL https://dl.min.io/client/mc/release/linux-amd64/mc > /usr/local/bin/mc && chmod +x /usr/local/bin/mc + + - name: Setup Minio + run: | + mc config host add myminio http://minio:9000 root scuffle123; + mc rb --force myminio/scuffle-video || true; + mc rb --force myminio/scuffle-image-processor || true; + mc rb --force myminio/scuffle-image-processor-public || true; + mc mb myminio/scuffle-video; + mc mb myminio/scuffle-image-processor; + mc mb myminio/scuffle-image-processor-public; + mc anonymous set download myminio/scuffle-video; + mc anonymous set download myminio/scuffle-image-processor-public; + + - name: Test + run: mask test --ci + + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v3 + with: + token: ${{ secrets.CODECOV_TOKEN }} + files: lcov.info + fail_ci_if_error: true + + build: + needs: [pre_job, lint, test] + if: needs.pre_job.outputs.should_skip != 'true' + runs-on: ubuntu-latest + container: ghcr.io/scuffletv/ci/build:latest + steps: + - uses: actions/checkout@v4 + + # This is needed to prevent git from complaining about the + # `fatal: detected dubious ownership in repository at` error + # when trying to build the website. + - name: Configure git + run: | + git config --global --add safe.directory '*' + + - name: Install dependencies + run: pnpm install -r + + - name: Build Player Demo + run: pnpm -F="@scuffle/player" build + + - name: Upload Artifact (Player Demo) + uses: actions/upload-artifact@v4 + with: + name: video-player-demo + if-no-files-found: error + path: video/player/dist/* + + - name: Build Website + run: pnpm -F="website" build + + - name: Upload Artifact (Website) + uses: actions/upload-artifact@v4 + with: + name: platform-website + if-no-files-found: error + path: platform/website/dist/* + + - name: Build (release fat-lto) + if: github.ref == 'refs/heads/main' + run: cargo build --profile release-fat + + - name: Build (release thin-lto) + if: github.ref != 'refs/heads/main' + run: cargo build --profile release + + - name: Upload Artifact (platform-api) + uses: actions/upload-artifact@v4 + with: + name: platform-api + if-no-files-found: error + path: target/release/platform-api + + - name: Upload Artifact (platform-image-processor) + uses: actions/upload-artifact@v4 + with: + name: image-processor + if-no-files-found: error + path: target/release/platform-image-processor + + - name: Upload Artifact (video-api) + uses: actions/upload-artifact@v4 + with: + name: video-api + if-no-files-found: error + path: target/release/video-api + + - name: Upload Artifact (video-ingest) + uses: actions/upload-artifact@v4 + with: + name: video-ingest + if-no-files-found: error + path: target/release/video-ingest + + - name: Upload Artifact (video-transcoder) + uses: actions/upload-artifact@v4 + with: + name: video-transcoder + if-no-files-found: error + path: target/release/video-transcoder + + - name: Upload Artifact (video-edge) + uses: actions/upload-artifact@v4 + with: + name: video-edge + if-no-files-found: error + path: target/release/video-edge + + - name: Upload Artifact (video-cli) + uses: actions/upload-artifact@v4 + with: + name: video-cli + if-no-files-found: error + path: target/release/video-cli diff --git a/.github/workflows/sync-labels.yml b/.github/workflows/sync-labels.yml index 048a1959..7fc30018 100644 --- a/.github/workflows/sync-labels.yml +++ b/.github/workflows/sync-labels.yml @@ -19,7 +19,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Sync labels uses: crazy-max/ghaction-github-labeler@v3 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d08a6ade..def4165d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -27,7 +27,7 @@ However, this approach necessitates a more intricate build system. When committing to our `main` branch, please adhere to our conventions: - Ensure every commit can be compiled successfully and is formatted. -- Follow the format detailed here: https://karma-runner.github.io/6.4/dev/git-commit-msg.html +- Follow the format detailed [here](https://karma-runner.github.io/6.4/dev/git-commit-msg.html). Example: @@ -96,44 +96,54 @@ To begin, ensure the following are installed: For WSL2 users, setting up systemd is recommended to run services like Docker inside WSL2, bypassing Docker Desktop. Instructions are available [here](https://devblogs.microsoft.com/commandline/systemd-support-is-now-available-in-wsl/). -### For Ubuntu +### Install Components -To install necessary tools on Ubuntu: +A guide for installing everyone on Ubuntu: + +#### Rust ```bash sudo apt-get update -sudo apt-get install -y ca-certificates curl gnupg - -# Installing Rust +sudo apt-get install -y curl gnupg ca-certificates git curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh +``` -# Configuring apt to find nodejs -sudo mkdir -p /etc/apt/keyrings -curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | sudo gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg +Update your environment variables by adding the following to `~/.bashrc` or `~/.zshrc`: + +```bash +source $HOME/.cargo/env +export PATH="$HOME/.cargo/bin:$PATH" +``` + +To install Mask: + +```bash +cargo install mask +``` + +#### NodeJS +```bash NODE_MAJOR=20 -echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_MAJOR.x nodistro main" | sudo tee /etc/apt/sources.list.d/nodesource.list +sudo mkdir -p /etc/apt/keyrings +curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | sudo gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg +echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_MAJOR.x nodistro main" | sudo tee /etc/apt/sources.list.d/nodesource.list sudo apt-get update sudo apt-get install -y nodejs +``` -# install pnpm -curl -fsSL https://get.pnpm.io/install.sh | bash - - -# Running the install for nodejs, make, docker and git -sudo apt-get update -sudo apt-get install -y build-essential pkg-config libssl-dev docker.io git software-properties-common +#### Pnpm -# Add Hashicorp's GPG key -curl -fsSL https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg +```bash +curl -fsSL https://get.pnpm.io/install.sh | bash - +``` -# Add Hashicorp's repository -echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list +#### Docker -# Install Terraform -sudo apt-get update && sudo apt-get install -y terraform +```bash +sudo apt-get install -y docker.io -# Installing docker compose v2 DOCKER_CONFIG=${DOCKER_CONFIG:-$HOME/.docker} mkdir -p $DOCKER_CONFIG/cli-plugins curl -SL https://github.com/docker/compose/releases/download/v2.16.0/docker-compose-linux-x86_64 -o $DOCKER_CONFIG/cli-plugins/docker-compose @@ -147,17 +157,15 @@ sudo groupadd docker sudo usermod -aG docker $(whoami) ``` -Update your environment variables by adding the following to `~/.bashrc` or `~/.zshrc`: +#### C External Libraries ```bash -source $HOME/.cargo/env -export PATH="$HOME/.cargo/bin:$PATH" -``` - -To install Mask: +sudo apt-get update +sudo apt-get install pkg-config software-properties-common meson ninja-build nasm clang cmake make build-essential yasm autoconf automake libtool -```bash -cargo install mask +git clone https://github.com/ScuffleTV/external.git --depth 1 --recurse-submodule /tmp/scuffle-external +sudo /tmp/scuffle-external/build.sh --prefix /usr/local +sudo rm -rf /tmp/scuffle-external ``` ## Setting up the project diff --git a/Cargo.lock b/Cargo.lock index 0388ed17..ab4ad184 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1069,9 +1069,9 @@ dependencies = [ [[package]] name = "crossbeam-channel" -version = "0.5.9" +version = "0.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c3242926edf34aec4ac3a77108ad4854bffaa2e4ddc1824124ce59231302d5" +checksum = "82a9b73a36529d9c47029b9fb3a6f0ea3cc916a261195352ba19e770fc1748b2" dependencies = [ "cfg-if", "crossbeam-utils", @@ -1090,14 +1090,13 @@ dependencies = [ [[package]] name = "crossbeam-epoch" -version = "0.9.16" +version = "0.9.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d2fe95351b870527a5d09bf563ed3c97c0cffb87cf1c78a591bf48bb218d9aa" +checksum = "0e3681d554572a651dda4186cd47240627c3d0114d45a95f6ad27f2f22e7548d" dependencies = [ "autocfg", "cfg-if", "crossbeam-utils", - "memoffset", ] [[package]] @@ -1112,9 +1111,9 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.17" +version = "0.8.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06d96137f14f244c37f989d9fff8f95e6c18b918e71f36638f8c49112e4c78f" +checksum = "c3a430a770ebd84726f584a90ee7f020d28db52c6d02138900f22341f866d39c" dependencies = [ "cfg-if", ] @@ -2610,15 +2609,6 @@ version = "2.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" -[[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" @@ -2986,9 +2976,9 @@ checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "opencv" -version = "0.88.4" +version = "0.88.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82171a36b8642ad249ab90a34dfa00148b0a5a77d4486b4821fe19ae6587a93a" +checksum = "980aa24534b9bcfb03c259779ffcbe422e0395cf45700d6d85657734ea1d5c57" dependencies = [ "cc", "dunce", @@ -3005,9 +2995,9 @@ dependencies = [ [[package]] name = "opencv-binding-generator" -version = "0.81.0" +version = "0.82.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f1941efeef14b642f70c7d8294d12d340a60f03499961e24d4c0be5ca66c7bb" +checksum = "e4ac010a66cd1e1dc457c20d467a16286cc83381307cace05357b414c06740f6" dependencies = [ "clang", "clang-sys", diff --git a/Cargo.toml b/Cargo.toml index 8d80c958..261343e7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,7 +22,8 @@ members = [ resolver = "2" -[profile.release] +[profile.release-fat] +inherits = "release" lto = 'fat' [profile.wasm] diff --git a/binary-helper/src/global.rs b/binary-helper/src/global.rs index 532b53ce..de7436f8 100644 --- a/binary-helper/src/global.rs +++ b/binary-helper/src/global.rs @@ -64,8 +64,11 @@ pub async fn setup_nats( if let Some(tls) = &config.tls { options = options .require_tls(true) - .add_root_certificates((&tls.ca_cert).into()) .add_client_certificate((&tls.cert).into(), (&tls.key).into()); + + if let Some(ca_cert) = &tls.ca_cert { + options = options.add_root_certificates(ca_cert.into()) + } } options @@ -127,17 +130,11 @@ pub async fn setup_redis(config: &RedisConfig) -> anyhow::Result, _>>()?; - let key = rustls_pemfile::pkcs8_private_keys(&mut io::BufReader::new(io::Cursor::new(key))) .next() .ok_or_else(|| anyhow::anyhow!("failed to find private key in redis client private key file"))?? @@ -145,8 +142,14 @@ pub async fn setup_redis(config: &RedisConfig) -> anyhow::Result, _>>()?; - for cert in ca_certs { - cert_store.add(cert).context("failed to add redis ca cert")?; + let mut cert_store = RootCertStore::empty(); + if let Some(ca_cert) = &tls.ca_cert { + let ca_cert = tokio::fs::read(ca_cert).await.context("failed to read redis ca cert")?; + let ca_certs = + rustls_pemfile::certs(&mut io::BufReader::new(io::Cursor::new(ca_cert))).collect::, _>>()?; + for cert in ca_certs { + cert_store.add(cert).context("failed to add redis ca cert")?; + } } Some(fred::types::TlsConfig::from(fred::types::TlsConnector::from( diff --git a/binary-helper/src/lib.rs b/binary-helper/src/lib.rs index fbb6a0b5..3a138178 100644 --- a/binary-helper/src/lib.rs +++ b/binary-helper/src/lib.rs @@ -71,12 +71,17 @@ pub async fn grpc_server(config: &GrpcConfig) -> anyhow::Result { async fn new(ctx: Context, config: C) -> anyhow::Result where diff --git a/common/src/config.rs b/common/src/config.rs index 2f9262df..380f8f8e 100644 --- a/common/src/config.rs +++ b/common/src/config.rs @@ -17,7 +17,7 @@ pub struct TlsConfig { pub key: String, /// The path to the TLS CA certificate - pub ca_cert: String, + pub ca_cert: Option, } #[derive(Debug, Clone, PartialEq, config::Config, serde::Deserialize)] diff --git a/common/src/dataloader/cache.rs b/common/src/dataloader/cache.rs index 73199880..fd8b6248 100644 --- a/common/src/dataloader/cache.rs +++ b/common/src/dataloader/cache.rs @@ -309,7 +309,6 @@ impl, L: Loader, S> Cache for SharedCache { const _: () = { struct DummyLoader; - #[async_trait::async_trait] impl Loader for DummyLoader { type Error = (); type Key = (); diff --git a/common/src/dataloader/mod.rs b/common/src/dataloader/mod.rs index 89e96079..da9c32c8 100644 --- a/common/src/dataloader/mod.rs +++ b/common/src/dataloader/mod.rs @@ -19,13 +19,12 @@ use types::{BatchState, DataLoaderInnerHolder}; use self::types::DataLoaderInner; use self::utils::new_auto_loader; -#[async_trait::async_trait] pub trait Loader: Send + Sync + 'static { type Key: Eq + std::hash::Hash + Clone + Sync + Send; type Value: Clone + Sync + Send; type Error: Clone + Sync + Send; - async fn load(&self, key: &[Self::Key]) -> LoaderOutput; + fn load(&self, key: &[Self::Key]) -> impl std::future::Future> + Send; } pub struct DataLoader, S = RandomState> { diff --git a/common/src/grpc.rs b/common/src/grpc.rs index 38846a66..f7983c30 100644 --- a/common/src/grpc.rs +++ b/common/src/grpc.rs @@ -3,7 +3,6 @@ use std::fmt; use std::net::SocketAddr; use std::time::Duration; -use async_trait::async_trait; use futures::future; use http::Uri; use tokio::sync::mpsc::Sender; @@ -57,7 +56,7 @@ pub struct TlsSettings { /// The client certificate. pub identity: Identity, /// The CA certificate to verify the server. - pub ca_cert: Certificate, + pub ca_cert: Option, } /// Internal struct for controlling the channel. @@ -92,12 +91,16 @@ impl fmt::Display for EndpointType { } } -#[async_trait] +#[allow(async_fn_in_trait)] pub trait DnsResolver: Send + Sync + 'static { - async fn lookup(&self, hostname: &str, record_type: RecordType) -> Result; + fn lookup( + &self, + hostname: &str, + record_type: RecordType, + ) -> impl std::future::Future> + Send; } -#[async_trait] +#[allow(async_fn_in_trait)] impl DnsResolver for TokioAsyncResolver { #[inline(always)] async fn lookup(&self, hostname: &str, record_type: RecordType) -> Result { @@ -249,15 +252,20 @@ impl ChannelController { // If TLS is enabled, we need to add the TLS config to the Endpoint. let endpoint = if self.tls.is_some() { let tls = self.tls.as_ref().unwrap(); - let tls = if let Some(domain) = &tls.domain { + let tls_config = if let Some(domain) = &tls.domain { ClientTlsConfig::new().domain_name(domain) } else { ClientTlsConfig::new() } - .ca_certificate(tls.ca_cert.clone()) .identity(tls.identity.clone()); - match endpoint.tls_config(tls) { + let tls_config = if let Some(ca_cert) = &tls.ca_cert { + tls_config.ca_certificate(ca_cert.clone()) + } else { + tls_config + }; + + match endpoint.tls_config(tls_config) { Ok(endpoint) => endpoint, Err(e) => { tracing::warn!("invalid tls config: {}: {}", addr, e); diff --git a/common/src/tests/dataloader.rs b/common/src/tests/dataloader.rs index 4237553d..c48f1a7c 100644 --- a/common/src/tests/dataloader.rs +++ b/common/src/tests/dataloader.rs @@ -9,7 +9,6 @@ struct LoaderTest { results: DynBoxLoader, } -#[async_trait::async_trait] impl crate::dataloader::Loader for LoaderTest { type Error = (); type Key = u64; diff --git a/common/src/tests/grpc.rs b/common/src/tests/grpc.rs index 7a84cde3..f9652a88 100644 --- a/common/src/tests/grpc.rs +++ b/common/src/tests/grpc.rs @@ -2,7 +2,6 @@ use std::net::{IpAddr, SocketAddr}; use std::sync::Arc; use std::time::Duration; -use async_trait::async_trait; use tokio::sync::Mutex; use tonic::transport::Server; use trust_dns_resolver::error::ResolveError; @@ -22,7 +21,7 @@ struct TestImpl { name: String, } -#[async_trait] +#[async_trait::async_trait] impl pb::test_server::Test for TestImpl { async fn test( &self, @@ -229,7 +228,6 @@ async fn test_dns_resolve_v6() { async fn test_dns_resolve_cname() { struct Dns; - #[async_trait] impl DnsResolver for Dns { async fn lookup(&self, hostname: &str, record_type: RecordType) -> Result { assert_eq!(hostname, "localhost"); @@ -288,7 +286,6 @@ async fn test_headless_dns_resolve() { addresses: Vec, } - #[async_trait] impl DnsResolver for Dns { async fn lookup(&self, hostname: &str, record_type: RecordType) -> Result { assert_eq!(hostname, "localhost"); @@ -390,7 +387,6 @@ async fn test_dns_resolve_change() { addresses: Arc>>, } - #[async_trait] impl DnsResolver for Dns { async fn lookup(&self, hostname: &str, record_type: RecordType) -> Result { assert_eq!(hostname, "localhost"); diff --git a/dev/docker-compose.yml b/dev/docker-compose.yml index 5c059b45..70bc3d27 100644 --- a/dev/docker-compose.yml +++ b/dev/docker-compose.yml @@ -4,14 +4,16 @@ name: "db-scuffle-dev" services: cockroach: - image: ghcr.io/scuffletv/cockroach:latest + image: ghcr.io/scuffletv/ci/cockroach:latest + pull_policy: "always" command: start-single-node --insecure --store=type=mem,size=0.25 --advertise-addr=0.0.0.0 ports: - "5432:26257" - "8080:8080" nats: - image: nats:latest + image: ghcr.io/scuffletv/ci/nats:latest + pull_policy: "always" ports: - "4222:4222" - "8222:8222" @@ -20,7 +22,8 @@ services: - "-js" minio: - image: minio/minio + image: ghcr.io/scuffletv/ci/minio:latest + pull_policy: "always" ports: - "9000:9000" - "9001:9001" @@ -30,7 +33,8 @@ services: command: server /export --console-address ":9001" createbuckets: - image: minio/mc + image: minio/mc:latest + pull_policy: "always" depends_on: - minio entrypoint: > @@ -49,6 +53,7 @@ services: " redis: - image: redis:latest + image: ghcr.io/scuffletv/ci/redis:latest + pull_policy: "always" ports: - "6379:6379" diff --git a/docker/cve.sh b/docker/cve.sh index ff2feb3e..f90dba0d 100755 --- a/docker/cve.sh +++ b/docker/cve.sh @@ -1,13 +1,16 @@ -set -e +set -ex apt-get update apt-get install -y --no-install-recommends \ - libgnutls30 \ - libssl3 \ - libsystemd0 \ - libudev1 \ - tar \ + libc-bin=2.37-0ubuntu2.2 \ + libc6=2.37-0ubuntu2.2 \ + tar=1.34+dfsg-1.2ubuntu0.2 \ ca-certificates +apt-get remove --purge -y --allow-remove-essential \ + login \ + passwd + +apt clean rm -rf /var/lib/apt/lists/* diff --git a/docker/ffmpeg.sh b/docker/ffmpeg.sh new file mode 100755 index 00000000..7c825e22 --- /dev/null +++ b/docker/ffmpeg.sh @@ -0,0 +1,68 @@ +set -ex + +apt-get update +apt-get install -y --no-install-recommends \ + tar \ + make \ + zip \ + unzip \ + curl \ + wget \ + git \ + ssh \ + ca-certificates \ + pkg-config \ + gnupg2 \ + cmake \ + clang-format \ + ninja-build \ + nasm \ + yasm \ + meson \ + libtool \ + autoconf \ + automake \ + build-essential + +git clone https://github.com/ScuffleTV/external.git --depth 1 --recurse-submodule --shallow-submodules /tmp/external +/tmp/external/build.sh --prefix /usr/local --build "x264 x265 svt-av1 libvpx opus dav1d ffmpeg opencv" +ldconfig +rm -rf /tmp/external + +apt-get remove -y --purge \ + make \ + zip \ + unzip \ + curl \ + wget \ + git \ + ssh \ + ca-certificates \ + pkg-config \ + gnupg2 \ + cmake \ + clang-format \ + ninja-build \ + nasm \ + yasm \ + meson \ + libtool \ + autoconf \ + automake \ + build-essential \ + libpython3.11-stdlib \ + libpython3.11-minimal \ + libpython3.11 \ + python3.11 \ + python3.11-minimal \ + g++ \ + g++-12 \ + gcc \ + gcc-12 \ + "*-dev" \ + "*-dev-*" + +apt-get autoremove -y +apt-get clean +rm -rf /var/lib/apt/lists/* +rm /etc/ssh -rf diff --git a/docker/platform/api.Dockerfile b/docker/platform/api.Dockerfile index 6edde951..4af48391 100644 --- a/docker/platform/api.Dockerfile +++ b/docker/platform/api.Dockerfile @@ -1,4 +1,4 @@ -FROM ubuntu:latest +FROM ubuntu:lunar LABEL org.opencontainers.image.source=https://github.com/scuffletv/scuffle LABEL org.opencontainers.image.description="Platform API Container for ScuffleTV" @@ -6,12 +6,15 @@ LABEL org.opencontainers.image.licenses=BSD-4-Clause WORKDIR /app -RUN --mount=type=bind,src=docker/cve.sh,dst=/cve.sh --mount=type=bind,src=target/x86_64-unknown-linux-gnu/release/platform-api,dst=/mount/api /cve.sh && \ - cp /mount/api /app/api && \ - chmod +x /app/api +RUN --mount=type=bind,src=docker/cve.sh,dst=/mount/cve.sh \ + /mount/cve.sh + +RUN --mount=type=bind,src=target/release/platform-api,dst=/mount/platform-api \ + cp /mount/platform-api /app/platform-api && \ + chmod +x /app/platform-api STOPSIGNAL SIGTERM USER 1000 -ENTRYPOINT ["/app/api"] +ENTRYPOINT ["/app/platform-api"] diff --git a/docker/platform/image-processor.Dockerfile b/docker/platform/image-processor.Dockerfile new file mode 100644 index 00000000..0a34d7e9 --- /dev/null +++ b/docker/platform/image-processor.Dockerfile @@ -0,0 +1,23 @@ +FROM ubuntu:lunar + +LABEL org.opencontainers.image.source=https://github.com/scuffletv/scuffle +LABEL org.opencontainers.image.description="Platform Image Processor Container for ScuffleTV" +LABEL org.opencontainers.image.licenses=BSD-4-Clause + +WORKDIR /app + +RUN --mount=type=bind,src=docker/ffmpeg.sh,dst=/mount/ffmpeg.sh \ + /mount/ffmpeg.sh + +RUN --mount=type=bind,src=docker/cve.sh,dst=/mount/cve.sh \ + /mount/cve.sh + +RUN --mount=type=bind,src=target/release/platform-image-processor,dst=/mount/platform-image-processor \ + cp /mount/platform-image-processor /app/platform-image-processor && \ + chmod +x /app/platform-image-processor + +# STOPSIGNAL SIGTERM + +# USER 1000 + +# ENTRYPOINT ["/app/platform-image-processor"] diff --git a/docker/platform/website.Dockerfile b/docker/platform/website.Dockerfile index aa1ba3c2..483c4666 100644 --- a/docker/platform/website.Dockerfile +++ b/docker/platform/website.Dockerfile @@ -1,20 +1,12 @@ -FROM node:alpine +FROM nginx:alpine LABEL org.opencontainers.image.source=https://github.com/scuffletv/scuffle LABEL org.opencontainers.image.description="Platform Website Container for ScuffleTV" LABEL org.opencontainers.image.licenses=BSD-4-Clause -RUN apk add --upgrade libcrypto3 libssl3 --repository=https://dl-cdn.alpinelinux.org/alpine/edge/community - -COPY platform/website/build /app/build -COPY platform/website/entry.js /app/index.js - -RUN echo "{\"type\": \"module\"}" > /app/package.json && chown -R 1000:1000 /app - -WORKDIR /app +COPY docker/platform/website.nginx.conf /etc/nginx/conf.d/default.conf +COPY platform/website/dist /usr/share/nginx/html STOPSIGNAL SIGTERM -USER 1000 - -CMD ["node", "."] +ENTRYPOINT ["nginx", "-g", "daemon off;"] diff --git a/docker/platform/website.nginx.conf b/docker/platform/website.nginx.conf new file mode 100644 index 00000000..1090e9b4 --- /dev/null +++ b/docker/platform/website.nginx.conf @@ -0,0 +1,13 @@ +server { + listen 80; + server_name localhost; + + index index.html; + + access_log off; + + location / { + root /usr/share/nginx/html; + try_files $uri /index.html; + } +} diff --git a/docker/video/api.Dockerfile b/docker/video/api.Dockerfile new file mode 100644 index 00000000..7b4a362d --- /dev/null +++ b/docker/video/api.Dockerfile @@ -0,0 +1,20 @@ +FROM ubuntu:lunar + +LABEL org.opencontainers.image.source=https://github.com/scuffletv/scuffle +LABEL org.opencontainers.image.description="Video API Container for ScuffleTV" +LABEL org.opencontainers.image.licenses=BSD-4-Clause + +WORKDIR /app + +RUN --mount=type=bind,src=docker/cve.sh,dst=/mount/cve.sh \ + /mount/cve.sh + +RUN --mount=type=bind,src=target/release/video-api,dst=/mount/video-api \ + cp /mount/video-api /app/video-api && \ + chmod +x /app/video-api + +STOPSIGNAL SIGTERM + +USER 1000 + +ENTRYPOINT ["/app/video-api"] diff --git a/docker/video/edge.Dockerfile b/docker/video/edge.Dockerfile index 2782a81f..767a6886 100644 --- a/docker/video/edge.Dockerfile +++ b/docker/video/edge.Dockerfile @@ -1,4 +1,4 @@ -FROM ubuntu:latest +FROM ubuntu:lunar LABEL org.opencontainers.image.source=https://github.com/scuffletv/scuffle LABEL org.opencontainers.image.description="Video Edge Container for ScuffleTV" @@ -6,12 +6,15 @@ LABEL org.opencontainers.image.licenses=BSD-4-Clause WORKDIR /app -RUN --mount=type=bind,src=docker/cve.sh,dst=/cve.sh --mount=type=bind,src=target/x86_64-unknown-linux-gnu/release/video-edge,dst=/mount/edge /cve.sh && \ - cp /mount/edge /app/edge && \ - chmod +x /app/edge +RUN --mount=type=bind,src=docker/cve.sh,dst=/mount/cve.sh \ + /mount/cve.sh + +RUN --mount=type=bind,src=target/release/video-edge,dst=/mount/video-edge \ + cp /mount/video-edge /app/video-edge && \ + chmod +x /app/video-edge STOPSIGNAL SIGTERM USER 1000 -ENTRYPOINT ["/app/edge"] +ENTRYPOINT ["/app/video-edge"] diff --git a/docker/video/ingest.Dockerfile b/docker/video/ingest.Dockerfile index f972e21c..b03ab9e8 100644 --- a/docker/video/ingest.Dockerfile +++ b/docker/video/ingest.Dockerfile @@ -1,4 +1,4 @@ -FROM ubuntu:latest +FROM ubuntu:lunar LABEL org.opencontainers.image.source=https://github.com/scuffletv/scuffle LABEL org.opencontainers.image.description="Video Ingest Container for ScuffleTV" @@ -6,12 +6,16 @@ LABEL org.opencontainers.image.licenses=BSD-4-Clause WORKDIR /app -RUN --mount=type=bind,src=docker/cve.sh,dst=/cve.sh --mount=type=bind,src=target/x86_64-unknown-linux-gnu/release/video-ingest,dst=/mount/ingest /cve.sh && \ - cp /mount/ingest /app/ingest && \ - chmod +x /app/ingest +RUN --mount=type=bind,src=docker/cve.sh,dst=/mount/cve.sh \ + /mount/cve.sh + +RUN --mount=type=bind,src=target/release/video-ingest,dst=/mount/video-ingest \ + cp /mount/video-ingest /app/video-ingest && \ + chmod +x /app/video-ingest + STOPSIGNAL SIGTERM USER 1000 -ENTRYPOINT ["/app/ingest"] +ENTRYPOINT ["/app/video-ingest"] diff --git a/docker/video/player-demo.Dockerfile b/docker/video/player-demo.Dockerfile new file mode 100644 index 00000000..587bb7a3 --- /dev/null +++ b/docker/video/player-demo.Dockerfile @@ -0,0 +1,12 @@ +FROM nginx:alpine + +LABEL org.opencontainers.image.source=https://github.com/scuffletv/scuffle +LABEL org.opencontainers.image.description="Video Player Demo Container for ScuffleTV" +LABEL org.opencontainers.image.licenses=BSD-4-Clause + +COPY docker/platform/website.nginx.conf /etc/nginx/conf.d/default.conf +COPY video/player/dist /usr/share/nginx/html + +STOPSIGNAL SIGTERM + +ENTRYPOINT ["nginx", "-g", "daemon off;"] diff --git a/docker/video/player-demo.nginx.conf b/docker/video/player-demo.nginx.conf new file mode 100644 index 00000000..1090e9b4 --- /dev/null +++ b/docker/video/player-demo.nginx.conf @@ -0,0 +1,13 @@ +server { + listen 80; + server_name localhost; + + index index.html; + + access_log off; + + location / { + root /usr/share/nginx/html; + try_files $uri /index.html; + } +} diff --git a/docker/video/transcoder.Dockerfile b/docker/video/transcoder.Dockerfile index 626c6b65..15e97e4e 100644 --- a/docker/video/transcoder.Dockerfile +++ b/docker/video/transcoder.Dockerfile @@ -1,4 +1,4 @@ -FROM ubuntu:latest +FROM ubuntu:lunar LABEL org.opencontainers.image.source=https://github.com/scuffletv/scuffle LABEL org.opencontainers.image.description="Video Transcoder Container for ScuffleTV" @@ -6,12 +6,18 @@ LABEL org.opencontainers.image.licenses=BSD-4-Clause WORKDIR /app -RUN --mount=type=bind,src=docker/cve.sh,dst=/cve.sh --mount=type=bind,src=target/x86_64-unknown-linux-gnu/release/video-transcoder,dst=/mount/transcoder /cve.sh && \ - cp /mount/transcoder /app/transcoder && \ - chmod +x /app/transcoder +RUN --mount=type=bind,src=docker/ffmpeg.sh,dst=/mount/ffmpeg.sh \ + /mount/ffmpeg.sh + +RUN --mount=type=bind,src=docker/cve.sh,dst=/mount/cve.sh \ + /mount/cve.sh + +RUN --mount=type=bind,src=target/release/video-transcoder,dst=/mount/video-transcoder \ + cp /mount/video-transcoder /app/video-transcoder && \ + chmod +x /app/video-transcoder STOPSIGNAL SIGTERM USER 1000 -ENTRYPOINT ["/app/transcoder"] +ENTRYPOINT ["/app/video-transcoder"] diff --git a/maskfile.md b/maskfile.md index 9cefd0a1..41a53c6f 100644 --- a/maskfile.md +++ b/maskfile.md @@ -119,6 +119,10 @@ fi - flags: --html - type: bool - desc: Outputs HTML coverage report +- ci + - flags: --ci + - type: bool + - desc: Runs tests in CI mode ```bash set -e @@ -126,7 +130,14 @@ if [[ "$verbose" == "true" ]]; then set -x fi -cargo llvm-cov nextest --lcov --output-path lcov.info --ignore-filename-regex "(main\.rs|tests|.*\.nocov\.rs)" --workspace --fail-fast --exclude video-player +profile="default" +extra_args=() +if [ "$ci" == "true" ]; then + profile="ci" + extra_args+=(-E 'not test(/_v6/)') +fi + +cargo llvm-cov nextest --lcov --output-path lcov.info --ignore-filename-regex "(main\.rs|tests|.*\.nocov\.rs)" --workspace --fail-fast --exclude video-player --profile $profile "${extra_args[@]}" if [ "$html" == "true" ]; then cargo llvm-cov report --html fi @@ -154,7 +165,9 @@ if [[ "$verbose" == "true" ]]; then fi # We load the .env file -export $(cat .env | xargs) +if [ -f .env ]; then + export $(cat .env | xargs) +fi action="setup" @@ -389,3 +402,58 @@ fi cloc $(git ls-files) ``` + +## docker + +> Builds docker images + +**OPTIONS** + +- version + - flags: --version + - type: string + - desc: Version to use + +```bash +set -e +if [[ "$verbose" == "true" ]]; then + set -x +fi + +if [ "$version" == "" ]; then + version="latest" +fi + +$MASK docker build --file ./docker/platform/api.Dockerfile --tag ghcr.io/scuffletv/platform/api:$version +$MASK docker build --file ./docker/platform/image-processor.Dockerfile --tag ghcr.io/scuffletv/platform/image-processor:$version +$MASK docker build --file ./docker/platform/website.Dockerfile --tag ghcr.io/scuffletv/platform/website:$version + +$MASK docker build --file ./docker/video/api.Dockerfile --tag ghcr.io/scuffletv/video/api:$version +$MASK docker build --file ./docker/video/edge.Dockerfile --tag ghcr.io/scuffletv/video/edge:$version +$MASK docker build --file ./docker/video/ingest.Dockerfile --tag ghcr.io/scuffletv/video/ingest:$version +$MASK docker build --file ./docker/video/player-demo.Dockerfile --tag ghcr.io/scuffletv/video/player-demo:$version +$MASK docker build --file ./docker/video/transcoder.Dockerfile --tag ghcr.io/scuffletv/video/transcoder:$version + +``` + +### build + +**OPTIONS** + +- file + - flags: --file + - type: string + - desc: Dockerfile to use +- tag + - flags: --tag + - type: string + - desc: Tag to use + +```bash +set -e +if [[ "$verbose" == "true" ]]; then + set -x +fi + +docker build -f $file --tag $tag . +``` diff --git a/platform/api/src/database/two_fa_request.rs b/platform/api/src/database/two_fa_request.rs index 6b911739..b086a246 100644 --- a/platform/api/src/database/two_fa_request.rs +++ b/platform/api/src/database/two_fa_request.rs @@ -16,14 +16,13 @@ pub struct TwoFaRequest { pub action: Protobuf, } -#[async_trait::async_trait] +#[allow(async_fn_in_trait)] pub trait TwoFaRequestActionTrait { type Result; async fn execute(self, global: &Arc, user_id: Ulid) -> Self::Result; } -#[async_trait::async_trait] impl TwoFaRequestActionTrait for Login { type Result = sqlx::Result; @@ -70,7 +69,6 @@ impl TwoFaRequestActionTrait for Login { } } -#[async_trait::async_trait] impl TwoFaRequestActionTrait for ChangePassword { type Result = sqlx::Result<()>; diff --git a/platform/api/src/dataloader/category.rs b/platform/api/src/dataloader/category.rs index 81b75b98..4a461bb2 100644 --- a/platform/api/src/dataloader/category.rs +++ b/platform/api/src/dataloader/category.rs @@ -16,7 +16,6 @@ impl CategoryByIdLoader { } } -#[async_trait::async_trait] impl Loader for CategoryByIdLoader { type Error = (); type Key = Ulid; diff --git a/platform/api/src/dataloader/global_state.rs b/platform/api/src/dataloader/global_state.rs index 42c926ec..5282bc21 100644 --- a/platform/api/src/dataloader/global_state.rs +++ b/platform/api/src/dataloader/global_state.rs @@ -15,7 +15,6 @@ impl GlobalStateLoader { } } -#[async_trait::async_trait] impl Loader for GlobalStateLoader { type Error = (); type Key = (); diff --git a/platform/api/src/dataloader/role.rs b/platform/api/src/dataloader/role.rs index 84d42584..7eff9da8 100644 --- a/platform/api/src/dataloader/role.rs +++ b/platform/api/src/dataloader/role.rs @@ -16,7 +16,6 @@ impl RoleByIdLoader { } } -#[async_trait::async_trait] impl Loader for RoleByIdLoader { type Error = (); type Key = Ulid; diff --git a/platform/api/src/dataloader/session.rs b/platform/api/src/dataloader/session.rs index 36dbfbb9..4c95ddcd 100644 --- a/platform/api/src/dataloader/session.rs +++ b/platform/api/src/dataloader/session.rs @@ -16,7 +16,6 @@ impl SessionByIdLoader { } } -#[async_trait::async_trait] impl Loader for SessionByIdLoader { type Error = (); type Key = Ulid; diff --git a/platform/api/src/dataloader/user.rs b/platform/api/src/dataloader/user.rs index 2e0507c2..b59adc2b 100644 --- a/platform/api/src/dataloader/user.rs +++ b/platform/api/src/dataloader/user.rs @@ -16,7 +16,6 @@ impl UserByUsernameLoader { } } -#[async_trait::async_trait] impl Loader for UserByUsernameLoader { type Error = (); type Key = String; @@ -45,7 +44,6 @@ impl UserByIdLoader { } } -#[async_trait::async_trait] impl Loader for UserByIdLoader { type Error = (); type Key = Ulid; diff --git a/platform/api/src/main.rs b/platform/api/src/main.rs index 6a321c7f..95328b5a 100644 --- a/platform/api/src/main.rs +++ b/platform/api/src/main.rs @@ -131,7 +131,6 @@ impl platform_api::global::ApiState for GlobalState { } } -#[async_trait::async_trait] impl binary_helper::Global for GlobalState { async fn new(ctx: Context, config: AppConfig) -> anyhow::Result { let (nats, jetstream) = setup_nats(&config.name, &config.nats).await?; diff --git a/platform/image_processor/Cargo.toml b/platform/image_processor/Cargo.toml index 24e1bb24..c3ea5934 100644 --- a/platform/image_processor/Cargo.toml +++ b/platform/image_processor/Cargo.toml @@ -25,7 +25,7 @@ itertools = "0.12.0" scopeguard = "1.2.0" rgb = "0.8.37" imgref = "1.10.0" -opencv = { version = "0.88.4", default-features = false, features = ["clang-runtime", "imgproc"]} +opencv = { version = "0.88.5", default-features = false, features = ["clang-runtime", "imgproc"] } ffmpeg-next = "6.1.0" libavif-sys = { version = "0.15.0" } libwebp-sys2 = { version = "0.1.9", features = ["1_2", "demux", "mux", "static"] } diff --git a/platform/image_processor/src/main.rs b/platform/image_processor/src/main.rs index c586f022..67ed3dbc 100644 --- a/platform/image_processor/src/main.rs +++ b/platform/image_processor/src/main.rs @@ -52,7 +52,6 @@ impl platform_image_processor::global::ImageProcessorState for GlobalState { } } -#[async_trait::async_trait] impl binary_helper::Global for GlobalState { async fn new(ctx: Context, config: AppConfig) -> anyhow::Result { let db = setup_database(&config.database).await?; diff --git a/platform/image_processor/src/tests/processor/encoder.rs b/platform/image_processor/src/tests/processor/encoder.rs index 63d96152..daf7b298 100644 --- a/platform/image_processor/src/tests/processor/encoder.rs +++ b/platform/image_processor/src/tests/processor/encoder.rs @@ -84,13 +84,7 @@ fn encode(asset_name: &str, backend: DecoderBackend, frontend: EncoderFrontend) #[test] fn encode_test() { - encode("meow.gif", DecoderBackend::Ffmpeg, EncoderFrontend::Gifski); encode("meow.gif", DecoderBackend::Ffmpeg, EncoderFrontend::LibWebp); - encode("meow.gif", DecoderBackend::Ffmpeg, EncoderFrontend::LibAvif); - encode("meow.webp", DecoderBackend::LibWebp, EncoderFrontend::Gifski); - encode("meow.webp", DecoderBackend::LibWebp, EncoderFrontend::LibWebp); encode("meow.webp", DecoderBackend::LibWebp, EncoderFrontend::LibAvif); encode("meow.avif", DecoderBackend::LibAvif, EncoderFrontend::Gifski); - encode("meow.avif", DecoderBackend::LibAvif, EncoderFrontend::LibWebp); - encode("meow.avif", DecoderBackend::LibAvif, EncoderFrontend::LibAvif); } diff --git a/platform/website/.dockerignore b/platform/website/.dockerignore index 94478ebe..63259a55 100644 --- a/platform/website/.dockerignore +++ b/platform/website/.dockerignore @@ -1,6 +1,7 @@ .DS_Store node_modules /build +/dist /.svelte-kit /package .env diff --git a/platform/website/.eslintignore b/platform/website/.eslintignore index ad3444c7..f4fb6e1f 100644 --- a/platform/website/.eslintignore +++ b/platform/website/.eslintignore @@ -1,6 +1,7 @@ .DS_Store node_modules /build +/dist /.svelte-kit /package .env diff --git a/platform/website/.gitignore b/platform/website/.gitignore index a06d5bb8..7f89a741 100644 --- a/platform/website/.gitignore +++ b/platform/website/.gitignore @@ -11,3 +11,4 @@ vite.config.js.timestamp-* vite.config.ts.timestamp-* wasm.d.ts src/gql +/dist diff --git a/platform/website/package.json b/platform/website/package.json index 3c8610b6..a0326782 100644 --- a/platform/website/package.json +++ b/platform/website/package.json @@ -3,9 +3,9 @@ "version": "0.0.1", "private": true, "scripts": { - "dev:pre": "pnpm run codegen", + "dev:pre": "pnpm run codegen && pnpm -F=@scuffle/player run wasm:build:dev", "dev": "pnpm run dev:pre && vite dev", - "build": "pnpm run codegen && NODE_ENV=production vite build", + "build": "pnpm run codegen && pnpm -F=@scuffle/player run wasm:build && NODE_ENV=production vite build", "preview": "vite preview", "test": "playwright test", "test:unit": "vitest", diff --git a/platform/website/svelte.config.js b/platform/website/svelte.config.js index f13d9c58..8a76e723 100644 --- a/platform/website/svelte.config.js +++ b/platform/website/svelte.config.js @@ -8,7 +8,9 @@ const config = { kit: { adapter: adapter({ envPrefix: "SCUF_", - fallback: "fallback.html", + pages: "dist", + assets: "dist", + fallback: "index.html", }), alias: { $: "./src", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c42772be..3e1ac097 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -104,7 +104,7 @@ importers: version: link:../../video/player '@sveltejs/adapter-static': specifier: ^3.0.0 - version: 3.0.0(@sveltejs/kit@2.0.0) + version: 3.0.1(@sveltejs/kit@2.0.0) '@sveltejs/kit': specifier: ^2.0.0 version: 2.0.0(@sveltejs/vite-plugin-svelte@3.0.1)(svelte@4.2.8)(vite@5.0.9) @@ -2327,8 +2327,8 @@ packages: resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} dev: true - /@sveltejs/adapter-static@3.0.0(@sveltejs/kit@2.0.0): - resolution: {integrity: sha512-nNsm8XChhJgQdeVegdXU+EnpznSUlsIjlLJa9SNmvSVN3rcPtB2BAnGuS/EWCtgPLkVcy9V2kW5knronqmF+KQ==} + /@sveltejs/adapter-static@3.0.1(@sveltejs/kit@2.0.0): + resolution: {integrity: sha512-6lMvf7xYEJ+oGeR5L8DFJJrowkefTK6ZgA4JiMqoClMkKq0s6yvsd3FZfCFvX1fQ0tpCD7fkuRVHsnUVgsHyNg==} peerDependencies: '@sveltejs/kit': ^2.0.0 dependencies: diff --git a/proto/scuffle/video/internal/ingest.proto b/proto/scuffle/video/internal/ingest.proto index 333b229a..7dc342d0 100644 --- a/proto/scuffle/video/internal/ingest.proto +++ b/proto/scuffle/video/internal/ingest.proto @@ -44,7 +44,9 @@ message IngestWatchResponse { SHUTDOWN_TRANSCODER = 1; } - enum Ready { READY = 0; } + enum Ready { + READY = 0; + } oneof message { Media media = 1; diff --git a/proto/scuffle/video/v1/types/playback_session.proto b/proto/scuffle/video/v1/types/playback_session.proto index 01e6ceeb..435bbf61 100644 --- a/proto/scuffle/video/v1/types/playback_session.proto +++ b/proto/scuffle/video/v1/types/playback_session.proto @@ -47,17 +47,23 @@ message PlaybackSession { // The origin of the user that created the playback session. optional string origin = 11; - enum Device { UNKNOWN_DEVICE = 0; } + enum Device { + UNKNOWN_DEVICE = 0; + } // The device of the user that created the playback session. Device device = 12; - enum Platform { UNKNOWN_PLATFORM = 0; } + enum Platform { + UNKNOWN_PLATFORM = 0; + } // The platform of the user that created the playback session. Platform platform = 13; - enum Browser { UNKNOWN_BROWSER = 0; } + enum Browser { + UNKNOWN_BROWSER = 0; + } // The browser of the user that created the playback session. Browser browser = 14; diff --git a/proto/scuffle/video/v1/types/recording_lifecycle_policy.proto b/proto/scuffle/video/v1/types/recording_lifecycle_policy.proto index b67d610a..dfd8aae1 100644 --- a/proto/scuffle/video/v1/types/recording_lifecycle_policy.proto +++ b/proto/scuffle/video/v1/types/recording_lifecycle_policy.proto @@ -12,7 +12,9 @@ message RecordingLifecyclePolicy { int32 after_days = 1; // Enum of possible actions to perform after the specified number of days. - enum Action { DELETE = 0; } + enum Action { + DELETE = 0; + } // The action to perform after the specified number of days. Action action = 2; diff --git a/video/api/src/api/access_token/create.rs b/video/api/src/api/access_token/create.rs index 7d7f1587..553abe93 100644 --- a/video/api/src/api/access_token/create.rs +++ b/video/api/src/api/access_token/create.rs @@ -82,7 +82,6 @@ pub fn build_query( Ok(qb) } -#[async_trait::async_trait] impl ApiRequest for tonic::Request { async fn process( &self, diff --git a/video/api/src/api/access_token/delete.rs b/video/api/src/api/access_token/delete.rs index e5727e54..ff31464f 100644 --- a/video/api/src/api/access_token/delete.rs +++ b/video/api/src/api/access_token/delete.rs @@ -19,7 +19,6 @@ impl_request_scopes!( RateLimitResource::AccessTokenDelete ); -#[async_trait::async_trait] impl ApiRequest for tonic::Request { async fn process( &self, diff --git a/video/api/src/api/access_token/get.rs b/video/api/src/api/access_token/get.rs index 11507350..c19fb956 100644 --- a/video/api/src/api/access_token/get.rs +++ b/video/api/src/api/access_token/get.rs @@ -34,7 +34,6 @@ pub fn build_query( Ok(qb) } -#[async_trait::async_trait] impl ApiRequest for tonic::Request { async fn process( &self, diff --git a/video/api/src/api/events/ack.rs b/video/api/src/api/events/ack.rs index 31978d53..16daf767 100644 --- a/video/api/src/api/events/ack.rs +++ b/video/api/src/api/events/ack.rs @@ -22,7 +22,6 @@ impl_request_scopes!( RateLimitResource::EventsSubscribe ); -#[async_trait::async_trait] impl ApiRequest for tonic::Request { async fn process( &self, @@ -70,7 +69,7 @@ impl ApiRequest for tonic::Request { let lease_duration = config.nats_stream_message_lease_duration.as_secs().max(1) as i64; - let _: () = if matches!(ack_kind, AckKind::Progress) { + if matches!(ack_kind, AckKind::Progress) { global.redis().expire(&key, lease_duration) } else { global.redis().del(&key) diff --git a/video/api/src/api/events/fetch.rs b/video/api/src/api/events/fetch.rs index bdd63f5a..dad4f03f 100644 --- a/video/api/src/api/events/fetch.rs +++ b/video/api/src/api/events/fetch.rs @@ -32,7 +32,6 @@ impl_request_scopes!( pub type Stream = Pin> + Send>>; -#[async_trait::async_trait] impl ApiRequest for tonic::Request { async fn process( &self, @@ -147,7 +146,7 @@ impl ApiRequest for tonic::Request { continue; }; - let _: () = global.redis().set(&ack_key(organization_id, id), reply.as_ref(), Some(Expiration::EX(lease_duration)), None, false).await.map_err(|err| { + global.redis().set(&ack_key(organization_id, id), reply.as_ref(), Some(Expiration::EX(lease_duration)), None, false).await.map_err(|err| { tracing::error!(err = %err, "failed to set event id in redis"); tonic::Status::internal("failed to set event id in redis") })?; diff --git a/video/api/src/api/mod.rs b/video/api/src/api/mod.rs index 9cbf2bab..4b0ead57 100644 --- a/video/api/src/api/mod.rs +++ b/video/api/src/api/mod.rs @@ -1,6 +1,6 @@ use std::sync::Arc; -use anyhow::Result; +use anyhow::{Context, Result}; use tokio::select; use tonic::service::interceptor; use tonic::transport::{Certificate, Identity, Server, ServerTlsConfig}; @@ -43,15 +43,21 @@ pub async fn run(global: Arc) -> Result<()> { tracing::info!("API Listening on {}", config.bind_address); let server = if let Some(tls) = &config.tls { - let cert = tokio::fs::read(&tls.cert).await?; - let key = tokio::fs::read(&tls.key).await?; - let ca_cert = tokio::fs::read(&tls.ca_cert).await?; - tracing::info!("API TLS enabled"); - Server::builder().tls_config( - ServerTlsConfig::new() - .identity(Identity::from_pem(cert, key)) - .client_ca_root(Certificate::from_pem(ca_cert)), - )? + let cert = tokio::fs::read(&tls.cert).await.context("Failed to read TLS cert")?; + let key = tokio::fs::read(&tls.key).await.context("Failed to read TLS key")?; + + let ssl = ServerTlsConfig::new().identity(Identity::from_pem(cert, key)); + + let ssl = if let Some(ca_cert) = &tls.ca_cert { + let ca_cert = tokio::fs::read(ca_cert).await.context("Failed to read CA cert")?; + tracing::info!("API TLS enabled with client verification"); + ssl.client_ca_root(Certificate::from_pem(ca_cert)) + } else { + tracing::info!("API TLS enabled"); + ssl + }; + + Server::builder().tls_config(ssl).context("Failed to configure TLS")? } else { tracing::info!("API TLS disabled"); Server::builder() diff --git a/video/api/src/api/playback_key_pair/create.rs b/video/api/src/api/playback_key_pair/create.rs index db973c2b..8925e181 100644 --- a/video/api/src/api/playback_key_pair/create.rs +++ b/video/api/src/api/playback_key_pair/create.rs @@ -64,7 +64,6 @@ pub fn build_query( Ok(qb) } -#[async_trait::async_trait] impl ApiRequest for tonic::Request { async fn process( &self, diff --git a/video/api/src/api/playback_key_pair/delete.rs b/video/api/src/api/playback_key_pair/delete.rs index 56a38cdd..57862799 100644 --- a/video/api/src/api/playback_key_pair/delete.rs +++ b/video/api/src/api/playback_key_pair/delete.rs @@ -19,7 +19,6 @@ impl_request_scopes!( RateLimitResource::PlaybackKeyPairDelete ); -#[async_trait::async_trait] impl ApiRequest for tonic::Request { async fn process( &self, diff --git a/video/api/src/api/playback_key_pair/get.rs b/video/api/src/api/playback_key_pair/get.rs index fdaf9641..c2ce22f9 100644 --- a/video/api/src/api/playback_key_pair/get.rs +++ b/video/api/src/api/playback_key_pair/get.rs @@ -33,7 +33,6 @@ pub fn build_query( Ok(qb) } -#[async_trait::async_trait] impl ApiRequest for tonic::Request { async fn process( &self, diff --git a/video/api/src/api/playback_key_pair/modify.rs b/video/api/src/api/playback_key_pair/modify.rs index 84eb1e3e..3000ea19 100644 --- a/video/api/src/api/playback_key_pair/modify.rs +++ b/video/api/src/api/playback_key_pair/modify.rs @@ -63,7 +63,6 @@ pub fn build_query( Ok(qb) } -#[async_trait::async_trait] impl ApiRequest for tonic::Request { async fn process( &self, diff --git a/video/api/src/api/playback_session/count.rs b/video/api/src/api/playback_session/count.rs index ee1813f2..caef7ac6 100644 --- a/video/api/src/api/playback_session/count.rs +++ b/video/api/src/api/playback_session/count.rs @@ -68,7 +68,6 @@ pub fn build_query<'a>( Ok(qb) } -#[async_trait::async_trait] impl ApiRequest for tonic::Request { async fn process( &self, diff --git a/video/api/src/api/playback_session/get.rs b/video/api/src/api/playback_session/get.rs index 4e0638fd..8e04d20d 100644 --- a/video/api/src/api/playback_session/get.rs +++ b/video/api/src/api/playback_session/get.rs @@ -79,7 +79,6 @@ pub fn build_query<'a>( Ok(qb) } -#[async_trait::async_trait] impl ApiRequest for tonic::Request { async fn process( &self, diff --git a/video/api/src/api/playback_session/revoke.rs b/video/api/src/api/playback_session/revoke.rs index b26b01d3..f9b83ced 100644 --- a/video/api/src/api/playback_session/revoke.rs +++ b/video/api/src/api/playback_session/revoke.rs @@ -19,7 +19,6 @@ impl_request_scopes!( RateLimitResource::PlaybackSessionRevoke ); -#[async_trait::async_trait] impl ApiRequest for tonic::Request { async fn process( &self, diff --git a/video/api/src/api/recording/delete.rs b/video/api/src/api/recording/delete.rs index 98798322..a730c9d0 100644 --- a/video/api/src/api/recording/delete.rs +++ b/video/api/src/api/recording/delete.rs @@ -182,7 +182,6 @@ where Some(()) } -#[async_trait::async_trait] impl ApiRequest for tonic::Request { async fn process( &self, diff --git a/video/api/src/api/recording/get.rs b/video/api/src/api/recording/get.rs index d0ebbe16..cf41ea13 100644 --- a/video/api/src/api/recording/get.rs +++ b/video/api/src/api/recording/get.rs @@ -17,7 +17,6 @@ impl_request_scopes!( RateLimitResource::RecordingGet ); -#[async_trait::async_trait] impl ApiRequest for tonic::Request { async fn process( &self, diff --git a/video/api/src/api/recording/modify.rs b/video/api/src/api/recording/modify.rs index e2e790a9..23d3decb 100644 --- a/video/api/src/api/recording/modify.rs +++ b/video/api/src/api/recording/modify.rs @@ -21,7 +21,6 @@ impl_request_scopes!( RateLimitResource::RecordingModify ); -#[async_trait::async_trait] impl ApiRequest for tonic::Request { async fn process( &self, diff --git a/video/api/src/api/recording_config/create.rs b/video/api/src/api/recording_config/create.rs index 07bb4596..4a971cb8 100644 --- a/video/api/src/api/recording_config/create.rs +++ b/video/api/src/api/recording_config/create.rs @@ -97,7 +97,6 @@ pub async fn build_query( Ok(qb) } -#[async_trait::async_trait] impl ApiRequest for tonic::Request { async fn process( &self, diff --git a/video/api/src/api/recording_config/delete.rs b/video/api/src/api/recording_config/delete.rs index 458baccd..f7a20297 100644 --- a/video/api/src/api/recording_config/delete.rs +++ b/video/api/src/api/recording_config/delete.rs @@ -19,7 +19,6 @@ impl_request_scopes!( RateLimitResource::RecordingConfigDelete ); -#[async_trait::async_trait] impl ApiRequest for tonic::Request { async fn process( &self, diff --git a/video/api/src/api/recording_config/get.rs b/video/api/src/api/recording_config/get.rs index cf074e4c..c3b7fd32 100644 --- a/video/api/src/api/recording_config/get.rs +++ b/video/api/src/api/recording_config/get.rs @@ -33,7 +33,6 @@ pub fn build_query( Ok(qb) } -#[async_trait::async_trait] impl ApiRequest for tonic::Request { async fn process( &self, diff --git a/video/api/src/api/recording_config/modify.rs b/video/api/src/api/recording_config/modify.rs index bac5a1a5..950726fa 100644 --- a/video/api/src/api/recording_config/modify.rs +++ b/video/api/src/api/recording_config/modify.rs @@ -102,7 +102,6 @@ pub async fn build_query<'a, G: ApiGlobal>( Ok(qb) } -#[async_trait::async_trait] impl ApiRequest for tonic::Request { async fn process( &self, diff --git a/video/api/src/api/room/create.rs b/video/api/src/api/room/create.rs index f6e65c85..b517a300 100644 --- a/video/api/src/api/room/create.rs +++ b/video/api/src/api/room/create.rs @@ -101,7 +101,6 @@ pub async fn build_query( Ok(qb) } -#[async_trait::async_trait] impl ApiRequest for tonic::Request { async fn process( &self, diff --git a/video/api/src/api/room/delete.rs b/video/api/src/api/room/delete.rs index e28a555c..e4f23e2a 100644 --- a/video/api/src/api/room/delete.rs +++ b/video/api/src/api/room/delete.rs @@ -19,7 +19,6 @@ impl_request_scopes!( RateLimitResource::RoomDelete ); -#[async_trait::async_trait] impl ApiRequest for tonic::Request { async fn process( &self, diff --git a/video/api/src/api/room/disconnect.rs b/video/api/src/api/room/disconnect.rs index be9270d2..14f9511a 100644 --- a/video/api/src/api/room/disconnect.rs +++ b/video/api/src/api/room/disconnect.rs @@ -19,7 +19,6 @@ impl_request_scopes!( RateLimitResource::RoomDisconnect ); -#[async_trait::async_trait] impl ApiRequest for tonic::Request { async fn process( &self, diff --git a/video/api/src/api/room/get.rs b/video/api/src/api/room/get.rs index d74637a1..e9c94455 100644 --- a/video/api/src/api/room/get.rs +++ b/video/api/src/api/room/get.rs @@ -61,7 +61,6 @@ pub fn build_query( Ok(qb) } -#[async_trait::async_trait] impl ApiRequest for tonic::Request { async fn process( &self, diff --git a/video/api/src/api/room/modify.rs b/video/api/src/api/room/modify.rs index 6e4e71fc..53bf975b 100644 --- a/video/api/src/api/room/modify.rs +++ b/video/api/src/api/room/modify.rs @@ -112,7 +112,6 @@ pub async fn build_query<'a, G: ApiGlobal>( Ok(qb) } -#[async_trait::async_trait] impl ApiRequest for tonic::Request { async fn process( &self, diff --git a/video/api/src/api/room/reset_key.rs b/video/api/src/api/room/reset_key.rs index 08e62472..70d5c3dc 100644 --- a/video/api/src/api/room/reset_key.rs +++ b/video/api/src/api/room/reset_key.rs @@ -26,7 +26,6 @@ struct RoomResetKeyRow { stream_key: String, } -#[async_trait::async_trait] impl ApiRequest for tonic::Request { async fn process( &self, diff --git a/video/api/src/api/s3_bucket/create.rs b/video/api/src/api/s3_bucket/create.rs index 1c09d7e1..4217c2e3 100644 --- a/video/api/src/api/s3_bucket/create.rs +++ b/video/api/src/api/s3_bucket/create.rs @@ -86,7 +86,6 @@ pub fn build_query<'a>( Ok(qb) } -#[async_trait::async_trait] impl ApiRequest for tonic::Request { async fn process( &self, diff --git a/video/api/src/api/s3_bucket/delete.rs b/video/api/src/api/s3_bucket/delete.rs index 4d2e72ae..e86ce310 100644 --- a/video/api/src/api/s3_bucket/delete.rs +++ b/video/api/src/api/s3_bucket/delete.rs @@ -19,7 +19,6 @@ impl_request_scopes!( RateLimitResource::S3BucketDelete ); -#[async_trait::async_trait] impl ApiRequest for tonic::Request { async fn process( &self, diff --git a/video/api/src/api/s3_bucket/get.rs b/video/api/src/api/s3_bucket/get.rs index 5cd35b8f..83ff2286 100644 --- a/video/api/src/api/s3_bucket/get.rs +++ b/video/api/src/api/s3_bucket/get.rs @@ -33,7 +33,6 @@ pub fn build_query( Ok(qb) } -#[async_trait::async_trait] impl ApiRequest for tonic::Request { async fn process( &self, diff --git a/video/api/src/api/s3_bucket/modify.rs b/video/api/src/api/s3_bucket/modify.rs index 42ac09d0..a9f1a353 100644 --- a/video/api/src/api/s3_bucket/modify.rs +++ b/video/api/src/api/s3_bucket/modify.rs @@ -104,7 +104,6 @@ pub fn build_query<'a>( Ok(qb) } -#[async_trait::async_trait] impl ApiRequest for tonic::Request { async fn process( &self, diff --git a/video/api/src/api/transcoding_config/create.rs b/video/api/src/api/transcoding_config/create.rs index 0dd0a8d1..6c673715 100644 --- a/video/api/src/api/transcoding_config/create.rs +++ b/video/api/src/api/transcoding_config/create.rs @@ -66,7 +66,6 @@ pub fn build_query( Ok(qb) } -#[async_trait::async_trait] impl ApiRequest for tonic::Request { async fn process( &self, diff --git a/video/api/src/api/transcoding_config/delete.rs b/video/api/src/api/transcoding_config/delete.rs index d078cc6e..1a04ef58 100644 --- a/video/api/src/api/transcoding_config/delete.rs +++ b/video/api/src/api/transcoding_config/delete.rs @@ -19,7 +19,6 @@ impl_request_scopes!( RateLimitResource::TranscodingConfigDelete ); -#[async_trait::async_trait] impl ApiRequest for tonic::Request { async fn process( &self, diff --git a/video/api/src/api/transcoding_config/get.rs b/video/api/src/api/transcoding_config/get.rs index ec9e9f1b..749f6adb 100644 --- a/video/api/src/api/transcoding_config/get.rs +++ b/video/api/src/api/transcoding_config/get.rs @@ -33,7 +33,6 @@ pub fn build_query( Ok(qb) } -#[async_trait::async_trait] impl ApiRequest for tonic::Request { async fn process( &self, diff --git a/video/api/src/api/transcoding_config/modify.rs b/video/api/src/api/transcoding_config/modify.rs index f5982aa7..d406b072 100644 --- a/video/api/src/api/transcoding_config/modify.rs +++ b/video/api/src/api/transcoding_config/modify.rs @@ -71,7 +71,6 @@ pub fn build_query<'a>( Ok(qb) } -#[async_trait::async_trait] impl ApiRequest for tonic::Request { async fn process( &self, diff --git a/video/api/src/api/utils/mod.rs b/video/api/src/api/utils/mod.rs index 764f4bfc..54faa15b 100644 --- a/video/api/src/api/utils/mod.rs +++ b/video/api/src/api/utils/mod.rs @@ -33,7 +33,7 @@ macro_rules! impl_request_scopes { pub(crate) use impl_request_scopes; -#[async_trait::async_trait] +#[allow(async_fn_in_trait)] pub trait ApiRequest { async fn process(&self, global: &Arc, access_token: &AccessToken) -> tonic::Result>; } diff --git a/video/api/src/api/utils/tags.rs b/video/api/src/api/utils/tags.rs index 119b255b..2ed1ad6d 100644 --- a/video/api/src/api/utils/tags.rs +++ b/video/api/src/api/utils/tags.rs @@ -165,7 +165,6 @@ macro_rules! impl_tag_req { Ok(crate::api::utils::tags::add_tag_query::<<$req as crate::api::utils::TonicRequest>::Table>(&tags.tags, pb::ext::UlidExt::into_ulid(req.id), Some(access_token.organization_id.0))) } - #[async_trait::async_trait] impl crate::api::utils::ApiRequest<$resp> for tonic::Request<$req> { async fn process(&self, global: &std::sync::Arc, access_token: &video_common::database::AccessToken) -> tonic::Result> { let req = self.get_ref(); @@ -205,7 +204,6 @@ macro_rules! impl_untag_req { Ok(crate::api::utils::tags::remove_tag_query::<<$req as crate::api::utils::TonicRequest>::Table>(&req.tags, pb::ext::UlidExt::into_ulid(req.id), Some(access_token.organization_id.0))) } - #[async_trait::async_trait] impl crate::api::utils::ApiRequest<$resp> for tonic::Request<$req> { async fn process(&self, global: &std::sync::Arc, access_token: &video_common::database::AccessToken) -> tonic::Result> { let req = self.get_ref(); diff --git a/video/api/src/dataloaders/access_token.rs b/video/api/src/dataloaders/access_token.rs index f1fedec8..4ca69046 100644 --- a/video/api/src/dataloaders/access_token.rs +++ b/video/api/src/dataloaders/access_token.rs @@ -13,7 +13,6 @@ impl AccessTokenLoader { } } -#[async_trait::async_trait] impl Loader for AccessTokenLoader { type Error = (); type Key = (Ulid, Ulid); diff --git a/video/api/src/dataloaders/recording_state.rs b/video/api/src/dataloaders/recording_state.rs index c6dd4218..53c3ac85 100644 --- a/video/api/src/dataloaders/recording_state.rs +++ b/video/api/src/dataloaders/recording_state.rs @@ -47,7 +47,6 @@ pub struct RecordingRenditionState { pub start_time: f32, } -#[async_trait::async_trait] impl Loader for RecordingStateLoader { type Error = (); type Key = (Ulid, Ulid); diff --git a/video/api/src/dataloaders/room.rs b/video/api/src/dataloaders/room.rs index f614be8e..3bb76f7c 100644 --- a/video/api/src/dataloaders/room.rs +++ b/video/api/src/dataloaders/room.rs @@ -13,7 +13,6 @@ impl RoomLoader { } } -#[async_trait::async_trait] impl Loader for RoomLoader { type Error = (); type Key = (Ulid, Ulid); diff --git a/video/api/src/main.rs b/video/api/src/main.rs index 42dcf228..31d64c9b 100644 --- a/video/api/src/main.rs +++ b/video/api/src/main.rs @@ -72,7 +72,6 @@ impl video_api::global::ApiState for GlobalState { } } -#[async_trait::async_trait] impl binary_helper::Global for GlobalState { async fn new(ctx: Context, config: AppConfig) -> anyhow::Result { let (nats, jetstream) = setup_nats(&config.name, &config.nats).await?; diff --git a/video/assets/avc_aac_long.flv b/video/assets/avc_aac_long.flv new file mode 100644 index 00000000..0e018d07 Binary files /dev/null and b/video/assets/avc_aac_long.flv differ diff --git a/video/cli/src/cli/access_token/create.rs b/video/cli/src/cli/access_token/create.rs index c856855a..b706dfff 100644 --- a/video/cli/src/cli/access_token/create.rs +++ b/video/cli/src/cli/access_token/create.rs @@ -26,7 +26,6 @@ pub struct Create { tags: String, } -#[async_trait::async_trait] impl Invokable for Create { async fn invoke(&self, invoker: &mut Invoker, _: &Cli) -> anyhow::Result<()> { let resp = invoker diff --git a/video/cli/src/cli/access_token/delete.rs b/video/cli/src/cli/access_token/delete.rs index 4c4bd222..8669b4b4 100644 --- a/video/cli/src/cli/access_token/delete.rs +++ b/video/cli/src/cli/access_token/delete.rs @@ -12,7 +12,6 @@ pub struct Delete { ids: Vec, } -#[async_trait::async_trait] impl Invokable for Delete { async fn invoke(&self, invoker: &mut Invoker, _: &Cli) -> anyhow::Result<()> { let resp = invoker diff --git a/video/cli/src/cli/access_token/get.rs b/video/cli/src/cli/access_token/get.rs index af1f0d2c..bfdaa255 100644 --- a/video/cli/src/cli/access_token/get.rs +++ b/video/cli/src/cli/access_token/get.rs @@ -30,7 +30,6 @@ pub struct Get { reverse: bool, } -#[async_trait::async_trait] impl Invokable for Get { async fn invoke(&self, invoker: &mut Invoker, _: &Cli) -> anyhow::Result<()> { let resp = invoker diff --git a/video/cli/src/cli/access_token/mod.rs b/video/cli/src/cli/access_token/mod.rs index cfd55282..68596b08 100644 --- a/video/cli/src/cli/access_token/mod.rs +++ b/video/cli/src/cli/access_token/mod.rs @@ -32,7 +32,6 @@ pub enum Commands { Untag(untag::Untag), } -#[async_trait::async_trait] impl Invokable for Commands { async fn invoke(&self, invoker: &mut Invoker, args: &Cli) -> anyhow::Result<()> { match self { diff --git a/video/cli/src/cli/access_token/tag.rs b/video/cli/src/cli/access_token/tag.rs index e4e7698c..f6925050 100644 --- a/video/cli/src/cli/access_token/tag.rs +++ b/video/cli/src/cli/access_token/tag.rs @@ -18,7 +18,6 @@ pub struct Tag { tags: String, } -#[async_trait::async_trait] impl Invokable for Tag { async fn invoke(&self, invoker: &mut Invoker, _: &Cli) -> anyhow::Result<()> { let resp = invoker diff --git a/video/cli/src/cli/access_token/untag.rs b/video/cli/src/cli/access_token/untag.rs index da33f7f5..0c3ca509 100644 --- a/video/cli/src/cli/access_token/untag.rs +++ b/video/cli/src/cli/access_token/untag.rs @@ -16,7 +16,6 @@ pub struct Untag { tags: Vec, } -#[async_trait::async_trait] impl Invokable for Untag { async fn invoke(&self, invoker: &mut Invoker, _: &Cli) -> anyhow::Result<()> { let resp = invoker diff --git a/video/cli/src/cli/events/ack.rs b/video/cli/src/cli/events/ack.rs index f3e3b384..1eb4a5b0 100644 --- a/video/cli/src/cli/events/ack.rs +++ b/video/cli/src/cli/events/ack.rs @@ -44,7 +44,6 @@ pub struct EventAck { action: String, } -#[async_trait::async_trait] impl Invokable for Ack { async fn invoke(&self, invoker: &mut Invoker, _: &Cli) -> anyhow::Result<()> { invoker diff --git a/video/cli/src/cli/events/fetch.rs b/video/cli/src/cli/events/fetch.rs index 43769597..ade49efc 100644 --- a/video/cli/src/cli/events/fetch.rs +++ b/video/cli/src/cli/events/fetch.rs @@ -70,7 +70,6 @@ struct EventPayload { cause: Option, } -#[async_trait::async_trait] impl Invokable for Fetch { async fn invoke(&self, invoker: &mut Invoker, cli: &Cli) -> anyhow::Result<()> { loop { diff --git a/video/cli/src/cli/events/mod.rs b/video/cli/src/cli/events/mod.rs index 20e712c5..55a785cc 100644 --- a/video/cli/src/cli/events/mod.rs +++ b/video/cli/src/cli/events/mod.rs @@ -12,7 +12,6 @@ pub enum Commands { Ack(ack::Ack), } -#[async_trait::async_trait] impl Invokable for Commands { async fn invoke(&self, invoker: &mut Invoker, args: &Cli) -> anyhow::Result<()> { match self { diff --git a/video/cli/src/cli/mod.rs b/video/cli/src/cli/mod.rs index 347f4206..995ec2b9 100644 --- a/video/cli/src/cli/mod.rs +++ b/video/cli/src/cli/mod.rs @@ -63,7 +63,6 @@ pub struct SubCommand { pub command: T, } -#[async_trait::async_trait] pub trait Invokable { async fn invoke(&self, invoker: &mut Invoker, args: &Cli) -> anyhow::Result<()>; } @@ -101,7 +100,6 @@ pub enum Commands { TranscodingConfig(SubCommand), } -#[async_trait::async_trait] impl Invokable for Commands { async fn invoke(&self, invoker: &mut Invoker, args: &Cli) -> anyhow::Result<()> { match self { diff --git a/video/cli/src/cli/organization/create.rs b/video/cli/src/cli/organization/create.rs index 17aac2ad..5abda338 100644 --- a/video/cli/src/cli/organization/create.rs +++ b/video/cli/src/cli/organization/create.rs @@ -15,7 +15,6 @@ pub struct Create { tags: String, } -#[async_trait::async_trait] impl Invokable for Create { async fn invoke(&self, invoker: &mut Invoker, _: &Cli) -> anyhow::Result<()> { let resp = invoker diff --git a/video/cli/src/cli/organization/delete.rs b/video/cli/src/cli/organization/delete.rs index 8c94d0b6..33441476 100644 --- a/video/cli/src/cli/organization/delete.rs +++ b/video/cli/src/cli/organization/delete.rs @@ -11,7 +11,6 @@ pub struct Delete { ids: Vec, } -#[async_trait::async_trait] impl Invokable for Delete { async fn invoke(&self, invoker: &mut Invoker, _: &Cli) -> anyhow::Result<()> { let resp = invoker.invoke(OrganizationDeleteRequest { ids: self.ids.clone() }).await?; diff --git a/video/cli/src/cli/organization/get.rs b/video/cli/src/cli/organization/get.rs index 2a79429a..2371164f 100644 --- a/video/cli/src/cli/organization/get.rs +++ b/video/cli/src/cli/organization/get.rs @@ -28,7 +28,6 @@ pub struct Get { reverse: bool, } -#[async_trait::async_trait] impl Invokable for Get { async fn invoke(&self, invoker: &mut Invoker, _: &Cli) -> anyhow::Result<()> { let resp = invoker diff --git a/video/cli/src/cli/organization/mod.rs b/video/cli/src/cli/organization/mod.rs index 5b9e60c2..0cc8c223 100644 --- a/video/cli/src/cli/organization/mod.rs +++ b/video/cli/src/cli/organization/mod.rs @@ -28,7 +28,6 @@ pub enum Commands { Untag(untag::Untag), } -#[async_trait::async_trait] impl Invokable for Commands { async fn invoke(&self, invoker: &mut Invoker, args: &Cli) -> anyhow::Result<()> { match self { diff --git a/video/cli/src/cli/organization/modify.rs b/video/cli/src/cli/organization/modify.rs index c1441b04..be065205 100644 --- a/video/cli/src/cli/organization/modify.rs +++ b/video/cli/src/cli/organization/modify.rs @@ -20,7 +20,6 @@ pub struct Modify { tags: Option, } -#[async_trait::async_trait] impl Invokable for Modify { async fn invoke(&self, invoker: &mut Invoker, _: &Cli) -> anyhow::Result<()> { if self.name.is_none() && self.tags.is_none() { diff --git a/video/cli/src/cli/organization/tag.rs b/video/cli/src/cli/organization/tag.rs index 7fe0cf54..7edf224b 100644 --- a/video/cli/src/cli/organization/tag.rs +++ b/video/cli/src/cli/organization/tag.rs @@ -16,7 +16,6 @@ pub struct Tag { tags: String, } -#[async_trait::async_trait] impl Invokable for Tag { async fn invoke(&self, invoker: &mut Invoker, _: &Cli) -> anyhow::Result<()> { let resp = invoker diff --git a/video/cli/src/cli/organization/untag.rs b/video/cli/src/cli/organization/untag.rs index 3445f3e3..29892218 100644 --- a/video/cli/src/cli/organization/untag.rs +++ b/video/cli/src/cli/organization/untag.rs @@ -15,7 +15,6 @@ pub struct Untag { tags: Vec, } -#[async_trait::async_trait] impl Invokable for Untag { async fn invoke(&self, invoker: &mut Invoker, _: &Cli) -> anyhow::Result<()> { let resp = invoker diff --git a/video/cli/src/cli/playback_key_pair/create.rs b/video/cli/src/cli/playback_key_pair/create.rs index c93cc59c..d11926a1 100644 --- a/video/cli/src/cli/playback_key_pair/create.rs +++ b/video/cli/src/cli/playback_key_pair/create.rs @@ -17,7 +17,6 @@ pub struct Create { tags: String, } -#[async_trait::async_trait] impl Invokable for Create { async fn invoke(&self, invoker: &mut Invoker, _: &Cli) -> anyhow::Result<()> { let public_key = if self.public_key == "-" { diff --git a/video/cli/src/cli/playback_key_pair/delete.rs b/video/cli/src/cli/playback_key_pair/delete.rs index 7ebcfa9d..466a6a92 100644 --- a/video/cli/src/cli/playback_key_pair/delete.rs +++ b/video/cli/src/cli/playback_key_pair/delete.rs @@ -12,7 +12,6 @@ pub struct Delete { ids: Vec, } -#[async_trait::async_trait] impl Invokable for Delete { async fn invoke(&self, invoker: &mut Invoker, _: &Cli) -> anyhow::Result<()> { if self.ids.is_empty() { diff --git a/video/cli/src/cli/playback_key_pair/get.rs b/video/cli/src/cli/playback_key_pair/get.rs index b5a92368..101e737f 100644 --- a/video/cli/src/cli/playback_key_pair/get.rs +++ b/video/cli/src/cli/playback_key_pair/get.rs @@ -30,7 +30,6 @@ pub struct Get { reverse: bool, } -#[async_trait::async_trait] impl Invokable for Get { async fn invoke(&self, invoker: &mut Invoker, _: &Cli) -> anyhow::Result<()> { let resp = invoker diff --git a/video/cli/src/cli/playback_key_pair/mod.rs b/video/cli/src/cli/playback_key_pair/mod.rs index bcac294c..41855662 100644 --- a/video/cli/src/cli/playback_key_pair/mod.rs +++ b/video/cli/src/cli/playback_key_pair/mod.rs @@ -35,7 +35,6 @@ pub enum Commands { Untag(untag::Untag), } -#[async_trait::async_trait] impl Invokable for Commands { async fn invoke(&self, invoker: &mut Invoker, args: &Cli) -> anyhow::Result<()> { match self { diff --git a/video/cli/src/cli/playback_key_pair/modify.rs b/video/cli/src/cli/playback_key_pair/modify.rs index 1bc3dfda..5f552150 100644 --- a/video/cli/src/cli/playback_key_pair/modify.rs +++ b/video/cli/src/cli/playback_key_pair/modify.rs @@ -22,7 +22,6 @@ pub struct Modify { tags: Option, } -#[async_trait::async_trait] impl Invokable for Modify { async fn invoke(&self, invoker: &mut Invoker, _: &Cli) -> anyhow::Result<()> { if self.public_key.is_none() && self.tags.is_none() { diff --git a/video/cli/src/cli/playback_key_pair/tag.rs b/video/cli/src/cli/playback_key_pair/tag.rs index 4375f8c7..b5c5a6f8 100644 --- a/video/cli/src/cli/playback_key_pair/tag.rs +++ b/video/cli/src/cli/playback_key_pair/tag.rs @@ -18,7 +18,6 @@ pub struct Tag { tags: String, } -#[async_trait::async_trait] impl Invokable for Tag { async fn invoke(&self, invoker: &mut Invoker, _: &Cli) -> anyhow::Result<()> { let resp = invoker diff --git a/video/cli/src/cli/playback_key_pair/untag.rs b/video/cli/src/cli/playback_key_pair/untag.rs index 3ebb6b3f..84a5fe55 100644 --- a/video/cli/src/cli/playback_key_pair/untag.rs +++ b/video/cli/src/cli/playback_key_pair/untag.rs @@ -15,7 +15,6 @@ pub struct Untag { tags: Vec, } -#[async_trait::async_trait] impl Invokable for Untag { async fn invoke(&self, invoker: &mut Invoker, _: &Cli) -> anyhow::Result<()> { let resp = invoker diff --git a/video/cli/src/cli/playback_session/count.rs b/video/cli/src/cli/playback_session/count.rs index 4507d08e..39a5f027 100644 --- a/video/cli/src/cli/playback_session/count.rs +++ b/video/cli/src/cli/playback_session/count.rs @@ -27,7 +27,6 @@ struct PlaybackSessionCount { deduplicated_count: u64, } -#[async_trait::async_trait] impl Invokable for Count { async fn invoke(&self, invoker: &mut Invoker, _: &Cli) -> anyhow::Result<()> { let resp = invoker diff --git a/video/cli/src/cli/playback_session/get.rs b/video/cli/src/cli/playback_session/get.rs index 8543e3e3..4344f04d 100644 --- a/video/cli/src/cli/playback_session/get.rs +++ b/video/cli/src/cli/playback_session/get.rs @@ -49,7 +49,6 @@ pub struct Get { reverse: bool, } -#[async_trait::async_trait] impl Invokable for Get { async fn invoke(&self, invoker: &mut Invoker, _: &Cli) -> anyhow::Result<()> { let resp = invoker diff --git a/video/cli/src/cli/playback_session/mod.rs b/video/cli/src/cli/playback_session/mod.rs index f437de97..d97132d2 100644 --- a/video/cli/src/cli/playback_session/mod.rs +++ b/video/cli/src/cli/playback_session/mod.rs @@ -22,7 +22,6 @@ pub enum Commands { Count(count::Count), } -#[async_trait::async_trait] impl Invokable for Commands { async fn invoke(&self, invoker: &mut Invoker, args: &Cli) -> anyhow::Result<()> { match self { diff --git a/video/cli/src/cli/playback_session/revoke.rs b/video/cli/src/cli/playback_session/revoke.rs index 06181749..2fb59848 100644 --- a/video/cli/src/cli/playback_session/revoke.rs +++ b/video/cli/src/cli/playback_session/revoke.rs @@ -38,7 +38,6 @@ struct PlaybackSessionRevoke { count: usize, } -#[async_trait::async_trait] impl Invokable for Revoke { async fn invoke(&self, invoker: &mut Invoker, _: &Cli) -> anyhow::Result<()> { let result = invoker diff --git a/video/cli/src/cli/recording/delete.rs b/video/cli/src/cli/recording/delete.rs index d9de7047..1ec4fd2e 100644 --- a/video/cli/src/cli/recording/delete.rs +++ b/video/cli/src/cli/recording/delete.rs @@ -12,7 +12,6 @@ pub struct Delete { ids: Vec, } -#[async_trait::async_trait] impl Invokable for Delete { async fn invoke(&self, invoker: &mut Invoker, _: &Cli) -> anyhow::Result<()> { let resp = invoker diff --git a/video/cli/src/cli/recording/get.rs b/video/cli/src/cli/recording/get.rs index 961079c2..bfea9bc7 100644 --- a/video/cli/src/cli/recording/get.rs +++ b/video/cli/src/cli/recording/get.rs @@ -50,7 +50,6 @@ pub struct Get { reverse: bool, } -#[async_trait::async_trait] impl Invokable for Get { async fn invoke(&self, invoker: &mut Invoker, _: &Cli) -> anyhow::Result<()> { let resp = invoker diff --git a/video/cli/src/cli/recording/mod.rs b/video/cli/src/cli/recording/mod.rs index 38faf2fd..d3758a5b 100644 --- a/video/cli/src/cli/recording/mod.rs +++ b/video/cli/src/cli/recording/mod.rs @@ -37,7 +37,6 @@ pub enum Visibility { Private, } -#[async_trait::async_trait] impl Invokable for Commands { async fn invoke(&self, invoker: &mut Invoker, args: &Cli) -> anyhow::Result<()> { match self { diff --git a/video/cli/src/cli/recording/modify.rs b/video/cli/src/cli/recording/modify.rs index 2ca149a7..8293ac78 100644 --- a/video/cli/src/cli/recording/modify.rs +++ b/video/cli/src/cli/recording/modify.rs @@ -29,7 +29,6 @@ pub struct Modify { tags: Option, } -#[async_trait::async_trait] impl Invokable for Modify { async fn invoke(&self, invoker: &mut Invoker, _: &Cli) -> anyhow::Result<()> { if self.room_id.is_none() && self.recording_config_id.is_none() && self.visibility.is_none() && self.tags.is_none() { diff --git a/video/cli/src/cli/recording/tag.rs b/video/cli/src/cli/recording/tag.rs index 6ceedb5e..b4447aa9 100644 --- a/video/cli/src/cli/recording/tag.rs +++ b/video/cli/src/cli/recording/tag.rs @@ -18,7 +18,6 @@ pub struct Tag { tags: String, } -#[async_trait::async_trait] impl Invokable for Tag { async fn invoke(&self, invoker: &mut Invoker, _: &Cli) -> anyhow::Result<()> { let resp = invoker diff --git a/video/cli/src/cli/recording/untag.rs b/video/cli/src/cli/recording/untag.rs index 5aa7d75f..031cf167 100644 --- a/video/cli/src/cli/recording/untag.rs +++ b/video/cli/src/cli/recording/untag.rs @@ -15,7 +15,6 @@ pub struct Untag { tags: Vec, } -#[async_trait::async_trait] impl Invokable for Untag { async fn invoke(&self, invoker: &mut Invoker, _: &Cli) -> anyhow::Result<()> { let resp = invoker diff --git a/video/cli/src/cli/recording_config/create.rs b/video/cli/src/cli/recording_config/create.rs index d0a9a2e6..9493d7ba 100644 --- a/video/cli/src/cli/recording_config/create.rs +++ b/video/cli/src/cli/recording_config/create.rs @@ -24,7 +24,6 @@ pub struct Create { tags: String, } -#[async_trait::async_trait] impl Invokable for Create { async fn invoke(&self, invoker: &mut Invoker, _: &Cli) -> anyhow::Result<()> { let resp = invoker diff --git a/video/cli/src/cli/recording_config/delete.rs b/video/cli/src/cli/recording_config/delete.rs index 622ae60c..340b3946 100644 --- a/video/cli/src/cli/recording_config/delete.rs +++ b/video/cli/src/cli/recording_config/delete.rs @@ -11,7 +11,6 @@ pub struct Delete { ids: Vec, } -#[async_trait::async_trait] impl Invokable for Delete { async fn invoke(&self, invoker: &mut Invoker, _: &Cli) -> anyhow::Result<()> { if self.ids.is_empty() { diff --git a/video/cli/src/cli/recording_config/get.rs b/video/cli/src/cli/recording_config/get.rs index 98fe2c62..6cd3022f 100644 --- a/video/cli/src/cli/recording_config/get.rs +++ b/video/cli/src/cli/recording_config/get.rs @@ -28,7 +28,6 @@ pub struct Get { reverse: bool, } -#[async_trait::async_trait] impl Invokable for Get { async fn invoke(&self, invoker: &mut Invoker, _: &Cli) -> anyhow::Result<()> { let resp = invoker diff --git a/video/cli/src/cli/recording_config/mod.rs b/video/cli/src/cli/recording_config/mod.rs index 024c93e6..546c785c 100644 --- a/video/cli/src/cli/recording_config/mod.rs +++ b/video/cli/src/cli/recording_config/mod.rs @@ -55,7 +55,6 @@ impl From for i32 { } } -#[async_trait::async_trait] impl Invokable for Commands { async fn invoke(&self, invoker: &mut Invoker, args: &Cli) -> anyhow::Result<()> { match self { diff --git a/video/cli/src/cli/recording_config/modify.rs b/video/cli/src/cli/recording_config/modify.rs index 77a82624..193b4623 100644 --- a/video/cli/src/cli/recording_config/modify.rs +++ b/video/cli/src/cli/recording_config/modify.rs @@ -29,7 +29,6 @@ pub struct Modify { tags: Option, } -#[async_trait::async_trait] impl Invokable for Modify { async fn invoke(&self, invoker: &mut Invoker, _: &Cli) -> anyhow::Result<()> { if self.renditions.is_none() diff --git a/video/cli/src/cli/recording_config/tag.rs b/video/cli/src/cli/recording_config/tag.rs index e2ce8a05..0e2a661e 100644 --- a/video/cli/src/cli/recording_config/tag.rs +++ b/video/cli/src/cli/recording_config/tag.rs @@ -18,7 +18,6 @@ pub struct Tag { tags: String, } -#[async_trait::async_trait] impl Invokable for Tag { async fn invoke(&self, invoker: &mut Invoker, _: &Cli) -> anyhow::Result<()> { let resp = invoker diff --git a/video/cli/src/cli/recording_config/untag.rs b/video/cli/src/cli/recording_config/untag.rs index 19a3642e..cce0c17e 100644 --- a/video/cli/src/cli/recording_config/untag.rs +++ b/video/cli/src/cli/recording_config/untag.rs @@ -15,7 +15,6 @@ pub struct Untag { tags: Vec, } -#[async_trait::async_trait] impl Invokable for Untag { async fn invoke(&self, invoker: &mut Invoker, _: &Cli) -> anyhow::Result<()> { let resp = invoker diff --git a/video/cli/src/cli/room/create.rs b/video/cli/src/cli/room/create.rs index 0c04ed94..76c0c7d2 100644 --- a/video/cli/src/cli/room/create.rs +++ b/video/cli/src/cli/room/create.rs @@ -25,7 +25,6 @@ pub struct Create { tags: String, } -#[async_trait::async_trait] impl Invokable for Create { async fn invoke(&self, invoker: &mut Invoker, _: &Cli) -> anyhow::Result<()> { let resp = invoker diff --git a/video/cli/src/cli/room/delete.rs b/video/cli/src/cli/room/delete.rs index 407bf9e7..8801e21d 100644 --- a/video/cli/src/cli/room/delete.rs +++ b/video/cli/src/cli/room/delete.rs @@ -10,7 +10,6 @@ pub struct Delete { ids: Vec, } -#[async_trait::async_trait] impl Invokable for Delete { async fn invoke(&self, invoker: &mut Invoker, _: &Cli) -> anyhow::Result<()> { let resp = invoker diff --git a/video/cli/src/cli/room/disconnect.rs b/video/cli/src/cli/room/disconnect.rs index a7fd1b49..71655aa5 100644 --- a/video/cli/src/cli/room/disconnect.rs +++ b/video/cli/src/cli/room/disconnect.rs @@ -10,7 +10,6 @@ pub struct Disconnect { ids: Vec, } -#[async_trait::async_trait] impl Invokable for Disconnect { async fn invoke(&self, invoker: &mut Invoker, _: &Cli) -> anyhow::Result<()> { let resp = invoker diff --git a/video/cli/src/cli/room/get.rs b/video/cli/src/cli/room/get.rs index 165b4021..e3e5c0fc 100644 --- a/video/cli/src/cli/room/get.rs +++ b/video/cli/src/cli/room/get.rs @@ -53,7 +53,6 @@ pub enum Status { Ready, } -#[async_trait::async_trait] impl Invokable for Get { async fn invoke(&self, invoker: &mut Invoker, _: &Cli) -> anyhow::Result<()> { let resp = invoker diff --git a/video/cli/src/cli/room/mod.rs b/video/cli/src/cli/room/mod.rs index 62dd407e..f1d2038f 100644 --- a/video/cli/src/cli/room/mod.rs +++ b/video/cli/src/cli/room/mod.rs @@ -48,7 +48,6 @@ pub enum Visibility { Private, } -#[async_trait::async_trait] impl Invokable for Commands { async fn invoke(&self, invoker: &mut Invoker, args: &Cli) -> anyhow::Result<()> { match self { diff --git a/video/cli/src/cli/room/modify.rs b/video/cli/src/cli/room/modify.rs index 8be4be4f..d290c239 100644 --- a/video/cli/src/cli/room/modify.rs +++ b/video/cli/src/cli/room/modify.rs @@ -37,7 +37,6 @@ pub struct Modify { tags: Option, } -#[async_trait::async_trait] impl Invokable for Modify { async fn invoke(&self, invoker: &mut Invoker, _: &Cli) -> anyhow::Result<()> { let resp = invoker diff --git a/video/cli/src/cli/room/reset_key.rs b/video/cli/src/cli/room/reset_key.rs index d786a4f5..b64a229b 100644 --- a/video/cli/src/cli/room/reset_key.rs +++ b/video/cli/src/cli/room/reset_key.rs @@ -24,7 +24,6 @@ struct KeyReset { key: String, } -#[async_trait::async_trait] impl Invokable for ResetKey { async fn invoke(&self, invoker: &mut Invoker, _: &Cli) -> anyhow::Result<()> { if self.ids.is_empty() { diff --git a/video/cli/src/cli/room/tag.rs b/video/cli/src/cli/room/tag.rs index 4ded5ba8..be927825 100644 --- a/video/cli/src/cli/room/tag.rs +++ b/video/cli/src/cli/room/tag.rs @@ -18,7 +18,6 @@ pub struct Tag { tags: String, } -#[async_trait::async_trait] impl Invokable for Tag { async fn invoke(&self, invoker: &mut Invoker, _: &Cli) -> anyhow::Result<()> { let resp = invoker diff --git a/video/cli/src/cli/room/untag.rs b/video/cli/src/cli/room/untag.rs index c2827619..64f364d8 100644 --- a/video/cli/src/cli/room/untag.rs +++ b/video/cli/src/cli/room/untag.rs @@ -15,7 +15,6 @@ pub struct Untag { tags: Vec, } -#[async_trait::async_trait] impl Invokable for Untag { async fn invoke(&self, invoker: &mut Invoker, _: &Cli) -> anyhow::Result<()> { let resp = invoker diff --git a/video/cli/src/cli/s3_bucket/create.rs b/video/cli/src/cli/s3_bucket/create.rs index b86561d2..a8d9b63b 100644 --- a/video/cli/src/cli/s3_bucket/create.rs +++ b/video/cli/src/cli/s3_bucket/create.rs @@ -34,7 +34,6 @@ pub struct Create { tags: String, } -#[async_trait::async_trait] impl Invokable for Create { async fn invoke(&self, invoker: &mut Invoker, _: &Cli) -> anyhow::Result<()> { let resp = invoker diff --git a/video/cli/src/cli/s3_bucket/delete.rs b/video/cli/src/cli/s3_bucket/delete.rs index da16b87c..b049e284 100644 --- a/video/cli/src/cli/s3_bucket/delete.rs +++ b/video/cli/src/cli/s3_bucket/delete.rs @@ -11,7 +11,6 @@ pub struct Delete { ids: Vec, } -#[async_trait::async_trait] impl Invokable for Delete { async fn invoke(&self, invoker: &mut Invoker, _: &Cli) -> anyhow::Result<()> { if self.ids.is_empty() { diff --git a/video/cli/src/cli/s3_bucket/get.rs b/video/cli/src/cli/s3_bucket/get.rs index 28aa623e..a8f61b2b 100644 --- a/video/cli/src/cli/s3_bucket/get.rs +++ b/video/cli/src/cli/s3_bucket/get.rs @@ -28,7 +28,6 @@ pub struct Get { reverse: bool, } -#[async_trait::async_trait] impl Invokable for Get { async fn invoke(&self, invoker: &mut Invoker, _: &Cli) -> anyhow::Result<()> { let resp = invoker diff --git a/video/cli/src/cli/s3_bucket/mod.rs b/video/cli/src/cli/s3_bucket/mod.rs index 2decf2fc..023ad992 100644 --- a/video/cli/src/cli/s3_bucket/mod.rs +++ b/video/cli/src/cli/s3_bucket/mod.rs @@ -32,7 +32,6 @@ pub enum Commands { Untag(untag::Untag), } -#[async_trait::async_trait] impl Invokable for Commands { async fn invoke(&self, invoker: &mut Invoker, args: &Cli) -> anyhow::Result<()> { match self { diff --git a/video/cli/src/cli/s3_bucket/modify.rs b/video/cli/src/cli/s3_bucket/modify.rs index f6592dd4..1307b612 100644 --- a/video/cli/src/cli/s3_bucket/modify.rs +++ b/video/cli/src/cli/s3_bucket/modify.rs @@ -40,7 +40,6 @@ pub struct Modify { tags: Option, } -#[async_trait::async_trait] impl Invokable for Modify { async fn invoke(&self, invoker: &mut Invoker, _: &Cli) -> anyhow::Result<()> { if self.access_key_id.is_none() diff --git a/video/cli/src/cli/s3_bucket/tag.rs b/video/cli/src/cli/s3_bucket/tag.rs index 6b089273..3801cb0c 100644 --- a/video/cli/src/cli/s3_bucket/tag.rs +++ b/video/cli/src/cli/s3_bucket/tag.rs @@ -18,7 +18,6 @@ pub struct Tag { tags: String, } -#[async_trait::async_trait] impl Invokable for Tag { async fn invoke(&self, invoker: &mut Invoker, _: &Cli) -> anyhow::Result<()> { let resp = invoker diff --git a/video/cli/src/cli/s3_bucket/untag.rs b/video/cli/src/cli/s3_bucket/untag.rs index 4d3df629..02153745 100644 --- a/video/cli/src/cli/s3_bucket/untag.rs +++ b/video/cli/src/cli/s3_bucket/untag.rs @@ -15,7 +15,6 @@ pub struct Untag { tags: Vec, } -#[async_trait::async_trait] impl Invokable for Untag { async fn invoke(&self, invoker: &mut Invoker, _: &Cli) -> anyhow::Result<()> { let resp = invoker diff --git a/video/cli/src/cli/transcoding_config/create.rs b/video/cli/src/cli/transcoding_config/create.rs index c56ce546..acc92c7e 100644 --- a/video/cli/src/cli/transcoding_config/create.rs +++ b/video/cli/src/cli/transcoding_config/create.rs @@ -15,7 +15,6 @@ pub struct Create { tags: String, } -#[async_trait::async_trait] impl Invokable for Create { async fn invoke(&self, invoker: &mut Invoker, _: &Cli) -> anyhow::Result<()> { let resp = invoker diff --git a/video/cli/src/cli/transcoding_config/delete.rs b/video/cli/src/cli/transcoding_config/delete.rs index 146f736b..b50232d8 100644 --- a/video/cli/src/cli/transcoding_config/delete.rs +++ b/video/cli/src/cli/transcoding_config/delete.rs @@ -11,7 +11,6 @@ pub struct Delete { ids: Vec, } -#[async_trait::async_trait] impl Invokable for Delete { async fn invoke(&self, invoker: &mut Invoker, _: &Cli) -> anyhow::Result<()> { if self.ids.is_empty() { diff --git a/video/cli/src/cli/transcoding_config/get.rs b/video/cli/src/cli/transcoding_config/get.rs index 5ba339ce..6d20c704 100644 --- a/video/cli/src/cli/transcoding_config/get.rs +++ b/video/cli/src/cli/transcoding_config/get.rs @@ -27,7 +27,6 @@ pub struct Get { reverse: bool, } -#[async_trait::async_trait] impl Invokable for Get { async fn invoke(&self, invoker: &mut Invoker, _: &Cli) -> anyhow::Result<()> { let resp = invoker diff --git a/video/cli/src/cli/transcoding_config/mod.rs b/video/cli/src/cli/transcoding_config/mod.rs index eb50f6c0..c4b858ef 100644 --- a/video/cli/src/cli/transcoding_config/mod.rs +++ b/video/cli/src/cli/transcoding_config/mod.rs @@ -35,7 +35,6 @@ pub enum Commands { pub use super::recording_config::Rendition; -#[async_trait::async_trait] impl Invokable for Commands { async fn invoke(&self, invoker: &mut Invoker, args: &Cli) -> anyhow::Result<()> { match self { diff --git a/video/cli/src/cli/transcoding_config/modify.rs b/video/cli/src/cli/transcoding_config/modify.rs index 4efd62e7..2cf50eb0 100644 --- a/video/cli/src/cli/transcoding_config/modify.rs +++ b/video/cli/src/cli/transcoding_config/modify.rs @@ -20,7 +20,6 @@ pub struct Modify { tags: Option, } -#[async_trait::async_trait] impl Invokable for Modify { async fn invoke(&self, invoker: &mut Invoker, _: &Cli) -> anyhow::Result<()> { if self.renditions.is_none() && self.tags.is_none() { diff --git a/video/cli/src/cli/transcoding_config/tag.rs b/video/cli/src/cli/transcoding_config/tag.rs index c022b813..f93d6c8d 100644 --- a/video/cli/src/cli/transcoding_config/tag.rs +++ b/video/cli/src/cli/transcoding_config/tag.rs @@ -18,7 +18,6 @@ pub struct Tag { tags: String, } -#[async_trait::async_trait] impl Invokable for Tag { async fn invoke(&self, invoker: &mut Invoker, _: &Cli) -> anyhow::Result<()> { let resp = invoker diff --git a/video/cli/src/cli/transcoding_config/untag.rs b/video/cli/src/cli/transcoding_config/untag.rs index 7e3489b2..7a78a8d8 100644 --- a/video/cli/src/cli/transcoding_config/untag.rs +++ b/video/cli/src/cli/transcoding_config/untag.rs @@ -15,7 +15,6 @@ pub struct Untag { tags: Vec, } -#[async_trait::async_trait] impl Invokable for Untag { async fn invoke(&self, invoker: &mut Invoker, _: &Cli) -> anyhow::Result<()> { let resp = invoker diff --git a/video/cli/src/invoker/request.rs b/video/cli/src/invoker/request.rs index a4ab5d3d..22679954 100644 --- a/video/cli/src/invoker/request.rs +++ b/video/cli/src/invoker/request.rs @@ -1,4 +1,3 @@ -#[async_trait::async_trait] pub trait RequestHandler { type Response; @@ -8,7 +7,6 @@ pub trait RequestHandler { macro_rules! impl_request { ($e:ident; $(|$self:ident, $req:ident: $req_ty:ty| -> $resp_ty:ty $action:block),* $(,)*) => { $( - #[async_trait::async_trait] impl crate::invoker::request::RequestHandler<$req_ty> for $e { type Response = $resp_ty; diff --git a/video/common/src/ext.rs b/video/common/src/ext.rs index f80cd552..53616875 100644 --- a/video/common/src/ext.rs +++ b/video/common/src/ext.rs @@ -1,7 +1,6 @@ -use async_trait::async_trait; use tokio::io::AsyncReadExt as _; -#[async_trait] +#[allow(async_fn_in_trait)] pub trait AsyncReadExt: tokio::io::AsyncRead + Unpin { async fn read_all(&mut self) -> std::io::Result> { let mut buf = Vec::new(); diff --git a/video/edge/src/edge/stream/mod.rs b/video/edge/src/edge/stream/mod.rs index 9d5b7ab5..0670e44c 100644 --- a/video/edge/src/edge/stream/mod.rs +++ b/video/edge/src/edge/stream/mod.rs @@ -675,7 +675,7 @@ async fn room_screenshot(req: Request) -> Result for GlobalState { async fn new(ctx: Context, config: AppConfig) -> anyhow::Result { let (nats, jetstream) = setup_nats(&config.name, &config.nats).await?; @@ -72,7 +71,8 @@ impl binary_helper::Global for GlobalState { Ok(metadata_store) => metadata_store, Err(err) => { tracing::warn!("failed to get metadata kv store: {}", err); - let metadata_store = jetstream + + jetstream .create_key_value(async_nats::jetstream::kv::Config { bucket: config.extra.edge.metadata_kv_store.clone(), max_age: Duration::from_secs(60), // 1 minutes max age @@ -80,9 +80,7 @@ impl binary_helper::Global for GlobalState { ..Default::default() }) .await - .context("failed to create metadata kv store")?; - - metadata_store + .context("failed to create metadata kv store")? } }; @@ -90,7 +88,8 @@ impl binary_helper::Global for GlobalState { Ok(media_store) => media_store, Err(err) => { tracing::warn!("failed to get media object store: {}", err); - let media_store = jetstream + + jetstream .create_object_store(async_nats::jetstream::object_store::Config { bucket: config.extra.edge.media_ob_store.clone(), max_age: Duration::from_secs(60), // 1 minutes max age @@ -98,9 +97,7 @@ impl binary_helper::Global for GlobalState { ..Default::default() }) .await - .context("failed to create media object store")?; - - media_store + .context("failed to create media object store")? } }; diff --git a/video/ingest/src/config.rs b/video/ingest/src/config.rs index c4d9106d..067f0aed 100644 --- a/video/ingest/src/config.rs +++ b/video/ingest/src/config.rs @@ -61,7 +61,7 @@ impl Default for IngestConfig { events_subject: "events".to_string(), bitrate_update_interval: Duration::from_secs(5), max_bitrate: 12000 * 1024, - max_bytes_between_keyframes: 12000 * 1024 * 5 / 8, + max_bytes_between_keyframes: 5 * 12000 * 1024 / 8, max_time_between_keyframes: Duration::from_secs(10), transcoder_timeout: Duration::from_secs(60), rtmp: Default::default(), diff --git a/video/ingest/src/ingest/connection.rs b/video/ingest/src/ingest/connection.rs index a051154a..065efc61 100644 --- a/video/ingest/src/ingest/connection.rs +++ b/video/ingest/src/ingest/connection.rs @@ -331,6 +331,8 @@ impl Connection { Ok(Data::Closed(c)) => { clean_shutdown = c; + tracing::debug!("session closed: {c}"); + false } } @@ -752,6 +754,13 @@ impl Connection { config.max_bytes_between_keyframes, )); + tracing::debug!( + "keyframe bitrate distance exceeded: {:?} - {} > {}", + Instant::now() - self.last_keyframe, + self.bytes_tracker.since_keyframe(), + config.max_bytes_between_keyframes + ); + return false; } @@ -761,6 +770,12 @@ impl Connection { config.max_bitrate, )); + tracing::debug!( + "bitrate limit exceeded: {} > {}", + self.bytes_tracker.total() * 8 / config.bitrate_update_interval.as_secs(), + config.max_bitrate + ); + return false; } @@ -832,6 +847,8 @@ impl Connection { if bitrate >= config.max_bitrate { self.error = Some(IngestError::BitrateLimit(bitrate, config.max_bitrate)); + tracing::debug!("bitrate limit exceeded: {} > {}", bitrate, config.max_bitrate); + return false; } @@ -850,6 +867,28 @@ impl Connection { } pub async fn on_media_segment(&mut self, global: &Arc, segment: MediaSegment) -> bool { + let config = global.config::(); + + if Instant::now() - self.last_keyframe >= config.max_time_between_keyframes { + self.error = Some(IngestError::KeyframeTimeLimit(config.max_time_between_keyframes.as_secs())); + + tracing::debug!( + "keyframe time limit exceeded: {:?} > {:?}", + Instant::now() - self.last_keyframe, + config.max_time_between_keyframes + ); + + return false; + } + + if Instant::now() - self.last_transcoder_publish >= config.transcoder_timeout { + tracing::error!("no transcoder available to publish to"); + + self.error = Some(IngestError::NoTranscoderAvailable); + + return false; + } + if segment.keyframe { self.last_keyframe = Instant::now(); @@ -954,22 +993,6 @@ impl Connection { } } - let config = global.config::(); - - if Instant::now() - self.last_keyframe >= config.max_time_between_keyframes { - self.error = Some(IngestError::KeyframeTimeLimit(config.max_time_between_keyframes.as_secs())); - - return false; - } - - if Instant::now() - self.last_transcoder_publish >= config.transcoder_timeout { - tracing::error!("no transcoder available to publish to"); - - self.error = Some(IngestError::NoTranscoderAvailable); - - return false; - } - if segment.keyframe && segment.ty == transmuxer::MediaType::Video { self.fragment_list.clear(); self.fragment_list.push(segment); @@ -987,6 +1010,7 @@ impl Connection { if !self.send_update(Update { bitrate: bitrate as i32 }) { self.error = Some(IngestError::FailedToUpdateBitrate); + tracing::error!("failed to send bitrate update"); false } else { true diff --git a/video/ingest/src/main.rs b/video/ingest/src/main.rs index 03de651b..a68676ec 100644 --- a/video/ingest/src/main.rs +++ b/video/ingest/src/main.rs @@ -51,7 +51,6 @@ impl video_ingest::global::IngestState for GlobalState { } } -#[async_trait::async_trait] impl binary_helper::Global for GlobalState { async fn new(ctx: Context, mut config: AppConfig) -> anyhow::Result { let (nats, jetstream) = setup_nats(&config.name, &config.nats).await?; diff --git a/video/ingest/src/tests/ingest.rs b/video/ingest/src/tests/ingest.rs index 66025dfe..a0cea71d 100644 --- a/video/ingest/src/tests/ingest.rs +++ b/video/ingest/src/tests/ingest.rs @@ -166,7 +166,7 @@ impl TestState { async fn setup_with_tls(tls_dir: &Path) -> Self { Self::setup_new(Some(TlsConfig { cert: tls_dir.join("server.crt").to_str().unwrap().to_string(), - ca_cert: tls_dir.join("ca.crt").to_str().unwrap().to_string(), + ca_cert: Some(tls_dir.join("ca.crt").to_str().unwrap().to_string()), key: tls_dir.join("server.key").to_str().unwrap().to_string(), domain: Some("localhost".to_string()), })) diff --git a/video/lib/rtmp/Cargo.toml b/video/lib/rtmp/Cargo.toml index b3200003..5555e0c1 100644 --- a/video/lib/rtmp/Cargo.toml +++ b/video/lib/rtmp/Cargo.toml @@ -22,8 +22,8 @@ bytesio = { workspace = true, features = ["default"] } flv = { workspace = true } h264 = { workspace = true } amf0 = { workspace = true } +common = { workspace = true } [dev-dependencies] tokio = { version = "1.35", features = ["full"] } serde_json = "1.0" -common = { workspace = true } diff --git a/video/lib/rtmp/src/session/server_session.rs b/video/lib/rtmp/src/session/server_session.rs index 68fc0e1b..50520570 100644 --- a/video/lib/rtmp/src/session/server_session.rs +++ b/video/lib/rtmp/src/session/server_session.rs @@ -6,6 +6,7 @@ use bytes::Bytes; use bytesio::bytes_writer::BytesWriter; use bytesio::bytesio::{AsyncReadWrite, BytesIO}; use bytesio::bytesio_errors::BytesIOError; +use common::prelude::FutureTimeout; use tokio::sync::oneshot; use super::define::RtmpCommand; @@ -245,7 +246,11 @@ impl Session { return Err(SessionError::UnknownStreamID(stream_id)); }; - if self.data_producer.send(data).await.is_err() { + if matches!( + self.data_producer.send(data).timeout(Duration::from_secs(2)).await, + Err(_) | Ok(Err(_)) + ) { + tracing::debug!("Publisher dropped"); return Err(SessionError::PublisherDropped); } diff --git a/video/lib/transmuxer/src/codecs/avc.rs b/video/lib/transmuxer/src/codecs/avc.rs index 181953f7..e6272969 100644 --- a/video/lib/transmuxer/src/codecs/avc.rs +++ b/video/lib/transmuxer/src/codecs/avc.rs @@ -39,14 +39,12 @@ pub fn stsd_entry(config: AVCDecoderConfigurationRecord) -> Result<(DynBox, Sps) pub fn trun_sample( frame_type: FrameType, - timestamp: u32, - last_video_timestamp: u32, composition_time: u32, duration: u32, data: &Bytes, ) -> Result { Ok(TrunSample { - composition_time_offset: Some((timestamp + composition_time - last_video_timestamp) as i64), + composition_time_offset: Some(composition_time as i64), duration: Some(duration), flags: Some(TrunSampleFlag { reserved: 0, diff --git a/video/lib/transmuxer/src/codecs/hevc.rs b/video/lib/transmuxer/src/codecs/hevc.rs index cdfb9b85..ca4d8594 100644 --- a/video/lib/transmuxer/src/codecs/hevc.rs +++ b/video/lib/transmuxer/src/codecs/hevc.rs @@ -44,14 +44,12 @@ pub fn stsd_entry(config: HEVCDecoderConfigurationRecord) -> Result<(DynBox, Sps pub fn trun_sample( frame_type: FrameType, - timestamp: u32, - last_video_timestamp: u32, composition_time: i32, duration: u32, data: &Bytes, ) -> Result { Ok(TrunSample { - composition_time_offset: Some(timestamp as i64 + composition_time as i64 - last_video_timestamp as i64), + composition_time_offset: Some(composition_time as i64), duration: Some(duration), flags: Some(TrunSampleFlag { reserved: 0, diff --git a/video/lib/transmuxer/src/lib.rs b/video/lib/transmuxer/src/lib.rs index 3d7ecf2c..c10208d4 100644 --- a/video/lib/transmuxer/src/lib.rs +++ b/video/lib/transmuxer/src/lib.rs @@ -177,14 +177,9 @@ impl Transmuxer { frame_type, data: FlvTagVideoData::Avc(AvcPacket::Nalu { composition_time, data }), } => { - let sample = codecs::avc::trun_sample( - frame_type, - tag.timestamp, - self.last_video_timestamp, - composition_time, - duration, - &data, - )?; + let composition_time = ((composition_time as f64 * video_settings.framerate) / 1000.0).floor() * 1000.0; + + let sample = codecs::avc::trun_sample(frame_type, composition_time as u32, duration, &data)?; trun_sample = sample; total_duration = duration; @@ -208,14 +203,10 @@ impl Transmuxer { frame_type, data: FlvTagVideoData::Enhanced(EnhancedPacket::Hevc(HevcPacket::Nalu { composition_time, data })), } => { - let sample = codecs::hevc::trun_sample( - frame_type, - tag.timestamp, - self.last_video_timestamp, - composition_time.unwrap_or_default(), - duration, - &data, - )?; + let composition_time = + ((composition_time.unwrap_or_default() as f64 * video_settings.framerate) / 1000.0).floor() * 1000.0; + + let sample = codecs::hevc::trun_sample(frame_type, composition_time as i32, duration, &data)?; trun_sample = sample; total_duration = duration; diff --git a/video/lib/transmuxer/src/tests/mod.rs b/video/lib/transmuxer/src/tests/mod.rs index 15e206c1..431e8424 100644 --- a/video/lib/transmuxer/src/tests/mod.rs +++ b/video/lib/transmuxer/src/tests/mod.rs @@ -331,7 +331,7 @@ fn test_transmuxer_hevc_aac() { let json: serde_json::Value = serde_json::from_slice(&output.stdout).unwrap(); assert_eq!(json["format"]["format_name"], "mov,mp4,m4a,3gp,3g2,mj2"); - assert_eq!(json["format"]["duration"], "3.083423"); + assert_eq!(json["format"]["duration"], "3.090277"); assert_eq!(json["format"]["tags"]["major_brand"], "iso5"); assert_eq!(json["format"]["tags"]["minor_version"], "512"); assert_eq!(json["format"]["tags"]["compatible_brands"], "iso5iso6hev1mp41"); diff --git a/video/player/demo/index.ts b/video/player/demo/index.ts index c542f1ee..d9510d9b 100644 --- a/video/player/demo/index.ts +++ b/video/player/demo/index.ts @@ -22,22 +22,17 @@ function saveBandwidthEstimate(bandwidth: number) { ); } -const player = new Player(video, { - organization_id: "01HJES00BPBT13KS5BQM2V7EWS", - server: "https://troy-edge.scuffle.tv", - abr_default_bandwidth: bandwidthEstimate(), - logging_level: "info", -}); - declare global { interface Window { SCUFFLE_PLAYER: Player; } } -window.SCUFFLE_PLAYER = player; +let player: Player; -console.log(player); +const organizationId = document.getElementById("organization-id") as HTMLInputElement; +const edgeEndpoint = document.getElementById("edge-endpoint") as HTMLInputElement; +const edgeToken = document.getElementById("edge-token") as HTMLInputElement; const bufferSize = document.getElementById("buffer-size") as HTMLElement; const videoTime = document.getElementById("video-time") as HTMLElement; @@ -59,15 +54,21 @@ const jumpToLive = document.getElementById("jump-to-live") as HTMLButtonElement; const destroy = document.getElementById("destroy") as HTMLButtonElement; const copyShareLink = document.getElementById("copy-share") as HTMLButtonElement; -copyShareLink.addEventListener("click", () => { - const url = new URL(window.location.href); - +function urlParams(includeTime = true) { const params = new URLSearchParams(); params.set("id", roomId.value); params.set("type", player.roomId ? "room" : "recording"); - params.set("currentTime", `${video.currentTime}`); + if (includeTime) params.set("currentTime", `${video.currentTime}`); + params.set("organization_id", organizationId.value); + params.set("edge_endpoint", edgeEndpoint.value); + params.set("edge_token", edgeToken.value); + return params; +} - url.hash = params.toString(); +copyShareLink.addEventListener("click", () => { + const url = new URL(window.location.href); + + url.hash = urlParams().toString(); navigator.clipboard.writeText(url.href); @@ -82,11 +83,73 @@ copyShareLink.addEventListener("click", () => { let lastFrameTime = 0; let frameCount = 0; -player.lowLatency = true; -player.abrEnabled = true; +function initPlayer() { + player.lowLatency = true; + player.abrEnabled = true; + + toggleAbr.innerText = player.abrEnabled ? "Disable ABR" : "Enable ABR"; + toggleLowLatency.innerText = player.lowLatency ? "Disable Low Latency" : "Enable Low Latency"; + + player.on("destroyed", () => { + console.log("destroyed"); + destroyed = true; + }); + + player.on("error", (evt) => { + console.error(evt); + }); + + player.on("abr", () => { + toggleAbr.innerText = player.abrEnabled ? "Disable ABR" : "Enable ABR"; + }); + + player.on("variant", (change) => { + console.log(change); + variantId.innerText = `${player.variantId}`; + }); + + player.on("manifestloaded", () => { + selectTracksDiv.innerHTML = ""; + forceTracksDiv.innerHTML = ""; + + player.variants.forEach((variant, idx) => { + const button = document.createElement("button"); + button.innerText = `${variant.audio_track.name} - ${variant.video_track?.name}`; + button.addEventListener("click", () => { + player.nextVariantId = idx; + }); + selectTracksDiv.appendChild(button); + + const forceButton = document.createElement("button"); + forceButton.innerText = `${variant.audio_track.name} - ${variant.video_track?.name}`; + forceButton.addEventListener("click", () => { + player.variantId = idx; + }); + forceTracksDiv.appendChild(forceButton); + }); + }); -toggleAbr.innerText = player.abrEnabled ? "Disable ABR" : "Enable ABR"; -toggleLowLatency.innerText = player.lowLatency ? "Disable Low Latency" : "Enable Low Latency"; + player.on("started", () => { + console.log("started"); + }); + + player.on("stopped", () => { + console.log("stopped"); + }); + + player.on("finished", () => { + console.log("finished"); + }); + + player.on("realtime", () => { + console.log("realtime mode changed", player.realtimeMode); + realTime.innerText = `${player.realtimeMode}`; + }); + + player.on("visibility", () => { + console.log("visibility changed", player.visible); + }); +} toggleLowLatency.addEventListener("click", () => { player.lowLatency = !player.lowLatency; @@ -109,11 +172,6 @@ destroy.addEventListener("click", () => { let destroyed = false; -player.on("destroyed", () => { - console.log("destroyed"); - destroyed = true; -}); - const loop = () => { if (destroyed) return; @@ -175,70 +233,44 @@ const loop = () => { window.requestAnimationFrame(loop); -player.on("error", (evt) => { - console.error(evt); -}); +const loadRoomButton = document.getElementById("load-room") as HTMLButtonElement; +const loadRecordingButton = document.getElementById("load-recording") as HTMLButtonElement; +const roomId = document.getElementById("video-id") as HTMLInputElement; -player.on("abr", () => { - toggleAbr.innerText = player.abrEnabled ? "Disable ABR" : "Enable ABR"; -}); +function loadVideo(type: string, startTime: number) { + if (player) player.destroy(); -player.on("variant", (change) => { - console.log(change); - variantId.innerText = `${player.variantId}`; -}); + if (!organizationId.value) { + alert("Organization ID is required"); + return; + } -player.on("manifestloaded", () => { - selectTracksDiv.innerHTML = ""; - forceTracksDiv.innerHTML = ""; + if (!edgeEndpoint.value) { + alert("Edge endpoint is required"); + return; + } - player.variants.forEach((variant, idx) => { - const button = document.createElement("button"); - button.innerText = `${variant.audio_track.name} - ${variant.video_track?.name}`; - button.addEventListener("click", () => { - player.nextVariantId = idx; - }); - selectTracksDiv.appendChild(button); + if (!roomId.value) { + alert("Room/Recording ID is required"); + return; + } - const forceButton = document.createElement("button"); - forceButton.innerText = `${variant.audio_track.name} - ${variant.video_track?.name}`; - forceButton.addEventListener("click", () => { - player.variantId = idx; - }); - forceTracksDiv.appendChild(forceButton); + player = new Player(video, { + organization_id: organizationId.value, + server: edgeEndpoint.value, + abr_default_bandwidth: bandwidthEstimate(), + logging_level: "info", }); -}); -player.on("started", () => { - console.log("started"); -}); + window.SCUFFLE_PLAYER = player; -player.on("stopped", () => { - console.log("stopped"); -}); + initPlayer(); + console.log(player); -player.on("finished", () => { - console.log("finished"); -}); - -player.on("realtime", () => { - console.log("realtime mode changed", player.realtimeMode); - realTime.innerText = `${player.realtimeMode}`; -}); - -player.on("visibility", () => { - console.log("visibility changed", player.visible); -}); - -const loadRoomButton = document.getElementById("load-room") as HTMLButtonElement; -const loadRecordingButton = document.getElementById("load-recording") as HTMLButtonElement; -const roomId = document.getElementById("video-id") as HTMLInputElement; - -function loadVideo(type: string, startTime: number) { if (type === "room") { - player.loadRoom(roomId.value); + player.loadRoom(roomId.value, edgeToken.value || undefined); } else if (type === "recording") { - player.loadRecording(roomId.value); + player.loadRecording(roomId.value, edgeToken.value || undefined); } else { throw new Error("Invalid type"); } @@ -248,12 +280,7 @@ function loadVideo(type: string, startTime: number) { // Update URL fragment const url = new URL(window.location.href); - const params = new URLSearchParams(); - params.set("id", roomId.value); - params.set("type", type); - if (startTime != -1.0) { - params.set("currentTime", `${startTime}`); - } + const params = urlParams(false); url.hash = params.toString(); @@ -290,12 +317,15 @@ if (urlFragment) { // Parse the fragment as query parameters const params = new URLSearchParams(urlFragment); - const id = params.get("id"); const type = params.get("type"); const currentTime = params.get("currentTime") || "-1.0"; - if (id && type) { - roomId.value = id; + roomId.value = params.get("id") || ""; + organizationId.value = params.get("organization_id") || ""; + edgeEndpoint.value = params.get("edge_endpoint") || ""; + edgeToken.value = params.get("edge_token") || ""; + + if (roomId.value && type && organizationId.value && edgeEndpoint.value) { loadVideo(type, parseFloat(currentTime)); } } diff --git a/video/player/index.html b/video/player/index.html index ab226f73..42a99091 100644 --- a/video/player/index.html +++ b/video/player/index.html @@ -8,6 +8,9 @@
+ + +

Input

diff --git a/video/transcoder/src/main.rs b/video/transcoder/src/main.rs index 66d407b9..1378b89d 100644 --- a/video/transcoder/src/main.rs +++ b/video/transcoder/src/main.rs @@ -61,7 +61,6 @@ impl video_transcoder::global::TranscoderState for GlobalState { } } -#[async_trait::async_trait] impl binary_helper::Global for GlobalState { async fn new(ctx: Context, config: AppConfig) -> anyhow::Result { let (nats, jetstream) = setup_nats(&config.name, &config.nats).await?; @@ -71,7 +70,8 @@ impl binary_helper::Global for GlobalState { Ok(metadata_store) => metadata_store, Err(err) => { tracing::warn!("failed to get metadata kv store: {}", err); - let metadata_store = jetstream + + jetstream .create_key_value(async_nats::jetstream::kv::Config { bucket: config.extra.transcoder.metadata_kv_store.clone(), max_age: Duration::from_secs(60), // 1 minutes max age @@ -79,9 +79,7 @@ impl binary_helper::Global for GlobalState { ..Default::default() }) .await - .context("failed to create metadata kv store")?; - - metadata_store + .context("failed to create metadata kv store")? } }; @@ -89,7 +87,8 @@ impl binary_helper::Global for GlobalState { Ok(media_store) => media_store, Err(err) => { tracing::warn!("failed to get media object store: {}", err); - let media_store = jetstream + + jetstream .create_object_store(async_nats::jetstream::object_store::Config { bucket: config.extra.transcoder.media_ob_store.clone(), max_age: Duration::from_secs(60), // 1 minutes max age @@ -97,20 +96,25 @@ impl binary_helper::Global for GlobalState { ..Default::default() }) .await - .context("failed to create media object store")?; - - media_store + .context("failed to create media object store")? } }; let ingest_tls = if let Some(tls) = &config.extra.transcoder.ingest_tls { let cert = tokio::fs::read(&tls.cert).await.context("failed to read ingest tls cert")?; let key = tokio::fs::read(&tls.key).await.context("failed to read ingest tls key")?; - let ca_cert = tokio::fs::read(&tls.ca_cert).await.context("failed to read ingest tls ca")?; + + let ca_cert = if let Some(ca_cert) = &tls.ca_cert { + Some(tonic::transport::Certificate::from_pem( + tokio::fs::read(&ca_cert).await.context("failed to read ingest tls ca")?, + )) + } else { + None + }; Some(TlsSettings { domain: tls.domain.clone(), - ca_cert: tonic::transport::Certificate::from_pem(ca_cert), + ca_cert, identity: tonic::transport::Identity::from_pem(cert, key), }) } else { diff --git a/video/transcoder/src/tests/global.rs b/video/transcoder/src/tests/global.rs index 4431c2b2..f6c051be 100644 --- a/video/transcoder/src/tests/global.rs +++ b/video/transcoder/src/tests/global.rs @@ -110,12 +110,15 @@ pub async fn mock_global_state(config: TranscoderConfig) -> (Arc, H ingest_tls: config.ingest_tls.as_ref().map(|tls| { let cert = std::fs::read(&tls.cert).expect("failed to read redis cert"); let key = std::fs::read(&tls.key).expect("failed to read redis key"); - let ca_cert = std::fs::read(&tls.ca_cert).expect("failed to read redis ca"); + + let ca_cert = tls.ca_cert.as_ref().map(|ca_cert| { + tonic::transport::Certificate::from_pem(std::fs::read(ca_cert).expect("failed to read ingest tls ca")) + }); TlsSettings { domain: tls.domain.clone(), - ca_cert: tonic::transport::Certificate::from_pem(ca_cert), identity: tonic::transport::Identity::from_pem(cert, key), + ca_cert, } }), config, diff --git a/video/transcoder/src/transcoder/job/mod.rs b/video/transcoder/src/transcoder/job/mod.rs index a4bc0ca4..41c3e43a 100644 --- a/video/transcoder/src/transcoder/job/mod.rs +++ b/video/transcoder/src/transcoder/job/mod.rs @@ -68,7 +68,7 @@ pub async fn handle_message(global: Arc, msg: Message, s let mut streams = futures::stream::select_all( job.tracks .drain(..) - .map(|(t, rendition)| Box::pin(track_parser(unix_stream(t, 256 * 1024))).map(move |r| (r, rendition))), + .map(|(t, rendition)| Box::pin(track_parser(unix_stream(t, 1024 * 1024))).map(move |r| (r, rendition))), ); if let Err(err) = job.run(&global, shutdown_token, &mut streams).await { @@ -280,7 +280,31 @@ impl Job { )) })?; }, - msg = self.recv.next() => self.handle_msg(global, msg).await?, + msg = self.recv.next() => { + let mut items = Vec::new(); + + { + let mut msg_handle_fut = self.handle_msg(global, msg); + let mut msg_handle_fut = pin!(msg_handle_fut); + loop { + select! { + r = &mut msg_handle_fut => { + r?; + break; + }, + Some(item) = streams.next() => { + tracing::debug!(count = %items.len() + 1, "got item while handling message"); + items.push(item); + } + } + } + } + + for item in items { + let (result, rendition) = item; + self.handle_track(global, rendition, result)?; + } + }, r = self.ffmpeg.process.wait() => { r?; break; @@ -311,6 +335,7 @@ impl Job { } } => { let (screenshot, start_time) = screenshot?; + self.screenshot_idx += 1; let id = Ulid::new(); @@ -367,7 +392,12 @@ impl Job { match msg { ingest_watch_response::Message::Media(media) => { if let Some(stdin) = &mut self.ffmpeg.stdin { - stdin.write_all(&media.data).await?; + stdin + .write_all(&media.data) + .timeout(Duration::from_secs(1)) + .await + .context("ffmped is blocked")? + .context("ffmpeg stdin closed")?; } else { anyhow::bail!("ffmpeg stdin was not open"); } @@ -381,8 +411,7 @@ impl Job { && self.last_screenshot.elapsed() > Duration::from_secs(5) && self.screenshot_task.is_none() { - self.take_screenshot(global, &media.data, media.timestamp as f64 / media.timescale as f64) - .await?; + self.take_screenshot(global, &media.data, media.timestamp as f64 / media.timescale as f64)?; } } ingest_watch_response::media::Type::Audio => {} @@ -408,27 +437,30 @@ impl Job { Ok(()) } - async fn take_screenshot(&mut self, global: &Arc, data: &Bytes, start_time: f64) -> Result<()> { + fn take_screenshot(&mut self, global: &Arc, data: &Bytes, start_time: f64) -> Result<()> { if !self.ready { return Ok(()); } - if let Some(init_segment) = &self.init_segment { + if let Some(init_segment) = self.init_segment.clone() { let (width, height) = screenshot_size(&self.video_input); let config = global.config::(); let mut child = spawn_ffmpeg_screenshot(config.ffmpeg_gid, config.ffmpeg_uid, width, height)?; - let mut stdin = child.stdin.take().unwrap(); - stdin.write_all(init_segment).await?; - stdin.write_all(data).await?; - self.last_screenshot = Instant::now(); tracing::debug!("taking screenshot"); + let data = data.clone(); + self.screenshot_task = Some(Box::pin(async move { + let stdin = child.stdin.as_mut().unwrap(); + stdin.write_all(&init_segment).await?; + stdin.write_all(&data).await?; + stdin.flush().await?; + let start = Instant::now(); let output = child.wait_with_output().await?; if !output.status.success() { diff --git a/video/transcoder/src/transcoder/job/utils/ffmpeg.rs b/video/transcoder/src/transcoder/job/utils/ffmpeg.rs index 3e538290..ba23708f 100644 --- a/video/transcoder/src/transcoder/job/utils/ffmpeg.rs +++ b/video/transcoder/src/transcoder/job/utils/ffmpeg.rs @@ -52,6 +52,7 @@ pub fn spawn_ffmpeg( let mut args = vec_of_strings![ "-v", "error", "-i", "-", + "-timeout", "1000000", "-probesize", "250M", "-analyzeduration", "250M", "-max_muxing_queue_size", "1024",