Full Code of livekit/egress for AI

main bafdf0955fd8 cached
184 files
881.4 KB
263.2k tokens
1325 symbols
1 requests
Download .txt
Showing preview only (935K chars total). Download the full file or copy to clipboard to get everything.
Repository: livekit/egress
Branch: main
Commit: bafdf0955fd8
Files: 184
Total size: 881.4 KB

Directory structure:
gitextract_ye_8hhme/

├── .github/
│   ├── CODEOWNERS
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug_report.md
│   │   └── feature_request.md
│   └── workflows/
│       ├── publish-chrome.yaml
│       ├── publish-egress.yaml
│       ├── publish-gstreamer-base.yaml
│       ├── publish-gstreamer.yaml
│       ├── publish-template-sdk.yaml
│       ├── publish-template.yaml
│       ├── slack-notifier.yaml
│       ├── test-cleanup.yaml
│       ├── test-integration.yaml
│       └── test-template.yaml
├── .gitignore
├── .golangci.yaml
├── LICENSE
├── NOTICE
├── README.md
├── bootstrap.sh
├── build/
│   ├── chrome/
│   │   ├── Dockerfile
│   │   ├── README.md
│   │   ├── install-chrome
│   │   └── scripts/
│   │       ├── amd64.sh
│   │       ├── arm64.sh
│   │       └── driver.sh
│   ├── egress/
│   │   ├── Dockerfile
│   │   └── entrypoint.sh
│   ├── gstreamer/
│   │   ├── Dockerfile-base
│   │   ├── Dockerfile-dev
│   │   ├── Dockerfile-prod
│   │   ├── Dockerfile-prod-rs
│   │   ├── compile
│   │   ├── compile-rs
│   │   ├── install-dependencies
│   │   └── tag.sh
│   ├── template/
│   │   └── Dockerfile
│   └── test/
│       ├── Dockerfile
│       ├── entrypoint.sh
│       └── fetch-media-samples.sh
├── chrome-sandboxing-seccomp-profile.json
├── cmd/
│   ├── server/
│   │   ├── http.go
│   │   └── main.go
│   └── template_version/
│       └── main.go
├── go.mod
├── go.sum
├── magefile.go
├── pkg/
│   ├── config/
│   │   ├── base.go
│   │   ├── config_test.go
│   │   ├── encoding.go
│   │   ├── manifest.go
│   │   ├── output.go
│   │   ├── output_file.go
│   │   ├── output_image.go
│   │   ├── output_segment.go
│   │   ├── output_stream.go
│   │   ├── pipeline.go
│   │   ├── retry_test.go
│   │   ├── service.go
│   │   ├── storage.go
│   │   ├── test_overrides.go
│   │   ├── urls.go
│   │   └── urls_test.go
│   ├── errors/
│   │   └── errors.go
│   ├── gstreamer/
│   │   ├── bin.go
│   │   ├── builder.go
│   │   ├── callbacks.go
│   │   ├── pads.go
│   │   ├── pipeline.go
│   │   ├── queue_monitor.go
│   │   ├── state.go
│   │   └── time_provider.go
│   ├── handler/
│   │   ├── handler.go
│   │   ├── handler_ipc.go
│   │   └── handler_rpc.go
│   ├── info/
│   │   └── io.go
│   ├── ipc/
│   │   ├── conn.go
│   │   ├── ipc.pb.go
│   │   ├── ipc.proto
│   │   └── ipc_grpc.pb.go
│   ├── logging/
│   │   ├── csv.go
│   │   ├── handler.go
│   │   └── s3.go
│   ├── pipeline/
│   │   ├── builder/
│   │   │   ├── audio.go
│   │   │   ├── file.go
│   │   │   ├── image.go
│   │   │   ├── muxer.go
│   │   │   ├── muxer_test.go
│   │   │   ├── pts_fixer.go
│   │   │   ├── segment.go
│   │   │   ├── stream.go
│   │   │   ├── video.go
│   │   │   ├── vp9_probe.go
│   │   │   └── websocket.go
│   │   ├── controller.go
│   │   ├── debug.go
│   │   ├── sink/
│   │   │   ├── file.go
│   │   │   ├── image.go
│   │   │   ├── m3u8/
│   │   │   │   ├── writer.go
│   │   │   │   └── writer_test.go
│   │   │   ├── segments.go
│   │   │   ├── sink.go
│   │   │   ├── stream.go
│   │   │   ├── uploader/
│   │   │   │   ├── uploader.go
│   │   │   │   └── uploader_test.go
│   │   │   └── websocket.go
│   │   ├── source/
│   │   │   ├── pulse/
│   │   │   │   └── pactl.go
│   │   │   ├── sdk/
│   │   │   │   ├── appwriter.go
│   │   │   │   └── translator.go
│   │   │   ├── sdk.go
│   │   │   ├── source.go
│   │   │   ├── tracer.go
│   │   │   ├── track_worker.go
│   │   │   ├── track_worker_test.go
│   │   │   └── web.go
│   │   ├── tempo/
│   │   │   ├── controller.go
│   │   │   └── controller_test.go
│   │   └── watch.go
│   ├── server/
│   │   ├── integration.go
│   │   ├── server.go
│   │   ├── server_ipc.go
│   │   └── server_rpc.go
│   ├── service/
│   │   ├── debug.go
│   │   ├── metrics.go
│   │   ├── process.go
│   │   └── servicefakes/
│   │       └── fake_process_manager.go
│   ├── stats/
│   │   ├── handler.go
│   │   ├── monitor.go
│   │   ├── monitor_memory_test.go
│   │   └── monitor_prom.go
│   └── types/
│       ├── types.go
│       └── types_test.go
├── renovate.json
├── template-default/
│   ├── .gitignore
│   ├── .prettierrc
│   ├── README.md
│   ├── eslint.config.js
│   ├── index.html
│   ├── package.json
│   ├── public/
│   │   ├── manifest.json
│   │   └── robots.txt
│   ├── src/
│   │   ├── App.css
│   │   ├── App.tsx
│   │   ├── Room.tsx
│   │   ├── SingleSpeakerLayout.tsx
│   │   ├── SpeakerLayout.tsx
│   │   ├── common.ts
│   │   ├── index.css
│   │   ├── index.tsx
│   │   └── vite-env.d.ts
│   ├── tsconfig.app.json
│   ├── tsconfig.json
│   ├── tsconfig.node.json
│   └── vite.config.ts
├── template-sdk/
│   ├── .gitignore
│   ├── .npmignore
│   ├── .prettierrc
│   ├── README.md
│   ├── package.json
│   ├── src/
│   │   └── index.ts
│   └── tsconfig.json
├── test/
│   ├── agents/
│   │   ├── .gitignore
│   │   ├── guest.py
│   │   ├── host.py
│   │   └── requirements.txt
│   ├── agents.go
│   ├── builder.go
│   ├── config-sample.yaml
│   ├── content_checks.go
│   ├── download.go
│   ├── edge.go
│   ├── ffprobe.go
│   ├── file.go
│   ├── flags.go
│   ├── images.go
│   ├── integration.go
│   ├── integration_test.go
│   ├── ioserver.go
│   ├── multi.go
│   ├── publish.go
│   ├── runner.go
│   ├── segments.go
│   ├── stream.go
│   └── test_content.go
└── version/
    └── version.go

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/CODEOWNERS
================================================
* @livekit/cs-devs


================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.md
================================================
---
name: Bug report
about: Report an egress issue
title: "[BUG]"
labels: bug
assignees: frostbyte73

---

**Describe the bug**
A clear and concise description of what the bug is.

**Egress Version**
What version are you running?

**Egress Request**
Post the request here (be sure to remove any PII).

**Additional context**
Add any other context about the problem here.

**Logs**
Post any relevant logs from the egress service here.


================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.md
================================================
---
name: Feature request
about: Suggest an idea for this project
title: "[FEATURE]"
labels: enhancement, help wanted
assignees: ''

---

**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]

**Describe the solution you'd like**
A clear and concise description of what you want to happen.

**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.

**Additional context**
Add any other context or screenshots about the feature request here.


================================================
FILE: .github/workflows/publish-chrome.yaml
================================================
name: Publish Chrome

on:
  workflow_dispatch:
    inputs:
      chrome_version:
        description: "Version of Chrome to build"
        required: true
        type: string
      image_tag:
        description: "Docker image tag (defaults to chrome_version if empty)"
        required: false
        type: string

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v6

      - name: Install the Linode CLI
        uses: linode/action-linode-cli@v1
        with:
          token: ${{ secrets.LINODE_PAT }}

      - name: Get firewall id
        id: firewall
        shell: bash
        env:
          LINODE_CLI_TOKEN: ${{ secrets.LINODE_PAT }}
        run: |
          set -euo pipefail

          firewall_id="$(linode-cli firewalls list --label chrome-builder --json | jq -r '.[0].id // empty')"

          if [ -z "$firewall_id" ]; then
            echo "Firewall with label chrome-builder not found"
            exit 1
          fi

          echo "firewall_id=$firewall_id" >> "$GITHUB_OUTPUT"
          echo "Using firewall_id=$firewall_id"

      - name: Build cloud-init user-data
        id: userdata
        shell: bash
        run: |
          set -euo pipefail

          cat > cloud-init.yaml <<'EOF'
          #cloud-config
          package_update: true
          package_upgrade: false

          packages:
            - sudo
            - zip
            - unzip
            - curl
            - git
            - netcat-openbsd

          users:
            - name: chrome
              gecos: Chrome Builder
              shell: /bin/bash
              groups: [sudo]
              sudo: ALL=(ALL) NOPASSWD:ALL
              lock_passwd: true
              ssh_authorized_keys:
                - ${LINODE_SSH_PUBLIC_KEY}

          write_files:
            - path: /etc/ssh/sshd_config.d/99-github-actions.conf
              permissions: '0644'
              content: |
                PasswordAuthentication no
                ClientAliveInterval 60
                ClientAliveCountMax 3

          runcmd:
            - mkdir -p /home/chrome/.ssh
            - chmod 700 /home/chrome/.ssh
            - printf '%s\n' "${LINODE_SSH_PUBLIC_KEY}" > /home/chrome/.ssh/authorized_keys
            - chmod 600 /home/chrome/.ssh/authorized_keys
            - chown -R chrome:chrome /home/chrome/.ssh
            - systemctl restart ssh || systemctl restart sshd || true
          EOF

          sed "s|\${LINODE_SSH_PUBLIC_KEY}|${{ secrets.LINODE_SSH_PUBLIC_KEY }}|g" cloud-init.yaml > cloud-init.rendered.yaml

          user_data_b64="$(base64 -w 0 cloud-init.rendered.yaml)"
          echo "user_data_b64=$user_data_b64" >> "$GITHUB_OUTPUT"

      - name: Get or create builder
        id: builder
        shell: bash
        env:
          LINODE_CLI_TOKEN: ${{ secrets.LINODE_PAT }}
        run: |
          set -euo pipefail

          builder_json="$(linode-cli linodes list --label chrome-builder --json)"
          builder_id="$(echo "$builder_json" | jq -r '.[0].id // empty')"

          if [ -n "$builder_id" ]; then
            echo "Reusing existing builder: $builder_id"

            builder_ip="$(echo "$builder_json" | jq -r '.[0].ipv4[0] // empty')"
            builder_status="$(echo "$builder_json" | jq -r '.[0].status // empty')"

            if [ "$builder_status" = "offline" ]; then
              echo "Booting existing builder"
              linode-cli linodes boot "$builder_id"
            fi

            echo "builder_created=false" >> "$GITHUB_OUTPUT"
            echo "builder_id=$builder_id" >> "$GITHUB_OUTPUT"
            echo "builder_ip=$builder_ip" >> "$GITHUB_OUTPUT"
            exit 0
          fi

          echo "No existing builder found, creating a new one"

          builder_info="$(linode-cli linodes create \
            --backups_enabled false \
            --booted true \
            --image linode/ubuntu22.04 \
            --label chrome-builder \
            --private_ip false \
            --region us-west \
            --root_pass "${{ secrets.LINODE_ROOT_PASS }}" \
            --type g6-dedicated-56 \
            --authorized_keys "${{ secrets.LINODE_SSH_PUBLIC_KEY }}" \
            --firewall_id "${{ steps.firewall.outputs.firewall_id }}" \
            --metadata.user_data "${{ steps.userdata.outputs.user_data_b64 }}" \
            --json)"

          echo "$builder_info"

          builder_id="$(echo "$builder_info" | jq -r '.[0].id')"
          builder_ip="$(echo "$builder_info" | jq -r '.[0].ipv4[0]')"

          echo "builder_created=true" >> "$GITHUB_OUTPUT"
          echo "builder_id=$builder_id" >> "$GITHUB_OUTPUT"
          echo "builder_ip=$builder_ip" >> "$GITHUB_OUTPUT"

      - name: Wait for Builder status
        shell: bash
        env:
          LINODE_CLI_TOKEN: ${{ secrets.LINODE_PAT }}
        run: |
          set -euo pipefail

          status="$(linode-cli linodes view "${{ steps.builder.outputs.builder_id }}" --json | jq -r '.[0].status')"

          while [ "$status" = "provisioning" ] || [ "$status" = "booting" ]; do
            echo "Builder status: $status"
            sleep 5
            status="$(linode-cli linodes view "${{ steps.builder.outputs.builder_id }}" --json | jq -r '.[0].status')"
          done

          echo "Builder status: $status"

          if [ "$status" != "running" ]; then
            echo "Builder failed to reach running state"
            exit 1
          fi

      - name: Write SSH keys
        shell: bash
        run: |
          set -euo pipefail

          mkdir -p ~/.ssh
          chmod 700 ~/.ssh
          printf '%s\n' "${{ secrets.LINODE_SSH_PRIVATE_KEY }}" > ~/.ssh/linode_ed25519
          printf '%s\n' "${{ secrets.LINODE_SSH_PUBLIC_KEY }}" > ~/.ssh/linode_ed25519.pub
          chmod 600 ~/.ssh/linode_ed25519 ~/.ssh/linode_ed25519.pub

      - name: Wait for SSH and cloud-init if needed
        shell: bash
        run: |
          set -euo pipefail

          ip="${{ steps.builder.outputs.builder_ip }}"

          for i in $(seq 1 180); do
            if [ "${{ steps.builder.outputs.builder_created }}" = "true" ]; then
              remote_cmd='cloud-init status --wait >/dev/null 2>&1 || true; echo ready'
            else
              remote_cmd='echo ready'
            fi

            if ssh -i ~/.ssh/linode_ed25519 \
              -o BatchMode=yes \
              -o PasswordAuthentication=no \
              -o StrictHostKeyChecking=accept-new \
              -o ConnectTimeout=5 \
              root@"$ip" \
              "$remote_cmd" >/tmp/ssh-ready.txt 2>/tmp/ssh-ready.err; then
              echo "SSH is ready"
              break
            fi

            echo "Waiting for SSH on $ip..."
            cat /tmp/ssh-ready.err || true
            sleep 2
          done

          grep -q ready /tmp/ssh-ready.txt

      - name: Verify chrome user
        shell: bash
        run: |
          set -euo pipefail

          ssh -i ~/.ssh/linode_ed25519 \
            -o BatchMode=yes \
            -o PasswordAuthentication=no \
            -o StrictHostKeyChecking=yes \
            root@${{ steps.builder.outputs.builder_ip }} \
            'id chrome && sudo -iu chrome whoami'

      - name: Amd64
        shell: bash
        run: |
          set -euo pipefail

          ssh -i ~/.ssh/linode_ed25519 \
            -o BatchMode=yes \
            -o PasswordAuthentication=no \
            -o StrictHostKeyChecking=yes \
            -o ServerAliveInterval=60 \
            root@${{ steps.builder.outputs.builder_ip }} \
            "sudo -iu chrome bash -s -- '${{ inputs.chrome_version }}'" \
            < ./build/chrome/scripts/amd64.sh

      - name: Arm64
        shell: bash
        run: |
          set -euo pipefail

          ssh -i ~/.ssh/linode_ed25519 \
            -o BatchMode=yes \
            -o PasswordAuthentication=no \
            -o StrictHostKeyChecking=yes \
            -o ServerAliveInterval=60 \
            root@${{ steps.builder.outputs.builder_ip }} \
            "sudo -iu chrome bash -s -- '${{ inputs.chrome_version }}'" \
            < ./build/chrome/scripts/arm64.sh

      - name: Drivers
        shell: bash
        run: |
          set -euo pipefail

          ssh -i ~/.ssh/linode_ed25519 \
            -o BatchMode=yes \
            -o PasswordAuthentication=no \
            -o StrictHostKeyChecking=yes \
            -o ServerAliveInterval=60 \
            root@${{ steps.builder.outputs.builder_ip }} \
            "sudo -iu chrome bash -s -- '${{ inputs.chrome_version }}'" \
            < ./build/chrome/scripts/driver.sh

      - name: Prepare artifacts
        shell: bash
        run: |
          set -euo pipefail

          ssh -i ~/.ssh/linode_ed25519 \
            -o BatchMode=yes \
            -o PasswordAuthentication=no \
            -o StrictHostKeyChecking=yes \
            root@${{ steps.builder.outputs.builder_ip }} \
            'sudo -iu chrome bash -lc "cd /home/chrome && rm -f output.zip && zip -r output.zip ./output"'

      - name: Download artifacts
        shell: bash
        run: |
          set -euo pipefail

          rm -rf "${{ github.workspace }}/build/chrome/output"
          mkdir -p "${{ github.workspace }}/build/chrome"

          scp -i ~/.ssh/linode_ed25519 \
            -o BatchMode=yes \
            -o PasswordAuthentication=no \
            -o StrictHostKeyChecking=yes \
            root@${{ steps.builder.outputs.builder_ip }}:/home/chrome/output.zip \
            "${{ github.workspace }}/build/chrome/output.zip"

          unzip -o "${{ github.workspace }}/build/chrome/output.zip" -d "${{ github.workspace }}/build/chrome"

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v4

      - name: Login to DockerHub
        uses: docker/login-action@v4
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}

      - name: Build and push
        uses: docker/build-push-action@v7
        with:
          context: ./build/chrome
          file: ./build/chrome/Dockerfile
          push: true
          platforms: linux/amd64,linux/arm64
          tags: livekit/chrome-installer:${{ inputs.image_tag || inputs.chrome_version }}

      - name: Delete created builder on success
        if: success()
        shell: bash
        env:
          LINODE_CLI_TOKEN: ${{ secrets.LINODE_PAT }}
        run: |
          set -euo pipefail
          linode-cli linodes delete "${{ steps.builder.outputs.builder_id }}"


================================================
FILE: .github/workflows/publish-egress.yaml
================================================
# Copyright 2023 LiveKit, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

name: Publish Egress

# Controls when the action will run.
on:
  workflow_dispatch:
  push:
    # only publish on version tags
    tags:
      - 'v*.*.*'
jobs:
  docker:
    runs-on: namespace-profile-8vcpu-cache
    steps:
      - uses: actions/checkout@v6

      - uses: actions/cache@v5
        with:
          path: |
            ~/go/pkg/mod
            ~/go/bin
            ~/.cache
          key: "${{ runner.os }}-egress-${{ hashFiles('**/go.sum') }}"
          restore-keys: ${{ runner.os }}-egress

      - name: Docker metadata
        id: docker-md
        uses: docker/metadata-action@v6
        with:
          images: livekit/egress
          tags: |
            type=semver,pattern=v{{version}}
            type=semver,pattern=v{{major}}.{{minor}}

      - name: Set up Go
        uses: actions/setup-go@v6
        with:
          go-version: 1.26.1

      - name: Download Go modules
        run: go mod download

      - name: Get template image
        id: template-tag
        run: |
          TEMPLATE_TAG=`go run github.com/livekit/egress/cmd/template_version`
          echo "template_tag=$TEMPLATE_TAG" > "$GITHUB_OUTPUT"

      - name: Set up QEMU
        uses: docker/setup-qemu-action@v4

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v4

      - name: Login to DockerHub
        uses: docker/login-action@v4
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}

      - name: Build and push
        uses: docker/build-push-action@v7
        with:
          context: .
          file: ./build/egress/Dockerfile
          push: true
          platforms: linux/amd64,linux/arm64
          tags: ${{ steps.docker-md.outputs.tags }}
          labels: ${{ steps.docker-md.outputs.labels }}
          build-args: |
            TEMPLATE_TAG=${{ steps.template-tag.outputs.template_tag }}


================================================
FILE: .github/workflows/publish-gstreamer-base.yaml
================================================
on:
  workflow_call:
    inputs:
      version:
        required: true
        type: string
      buildjet-runs-on:
        required: true
        type: string
      arch:
        required: true
        type: string
    secrets:
      DOCKERHUB_USERNAME:
        required: true
      DOCKERHUB_TOKEN:
        required: true
env:
  GST_VERSION: "${{ inputs.version }}"
  LIBNICE_VERSION: "0.1.21"

jobs:
  base-gstreamer-build:
    runs-on: ${{ inputs.buildjet-runs-on }}

    steps:
      - name: Checkout code
        uses: actions/checkout@v6

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v4

      - name: Login to Docker Hub
        uses: docker/login-action@v4
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}

      - name: Build and push base
        uses: docker/build-push-action@v7
        with:
          context: ./build/gstreamer
          push: true
          build-args: |
            GSTREAMER_VERSION=${{ env.GST_VERSION }}
            LIBNICE_VERSION=${{ env.LIBNICE_VERSION }}
          file: ./build/gstreamer/Dockerfile-base
          tags: livekit/gstreamer:${{ env.GST_VERSION }}-base-${{ inputs.arch }}

      - name: Build and push dev
        uses: docker/build-push-action@v7
        with:
          context: ./build/gstreamer
          push: true
          build-args: |
            GSTREAMER_VERSION=${{ env.GST_VERSION }}
            LIBNICE_VERSION=${{ env.LIBNICE_VERSION }}
          file: ./build/gstreamer/Dockerfile-dev
          tags: livekit/gstreamer:${{ env.GST_VERSION }}-dev-${{ inputs.arch }}

      - name: Build and push prod
        uses: docker/build-push-action@v7
        with:
          context: ./build/gstreamer
          push: true
          build-args: |
            GSTREAMER_VERSION=${{ env.GST_VERSION }}
            LIBNICE_VERSION=${{ env.LIBNICE_VERSION }}
          file: ./build/gstreamer/Dockerfile-prod
          tags: livekit/gstreamer:${{ env.GST_VERSION }}-prod-${{ inputs.arch }}

      - name: Build and push prod RS
        uses: docker/build-push-action@v7
        with:
          context: ./build/gstreamer
          push: true
          build-args: |
            GSTREAMER_VERSION=${{ env.GST_VERSION }}
            LIBNICE_VERSION=${{ env.LIBNICE_VERSION }}
          file: ./build/gstreamer/Dockerfile-prod-rs
          tags: livekit/gstreamer:${{ env.GST_VERSION }}-prod-rs-${{ inputs.arch }}


================================================
FILE: .github/workflows/publish-gstreamer.yaml
================================================
name: Publish GStreamer

on:
  workflow_dispatch:
    inputs:
      version:
        description: "GStreamer version to publish (e.g. 1.24.4)"
        required: true
        type: string

jobs:
  gstreamer-build-amd64:
    uses: ./.github/workflows/publish-gstreamer-base.yaml
    with:
      version: ${{ inputs.version }}
      buildjet-runs-on: namespace-profile-8vcpu-cache
      arch: amd64
    secrets:
      DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
      DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}

  gstreamer-build-arm64:
    uses: ./.github/workflows/publish-gstreamer-base.yaml
    with:
      version: ${{ inputs.version }}
      buildjet-runs-on: namespace-profile-arm-16
      arch: arm64
    secrets:
      DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
      DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}

  tag-gstreamer-build:
    needs: [gstreamer-build-amd64, gstreamer-build-arm64]
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v6

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v4

      - name: Login to Docker Hub
        uses: docker/login-action@v4
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}

      - name: Run tag script
        run: ./build/gstreamer/tag.sh ${{ inputs.version }}


================================================
FILE: .github/workflows/publish-template-sdk.yaml
================================================
# Copyright 2023 LiveKit, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

name: Publish Template SDK
on:
  push:
    tags:
      - "template*"

jobs:
  deploy:
    runs-on: ubuntu-latest
    defaults:
      run:
        working-directory: ./template-sdk
    steps:
      - uses: actions/checkout@v6
      - uses: pnpm/action-setup@v5
        with:
          version: 10
      - name: Use Node.js 18
        uses: actions/setup-node@v6
        with:
          node-version: 24
          cache: "pnpm"
          cache-dependency-path: ./template-sdk/pnpm-lock.yaml

      - name: Install Dependencies
        run: pnpm install

      - name: Build
        run: pnpm build

      - name: Publish to npm
        run: |
          npm config set '//registry.npmjs.org/:_authToken' $NPM_TOKEN
          npm publish
        env:
          NPM_TOKEN: ${{ secrets.NPM_TOKEN }}


================================================
FILE: .github/workflows/publish-template.yaml
================================================
# Copyright 2023 LiveKit, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

name: Publish Templates
on:
  workflow_dispatch:
  pull_request:
    branches: [main]
    paths:
      - build/template/Dockerfile
      - template-default/**
      - template-sdk/**

jobs:
  docker:
    runs-on: namespace-profile-8vcpu-cache
    steps:
      - uses: actions/checkout@v6
        # for pull requests, need to checkout head for EndBug/add-and-commit to work
        if: github.event_name == 'pull_request'
        with:
          repository: ${{ github.event.pull_request.head.repo.full_name }}
          ref: ${{ github.event.pull_request.head.ref }}

      - uses: actions/checkout@v6
        if: github.event_name != 'pull_request'

      - name: Docker metadata
        id: docker-md
        uses: docker/metadata-action@v6
        with:
          images: livekit/egress-templates
          tags: |
            type=sha
            type=raw,value=latest,enable={{is_default_branch}}

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v4

      - name: Login to DockerHub
        uses: docker/login-action@v4
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}

      - name: Build and push
        uses: docker/build-push-action@v7
        with:
          context: .
          file: ./build/template/Dockerfile
          push: true
          platforms: linux/amd64,linux/arm64
          tags: ${{ steps.docker-md.outputs.tags }}
          labels: ${{ steps.docker-md.outputs.labels }}

      - name: Update template version
        run: |
          SHORT_SHA=`echo ${GITHUB_SHA} | cut -c 1-7`
          sed "s/TemplateVersion.*= \"[-a-z0-9]*\"/TemplateVersion = \"sha-${SHORT_SHA}\"/" < version/version.go  > version.go
          mv -f version.go version/version.go

      - name: Commit version changes
        uses: EndBug/add-and-commit@v9
        with:
          default_author: github_actions
          message: |
            Commit: https://github.com/${{ github.repository }}/commit/${{ github.sha }}
            Ref: https://github.com/${{ github.repository }}/tree/${{ github.ref_name }}
            By: ${{ github.actor }}
          push: true



================================================
FILE: .github/workflows/slack-notifier.yaml
================================================
name: PR Slack Notifier

on:
  pull_request:
    types: [review_requested, reopened, closed, synchronize]
  pull_request_review:
    types: [submitted]

permissions:
  contents: read
  pull-requests: write
  issues: write

concurrency:
  group: pr-slack-${{ github.event.pull_request.number }}-${{ github.workflow }}
  cancel-in-progress: false

jobs:
  notify-devs:
    runs-on: ubuntu-latest
    steps:
      - uses: livekit/slack-notifier-action@main
        with:
          config_json: ${{ secrets.SLACK_NOTIFY_CONFIG_JSON }}
          slack_token: ${{ secrets.SLACK_PR_NOTIFIER_TOKEN }}


================================================
FILE: .github/workflows/test-cleanup.yaml
================================================
name: Cleanup Integration Images

on:
  schedule:
    - cron: '0 6 * * *'
  workflow_dispatch:

permissions: {}

jobs:
  cleanup:
    runs-on: ubuntu-latest
    steps:
      - name: Delete old integration image tags
        env:
          DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_CLEANUP_USERNAME }}
          DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_CLEANUP_TOKEN }}
          REPO: livekit/egress-integration
          RETENTION_DAYS: 3
        run: |
          set -euo pipefail

          # Authenticate with Docker Hub
          TOKEN=$(curl -sf "https://hub.docker.com/v2/users/login" \
            -H "Content-Type: application/json" \
            -d "{\"username\":\"${DOCKERHUB_USERNAME}\",\"password\":\"${DOCKERHUB_TOKEN}\"}" \
            | jq -r '.token')

          if [ -z "$TOKEN" ] || [ "$TOKEN" = "null" ]; then
            echo "::error::Failed to authenticate with Docker Hub"
            exit 1
          fi

          CUTOFF=$(date -u -d "${RETENTION_DAYS} days ago" +%Y-%m-%dT%H:%M:%S.%NZ)
          echo "Deleting tags last updated before ${CUTOFF}"

          DELETED=0
          RETAINED=0
          PAGE=1

          while true; do
            RESPONSE=$(curl -sf "https://hub.docker.com/v2/repositories/${REPO}/tags?page_size=100&page=${PAGE}" \
              -H "Authorization: Bearer ${TOKEN}")

            TAGS=$(echo "$RESPONSE" | jq -r '.results // empty')
            if [ -z "$TAGS" ] || [ "$TAGS" = "[]" ]; then
              break
            fi

            for TAG_INFO in $(echo "$TAGS" | jq -c '.[]'); do
              TAG_NAME=$(echo "$TAG_INFO" | jq -r '.name')
              LAST_UPDATED=$(echo "$TAG_INFO" | jq -r '.last_updated')

              if [[ "$LAST_UPDATED" < "$CUTOFF" ]]; then
                STATUS=$(curl -s -o /dev/null -w "%{http_code}" -X DELETE \
                  "https://hub.docker.com/v2/repositories/${REPO}/tags/${TAG_NAME}" \
                  -H "Authorization: Bearer ${TOKEN}")

                if [ "$STATUS" = "204" ]; then
                  echo "Deleted: ${TAG_NAME} (last updated: ${LAST_UPDATED})"
                  DELETED=$((DELETED + 1))
                else
                  echo "::warning::Failed to delete ${TAG_NAME}: HTTP ${STATUS}"
                fi

                sleep 0.5
              else
                RETAINED=$((RETAINED + 1))
              fi
            done

            NEXT=$(echo "$RESPONSE" | jq -r '.next // empty')
            if [ -z "$NEXT" ] || [ "$NEXT" = "null" ]; then
              break
            fi
            PAGE=$((PAGE + 1))
          done

          echo ""
          echo "Summary: deleted=${DELETED} retained=${RETAINED}"


================================================
FILE: .github/workflows/test-integration.yaml
================================================
# Copyright 2023 LiveKit, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

name: Integration Test
on:
  workflow_dispatch:
  pull_request:
    branches: [ main ]
    paths:
      - build/chrome/**
      - build/egress/**
      - build/gstreamer/**
      - build/test/**
      - cmd/**
      - pkg/**
      - test/**
      - go.mod

jobs:
  build:
    runs-on: namespace-profile-8vcpu-cache
    outputs:
      image: ${{ steps.docker-md.outputs.tags }}
    steps:
      - uses: actions/checkout@v6
        with:
          lfs: true
      - name: Fetch media-samples (with LFS)
        env:
          GITHUB_TOKEN: ${{ github.token }}
        run: build/test/fetch-media-samples.sh

      - uses: actions/cache@v5
        with:
          path: |
            ~/go/pkg/mod
            ~/go/bin
            ~/.cache
          key: egress-integration-${{ hashFiles('**/go.sum') }}
          restore-keys: egress-integration

      - name: Docker metadata
        id: docker-md
        uses: docker/metadata-action@v6
        with:
          images: livekit/egress-integration
          tags: |
            type=sha

      - name: Set up Go
        uses: actions/setup-go@v6
        with:
          go-version: 1.26.1

      - name: Download Go modules
        run: go mod download

      - name: Get template image
        id: template-tag
        run: |
          TEMPLATE_TAG=`go run github.com/livekit/egress/cmd/template_version`
          echo "template_tag=$TEMPLATE_TAG" > "$GITHUB_OUTPUT"

      - name: Login to DockerHub
        uses: docker/login-action@v4
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}

      - name: Set up Buildx
        id: buildx
        uses: docker/setup-buildx-action@v4
        with:
          driver: docker-container
          install: true

      - name: Build and push
        uses: docker/build-push-action@v7
        with:
          builder: ${{ steps.buildx.outputs.name }}
          context: .
          file: ./build/test/Dockerfile
          push: true
          platforms: linux/amd64
          tags: ${{ steps.docker-md.outputs.tags }}
          labels: ${{ steps.docker-md.outputs.labels }}
          build-args: |
            DEADLOCK=1
            TEMPLATE_TAG=${{ steps.template-tag.outputs.template_tag }}
          # TODO: Enable caching once registry periodic cleanup is implemented
          #cache-from: type=registry,ref=livekit/egress-integration:buildcache
          #cache-to:   type=registry,ref=livekit/egress-integration:buildcache,mode=max,ignore-error=true

  test:
    needs: build
    strategy:
      fail-fast: false
      matrix:
        integration_type: [file-room, file-track, file-media, stream, segments, images, multi, edge]
    runs-on: namespace-profile-8vcpu-cache
    steps:
      - uses: shogo82148/actions-setup-redis@v1
        with:
          redis-version: '6.x'
          auto-start: true
      - run: redis-cli ping

      - name: Run tests
        env:
          IMAGE: ${{needs.build.outputs.image}}
        run: |
          docker run --rm \
            --network host \
            -e GITHUB_WORKFLOW=1 \
            -e EGRESS_CONFIG_STRING="$(echo ${{ secrets.EGRESS_CONFIG_STRING }} | base64 -d)" \
            -e INTEGRATION_TYPE="${{ matrix.integration_type }}" \
            -e S3_UPLOAD="$(echo ${{ secrets.S3_UPLOAD }} | base64 -d)" \
            -e GCP_UPLOAD="$(echo ${{ secrets.GCP_UPLOAD }} | base64 -d)" \
            ${{ env.IMAGE }}


================================================
FILE: .github/workflows/test-template.yaml
================================================
# Copyright 2023 LiveKit, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

name: Template Test
on:
  workflow_dispatch:
  pull_request:
    branches: [main]
    paths:
      - build/template/Dockerfile
      - template-default/**
      - template-sdk/**

defaults:
  run:
    working-directory: template-default

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      - uses: pnpm/action-setup@v5
        with:
          version: 10
      - name: Use Node.js 22
        uses: actions/setup-node@v6
        with:
          node-version: 24
          cache: "pnpm"
          cache-dependency-path: ./template-default/pnpm-lock.yaml

      - run: pnpm install
      - run: pnpm build


================================================
FILE: .gitignore
================================================
.idea/
.DS_Store

.github/workflows/config.yaml
build/plugins/
media-samples/
test/output/*
test/*.yaml
!test/config-sample.yaml


================================================
FILE: .golangci.yaml
================================================
version: "2"
run:
  build-tags:
    - deadlock
    - integration
  tests: true
linters:
  default: none
  enable:
    - asasalint
    - dupl
    - errname
    - fatcontext
    - forbidigo
    - goconst
    - govet
    - misspell
    - nilerr
    - revive
    - staticcheck
  settings:
    forbidigo:
      forbid:
        - pattern: sync\.Mutex
        - pattern: sync\.RWMutex
      analyze-types: true
    staticcheck:
      checks:
        - "all"
        - "-ST1000" # package comments — not useful for internal packages
        - "-ST1003" # naming conventions — would break exported API
    misspell:
      mode: default
      locale: US
    revive:
      confidence: 0.8
      severity: warning
      rules:
        - name: argument-limit
        - name: atomic
        - name: blank-imports
        - name: context-as-argument
        - name: context-keys-type
        - name: deep-exit
        - name: defer
        - name: dot-imports
        - name: early-return
        - name: errorf
        - name: error-strings
        - name: if-return
        - name: increment-decrement
        - name: indent-error-flow
        - name: range
        - name: range-val-address
        - name: receiver-naming
        - name: superfluous-else
        - name: unexported-return
        - name: unused-parameter
        - name: var-declaration
        - name: waitgroup-by-value
        - name: datarace
        - name: identical-branches
        - name: identical-switch-branches
        - name: unconditional-recursion
        - name: unreachable-code 
        - name: empty-block
  exclusions:
    generated: lax
    rules:
      - path: cmd/server/main.go
        text: 'pattern templates: no matching files found'
    paths:
      - third_party$
      - builtin$
      - examples$
issues:
  max-issues-per-linter: 0
  max-same-issues: 0


================================================
FILE: LICENSE
================================================
                                 Apache License
                           Version 2.0, January 2004
                        http://www.apache.org/licenses/

   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

   1. Definitions.

      "License" shall mean the terms and conditions for use, reproduction,
      and distribution as defined by Sections 1 through 9 of this document.

      "Licensor" shall mean the copyright owner or entity authorized by
      the copyright owner that is granting the License.

      "Legal Entity" shall mean the union of the acting entity and all
      other entities that control, are controlled by, or are under common
      control with that entity. For the purposes of this definition,
      "control" means (i) the power, direct or indirect, to cause the
      direction or management of such entity, whether by contract or
      otherwise, or (ii) ownership of fifty percent (50%) or more of the
      outstanding shares, or (iii) beneficial ownership of such entity.

      "You" (or "Your") shall mean an individual or Legal Entity
      exercising permissions granted by this License.

      "Source" form shall mean the preferred form for making modifications,
      including but not limited to software source code, documentation
      source, and configuration files.

      "Object" form shall mean any form resulting from mechanical
      transformation or translation of a Source form, including but
      not limited to compiled object code, generated documentation,
      and conversions to other media types.

      "Work" shall mean the work of authorship, whether in Source or
      Object form, made available under the License, as indicated by a
      copyright notice that is included in or attached to the work
      (an example is provided in the Appendix below).

      "Derivative Works" shall mean any work, whether in Source or Object
      form, that is based on (or derived from) the Work and for which the
      editorial revisions, annotations, elaborations, or other modifications
      represent, as a whole, an original work of authorship. For the purposes
      of this License, Derivative Works shall not include works that remain
      separable from, or merely link (or bind by name) to the interfaces of,
      the Work and Derivative Works thereof.

      "Contribution" shall mean any work of authorship, including
      the original version of the Work and any modifications or additions
      to that Work or Derivative Works thereof, that is intentionally
      submitted to Licensor for inclusion in the Work by the copyright owner
      or by an individual or Legal Entity authorized to submit on behalf of
      the copyright owner. For the purposes of this definition, "submitted"
      means any form of electronic, verbal, or written communication sent
      to the Licensor or its representatives, including but not limited to
      communication on electronic mailing lists, source code control systems,
      and issue tracking systems that are managed by, or on behalf of, the
      Licensor for the purpose of discussing and improving the Work, but
      excluding communication that is conspicuously marked or otherwise
      designated in writing by the copyright owner as "Not a Contribution."

      "Contributor" shall mean Licensor and any individual or Legal Entity
      on behalf of whom a Contribution has been received by Licensor and
      subsequently incorporated within the Work.

   2. Grant of Copyright License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      copyright license to reproduce, prepare Derivative Works of,
      publicly display, publicly perform, sublicense, and distribute the
      Work and such Derivative Works in Source or Object form.

   3. Grant of Patent License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      (except as stated in this section) patent license to make, have made,
      use, offer to sell, sell, import, and otherwise transfer the Work,
      where such license applies only to those patent claims licensable
      by such Contributor that are necessarily infringed by their
      Contribution(s) alone or by combination of their Contribution(s)
      with the Work to which such Contribution(s) was submitted. If You
      institute patent litigation against any entity (including a
      cross-claim or counterclaim in a lawsuit) alleging that the Work
      or a Contribution incorporated within the Work constitutes direct
      or contributory patent infringement, then any patent licenses
      granted to You under this License for that Work shall terminate
      as of the date such litigation is filed.

   4. Redistribution. You may reproduce and distribute copies of the
      Work or Derivative Works thereof in any medium, with or without
      modifications, and in Source or Object form, provided that You
      meet the following conditions:

      (a) You must give any other recipients of the Work or
          Derivative Works a copy of this License; and

      (b) You must cause any modified files to carry prominent notices
          stating that You changed the files; and

      (c) You must retain, in the Source form of any Derivative Works
          that You distribute, all copyright, patent, trademark, and
          attribution notices from the Source form of the Work,
          excluding those notices that do not pertain to any part of
          the Derivative Works; and

      (d) If the Work includes a "NOTICE" text file as part of its
          distribution, then any Derivative Works that You distribute must
          include a readable copy of the attribution notices contained
          within such NOTICE file, excluding those notices that do not
          pertain to any part of the Derivative Works, in at least one
          of the following places: within a NOTICE text file distributed
          as part of the Derivative Works; within the Source form or
          documentation, if provided along with the Derivative Works; or,
          within a display generated by the Derivative Works, if and
          wherever such third-party notices normally appear. The contents
          of the NOTICE file are for informational purposes only and
          do not modify the License. You may add Your own attribution
          notices within Derivative Works that You distribute, alongside
          or as an addendum to the NOTICE text from the Work, provided
          that such additional attribution notices cannot be construed
          as modifying the License.

      You may add Your own copyright statement to Your modifications and
      may provide additional or different license terms and conditions
      for use, reproduction, or distribution of Your modifications, or
      for any such Derivative Works as a whole, provided Your use,
      reproduction, and distribution of the Work otherwise complies with
      the conditions stated in this License.

   5. Submission of Contributions. Unless You explicitly state otherwise,
      any Contribution intentionally submitted for inclusion in the Work
      by You to the Licensor shall be under the terms and conditions of
      this License, without any additional terms or conditions.
      Notwithstanding the above, nothing herein shall supersede or modify
      the terms of any separate license agreement you may have executed
      with Licensor regarding such Contributions.

   6. Trademarks. This License does not grant permission to use the trade
      names, trademarks, service marks, or product names of the Licensor,
      except as required for reasonable and customary use in describing the
      origin of the Work and reproducing the content of the NOTICE file.

   7. Disclaimer of Warranty. Unless required by applicable law or
      agreed to in writing, Licensor provides the Work (and each
      Contributor provides its Contributions) on an "AS IS" BASIS,
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
      implied, including, without limitation, any warranties or conditions
      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
      PARTICULAR PURPOSE. You are solely responsible for determining the
      appropriateness of using or redistributing the Work and assume any
      risks associated with Your exercise of permissions under this License.

   8. Limitation of Liability. In no event and under no legal theory,
      whether in tort (including negligence), contract, or otherwise,
      unless required by applicable law (such as deliberate and grossly
      negligent acts) or agreed to in writing, shall any Contributor be
      liable to You for damages, including any direct, indirect, special,
      incidental, or consequential damages of any character arising as a
      result of this License or out of the use or inability to use the
      Work (including but not limited to damages for loss of goodwill,
      work stoppage, computer failure or malfunction, or any and all
      other commercial damages or losses), even if such Contributor
      has been advised of the possibility of such damages.

   9. Accepting Warranty or Additional Liability. While redistributing
      the Work or Derivative Works thereof, You may choose to offer,
      and charge a fee for, acceptance of support, warranty, indemnity,
      or other liability obligations and/or rights consistent with this
      License. However, in accepting such obligations, You may act only
      on Your own behalf and on Your sole responsibility, not on behalf
      of any other Contributor, and only if You agree to indemnify,
      defend, and hold each Contributor harmless for any liability
      incurred by, or claims asserted against, such Contributor by reason
      of your accepting any such warranty or additional liability.

   END OF TERMS AND CONDITIONS

   APPENDIX: How to apply the Apache License to your work.

      To apply the Apache License to your work, attach the following
      boilerplate notice, with the fields enclosed by brackets "[]"
      replaced with your own identifying information. (Don't include
      the brackets!)  The text should be enclosed in the appropriate
      comment syntax for the file format. We also recommend that a
      file or class name and description of purpose be included on the
      same "printed page" as the copyright notice for easier
      identification within third-party archives.

   Copyright [yyyy] [name of copyright owner]

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.


================================================
FILE: NOTICE
================================================
Copyright 2023 LiveKit, Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

   http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.


================================================
FILE: README.md
================================================
<!--BEGIN_BANNER_IMAGE-->

<picture>
  <source media="(prefers-color-scheme: dark)" srcset="/.github/banner_dark.png">
  <source media="(prefers-color-scheme: light)" srcset="/.github/banner_light.png">
  <img style="width:100%;" alt="The LiveKit icon, the name of the repository and some sample code in the background." src="https://raw.githubusercontent.com/livekit/egress/main/.github/banner_light.png">
</picture>

<!--END_BANNER_IMAGE-->

# LiveKit Egress

<!--BEGIN_DESCRIPTION-->
WebRTC is fantastic for last-mile media delivery, but interoperability with other services can be challenging.
An application may want to do things like store a session for future playback, relay a stream to a CDN, or process a track through a transcription service – workflows where media travels through a different system or protocol.
LiveKit Egress is the solution to these interoperability challenges. It provides a consistent set of APIs that gives you
universal export of your LiveKit sessions and tracks.
<!--END_DESCRIPTION-->

## Capabilities

1. **Room composite** for exporting an entire room.
2. **Web egress** for recordings that aren't attached to a single LiveKit room.
3. **Track composite** for exporting synchronized tracks of a single participant.
4. **Track egress** for exporting individual tracks.

Depending on your request type, the egress service will either launch Chrome using a web template
(room composite requests) or a supplied url (web requests), or it will use the Go SDK directly (track and track composite requests).
Irrespective of method used, when moving between protocols, containers or encodings, LiveKit's egress service will automatically transcode streams for you using GStreamer.

## Supported Output

| Egress Type     | MP4 File | OGG File | WebM File | HLS (TS Segments) | RTMP(s) Stream | SRT Stream | WebSocket Stream | Thumbnails (JPEGs) |
|-----------------|----------|----------|-----------|-------------------|----------------|------------------|------------------|--------------------|
| Room Composite  | ✅        | ✅        |           | ✅                 | ✅              | ✅              |                  | ✅                  |
| Web             | ✅        | ✅        |           | ✅                 | ✅              | ✅              |                  | ✅                  |
| Track Composite | ✅        | ✅        |           | ✅                 | ✅              | ✅              |                  | ✅                  |
| Track           | ✅        | ✅        | ✅         |                   |                |               | ✅                |                    |

Files can be uploaded to any S3 compatible storage, Azure, or GCP.

## Documentation

Full docs available [here](https://docs.livekit.io/guides/egress/)

### Config

The Egress service takes a yaml config file:

```yaml
# required fields
api_key: livekit server api key. LIVEKIT_API_KEY env can be used instead
api_secret: livekit server api secret. LIVEKIT_API_SECRET env can be used instead
ws_url: livekit server websocket url. LIVEKIT_WS_URL can be used instead
redis:
  address: must be the same redis address used by your livekit server
  username: redis username
  password: redis password
  db: redis db

# optional fields
health_port: port used for http health checks (default 0)
template_port: port used to host default templates (default 7980)
prometheus_port: port used to collect prometheus metrics (default 0)
debug_handler_port: port used to host http debug handlers (default 0)
logging:
  level: debug, info, warn, or error (default info)
  json: true
template_base: can be used to host custom templates (default http://localhost:<template_port>/)
backup_storage: files will be moved here when uploads fail. location must have write access granted for all users
enable_chrome_sandbox: if true, egress will run Chrome with sandboxing enabled. This requires a specific Docker setup, see below.
cpu_cost: # optionally override cpu cost estimation, used when accepting or denying requests
  room_composite_cpu_cost: 3.0
  web_cpu_cost: 3.0
  track_composite_cpu_cost: 2.0
  track_cpu_cost: 1.0
session_limits: # optional egress duration limits - once hit, egress will end with status EGRESS_LIMIT_REACHED
  file_output_max_duration: 1h
  stream_output_max_duration: 90m
  segment_output_max_duration: 3h

# file upload config - only one of the following. Can be overridden per request
storage:
  s3:
    access_key: AWS_ACCESS_KEY_ID env or EMPTY if using IAM Role or instance profile
    secret: AWS_SECRET_ACCESS_KEY env or EMPTY if using IAM Role or instance profile
    session_token: AWS_SESSION_TOKEN env or EMPTY if using IAM Role or instance profile
    region: AWS_DEFAULT_REGION env or EMPTY if using IAM Role or instance profile
    endpoint: (optional) custom endpoint
    bucket: bucket to upload files to
    # the following s3 options can only be set in config, *not* per request, they will be added to any per-request options
    proxy_config:
      url: (optional) proxy url
      username: (optional) proxy username
      password: (optional) proxy password
    max_retries: (optional, default=3) number or retries to attempt
    max_retry_delay: (optional, default=5s) max delay between retries (e.g. 5s, 100ms, 1m...)
    min_retry_delay: (optional, default=500ms) min delay between retries (e.g. 100ms, 1s...)
    aws_log_level: (optional, default=LogOff) log level for aws sdk (LogDebugWithRequestRetries, LogDebug, ...) 
  azure:
    account_name: AZURE_STORAGE_ACCOUNT env can be used instead
    account_key: AZURE_STORAGE_KEY env can be used instead
    container_name: container to upload files to
  gcp:
    credentials_json: GOOGLE_APPLICATION_CREDENTIALS env can be used instead
    bucket: bucket to upload files to
    proxy_config:
      url: (optional) proxy url
      username: (optional) proxy username
      password: (optional) proxy password
  alioss:
    access_key: Ali OSS AccessKeyId
    secret: Ali OSS AccessKeySecret
    region: Ali OSS region
    endpoint: (optional) custom endpoint
    bucket: bucket to upload files to

# dev/debugging fields
insecure: can be used to connect to an insecure websocket (default false)
debug:
  enable_profiling: create and upload pipeline dot file and pprof file on pipeline failure
  s3: upload config for dotfiles (see above)
  azure: upload config for dotfiles (see above)
  gcp: upload config for dotfiles (see above)
  alioss: upload config for dotfiles (see above)
```

The config file can be added to a mounted volume with its location passed in the EGRESS_CONFIG_FILE env var, or its body can be passed in the EGRESS_CONFIG_BODY env var.

### Filenames

The below templates can also be used in filename/filepath parameters:

| Egress Type     | {room_id} | {room_name} | {time} | {utc} | {publisher_identity} | {track_id} | {track_type} | {track_source} |
|-----------------|-----------|-------------|--------|-------|----------------------|------------|--------------|----------------|
| Room Composite  | ✅         | ✅           | ✅      | ✅     |                      |            |              |                |
| Web             |           |             | ✅      | ✅     |                      |            |              |                |
| Track Composite | ✅         | ✅           | ✅      | ✅     | ✅                    |            |              |                |
| Track           | ✅         | ✅           | ✅      | ✅     | ✅                    | ✅          | ✅            | ✅              |

* If no filename is provided with a request, one will be generated in the form of `"{room_name}-{time}"`.
* If your filename ends with a `/`, a file will be generated in that directory.
* For 1/2/2006, 3:04:05.789 PM, {time} format would display "2006-01-02T150405", and {utc} format "20060102150405789"

Examples:

| Request filename                         | Resulting filename                                |
|------------------------------------------|---------------------------------------------------|
| ""                                       | testroom-2022-10-04T011306.mp4                    |
| "livekit-recordings/"                    | livekit-recordings/testroom-2022-10-04T011306.mp4 |
| "{room_name}/{time}"                     | testroom/2022-10-04T011306.mp4                    |
| "{room_id}-{publisher_identity}.mp4"     | 10719607-f7b0-4d82-afe1-06b77e91fe12-david.mp4    |
| "{track_type}-{track_source}-{track_id}" | audio-microphone-TR_SKasdXCVgHsei.ogg             |

### Running locally

These changes are **not** recommended for a production setup.

To run against a local livekit server, you'll need to do the following:

- open `/usr/local/etc/redis.conf` and comment out the line that says `bind 127.0.0.1`
- change `protected-mode yes` to `protected-mode no` in the same file
- find your IP as seen by docker
  - `ws_url` needs to be set using the IP as Docker sees it
  - on linux, this should be `172.17.0.1`
  - on mac or windows, run `docker run -it --rm alpine nslookup host.docker.internal` and you should see something like
    `Name: host.docker.internal Address: 192.168.65.2`

These changes allow the service to connect to your local redis instance from inside the docker container.

Create a directory to mount. In this example, we will use `~/egress-test`.

Create a config.yaml in the above directory.

- `redis` and `ws_url` should use the above IP instead of `localhost`
- `insecure` should be set to true

```yaml
log_level: debug
api_key: your-api-key
api_secret: your-api-secret
ws_url: ws://192.168.65.2:7880
insecure: true
redis:
  address: 192.168.65.2:6379
```

Then to run the service:

```shell
docker run --rm \
    -e EGRESS_CONFIG_FILE=/out/config.yaml \
    -v ~/egress-test:/out \
    livekit/egress
```

You can then use our [cli](https://github.com/livekit/livekit-cli) to submit egress requests to your server.

### Chrome sandboxing

By default, Room Composite and Web egresses run with Chrome sandboxing disabled. This is because the default docker security settings prevent Chrome from
switching to a different kernel namespace, which is needed by Chrome to setup its sandbox.

Chrome sandboxing within Egress can be reenabled by setting the the `enable_chrome_sandbox` option to `true` in the egress configuration, and launching docker using the [provided
seccomp security profile](https://github.com/livekit/egress/blob/main/chrome-sandboxing-seccomp-profile.json):

```shell
docker run --rm \
    -e EGRESS_CONFIG_FILE=/out/config.yaml \
    -v ~/egress-test:/out \
    --security-opt seccomp=chrome-sandboxing-seccomp-profile.json \
    livekit/egress
```

This profile is based on the [default docker seccomp security profile](https://github.com/moby/moby/blob/master/profiles/seccomp/default.json) and allows
the 2 extra system calls (`clone` and `unshare`) that Chrome needs to setup the sandbox.

Note that kubernetes disables seccomp entirely by default, which means that running with Chrome sandboxing enabled is possible on a kubernetes cluster with
the default security settings.

## FAQ

### Can I store the files locally instead of uploading to cloud storage?
- Yes, you can mount a volume with your `docker run` command (e.g. `-v ~/livekit-egress:/out/`), and use the mounted
directory in your filenames (e.g. `/out/my-recording.mp4`). Since egress is not run as the root user, write permissions
will need to be enabled for all users.

### I get a `"no response from egress service"` error when sending a request

- Your livekit server cannot connect to an egress instance through redis. Make sure they are both able to reach the same redis db.
- If all of your egress instances are full, you'll need to deploy more instances or set up autoscaling.

### I get a different error when sending a request

- Make sure your egress, livekit, server sdk, and livekit-cli are all up to date.

### Can I run this without docker?

- It's possible, but not recommended. To do so, you would need to install gstreamer along with its plugins, chrome, xvfb,
  and have a pulseaudio server running.

## Testing and Development

To run the test against your own LiveKit rooms, a deployed LiveKit server with a secure websocket url is required.
First, create `egress/test/config.yaml`:

```yaml
log_level: debug
api_key: your-api-key
api_secret: your-api-secret
ws_url: wss://your-livekit-url.com
redis:
  address: 192.168.65.2:6379
room_only: false
web_only: false
track_composite_only: false
track_only: false
file_only: false
stream_only: false
segments_only: false
muting: false
dot_files: false
short: false
```

Join a room using https://example.livekit.io or your own client, then run `mage integration test/config.yaml`.
This will test recording different file types, output settings, and streams against your room.

<!--BEGIN_REPO_NAV-->
<br/><table>
<thead><tr><th colspan="2">LiveKit Ecosystem</th></tr></thead>
<tbody>
<tr><td>Agents SDKs</td><td><a href="https://github.com/livekit/agents">Python</a> · <a href="https://github.com/livekit/agents-js">Node.js</a></td></tr><tr></tr>
<tr><td>LiveKit SDKs</td><td><a href="https://github.com/livekit/client-sdk-js">Browser</a> · <a href="https://github.com/livekit/client-sdk-swift">Swift</a> · <a href="https://github.com/livekit/client-sdk-android">Android</a> · <a href="https://github.com/livekit/client-sdk-flutter">Flutter</a> · <a href="https://github.com/livekit/client-sdk-react-native">React Native</a> · <a href="https://github.com/livekit/rust-sdks">Rust</a> · <a href="https://github.com/livekit/node-sdks">Node.js</a> · <a href="https://github.com/livekit/python-sdks">Python</a> · <a href="https://github.com/livekit/client-sdk-unity">Unity</a> · <a href="https://github.com/livekit/client-sdk-unity-web">Unity (WebGL)</a> · <a href="https://github.com/livekit/client-sdk-esp32">ESP32</a> · <a href="https://github.com/livekit/client-sdk-cpp">C++</a></td></tr><tr></tr>
<tr><td>Starter Apps</td><td><a href="https://github.com/livekit-examples/agent-starter-python">Python Agent</a> · <a href="https://github.com/livekit-examples/agent-starter-node">TypeScript Agent</a> · <a href="https://github.com/livekit-examples/agent-starter-react">React App</a> · <a href="https://github.com/livekit-examples/agent-starter-swift">SwiftUI App</a> · <a href="https://github.com/livekit-examples/agent-starter-android">Android App</a> · <a href="https://github.com/livekit-examples/agent-starter-flutter">Flutter App</a> · <a href="https://github.com/livekit-examples/agent-starter-react-native">React Native App</a> · <a href="https://github.com/livekit-examples/agent-starter-embed">Web Embed</a></td></tr><tr></tr>
<tr><td>UI Components</td><td><a href="https://github.com/livekit/components-js">React</a> · <a href="https://github.com/livekit/components-android">Android Compose</a> · <a href="https://github.com/livekit/components-swift">SwiftUI</a> · <a href="https://github.com/livekit/components-flutter">Flutter</a></td></tr><tr></tr>
<tr><td>Server APIs</td><td><a href="https://github.com/livekit/node-sdks">Node.js</a> · <a href="https://github.com/livekit/server-sdk-go">Golang</a> · <a href="https://github.com/livekit/server-sdk-ruby">Ruby</a> · <a href="https://github.com/livekit/server-sdk-kotlin">Java/Kotlin</a> · <a href="https://github.com/livekit/python-sdks">Python</a> · <a href="https://github.com/livekit/rust-sdks">Rust</a> · <a href="https://github.com/agence104/livekit-server-sdk-php">PHP (community)</a> · <a href="https://github.com/pabloFuente/livekit-server-sdk-dotnet">.NET (community)</a></td></tr><tr></tr>
<tr><td>Resources</td><td><a href="https://docs.livekit.io">Docs</a> · <a href="https://docs.livekit.io/mcp">Docs MCP Server</a> · <a href="https://github.com/livekit/livekit-cli">CLI</a> · <a href="https://cloud.livekit.io">LiveKit Cloud</a></td></tr><tr></tr>
<tr><td>LiveKit Server OSS</td><td><a href="https://github.com/livekit/livekit">LiveKit server</a> · <b>Egress</b> · <a href="https://github.com/livekit/ingress">Ingress</a> · <a href="https://github.com/livekit/sip">SIP</a></td></tr><tr></tr>
<tr><td>Community</td><td><a href="https://community.livekit.io">Developer Community</a> · <a href="https://livekit.io/join-slack">Slack</a> · <a href="https://x.com/livekit">X</a> · <a href="https://www.youtube.com/@livekit_io">YouTube</a></td></tr>
</tbody>
</table>
<!--END_REPO_NAV-->


================================================
FILE: bootstrap.sh
================================================
#!/bin/bash
# Copyright 2023 LiveKit, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.


if ! command -v mage &> /dev/null
then
  pushd /tmp
  git clone https://github.com/magefile/mage
  cd mage
  go run bootstrap.go
  rm -rf /tmp/mage
  popd
fi

if ! command -v mage &> /dev/null
then
  echo "Ensure `go env GOPATH`/bin is in your \$PATH"
  exit 1
fi

go mod download


================================================
FILE: build/chrome/Dockerfile
================================================
# Copyright 2023 LiveKit, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

FROM ubuntu:24.04

RUN mkdir /chrome-installer
COPY output/arm64 /chrome-installer/arm64
COPY output/amd64 /chrome-installer/amd64
COPY install-chrome /chrome-installer/install-chrome


================================================
FILE: build/chrome/README.md
================================================
# Chrome installer

This dockerfile is used to install chrome on ubuntu amd64 and arm64.

There is no official or available arm64 build with H264 support, so we needed to compile it from source. 

## Usage

To install chrome, add the following to your dockerfile:

```dockerfile
ARG TARGETPLATFORM
COPY --from=livekit/chrome-installer:124.0.6367.201 /chrome-installer /chrome-installer
RUN /chrome-installer/install-chrome "$TARGETPLATFORM"
ENV PATH=${PATH}:/chrome
ENV CHROME_DEVEL_SANDBOX=/usr/local/sbin/chrome-devel-sandbox
```

## Compilation 

It must be cross compiled from an amd64 builder. This build takes multiple hours, even on fast machines.

Relevant docs:
* [Build instructions](https://chromium.googlesource.com/chromium/src/+/main/docs/linux/build_instructions.md)
* [Cross compiling](https://chromium.googlesource.com/chromium/src/+/main/docs/linux/chromium_arm.md)

### Requirements 

* 64-bit Intel machine (x86_64)
* Ubuntu 22.04 LTS
* 64+ CPU cores
* 128GB+ RAM
* 100GB+ disk space


================================================
FILE: build/chrome/install-chrome
================================================
#!/bin/bash
set -euxo pipefail

if [ "$1" = "linux/arm64" ]
then
  apt-get update
  apt-get install -y \
    ca-certificates \
    fonts-liberation \
    libasound2t64 \
    libatk-bridge2.0-0 \
    libatk1.0-0 \
    libc6 \
    libcairo2 \
    libcups2 \
    libdbus-1-3 \
    libexpat1 \
    libfontconfig1 \
    libgbm1 \
    libglib2.0-0 \
    libnspr4 \
    libnss3 \
    libpango-1.0-0 \
    libpangocairo-1.0-0 \
    libx11-6 \
    libx11-xcb1 \
    libxcb1 \
    libxcomposite1 \
    libxcursor1 \
    libxdamage1 \
    libxext6 \
    libxfixes3 \
    libxi6 \
    libxrandr2 \
    libxrender1 \
    libxss1 \
    libxtst6 \
    xdg-utils
  chmod +x /chrome-installer/arm64/chromedriver-mac-arm64/chromedriver
  mv -f /chrome-installer/arm64/chromedriver-mac-arm64/chromedriver /usr/local/bin/chromedriver
  mv /chrome-installer/arm64/ /chrome
  cp /chrome/chrome_sandbox /usr/local/sbin/chrome-devel-sandbox
  chown root:root /usr/local/sbin/chrome-devel-sandbox
  chmod 4755 /usr/local/sbin/chrome-devel-sandbox
else
  apt-get install -y /chrome-installer/amd64/google-chrome-stable_amd64.deb
  chmod +x /chrome-installer/amd64/chromedriver-linux64/chromedriver
  mv -f /chrome-installer/amd64/chromedriver-linux64/chromedriver /usr/local/bin/chromedriver
fi

rm -rf /chrome-installer


================================================
FILE: build/chrome/scripts/amd64.sh
================================================
#!/bin/bash
set -xeuo pipefail

wget https://dl.google.com/linux/chrome/deb/pool/main/g/google-chrome-stable/google-chrome-stable_"$1"-1_amd64.deb
mkdir -p "$HOME/output/amd64"
mv google-chrome-stable_"$1"-1_amd64.deb "$HOME/output/amd64/google-chrome-stable_amd64.deb"


================================================
FILE: build/chrome/scripts/arm64.sh
================================================
#!/bin/bash
set -xeuo pipefail

sudo apt-get update
sudo apt-get install -y \
  apt-utils \
  build-essential \
  curl \
  git \
  python3 \
  sudo \
  zip

if [ ! -d "$HOME/depot_tools" ]; then
  git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git "$HOME/depot_tools"
fi
export PATH="$PATH:$HOME/depot_tools"

mkdir -p "$HOME"

if [ ! -d "$HOME/chromium/.gclient" ] && [ ! -f "$HOME/chromium/.gclient" ]; then
  mkdir -p "$HOME/chromium"
  cd "$HOME/chromium"
  fetch --nohooks --no-history chromium
fi

cd "$HOME/chromium"

cat > .gclient <<'EOF'
solutions = [
  {
    "name": "src",
    "url": "https://chromium.googlesource.com/chromium/src.git",
    "managed": False,
    "custom_deps": {},
    "custom_vars": {
      "checkout_pgo_profiles": True,
    },
    "target_cpu": "arm64",
  },
]
EOF

cd src

git fetch --no-tags --depth=1 origin "refs/tags/$1:refs/tags/$1"
git checkout -B stable "tags/$1"

for attempt in 1 2 3 4 5; do
  if gclient sync -D --with_branch_heads -j 8; then
    break
  fi

  if [ "$attempt" -eq 5 ]; then
    echo "gclient sync failed after $attempt attempts"
    exit 1
  fi

  sleep_secs=$((attempt * 30))
  echo "gclient sync failed, retrying in ${sleep_secs}s..."
  sleep "$sleep_secs"
done

./build/install-build-deps.sh
./build/linux/sysroot_scripts/install-sysroot.py --arch=arm64
gclient runhooks

gn gen out/default --args='
  target_cpu="arm64"
  proprietary_codecs=true
  ffmpeg_branding="Chrome"

  is_official_build=true
  is_debug=false

  symbol_level=0
  blink_symbol_level=0
  v8_symbol_level=0

  enable_nacl=false
  rtc_use_pipewire=false

  is_component_build=false
  use_jumbo_build=true

  dcheck_always_on=false
'

export NINJA_SUMMARIZE_BUILD=1
autoninja -C out/default chrome chrome_sandbox -j "$(nproc)"

cd out/default

rm -rf "$HOME/output/arm64"
mkdir -p "$HOME/output/arm64/locales"

mv locales/en-US.pak "$HOME/output/arm64/locales/"

required_files=(
  chrome
  chrome-wrapper
  chrome_100_percent.pak
  chrome_200_percent.pak
  chrome_crashpad_handler
  chrome_sandbox
  icudtl.dat
  libEGL.so
  libGLESv2.so
  resources.pak
  snapshot_blob.bin
  v8_context_snapshot.bin
)

for f in "${required_files[@]}"; do
  if [ ! -e "$f" ]; then
    echo "Missing required build output: $f"
    exit 1
  fi
  mv "$f" "$HOME/output/arm64/"
done


================================================
FILE: build/chrome/scripts/driver.sh
================================================
#!/bin/bash
set -xeuo pipefail

wget https://storage.googleapis.com/chrome-for-testing-public/"$1"/linux64/chromedriver-linux64.zip
unzip chromedriver-linux64.zip -d "$HOME/output/amd64"
wget https://storage.googleapis.com/chrome-for-testing-public/"$1"/mac-arm64/chromedriver-mac-arm64.zip
unzip chromedriver-mac-arm64.zip -d "$HOME/output/arm64"


================================================
FILE: build/egress/Dockerfile
================================================
# Copyright 2023 LiveKit, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

ARG TEMPLATE_TAG=latest

FROM livekit/egress-templates:$TEMPLATE_TAG AS template

FROM livekit/gstreamer:1.24.12-dev

ARG TARGETPLATFORM
ARG TARGETARCH
ENV TARGETARCH=${TARGETARCH}
ENV TARGETPLATFORM=${TARGETPLATFORM}

WORKDIR /workspace

# install go
RUN wget https://go.dev/dl/go1.26.1.linux-${TARGETARCH}.tar.gz && \
    rm -rf /usr/local/go && \
    tar -C /usr/local -xzf go1.26.1.linux-${TARGETARCH}.tar.gz
ENV PATH="/usr/local/go/bin:${PATH}"

# download go modules
COPY go.mod .
COPY go.sum .
RUN go mod download

# copy source
COPY cmd/ cmd/
COPY pkg/ pkg/
COPY version/ version/

# copy templates
COPY --from=template workspace/build/ cmd/server/templates/
# delete .map files
RUN find cmd/server/templates/ -name *.map | xargs rm

# build
RUN CGO_ENABLED=1 GOOS=linux GOARCH=${TARGETARCH} GO111MODULE=on GODEBUG=disablethp=1 go build -a -o egress ./cmd/server

# install tini
ENV TINI_VERSION v0.19.0

ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini-${TARGETARCH} /tini
RUN chmod +x /tini

FROM livekit/gstreamer:1.24.12-prod

ARG TARGETPLATFORM

# install deps
RUN apt-get update && \
    apt-get install -y \
    curl \
    fonts-noto \
    gnupg \
    pulseaudio \
    unzip \
    wget \
    xvfb \
    gstreamer1.0-plugins-base-

# install chrome
COPY --from=livekit/chrome-installer:146.0.7680.177-1 /chrome-installer /chrome-installer

RUN /chrome-installer/install-chrome "$TARGETPLATFORM"

# clean up
RUN rm -rf /var/lib/apt/lists/*

# create egress user
RUN useradd -ms /bin/bash -g root -G sudo,pulse,pulse-access egress
RUN mkdir -p home/egress/tmp home/egress/.cache/xdgr && \
    chown -R egress /home/egress

# copy files
COPY --from=1 /workspace/egress /bin/
COPY --from=1 /tini /tini
COPY build/egress/entrypoint.sh /

# run
USER egress
ENV PATH=${PATH}:/chrome
ENV XDG_RUNTIME_DIR=/home/egress/.cache/xdgr
ENV CHROME_DEVEL_SANDBOX=/usr/local/sbin/chrome-devel-sandbox
ENTRYPOINT ["/entrypoint.sh"]


================================================
FILE: build/egress/entrypoint.sh
================================================
#!/usr/bin/env bash
# Copyright 2023 LiveKit, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

set -euo pipefail

# Clean out tmp
rm -rf /home/egress/tmp/*

# Start pulseaudio
rm -rf /var/run/pulse /var/lib/pulse /home/egress/.config/pulse /home/egress/.cache/xdgr/pulse
pulseaudio -D --verbose --exit-idle-time=-1 --disallow-exit > /dev/null 2>&1

# Run egress service
exec /tini -- egress


================================================
FILE: build/gstreamer/Dockerfile-base
================================================
FROM ubuntu:24.04

ARG GSTREAMER_VERSION

ARG LIBNICE_VERSION

COPY install-dependencies /

RUN /install-dependencies

ENV PATH=/root/.cargo/bin:$PATH

RUN for lib in gstreamer gst-plugins-base gst-plugins-good gst-plugins-bad gst-plugins-ugly gst-libav; \
        do \
            wget https://gstreamer.freedesktop.org/src/$lib/$lib-$GSTREAMER_VERSION.tar.xz && \
            tar -xf $lib-$GSTREAMER_VERSION.tar.xz && \
            rm $lib-$GSTREAMER_VERSION.tar.xz && \
            mv $lib-$GSTREAMER_VERSION $lib; \
        done

# rust plugins are apparently only realeased on gitlab

RUN wget https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/archive/gstreamer-$GSTREAMER_VERSION/gst-plugins-rs-gstreamer-$GSTREAMER_VERSION.tar.gz && \
tar xfz gst-plugins-rs-gstreamer-$GSTREAMER_VERSION.tar.gz && \
rm gst-plugins-rs-gstreamer-$GSTREAMER_VERSION.tar.gz && \
mv gst-plugins-rs-gstreamer-$GSTREAMER_VERSION gst-plugins-rs

RUN wget https://libnice.freedesktop.org/releases/libnice-$LIBNICE_VERSION.tar.gz && \
tar xfz libnice-$LIBNICE_VERSION.tar.gz && \
rm libnice-$LIBNICE_VERSION.tar.gz && \
mv libnice-$LIBNICE_VERSION libnice


================================================
FILE: build/gstreamer/Dockerfile-dev
================================================
ARG GSTREAMER_VERSION

FROM livekit/gstreamer:${GSTREAMER_VERSION}-base-${TARGETARCH}

ENV DEBUG=true
ENV OPTIMIZATIONS=false

COPY compile /
COPY compile-rs /

RUN /compile
RUN /compile-rs

FROM ubuntu:24.04

COPY install-dependencies /

RUN /install-dependencies

COPY --from=0 /compiled-binaries /


================================================
FILE: build/gstreamer/Dockerfile-prod
================================================
ARG GSTREAMER_VERSION

FROM livekit/gstreamer:${GSTREAMER_VERSION}-base-${TARGETARCH}

ENV DEBUG=false
ENV OPTIMIZATIONS=true

COPY compile /

RUN /compile

FROM ubuntu:24.04

RUN apt-get update && \
    apt-get dist-upgrade -y && \
    apt-get install -y --no-install-recommends \
        bubblewrap \
        ca-certificates \
        iso-codes \
        ladspa-sdk \
        liba52-0.7.4 \
        libaa1 \
        libaom3 \
        libass9 \
        libavcodec60 \
        libavfilter9 \
        libavformat60 \
        libavutil58 \
        libbs2b0 \
        libbz2-1.0 \
        libcaca0 \
        libcap2 \
        libchromaprint1 \
        libcurl3-gnutls \
        libdca0 \
        libde265-0 \
        libdv4 \
        libdvdnav4 \
        libdvdread8 \
        libdw1 \
        libegl1 \
        libepoxy0 \
        libfaac0 \
        libfaad2 \
        libfdk-aac2 \
        libflite1 \
        libgbm1 \
        libgcrypt20 \
        libgl1 \
        libgles1 \
        libgles2 \
        libglib2.0-0 \
        libgme0 \
        libgmp10 \
        libgsl27 \
        libgsm1 \
        libgudev-1.0-0 \
        libharfbuzz-icu0 \
        libjpeg8 \
        libkate1 \
        liblcms2-2 \
        liblilv-0-0 \
        libmjpegutils-2.1-0 \
        libmodplug1 \
        libmp3lame0 \
        libmpcdec6 \
        libmpeg2-4 \
        libmpg123-0 \
        libofa0 \
        libogg0 \
        libopencore-amrnb0 \
        libopencore-amrwb0 \
        libopenexr-3-1-30 \
        libopenjp2-7 \
        libopus0 \
        liborc-0.4-0 \
        libpango-1.0-0 \
        libpng16-16 \
        librsvg2-2 \
        librtmp1 \
        libsbc1 \
        libseccomp2 \
        libshout3 \
        libsndfile1 \
        libsoundtouch1 \
        libsoup2.4-1 \
        libspandsp2 \
        libspeex1 \
        libsrt1.5-openssl \
        libsrtp2-1 \
        libssl3 \
        libtag1v5 \
        libtheora0 \
        libtwolame0 \
        libunwind8 \
        libvisual-0.4-0 \
        libvo-aacenc0 \
        libvo-amrwbenc0 \
        libvorbis0a \
        libvpx9 \
        libvulkan1 \
        libwavpack1 \
        libwebp7 \
        libwebpdemux2 \
        libwebpmux3 \
        libwebrtc-audio-processing1 \
        libwildmidi2 \
        libwoff1 \
        libx264-164 \
        libx265-199 \
        libxkbcommon0 \
        libxslt1.1 \
        libzbar0 \
        libzvbi0 \
        mjpegtools \
        xdg-dbus-proxy && \
    apt-get clean && \
    rm -rf /var/lib/apt/lists/*

COPY --from=0 /compiled-binaries /


================================================
FILE: build/gstreamer/Dockerfile-prod-rs
================================================
ARG GSTREAMER_VERSION

FROM livekit/gstreamer:${GSTREAMER_VERSION}-base-${TARGETARCH}

FROM livekit/gstreamer:${GSTREAMER_VERSION}-dev-${TARGETARCH}

COPY --from=0 /gst-plugins-rs /gst-plugins-rs

ENV DEBUG=false
ENV OPTIMIZATIONS=true
ENV PATH=/root/.cargo/bin:$PATH

COPY compile-rs /

RUN /compile-rs 

FROM livekit/gstreamer:${GSTREAMER_VERSION}-prod-${TARGETARCH}

COPY --from=1 /compiled-binaries /


================================================
FILE: build/gstreamer/compile
================================================
#!/bin/bash
set -euxo pipefail

for repo in gstreamer libnice gst-plugins-base gst-plugins-good gst-plugins-bad gst-plugins-ugly gst-libav; do
  pushd $repo

  opts="-D prefix=/usr"

  if [[ $repo != "libnice" ]]; then
    opts="$opts -D tests=disabled -D doc=disabled"
  fi

  if [[ $repo == "gstreamer" ]]; then
    opts="$opts -D examples=disabled -D introspection=disabled"
  elif [[ $repo == "gst-plugins-base" ]]; then
    opts="$opts -D examples=disabled -D introspection=disabled -D qt5=disabled"
  elif [[ $repo == "gst-plugins-good" ]]; then
    opts="$opts -D examples=disabled -D pulse=enabled -D qt5=disabled"
  elif [[ $repo == "gst-plugins-bad" ]]; then
    opts="$opts -D gpl=enabled -D examples=disabled -D introspection=disabled"
  elif [[ $repo == "gst-plugins-ugly" ]]; then
    opts="$opts -D gpl=enabled"
  fi

  if [[ $DEBUG == 'true' ]]; then
    if [[ $OPTIMIZATIONS == 'true' ]]; then
      opts="$opts -D buildtype=debugoptimized"
    else
      opts="$opts -D buildtype=debug"
    fi
  else
    opts="$opts -D buildtype=release -D b_lto=true"
  fi

  rm -rf build
  meson setup build $opts

  if [[ -z "${NINJA_JOBS:-}" ]]; then
    # Limit to 4 jobs to avoid OOM issues
    NINJA_JOBS=4
  fi

  # This is needed for other plugins to be built properly
  ninja -j "${NINJA_JOBS}" -C build install
  # This is where we'll grab build artifacts from
  DESTDIR=/compiled-binaries ninja -j "${NINJA_JOBS}" -C build install
  popd
done

gst-inspect-1.0


================================================
FILE: build/gstreamer/compile-rs
================================================
#!/bin/bash
set -euxo pipefail

: "${CARGO_BUILD_JOBS:=4}"
export CARGO_BUILD_JOBS

for repo in gst-plugins-rs; do
  pushd $repo

  # strip binaries in debug mode
  mv Cargo.toml Cargo.toml.old
  sed s,'\[profile.release\]','[profile.release]\nstrip="debuginfo"', Cargo.toml.old > Cargo.toml
  cargo update -p time

  opts="-D prefix=/usr -D tests=disabled -D doc=disabled"

  if [[ $DEBUG == 'true' ]]; then
    if [[ $OPTIMIZATIONS == 'true' ]]; then
      opts="$opts -D buildtype=debugoptimized"
    else
      opts="$opts -D buildtype=debug"
    fi
  else
    opts="$opts -D buildtype=release -D b_lto=true"
  fi

  rm -rf build
  meson setup build $opts

  if [[ -z "${NINJA_JOBS:-}" ]]; then
    # Limit to 4 jobs to avoid OOM issues
    NINJA_JOBS=4
  fi

  # This is needed for other plugins to be built properly
  ninja -j "${NINJA_JOBS}" -C build install
  # This is where we'll grab build artifacts from
  DESTDIR=/compiled-binaries ninja -j "${NINJA_JOBS}" -C build install
  popd
done

gst-inspect-1.0


================================================
FILE: build/gstreamer/install-dependencies
================================================
#!/bin/bash
set -euxo pipefail

export DEBIAN_FRONTEND=noninteractive

apt-get update
apt-get dist-upgrade -y
apt-get install -y --no-install-recommends \
  bison \
  bubblewrap \
  ca-certificates \
  cmake \
  curl \
  flex \
  flite1-dev \
  gcc \
  gettext \
  git \
  gperf \
  iso-codes \
  liba52-0.7.4-dev \
  libaa1-dev \
  libaom-dev \
  libass-dev \
  libavcodec-dev \
  libavfilter-dev \
  libavformat-dev \
  libavutil-dev \
  libbs2b-dev \
  libbz2-dev \
  libcaca-dev \
  libcap-dev \
  libchromaprint-dev \
  libcurl4-gnutls-dev \
  libdca-dev \
  libde265-dev \
  libdrm-dev \
  libdv4-dev \
  libdvdnav-dev \
  libdvdread-dev \
  libdw-dev \
  libepoxy-dev \
  libfaac-dev \
  libfaad-dev \
  libfdk-aac-dev \
  libgbm-dev \
  libgcrypt20-dev \
  libgirepository1.0-dev \
  libgl-dev \
  libgles-dev \
  libglib2.0-dev \
  libgme-dev \
  libgmp-dev \
  libgsl-dev \
  libgsm1-dev \
  libgudev-1.0-dev \
  libjpeg-dev \
  libkate-dev \
  liblcms2-dev \
  liblilv-dev \
  libmjpegtools-dev \
  libmodplug-dev \
  libmp3lame-dev \
  libmpcdec-dev \
  libmpeg2-4-dev \
  libmpg123-dev \
  libofa0-dev \
  libogg-dev \
  libopencore-amrnb-dev \
  libopencore-amrwb-dev \
  libopenexr-dev \
  libopenjp2-7-dev \
  libopus-dev \
  liborc-0.4-dev \
  libpango1.0-dev \
  libpng-dev \
  libpulse-dev \
  librsvg2-dev \
  librtmp-dev \
  libsbc-dev \
  libseccomp-dev \
  libshout3-dev \
  libsndfile1-dev \
  libsoundtouch-dev \
  libsoup2.4-dev \
  libspandsp-dev \
  libspeex-dev \
  libsrt-gnutls-dev \
  libsrtp2-dev \
  libssl-dev \
  libtag1-dev \
  libtheora-dev \
  libtwolame-dev \
  libudev-dev \
  libunwind-dev \
  libvisual-0.4-dev \
  libvo-aacenc-dev \
  libvo-amrwbenc-dev \
  libvorbis-dev \
  libvpx-dev \
  libvulkan-dev \
  libwavpack-dev \
  libwebp-dev \
  libwebrtc-audio-processing-dev \
  libwildmidi-dev \
  libwoff-dev \
  libx264-dev \
  libx265-dev \
  libxkbcommon-dev \
  libxslt1-dev \
  libzbar-dev \
  libzvbi-dev \
  ninja-build \
  python3 \
  ruby \
  wget \
  xdg-dbus-proxy

# install meson version needed for gstreamer 1.26.7 as it's not available in ubuntu 24.04
MESON_VERSION=1.4.1
MESON_BASENAME="meson-${MESON_VERSION}"
MESON_BASE_URL="https://github.com/mesonbuild/meson/releases/download/${MESON_VERSION}"

pushd /tmp >/dev/null
curl -fsSL "${MESON_BASE_URL}/${MESON_BASENAME}.tar.gz" -o "${MESON_BASENAME}.tar.gz"
popd >/dev/null

tar -xzf "/tmp/${MESON_BASENAME}.tar.gz" -C /opt
rm -f "/tmp/${MESON_BASENAME}.tar.gz"

cat <<EOF >/usr/local/bin/meson
#!/bin/sh
exec /usr/bin/env python3 /opt/${MESON_BASENAME}/meson.py "\$@"
EOF
chmod +x /usr/local/bin/meson
ln -sf /usr/local/bin/meson /usr/bin/meson

apt-get clean
rm -rf /var/lib/apt/lists/*

# install rust
curl -o install-rustup.sh --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs
sh install-rustup.sh -y
source "$HOME/.cargo/env"
cargo install cargo-c
rm -rf install-rustup.sh


================================================
FILE: build/gstreamer/tag.sh
================================================
#!/bin/bash

image_suffix=(base dev prod prod-rs)
archs=(amd64 arm64)
gst_version=$1

for suffix in ${image_suffix[*]}
do
    digests=()
    for arch in ${archs[*]}
    do
        digest=`docker manifest inspect livekit/gstreamer:$gst_version-$suffix-$arch | jq ".manifests[] | select(.platform.architecture == \"$arch\").digest"`
        # remove quotes
        digest=${digest:1:$[${#digest}-2]}
        digests+=($digest)
    done

    manifests=""
    for digest in ${digests[*]}
    do
        manifests+=" livekit/gstreamer@$digest"
    done

    docker manifest create livekit/gstreamer:$gst_version-$suffix$manifests
    docker manifest push livekit/gstreamer:$gst_version-$suffix
done


================================================
FILE: build/template/Dockerfile
================================================
# Copyright 2023 LiveKit, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

FROM ubuntu:24.04

WORKDIR /workspace

RUN apt update
RUN apt install -y curl
RUN curl -sL https://deb.nodesource.com/setup_22.x | bash -
RUN apt update
RUN apt install -y nodejs
RUN npm install -g pnpm

# copy templates
COPY template-default/ .

# build
RUN pnpm install
RUN pnpm build


================================================
FILE: build/test/Dockerfile
================================================
# Copyright 2023 LiveKit, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# syntax=docker/dockerfile:1.6
ARG TARGETPLATFORM
ARG TEMPLATE_TAG=latest
ARG GO_VERSION=1.26.1
ARG LINT_VERSION=v2.11.3

FROM livekit/egress-templates:$TEMPLATE_TAG AS template

FROM livekit/gstreamer:1.24.12-dev AS builder

WORKDIR /workspace

ARG TARGETPLATFORM

# Deadlock 0 = off, 1 = on
ARG DEADLOCK=0
ARG GO_VERSION

# install go
RUN if [ "$TARGETPLATFORM" = "linux/arm64" ]; then GOARCH=arm64; else GOARCH=amd64; fi && \
    wget https://go.dev/dl/go${GO_VERSION}.linux-${GOARCH}.tar.gz && \
    rm -rf /usr/local/go && \
    tar -C /usr/local -xzf go${GO_VERSION}.linux-${GOARCH}.tar.gz
ENV PATH="/usr/local/go/bin:${PATH}"

ENV GOMODCACHE=/go/pkg/mod \
    GOCACHE=/go/build-cache

# download go modules
COPY go.mod .
COPY go.sum .
RUN --mount=type=cache,target=/go/pkg/mod,sharing=locked \
    --mount=type=cache,target=/go/build-cache \
    go mod download

# copy source
COPY cmd/ cmd/
COPY pkg/ pkg/
COPY test/ test/
COPY version/ version/

# copy templates
COPY --from=template workspace/build/ cmd/server/templates/
COPY --from=template workspace/build/ test/templates/

# build (service tests will need to launch the handler)
RUN --mount=type=cache,target=/go/pkg/mod,sharing=locked \
    --mount=type=cache,target=/go/build-cache \
    if [ "$TARGETPLATFORM" = "linux/arm64" ]; then GOARCH=arm64; else GOARCH=amd64; fi && \
    TAGS=""; \
    if [ "${DEADLOCK:-0}" = "1" ]; then TAGS="deadlock"; fi; \
    CGO_ENABLED=1 GOOS=linux GOARCH=${GOARCH} GODEBUG=disablethp=1 go build ${TAGS:+-tags} ${TAGS:+"$TAGS"} -o egress ./cmd/server

RUN --mount=type=cache,target=/go/pkg/mod,sharing=locked \
    --mount=type=cache,target=/go/build-cache \
    if [ "$TARGETPLATFORM" = "linux/arm64" ]; then GOARCH=arm64; else GOARCH=amd64; fi && \
    CGO_ENABLED=1 GOOS=linux GOARCH=${GOARCH} go test -c -v -race --tags=integration ./test

FROM golangci/golangci-lint:${LINT_VERSION} AS golangci

FROM builder AS lint

COPY --from=golangci /usr/bin/golangci-lint /usr/local/bin/golangci-lint
COPY .golangci.yaml .

RUN --mount=type=cache,target=/go/pkg/mod,sharing=locked \
    --mount=type=cache,target=/go/build-cache \
    CGO_ENABLED=1 \
    golangci-lint run \
        --timeout=5m \
        --modules-download-mode=mod \
        --build-tags="${LINT_TAGS}"
RUN echo ok >/lint_ok


FROM livekit/gstreamer:1.24.12-prod

ARG TARGETPLATFORM

# install deps
RUN apt-get update && \
    apt-get install -y \
        curl \
        ffmpeg \
        fonts-noto \
        gnupg \
        pulseaudio \
        python3 \
        python3-pip \
        unzip \
        wget \
        xvfb \
        gstreamer1.0-plugins-base-

# install go
COPY --from=1 /usr/local/go /usr/local/go
ENV PATH="/usr/local/go/bin:${PATH}"

# install chrome
COPY --from=livekit/chrome-installer:146.0.7680.177-1 /chrome-installer /chrome-installer

RUN /chrome-installer/install-chrome "$TARGETPLATFORM"

# clean up
RUN rm -rf /var/lib/apt/lists/*

# install rtsp server
RUN if [ "$TARGETPLATFORM" = "linux/arm64" ]; then ARCH=arm64v8; else ARCH=amd64; fi && \
    wget https://github.com/bluenviron/mediamtx/releases/download/v1.8.1/mediamtx_v1.8.1_linux_${ARCH}.tar.gz && \
    tar -zxvf mediamtx_v1.8.1_linux_${ARCH}.tar.gz && \
    rm mediamtx_v1.8.1_linux_${ARCH}.tar.gz && \
    sed -i 's_record: no_record: yes_g' mediamtx.yml && \
    sed -i 's_recordPath: ./recordings/%path/_recordPath: /out/output/stream-_g' mediamtx.yml

# create egress user
RUN useradd -ms /bin/bash -g root -G sudo,pulse,pulse-access egress
RUN mkdir -p home/egress/tmp home/egress/.cache/xdgr && \
    chown -R egress /home/egress

# copy files
COPY test/agents/requirements.txt /agents/requirements.txt
RUN pip install --break-system-packages --no-cache-dir -r /agents/requirements.txt

COPY test/ /workspace/test/
COPY --from=1 /workspace/egress /bin/
COPY --from=1 /workspace/test.test .
COPY media-samples /media-samples
COPY --from=1 /workspace/test/agents /agents
COPY build/test/entrypoint.sh .

# Force lint stage to run successfully
COPY --from=lint /lint_ok /__lint_ok

# run tests
USER egress
ENV PATH=${PATH}:/chrome
ENV XDG_RUNTIME_DIR=/home/egress/.cache/xdgr
ENV CHROME_DEVEL_SANDBOX=/usr/local/sbin/chrome-devel-sandbox
ENTRYPOINT ["./entrypoint.sh"]


================================================
FILE: build/test/entrypoint.sh
================================================
#!/usr/bin/env bash
# Copyright 2023 LiveKit, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

set -eo pipefail

# Start pulseaudio
rm -rf /var/run/pulse /var/lib/pulse /home/egress/.config/pulse /home/egress/.cache/xdgr/pulse
pulseaudio -D --verbose --exit-idle-time=-1 --disallow-exit > /dev/null 2>&1

# Run RTSP server
./mediamtx > /dev/null 2>&1 &

# Run tests
if [[ -z ${GITHUB_WORKFLOW+x} ]]; then
  exec ./test.test -test.v -test.timeout 30m
else
  go install github.com/gotesttools/gotestfmt/v2/cmd/gotestfmt@latest
  exec go tool test2json -p egress ./test.test -test.v -test.timeout 30m 2>&1 | "$HOME"/go/bin/gotestfmt
fi


================================================
FILE: build/test/fetch-media-samples.sh
================================================
#!/usr/bin/env bash
set -euo pipefail

REPO="livekit/media-samples"
DEST="media-samples"
REF="${1:-main}"

export GIT_TERMINAL_PROMPT=0

if ! command -v git-lfs >/dev/null 2>&1; then
  echo "git-lfs not found. Install it (brew install git-lfs / apt-get install git-lfs)" >&2
  exit 1
fi
git lfs install --local

# run git with an Authorization header only if GITHUB_TOKEN is set
g() {
  if [[ -n "${GITHUB_TOKEN:-}" ]]; then
    local b64
    b64="$(printf 'x-access-token:%s' "$GITHUB_TOKEN" | base64)"
    git -c "http.https://github.com/.extraheader=AUTHORIZATION: basic $b64" "$@"
  else
    git "$@"
  fi
}

if [[ -d "$DEST/.git" ]]; then
  git -C "$DEST" config core.hooksPath /dev/null
  g -C "$DEST" fetch --depth=1 origin "$REF"
  git -C "$DEST" checkout -f FETCH_HEAD
else
  tmpl="$(mktemp -d)"
  g -c core.hooksPath=/dev/null \
    clone --template "$tmpl" --depth 1 --branch "$REF" \
    "https://github.com/${REPO}.git" "$DEST"
  rm -rf "$tmpl"
fi

g -C "$DEST" lfs pull



================================================
FILE: chrome-sandboxing-seccomp-profile.json
================================================
{
	"defaultAction": "SCMP_ACT_ERRNO",
	"defaultErrnoRet": 1,
	"archMap": [
		{
			"architecture": "SCMP_ARCH_X86_64",
			"subArchitectures": [
				"SCMP_ARCH_X86",
				"SCMP_ARCH_X32"
			]
		},
		{
			"architecture": "SCMP_ARCH_AARCH64",
			"subArchitectures": [
				"SCMP_ARCH_ARM"
			]
		},
		{
			"architecture": "SCMP_ARCH_MIPS64",
			"subArchitectures": [
				"SCMP_ARCH_MIPS",
				"SCMP_ARCH_MIPS64N32"
			]
		},
		{
			"architecture": "SCMP_ARCH_MIPS64N32",
			"subArchitectures": [
				"SCMP_ARCH_MIPS",
				"SCMP_ARCH_MIPS64"
			]
		},
		{
			"architecture": "SCMP_ARCH_MIPSEL64",
			"subArchitectures": [
				"SCMP_ARCH_MIPSEL",
				"SCMP_ARCH_MIPSEL64N32"
			]
		},
		{
			"architecture": "SCMP_ARCH_MIPSEL64N32",
			"subArchitectures": [
				"SCMP_ARCH_MIPSEL",
				"SCMP_ARCH_MIPSEL64"
			]
		},
		{
			"architecture": "SCMP_ARCH_S390X",
			"subArchitectures": [
				"SCMP_ARCH_S390"
			]
		},
		{
			"architecture": "SCMP_ARCH_RISCV64",
			"subArchitectures": null
		}
	],
	"syscalls": [
		{
			"names": [
				"accept",
				"accept4",
				"access",
				"adjtimex",
				"alarm",
				"bind",
				"brk",
				"capget",
				"capset",
				"chdir",
				"chmod",
				"chown",
				"chown32",
				"clock_adjtime",
				"clock_adjtime64",
				"clock_getres",
				"clock_getres_time64",
				"clock_gettime",
				"clock_gettime64",
				"clock_nanosleep",
				"clock_nanosleep_time64",
				"close",
				"close_range",
				"connect",
				"copy_file_range",
				"creat",
				"dup",
				"dup2",
				"dup3",
				"epoll_create",
				"epoll_create1",
				"epoll_ctl",
				"epoll_ctl_old",
				"epoll_pwait",
				"epoll_pwait2",
				"epoll_wait",
				"epoll_wait_old",
				"eventfd",
				"eventfd2",
				"execve",
				"execveat",
				"exit",
				"exit_group",
				"faccessat",
				"faccessat2",
				"fadvise64",
				"fadvise64_64",
				"fallocate",
				"fanotify_mark",
				"fchdir",
				"fchmod",
				"fchmodat",
				"fchown",
				"fchown32",
				"fchownat",
				"fcntl",
				"fcntl64",
				"fdatasync",
				"fgetxattr",
				"flistxattr",
				"flock",
				"fork",
				"fremovexattr",
				"fsetxattr",
				"fstat",
				"fstat64",
				"fstatat64",
				"fstatfs",
				"fstatfs64",
				"fsync",
				"ftruncate",
				"ftruncate64",
				"futex",
				"futex_time64",
				"futex_waitv",
				"futimesat",
				"getcpu",
				"getcwd",
				"getdents",
				"getdents64",
				"getegid",
				"getegid32",
				"geteuid",
				"geteuid32",
				"getgid",
				"getgid32",
				"getgroups",
				"getgroups32",
				"getitimer",
				"getpeername",
				"getpgid",
				"getpgrp",
				"getpid",
				"getppid",
				"getpriority",
				"getrandom",
				"getresgid",
				"getresgid32",
				"getresuid",
				"getresuid32",
				"getrlimit",
				"get_robust_list",
				"getrusage",
				"getsid",
				"getsockname",
				"getsockopt",
				"get_thread_area",
				"gettid",
				"gettimeofday",
				"getuid",
				"getuid32",
				"getxattr",
				"inotify_add_watch",
				"inotify_init",
				"inotify_init1",
				"inotify_rm_watch",
				"io_cancel",
				"ioctl",
				"io_destroy",
				"io_getevents",
				"io_pgetevents",
				"io_pgetevents_time64",
				"ioprio_get",
				"ioprio_set",
				"io_setup",
				"io_submit",
				"io_uring_enter",
				"io_uring_register",
				"io_uring_setup",
				"ipc",
				"kill",
				"landlock_add_rule",
				"landlock_create_ruleset",
				"landlock_restrict_self",
				"lchown",
				"lchown32",
				"lgetxattr",
				"link",
				"linkat",
				"listen",
				"listxattr",
				"llistxattr",
				"_llseek",
				"lremovexattr",
				"lseek",
				"lsetxattr",
				"lstat",
				"lstat64",
				"madvise",
				"membarrier",
				"memfd_create",
				"memfd_secret",
				"mincore",
				"mkdir",
				"mkdirat",
				"mknod",
				"mknodat",
				"mlock",
				"mlock2",
				"mlockall",
				"mmap",
				"mmap2",
				"mprotect",
				"mq_getsetattr",
				"mq_notify",
				"mq_open",
				"mq_timedreceive",
				"mq_timedreceive_time64",
				"mq_timedsend",
				"mq_timedsend_time64",
				"mq_unlink",
				"mremap",
				"msgctl",
				"msgget",
				"msgrcv",
				"msgsnd",
				"msync",
				"munlock",
				"munlockall",
				"munmap",
				"name_to_handle_at",
				"nanosleep",
				"newfstatat",
				"_newselect",
				"open",
				"openat",
				"openat2",
				"pause",
				"pidfd_open",
				"pidfd_send_signal",
				"pipe",
				"pipe2",
				"pkey_alloc",
				"pkey_free",
				"pkey_mprotect",
				"poll",
				"ppoll",
				"ppoll_time64",
				"prctl",
				"pread64",
				"preadv",
				"preadv2",
				"prlimit64",
				"process_mrelease",
				"pselect6",
				"pselect6_time64",
				"pwrite64",
				"pwritev",
				"pwritev2",
				"read",
				"readahead",
				"readlink",
				"readlinkat",
				"readv",
				"recv",
				"recvfrom",
				"recvmmsg",
				"recvmmsg_time64",
				"recvmsg",
				"remap_file_pages",
				"removexattr",
				"rename",
				"renameat",
				"renameat2",
				"restart_syscall",
				"rmdir",
				"rseq",
				"rt_sigaction",
				"rt_sigpending",
				"rt_sigprocmask",
				"rt_sigqueueinfo",
				"rt_sigreturn",
				"rt_sigsuspend",
				"rt_sigtimedwait",
				"rt_sigtimedwait_time64",
				"rt_tgsigqueueinfo",
				"sched_getaffinity",
				"sched_getattr",
				"sched_getparam",
				"sched_get_priority_max",
				"sched_get_priority_min",
				"sched_getscheduler",
				"sched_rr_get_interval",
				"sched_rr_get_interval_time64",
				"sched_setaffinity",
				"sched_setattr",
				"sched_setparam",
				"sched_setscheduler",
				"sched_yield",
				"seccomp",
				"select",
				"semctl",
				"semget",
				"semop",
				"semtimedop",
				"semtimedop_time64",
				"send",
				"sendfile",
				"sendfile64",
				"sendmmsg",
				"sendmsg",
				"sendto",
				"setfsgid",
				"setfsgid32",
				"setfsuid",
				"setfsuid32",
				"setgid",
				"setgid32",
				"setgroups",
				"setgroups32",
				"setitimer",
				"setpgid",
				"setpriority",
				"setregid",
				"setregid32",
				"setresgid",
				"setresgid32",
				"setresuid",
				"setresuid32",
				"setreuid",
				"setreuid32",
				"setrlimit",
				"set_robust_list",
				"setsid",
				"setsockopt",
				"set_thread_area",
				"set_tid_address",
				"setuid",
				"setuid32",
				"setxattr",
				"shmat",
				"shmctl",
				"shmdt",
				"shmget",
				"shutdown",
				"sigaltstack",
				"signalfd",
				"signalfd4",
				"sigprocmask",
				"sigreturn",
				"socketcall",
				"socketpair",
				"splice",
				"stat",
				"stat64",
				"statfs",
				"statfs64",
				"statx",
				"symlink",
				"symlinkat",
				"sync",
				"sync_file_range",
				"syncfs",
				"sysinfo",
				"tee",
				"tgkill",
				"time",
				"timer_create",
				"timer_delete",
				"timer_getoverrun",
				"timer_gettime",
				"timer_gettime64",
				"timer_settime",
				"timer_settime64",
				"timerfd_create",
				"timerfd_gettime",
				"timerfd_gettime64",
				"timerfd_settime",
				"timerfd_settime64",
				"times",
				"tkill",
				"truncate",
				"truncate64",
				"ugetrlimit",
				"umask",
				"uname",
				"unlink",
				"unlinkat",
				"utime",
				"utimensat",
				"utimensat_time64",
				"utimes",
				"vfork",
				"vmsplice",
				"wait4",
				"waitid",
				"waitpid",
				"write",
				"writev",
				"clone",
				"unshare"
			],
			"action": "SCMP_ACT_ALLOW"
		},
		{
			"names": [
				"process_vm_readv",
				"process_vm_writev",
				"ptrace"
			],
			"action": "SCMP_ACT_ALLOW",
			"includes": {
				"minKernel": "4.8"
			}
		},
		{
			"names": [
				"socket"
			],
			"action": "SCMP_ACT_ALLOW",
			"args": [
				{
					"index": 0,
					"value": 40,
					"op": "SCMP_CMP_NE"
				}
			]
		},
		{
			"names": [
				"personality"
			],
			"action": "SCMP_ACT_ALLOW",
			"args": [
				{
					"index": 0,
					"value": 0,
					"op": "SCMP_CMP_EQ"
				}
			]
		},
		{
			"names": [
				"personality"
			],
			"action": "SCMP_ACT_ALLOW",
			"args": [
				{
					"index": 0,
					"value": 8,
					"op": "SCMP_CMP_EQ"
				}
			]
		},
		{
			"names": [
				"personality"
			],
			"action": "SCMP_ACT_ALLOW",
			"args": [
				{
					"index": 0,
					"value": 131072,
					"op": "SCMP_CMP_EQ"
				}
			]
		},
		{
			"names": [
				"personality"
			],
			"action": "SCMP_ACT_ALLOW",
			"args": [
				{
					"index": 0,
					"value": 131080,
					"op": "SCMP_CMP_EQ"
				}
			]
		},
		{
			"names": [
				"personality"
			],
			"action": "SCMP_ACT_ALLOW",
			"args": [
				{
					"index": 0,
					"value": 4294967295,
					"op": "SCMP_CMP_EQ"
				}
			]
		},
		{
			"names": [
				"sync_file_range2",
				"swapcontext"
			],
			"action": "SCMP_ACT_ALLOW",
			"includes": {
				"arches": [
					"ppc64le"
				]
			}
		},
		{
			"names": [
				"arm_fadvise64_64",
				"arm_sync_file_range",
				"sync_file_range2",
				"breakpoint",
				"cacheflush",
				"set_tls"
			],
			"action": "SCMP_ACT_ALLOW",
			"includes": {
				"arches": [
					"arm",
					"arm64"
				]
			}
		},
		{
			"names": [
				"arch_prctl"
			],
			"action": "SCMP_ACT_ALLOW",
			"includes": {
				"arches": [
					"amd64",
					"x32"
				]
			}
		},
		{
			"names": [
				"modify_ldt"
			],
			"action": "SCMP_ACT_ALLOW",
			"includes": {
				"arches": [
					"amd64",
					"x32",
					"x86"
				]
			}
		},
		{
			"names": [
				"s390_pci_mmio_read",
				"s390_pci_mmio_write",
				"s390_runtime_instr"
			],
			"action": "SCMP_ACT_ALLOW",
			"includes": {
				"arches": [
					"s390",
					"s390x"
				]
			}
		},
		{
			"names": [
				"riscv_flush_icache"
			],
			"action": "SCMP_ACT_ALLOW",
			"includes": {
				"arches": [
					"riscv64"
				]
			}
		},
		{
			"names": [
				"open_by_handle_at"
			],
			"action": "SCMP_ACT_ALLOW",
			"includes": {
				"caps": [
					"CAP_DAC_READ_SEARCH"
				]
			}
		},
		{
			"names": [
				"bpf",
				"clone3",
				"fanotify_init",
				"fsconfig",
				"fsmount",
				"fsopen",
				"fspick",
				"lookup_dcookie",
				"mount",
				"mount_setattr",
				"move_mount",
				"open_tree",
				"perf_event_open",
				"quotactl",
				"quotactl_fd",
				"setdomainname",
				"sethostname",
				"setns",
				"syslog",
				"umount",
				"umount2"
			],
			"action": "SCMP_ACT_ALLOW",
			"includes": {
				"caps": [
					"CAP_SYS_ADMIN"
				]
			}
		},
		{
			"names": [
				"clone3"
			],
			"action": "SCMP_ACT_ERRNO",
			"errnoRet": 38,
			"excludes": {
				"caps": [
					"CAP_SYS_ADMIN"
				]
			}
		},
		{
			"names": [
				"reboot"
			],
			"action": "SCMP_ACT_ALLOW",
			"includes": {
				"caps": [
					"CAP_SYS_BOOT"
				]
			}
		},
		{
			"names": [
				"chroot"
			],
			"action": "SCMP_ACT_ALLOW",
			"includes": {
				"caps": [
					"CAP_SYS_CHROOT"
				]
			}
		},
		{
			"names": [
				"delete_module",
				"init_module",
				"finit_module"
			],
			"action": "SCMP_ACT_ALLOW",
			"includes": {
				"caps": [
					"CAP_SYS_MODULE"
				]
			}
		},
		{
			"names": [
				"acct"
			],
			"action": "SCMP_ACT_ALLOW",
			"includes": {
				"caps": [
					"CAP_SYS_PACCT"
				]
			}
		},
		{
			"names": [
				"kcmp",
				"pidfd_getfd",
				"process_madvise",
				"process_vm_readv",
				"process_vm_writev",
				"ptrace"
			],
			"action": "SCMP_ACT_ALLOW",
			"includes": {
				"caps": [
					"CAP_SYS_PTRACE"
				]
			}
		},
		{
			"names": [
				"iopl",
				"ioperm"
			],
			"action": "SCMP_ACT_ALLOW",
			"includes": {
				"caps": [
					"CAP_SYS_RAWIO"
				]
			}
		},
		{
			"names": [
				"settimeofday",
				"stime",
				"clock_settime",
				"clock_settime64"
			],
			"action": "SCMP_ACT_ALLOW",
			"includes": {
				"caps": [
					"CAP_SYS_TIME"
				]
			}
		},
		{
			"names": [
				"vhangup"
			],
			"action": "SCMP_ACT_ALLOW",
			"includes": {
				"caps": [
					"CAP_SYS_TTY_CONFIG"
				]
			}
		},
		{
			"names": [
				"get_mempolicy",
				"mbind",
				"set_mempolicy"
			],
			"action": "SCMP_ACT_ALLOW",
			"includes": {
				"caps": [
					"CAP_SYS_NICE"
				]
			}
		},
		{
			"names": [
				"syslog"
			],
			"action": "SCMP_ACT_ALLOW",
			"includes": {
				"caps": [
					"CAP_SYSLOG"
				]
			}
		},
		{
			"names": [
				"bpf"
			],
			"action": "SCMP_ACT_ALLOW",
			"includes": {
				"caps": [
					"CAP_BPF"
				]
			}
		},
		{
			"names": [
				"perf_event_open"
			],
			"action": "SCMP_ACT_ALLOW",
			"includes": {
				"caps": [
					"CAP_PERFMON"
				]
			}
		}
	]
}


================================================
FILE: cmd/server/http.go
================================================
// Copyright 2023 LiveKit, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package main

import (
	"net/http"

	"github.com/livekit/egress/pkg/server"
	"github.com/livekit/protocol/logger"
)

type httpHandler struct {
	svc *server.Server
}

func (h *httpHandler) ServeHTTP(w http.ResponseWriter, _ *http.Request) {
	info, err := h.svc.Status()
	if err != nil {
		logger.Errorw("failed to read status", err)
	}

	w.Header().Set("Content-Type", "application/json")
	_, _ = w.Write(info)
}


================================================
FILE: cmd/server/main.go
================================================
// Copyright 2023 LiveKit, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package main

import (
	"context"
	"embed"
	"fmt"
	"io/fs"
	"net/http"
	"os"
	"os/signal"
	"syscall"

	"github.com/urfave/cli/v3"
	"google.golang.org/protobuf/encoding/protojson"

	"github.com/livekit/egress/pkg/config"
	"github.com/livekit/egress/pkg/errors"
	"github.com/livekit/egress/pkg/handler"
	"github.com/livekit/egress/pkg/info"
	"github.com/livekit/egress/pkg/server"
	"github.com/livekit/egress/version"
	"github.com/livekit/protocol/logger"
	lkredis "github.com/livekit/protocol/redis"
	"github.com/livekit/protocol/rpc"
	_ "github.com/livekit/protocol/utils/hwstats/maxprocs"
	"github.com/livekit/psrpc"
)

var (
	//go:embed templates
	templateEmbedFs embed.FS
)

func main() {
	cmd := &cli.Command{
		Name:        "egress",
		Usage:       "LiveKit Egress",
		Version:     version.Version,
		Description: "runs the recorder in standalone mode or as a service",
		Commands: []*cli.Command{
			{
				Name:        "run-handler",
				Description: "runs a request in a new process",
				Flags: []cli.Flag{
					&cli.StringFlag{
						Name: "request",
					},
					&cli.StringFlag{
						Name: "config",
					},
				},
				Action: runHandler,
				Hidden: true,
			},
		},
		Flags: []cli.Flag{
			&cli.StringFlag{
				Name:    "config",
				Usage:   "LiveKit Egress yaml config file",
				Sources: cli.EnvVars("EGRESS_CONFIG_FILE"),
			},
			&cli.StringFlag{
				Name:    "config-body",
				Usage:   "LiveKit Egress yaml config body",
				Sources: cli.EnvVars("EGRESS_CONFIG_BODY"),
			},
		},
		Action: runService,
	}

	if err := cmd.Run(context.Background(), os.Args); err != nil {
		fmt.Println(err)
		os.Exit(1)
	}
}

func runService(_ context.Context, c *cli.Command) error {
	configFile := c.String("config")
	configBody := c.String("config-body")
	if configBody == "" {
		if configFile == "" {
			return errors.ErrNoConfig
		}
		content, err := os.ReadFile(configFile)
		if err != nil {
			return err
		}
		configBody = string(content)
	}

	conf, err := config.NewServiceConfig(configBody)
	if err != nil {
		return err
	}

	rc, err := lkredis.GetRedisClient(conf.Redis)
	if err != nil {
		return err
	}

	bus := psrpc.NewRedisMessageBus(rc)
	ioClient, err := info.NewSessionReporter(&conf.BaseConfig, bus)
	if err != nil {
		return err
	}
	svc, err := server.NewServer(conf, bus, ioClient)
	if err != nil {
		return err
	}

	if conf.HealthPort != 0 {
		go func() {
			_ = http.ListenAndServe(fmt.Sprintf(":%d", conf.HealthPort), &httpHandler{svc: svc})
		}()
	}

	stopChan := make(chan os.Signal, 1)
	signal.Notify(stopChan, syscall.SIGTERM, syscall.SIGQUIT)

	killChan := make(chan os.Signal, 1)
	signal.Notify(killChan, syscall.SIGINT)

	go func() {
		select {
		case sig := <-stopChan:
			logger.Infow("exit requested, finishing recording then shutting down", "signal", sig)
			svc.Shutdown(true, false)
		case sig := <-killChan:
			logger.Infow("exit requested, stopping recording and shutting down", "signal", sig)
			svc.Shutdown(true, true)
		}
	}()

	rfs, err := fs.Sub(templateEmbedFs, "templates")
	if err != nil {
		return err
	}

	err = svc.StartTemplatesServer(rfs)
	if err != nil {
		return err
	}

	return svc.Run()
}

func runHandler(_ context.Context, c *cli.Command) error {
	configBody := c.String("config")
	if configBody == "" {
		return errors.ErrNoConfig
	}

	req := &rpc.StartEgressRequest{}
	reqString := c.String("request")
	err := protojson.Unmarshal([]byte(reqString), req)
	if err != nil {
		return err
	}

	conf, err := config.NewPipelineConfig(configBody, req)
	if err != nil {
		return err
	}

	logger.Debugw("handler launched")

	err = os.MkdirAll(conf.TmpDir, 0755)
	if err != nil {
		return err
	}
	defer os.RemoveAll(conf.TmpDir)
	_ = os.Setenv("TMPDIR", conf.TmpDir)

	rc, err := lkredis.GetRedisClient(conf.Redis)
	if err != nil {
		return err
	}

	killChan := make(chan os.Signal, 1)
	signal.Notify(killChan, syscall.SIGINT)

	bus := psrpc.NewRedisMessageBus(rc)
	h, err := handler.NewHandler(conf, bus)
	if err != nil {
		// service will send info update and shut down
		logger.Errorw("failed to create handler", err)
		return err
	}

	go func() {
		sig := <-killChan
		logger.Infow("exit requested, stopping recording and shutting down", "signal", sig)
		h.Kill()
	}()

	h.Run()
	return nil
}


================================================
FILE: cmd/template_version/main.go
================================================
// Copyright 2025 LiveKit, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package main

import (
	"fmt"

	"github.com/livekit/egress/version"
)

func main() {
	fmt.Println(version.TemplateVersion)
}


================================================
FILE: go.mod
================================================
module github.com/livekit/egress

replace github.com/go-gst/go-gst => github.com/livekit/gst-go v0.0.0-20250701011214-e7f61abd14cb

go 1.26.1

tool github.com/maxbrunsfeld/counterfeiter/v6

require (
	cloud.google.com/go/storage v1.55.0
	github.com/Azure/azure-storage-blob-go v0.15.0
	github.com/aws/aws-sdk-go-v2 v1.41.5
	github.com/aws/aws-sdk-go-v2/config v1.29.17
	github.com/aws/aws-sdk-go-v2/credentials v1.17.70
	github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.81
	github.com/aws/aws-sdk-go-v2/service/s3 v1.97.3
	github.com/aws/smithy-go v1.24.2
	github.com/chromedp/cdproto v0.0.0-20260405000525-47a8ff65b46a
	github.com/chromedp/chromedp v0.15.1
	github.com/frostbyte73/core v0.1.1
	github.com/go-gst/go-glib v1.4.1-0.20241209142714-f53cebf18559
	github.com/go-gst/go-gst v1.4.0
	github.com/go-jose/go-jose/v4 v4.1.4
	github.com/googleapis/gax-go/v2 v2.14.2
	github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674
	github.com/linkdata/deadlock v0.5.5
	github.com/livekit/livekit-server v1.9.12
	github.com/livekit/mageutil v0.0.0-20250511045019-0f1ff63f7731
	github.com/livekit/media-sdk v0.0.0-20260422170315-2c3eed337496
	github.com/livekit/protocol v1.45.6
	github.com/livekit/psrpc v0.7.1
	github.com/livekit/server-sdk-go/v2 v2.16.2-0.20260401161108-50e969e2961f
	github.com/livekit/storage v0.0.0-20251113154014-aa1f4d0ce057
	github.com/llehouerou/go-mp3 v1.2.0
	github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58
	github.com/pion/rtp v1.10.1
	github.com/pion/webrtc/v4 v4.2.7
	github.com/prometheus/client_golang v1.23.0
	github.com/prometheus/client_model v0.6.2
	github.com/prometheus/common v0.67.5
	github.com/stretchr/testify v1.11.1
	github.com/urfave/cli/v3 v3.3.9
	go.opentelemetry.io/otel v1.40.0
	go.uber.org/atomic v1.11.0
	go.uber.org/zap v1.27.1
	golang.org/x/exp v0.0.0-20260212183809-81e46e3db34a
	google.golang.org/api v0.238.0
	google.golang.org/grpc v1.79.3
	google.golang.org/protobuf v1.36.11
	gopkg.in/natefinch/lumberjack.v2 v2.2.1
	gopkg.in/yaml.v3 v3.0.1
)

require (
	buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.11-20260209202127-80ab13bee0bf.1 // indirect
	buf.build/go/protovalidate v1.1.2 // indirect
	buf.build/go/protoyaml v0.6.0 // indirect
	cel.dev/expr v0.25.1 // indirect
	cloud.google.com/go v0.121.1 // indirect
	cloud.google.com/go/auth v0.16.2 // indirect
	cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
	cloud.google.com/go/compute/metadata v0.9.0 // indirect
	cloud.google.com/go/iam v1.5.2 // indirect
	cloud.google.com/go/monitoring v1.24.2 // indirect
	github.com/Azure/azure-pipeline-go v0.2.3 // indirect
	github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0 // indirect
	github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.51.0 // indirect
	github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.51.0 // indirect
	github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible // indirect
	github.com/antlr4-go/antlr/v4 v4.13.1 // indirect
	github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.8 // indirect
	github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.32 // indirect
	github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.21 // indirect
	github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.21 // indirect
	github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 // indirect
	github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.22 // indirect
	github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7 // indirect
	github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.13 // indirect
	github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.21 // indirect
	github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.21 // indirect
	github.com/aws/aws-sdk-go-v2/service/sso v1.25.5 // indirect
	github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.3 // indirect
	github.com/aws/aws-sdk-go-v2/service/sts v1.34.0 // indirect
	github.com/benbjohnson/clock v1.3.5 // indirect
	github.com/beorn7/perks v1.0.1 // indirect
	github.com/bep/debounce v1.2.1 // indirect
	github.com/cespare/xxhash/v2 v2.3.0 // indirect
	github.com/chromedp/sysutil v1.1.0 // indirect
	github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5 // indirect
	github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
	github.com/dennwc/iters v1.2.2 // indirect
	github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
	github.com/elliotchance/orderedmap/v2 v2.7.0 // indirect
	github.com/envoyproxy/go-control-plane/envoy v1.36.0 // indirect
	github.com/envoyproxy/protoc-gen-validate v1.3.0 // indirect
	github.com/felixge/httpsnoop v1.0.4 // indirect
	github.com/fsnotify/fsnotify v1.9.0 // indirect
	github.com/gammazero/deque v1.2.1 // indirect
	github.com/go-gst/go-pointer v0.0.0-20241127163939-ba766f075b4c // indirect
	github.com/go-jose/go-jose/v3 v3.0.5 // indirect
	github.com/go-json-experiment/json v0.0.0-20260214004413-d219187c3433 // indirect
	github.com/go-logr/logr v1.4.3 // indirect
	github.com/go-logr/stdr v1.2.2 // indirect
	github.com/gobwas/httphead v0.1.0 // indirect
	github.com/gobwas/pool v0.2.1 // indirect
	github.com/gobwas/ws v1.4.0 // indirect
	github.com/google/cel-go v0.27.0 // indirect
	github.com/google/s2a-go v0.1.9 // indirect
	github.com/google/uuid v1.6.0 // indirect
	github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect
	github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
	github.com/hashicorp/go-retryablehttp v0.7.7 // indirect
	github.com/jellydator/ttlcache/v3 v3.4.0 // indirect
	github.com/jxskiss/base62 v1.1.0 // indirect
	github.com/klauspost/compress v1.18.4 // indirect
	github.com/klauspost/cpuid/v2 v2.3.0 // indirect
	github.com/lithammer/shortuuid/v4 v4.2.0 // indirect
	github.com/livekit/mediatransportutil v0.0.0-20260113174415-2e8ba344fca3 // indirect
	github.com/mackerelio/go-osstat v0.2.6 // indirect
	github.com/magefile/mage v1.15.0 // indirect
	github.com/mattn/go-ieproxy v0.0.12 // indirect
	github.com/maxbrunsfeld/counterfeiter/v6 v6.12.1 // indirect
	github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
	github.com/nats-io/nats.go v1.48.0 // indirect
	github.com/nats-io/nkeys v0.4.15 // indirect
	github.com/nats-io/nuid v1.0.1 // indirect
	github.com/petermattis/goid v0.0.0-20250813065127-a731cc31b4fe // indirect
	github.com/pion/datachannel v1.6.0 // indirect
	github.com/pion/dtls/v3 v3.1.2 // indirect
	github.com/pion/ice/v4 v4.2.0 // indirect
	github.com/pion/interceptor v0.1.44 // indirect
	github.com/pion/logging v0.2.4 // indirect
	github.com/pion/mdns/v2 v2.1.0 // indirect
	github.com/pion/randutil v0.1.0 // indirect
	github.com/pion/rtcp v1.2.16 // indirect
	github.com/pion/sctp v1.9.2 // indirect
	github.com/pion/sdp/v3 v3.0.18 // indirect
	github.com/pion/srtp/v3 v3.0.10 // indirect
	github.com/pion/stun/v3 v3.1.1 // indirect
	github.com/pion/transport/v4 v4.0.1 // indirect
	github.com/pion/turn/v4 v4.1.4 // indirect
	github.com/pkg/errors v0.9.1 // indirect
	github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
	github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
	github.com/prometheus/procfs v0.19.2 // indirect
	github.com/puzpuzpuz/xsync/v3 v3.5.1 // indirect
	github.com/redis/go-redis/v9 v9.17.3 // indirect
	github.com/spiffe/go-spiffe/v2 v2.6.0 // indirect
	github.com/twitchtv/twirp v8.1.3+incompatible // indirect
	github.com/wlynxg/anet v0.0.5 // indirect
	github.com/zeebo/xxh3 v1.1.0 // indirect
	go.opentelemetry.io/auto/sdk v1.2.1 // indirect
	go.opentelemetry.io/contrib/detectors/gcp v1.39.0 // indirect
	go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 // indirect
	go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect
	go.opentelemetry.io/otel/metric v1.40.0 // indirect
	go.opentelemetry.io/otel/sdk v1.40.0 // indirect
	go.opentelemetry.io/otel/sdk/metric v1.40.0 // indirect
	go.opentelemetry.io/otel/trace v1.40.0 // indirect
	go.uber.org/multierr v1.11.0 // indirect
	go.uber.org/zap/exp v0.3.0 // indirect
	go.yaml.in/yaml/v2 v2.4.3 // indirect
	golang.org/x/crypto v0.48.0 // indirect
	golang.org/x/mod v0.33.0 // indirect
	golang.org/x/net v0.50.0 // indirect
	golang.org/x/oauth2 v0.34.0 // indirect
	golang.org/x/sync v0.19.0 // indirect
	golang.org/x/sys v0.42.0 // indirect
	golang.org/x/text v0.34.0 // indirect
	golang.org/x/time v0.14.0 // indirect
	golang.org/x/tools v0.42.0 // indirect
	google.golang.org/genproto v0.0.0-20250505200425-f936aa4a68b2 // indirect
	google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57 // indirect
	google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 // indirect
)


================================================
FILE: go.sum
================================================
buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.11-20260209202127-80ab13bee0bf.1 h1:PMmTMyvHScV9Mn8wc6ASge9uRcHy0jtqPd+fM35LmsQ=
buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.11-20260209202127-80ab13bee0bf.1/go.mod h1:tvtbpgaVXZX4g6Pn+AnzFycuRK3MOz5HJfEGeEllXYM=
buf.build/go/protovalidate v1.1.2 h1:83vYHoY8f34hB8MeitGaYE3CGVPFxwdEUuskh5qQpA0=
buf.build/go/protovalidate v1.1.2/go.mod h1:Ez3z+w4c+wG+EpW8ovgZaZPnPl2XVF6kaxgcv1NG/QE=
buf.build/go/protoyaml v0.6.0 h1:Nzz1lvcXF8YgNZXk+voPPwdU8FjDPTUV4ndNTXN0n2w=
buf.build/go/protoyaml v0.6.0/go.mod h1:RgUOsBu/GYKLDSIRgQXniXbNgFlGEZnQpRAUdLAFV2Q=
cel.dev/expr v0.25.1 h1:1KrZg61W6TWSxuNZ37Xy49ps13NUovb66QLprthtwi4=
cel.dev/expr v0.25.1/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4=
cloud.google.com/go v0.121.1 h1:S3kTQSydxmu1JfLRLpKtxRPA7rSrYPRPEUmL/PavVUw=
cloud.google.com/go v0.121.1/go.mod h1:nRFlrHq39MNVWu+zESP2PosMWA0ryJw8KUBZ2iZpxbw=
cloud.google.com/go/auth v0.16.2 h1:QvBAGFPLrDeoiNjyfVunhQ10HKNYuOwZ5noee0M5df4=
cloud.google.com/go/auth v0.16.2/go.mod h1:sRBas2Y1fB1vZTdurouM0AzuYQBMZinrUYL8EufhtEA=
cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc=
cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c=
cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs=
cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10=
cloud.google.com/go/iam v1.5.2 h1:qgFRAGEmd8z6dJ/qyEchAuL9jpswyODjA2lS+w234g8=
cloud.google.com/go/iam v1.5.2/go.mod h1:SE1vg0N81zQqLzQEwxL2WI6yhetBdbNQuTvIKCSkUHE=
cloud.google.com/go/logging v1.13.0 h1:7j0HgAp0B94o1YRDqiqm26w4q1rDMH7XNRU34lJXHYc=
cloud.google.com/go/logging v1.13.0/go.mod h1:36CoKh6KA/M0PbhPKMq6/qety2DCAErbhXT62TuXALA=
cloud.google.com/go/longrunning v0.6.7 h1:IGtfDWHhQCgCjwQjV9iiLnUta9LBCo8R9QmAFsS/PrE=
cloud.google.com/go/longrunning v0.6.7/go.mod h1:EAFV3IZAKmM56TyiE6VAP3VoTzhZzySwI/YI1s/nRsY=
cloud.google.com/go/monitoring v1.24.2 h1:5OTsoJ1dXYIiMiuL+sYscLc9BumrL3CarVLL7dd7lHM=
cloud.google.com/go/monitoring v1.24.2/go.mod h1:x7yzPWcgDRnPEv3sI+jJGBkwl5qINf+6qY4eq0I9B4U=
cloud.google.com/go/storage v1.55.0 h1:NESjdAToN9u1tmhVqhXCaCwYBuvEhZLLv0gBr+2znf0=
cloud.google.com/go/storage v1.55.0/go.mod h1:ztSmTTwzsdXe5syLVS0YsbFxXuvEmEyZj7v7zChEmuY=
cloud.google.com/go/trace v1.11.6 h1:2O2zjPzqPYAHrn3OKl029qlqG6W8ZdYaOWRyr8NgMT4=
cloud.google.com/go/trace v1.11.6/go.mod h1:GA855OeDEBiBMzcckLPE2kDunIpC72N+Pq8WFieFjnI=
dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
github.com/Azure/azure-pipeline-go v0.2.3 h1:7U9HBg1JFK3jHl5qmo4CTZKFTVgMwdFHMVtCdfBE21U=
github.com/Azure/azure-pipeline-go v0.2.3/go.mod h1:x841ezTBIMG6O3lAcl8ATHnsOPVl2bqk7S3ta6S6u4k=
github.com/Azure/azure-storage-blob-go v0.15.0 h1:rXtgp8tN1p29GvpGgfJetavIG0V7OgcSXPpwp3tx6qk=
github.com/Azure/azure-storage-blob-go v0.15.0/go.mod h1:vbjsVbX0dlxnRc4FFMPsS9BsJWPcne7GB7onqlPvz58=
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0=
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs=
github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
github.com/Azure/go-autorest/autorest/adal v0.9.13 h1:Mp5hbtOePIzM8pJVRa3YLrWWmZtoxRXqUEzCfJt3+/Q=
github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M=
github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw=
github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74=
github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k=
github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+ZtXWSmf4Tg=
github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8=
github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo=
github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0 h1:sBEjpZlNHzK1voKq9695PJSX2o5NEXl7/OL3coiIY0c=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0/go.mod h1:P4WPRUkOhJC13W//jWpyfJNDAIpvRbAUIYLX/4jtlE0=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.51.0 h1:fYE9p3esPxA/C0rQ0AHhP0drtPXDRhaWiwg1DPqO7IU=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.51.0/go.mod h1:BnBReJLvVYx2CS/UHOgVz2BXKXD9wsQPxZug20nZhd0=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.51.0 h1:OqVGm6Ei3x5+yZmSJG1Mh2NwHvpVmZ08CB5qJhT9Nuk=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.51.0/go.mod h1:SZiPHWGOOk3bl8tkevxkoiwPgsIl6CwrWcbwjfHZpdM=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.51.0 h1:6/0iUd0xrnX7qt+mLNRwg5c0PGv8wpE8K90ryANQwMI=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.51.0/go.mod h1:otE2jQekW/PqXk1Awf5lmfokJx4uwuqcj1ab5SpGeW0=
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw=
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk=
github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible h1:8psS8a+wKfiLt1iVDX79F7Y6wUM49Lcha2FMXt4UM8g=
github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8=
github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ=
github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw=
github.com/aws/aws-sdk-go-v2 v1.41.5 h1:dj5kopbwUsVUVFgO4Fi5BIT3t4WyqIDjGKCangnV/yY=
github.com/aws/aws-sdk-go-v2 v1.41.5/go.mod h1:mwsPRE8ceUUpiTgF7QmQIJ7lgsKUPQOUl3o72QBrE1o=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.8 h1:eBMB84YGghSocM7PsjmmPffTa+1FBUeNvGvFou6V/4o=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.8/go.mod h1:lyw7GFp3qENLh7kwzf7iMzAxDn+NzjXEAGjKS2UOKqI=
github.com/aws/aws-sdk-go-v2/config v1.29.17 h1:jSuiQ5jEe4SAMH6lLRMY9OVC+TqJLP5655pBGjmnjr0=
github.com/aws/aws-sdk-go-v2/config v1.29.17/go.mod h1:9P4wwACpbeXs9Pm9w1QTh6BwWwJjwYvJ1iCt5QbCXh8=
github.com/aws/aws-sdk-go-v2/credentials v1.17.70 h1:ONnH5CM16RTXRkS8Z1qg7/s2eDOhHhaXVd72mmyv4/0=
github.com/aws/aws-sdk-go-v2/credentials v1.17.70/go.mod h1:M+lWhhmomVGgtuPOhO85u4pEa3SmssPTdcYpP/5J/xc=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.32 h1:KAXP9JSHO1vKGCr5f4O6WmlVKLFFXgWYAGoJosorxzU=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.32/go.mod h1:h4Sg6FQdexC1yYG9RDnOvLbW1a/P986++/Y/a+GyEM8=
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.81 h1:E5ff1vZlAudg24j5lF6F6/gBpln2LjWxGdQDBSLfVe4=
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.81/go.mod h1:hHBLCuhHI4Aokvs5vdVoCDBzmFy86yxs5J7LEPQwQEM=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.21 h1:Rgg6wvjjtX8bNHcvi9OnXWwcE0a2vGpbwmtICOsvcf4=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.21/go.mod h1:A/kJFst/nm//cyqonihbdpQZwiUhhzpqTsdbhDdRF9c=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.21 h1:PEgGVtPoB6NTpPrBgqSE5hE/o47Ij9qk/SEZFbUOe9A=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.21/go.mod h1:p+hz+PRAYlY3zcpJhPwXlLC4C+kqn70WIHwnzAfs6ps=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 h1:bIqFDwgGXXN1Kpp99pDOdKMTTb5d2KyU5X/BZxjOkRo=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3/go.mod h1:H5O/EsxDWyU+LP/V8i5sm8cxoZgc2fdNR9bxlOFrQTo=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.22 h1:rWyie/PxDRIdhNf4DzRk0lvjVOqFJuNnO8WwaIRVxzQ=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.22/go.mod h1:zd/JsJ4P7oGfUhXn1VyLqaRZwPmZwg44Jf2dS84Dm3Y=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7 h1:5EniKhLZe4xzL7a+fU3C2tfUN4nWIqlLesfrjkuPFTY=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7/go.mod h1:x0nZssQ3qZSnIcePWLvcoFisRXJzcTVvYpAAdYX8+GI=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.13 h1:JRaIgADQS/U6uXDqlPiefP32yXTda7Kqfx+LgspooZM=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.13/go.mod h1:CEuVn5WqOMilYl+tbccq8+N2ieCy0gVn3OtRb0vBNNM=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.21 h1:c31//R3xgIJMSC8S6hEVq+38DcvUlgFY0FM6mSI5oto=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.21/go.mod h1:r6+pf23ouCB718FUxaqzZdbpYFyDtehyZcmP5KL9FkA=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.21 h1:ZlvrNcHSFFWURB8avufQq9gFsheUgjVD9536obIknfM=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.21/go.mod h1:cv3TNhVrssKR0O/xxLJVRfd2oazSnZnkUeTf6ctUwfQ=
github.com/aws/aws-sdk-go-v2/service/s3 v1.97.3 h1:HwxWTbTrIHm5qY+CAEur0s/figc3qwvLWsNkF4RPToo=
github.com/aws/aws-sdk-go-v2/service/s3 v1.97.3/go.mod h1:uoA43SdFwacedBfSgfFSjjCvYe8aYBS7EnU5GZ/YKMM=
github.com/aws/aws-sdk-go-v2/service/sso v1.25.5 h1:AIRJ3lfb2w/1/8wOOSqYb9fUKGwQbtysJ2H1MofRUPg=
github.com/aws/aws-sdk-go-v2/service/sso v1.25.5/go.mod h1:b7SiVprpU+iGazDUqvRSLf5XmCdn+JtT1on7uNL6Ipc=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.3 h1:BpOxT3yhLwSJ77qIY3DoHAQjZsc4HEGfMCE4NGy3uFg=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.3/go.mod h1:vq/GQR1gOFLquZMSrxUK/cpvKCNVYibNyJ1m7JrU88E=
github.com/aws/aws-sdk-go-v2/service/sts v1.34.0 h1:NFOJ/NXEGV4Rq//71Hs1jC/NvPs1ezajK+yQmkwnPV0=
github.com/aws/aws-sdk-go-v2/service/sts v1.34.0/go.mod h1:7ph2tGpfQvwzgistp2+zga9f+bCjlQJPkPUmMgDSD7w=
github.com/aws/smithy-go v1.24.2 h1:FzA3bu/nt/vDvmnkg+R8Xl46gmzEDam6mZ1hzmwXFng=
github.com/aws/smithy-go v1.24.2/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc=
github.com/benbjohnson/clock v1.3.5 h1:VvXlSJBzZpA/zum6Sj74hxwYI2DIxRWuNIoXAzHZz5o=
github.com/benbjohnson/clock v1.3.5/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bep/debounce v1.2.1 h1:v67fRdBA9UQu2NhLFXrSg0Brw7CexQekrBwDMM8bzeY=
github.com/bep/debounce v1.2.1/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0=
github.com/brianvoe/gofakeit/v6 v6.28.0 h1:Xib46XXuQfmlLS2EXRuJpqcw8St6qSZz75OUo0tgAW4=
github.com/brianvoe/gofakeit/v6 v6.28.0/go.mod h1:Xj58BMSnFqcn/fAQeSK+/PLtC5kSb7FJIq4JyGa8vEs=
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chromedp/cdproto v0.0.0-20260405000525-47a8ff65b46a h1:Kk4P1W58eAf+OUGtx51cM7CcJokJuBEmOxxwPdHFH4Q=
github.com/chromedp/cdproto v0.0.0-20260405000525-47a8ff65b46a/go.mod h1:cbyjALe67vDvlvdiG9369P8w5U2w6IshwtyD2f2Tvag=
github.com/chromedp/chromedp v0.15.1 h1:EJWiPm7BNqDqjYy6U0lTSL5wNH+iNt9GjC3a4gfjNyQ=
github.com/chromedp/chromedp v0.15.1/go.mod h1:CdTHtUqD/dqaFw/cvFWtTydoEQS44wLBuwbMR9EkOY4=
github.com/chromedp/sysutil v1.1.0 h1:PUFNv5EcprjqXZD9nJb9b/c9ibAbxiYo4exNWZyipwM=
github.com/chromedp/sysutil v1.1.0/go.mod h1:WiThHUdltqCNKGc4gaU50XgYjwjYIhKWoHGPTUfWTJ8=
github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5 h1:6xNmx7iTtyBRev0+D/Tv1FZd4SCg8axKApyNyRsAt/w=
github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5/go.mod h1:KdCmV+x/BuvyMxRnYBlmVaq4OLiKW6iRQfvC62cvdkI=
github.com/containerd/continuity v0.4.5 h1:ZRoN1sXq9u7V6QoHMcVWGhOwDFqZ4B9i5H6un1Wh0x4=
github.com/containerd/continuity v0.4.5/go.mod h1:/lNJvtJKUQStBzpVQ1+rasXO1LAWtUQssk28EZvJ3nE=
github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI=
github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M=
github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE=
github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dennwc/iters v1.2.2 h1:XH2/Etihiy9ZvPOVCR+icQXeYlhbvS7k0qro4x/2qQo=
github.com/dennwc/iters v1.2.2/go.mod h1:M9KuuMBeyEXYTmB7EnI9SCyALFCmPWOIxn5W1L0CjGg=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
github.com/docker/cli v29.0.0+incompatible h1:KgsN2RUFMNM8wChxryicn4p46BdQWpXOA1XLGBGPGAw=
github.com/docker/cli v29.0.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94=
github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE=
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/elliotchance/orderedmap/v2 v2.7.0 h1:WHuf0DRo63uLnldCPp9ojm3gskYwEdIIfAUVG5KhoOc=
github.com/elliotchance/orderedmap/v2 v2.7.0/go.mod h1:85lZyVbpGaGvHvnKa7Qhx7zncAdBIBq6u56Hb1PRU5Q=
github.com/envoyproxy/go-control-plane v0.14.0 h1:hbG2kr4RuFj222B6+7T83thSPqLjwBIfQawTkC++2HA=
github.com/envoyproxy/go-control-plane v0.14.0/go.mod h1:NcS5X47pLl/hfqxU70yPwL9ZMkUlwlKxtAohpi2wBEU=
github.com/envoyproxy/go-control-plane/envoy v1.36.0 h1:yg/JjO5E7ubRyKX3m07GF3reDNEnfOboJ0QySbH736g=
github.com/envoyproxy/go-control-plane/envoy v1.36.0/go.mod h1:ty89S1YCCVruQAm9OtKeEkQLTb+Lkz0k8v9W0Oxsv98=
github.com/envoyproxy/go-control-plane/ratelimit v0.1.0 h1:/G9QYbddjL25KvtKTv3an9lx6VBE2cnb8wp1vEGNYGI=
github.com/envoyproxy/go-control-plane/ratelimit v0.1.0/go.mod h1:Wk+tMFAFbCXaJPzVVHnPgRKdUdwW/KdbRt94AzgRee4=
github.com/envoyproxy/protoc-gen-validate v1.3.0 h1:TvGH1wof4H33rezVKWSpqKz5NXWg5VPuZ0uONDT6eb4=
github.com/envoyproxy/protoc-gen-validate v1.3.0/go.mod h1:HvYl7zwPa5mffgyeTUHA9zHIH36nmrm7oCbo4YKoSWA=
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/form3tech-oss/jwt-go v3.2.2+incompatible h1:TcekIExNqud5crz4xD2pavyTgWiPvpYe4Xau31I0PRk=
github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
github.com/frostbyte73/core v0.1.1 h1:ChhJOR7bAKOCPbA+lqDLE2cGKlCG5JXsDvvQr4YaJIA=
github.com/frostbyte73/core v0.1.1/go.mod h1:mhfOtR+xWAvwXiwor7jnqPMnu4fxbv1F2MwZ0BEpzZo=
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/gammazero/deque v1.2.1 h1:9fnQVFCCZ9/NOc7ccTNqzoKd1tCWOqeI05/lPqFPMGQ=
github.com/gammazero/deque v1.2.1/go.mod h1:5nSFkzVm+afG9+gy0VIowlqVAW4N8zNcMne+CMQVD2g=
github.com/go-gst/go-glib v1.4.1-0.20241209142714-f53cebf18559 h1:AK60n6W3FLZTp9H1KU5VOa8XefNO0w0R3pfszphwX14=
github.com/go-gst/go-glib v1.4.1-0.20241209142714-f53cebf18559/go.mod h1:ZWT4LXOO2PH8lSNu/dR5O2yoNQJKEgmijNa2d7nByK8=
github.com/go-gst/go-pointer v0.0.0-20241127163939-ba766f075b4c h1:x8kKRVDmz5BRlolmDZGcsuZ1l+js6TRL3QWBJjGVctM=
github.com/go-gst/go-pointer v0.0.0-20241127163939-ba766f075b4c/go.mod h1:qKw5ZZ0U58W6PU/7F/Lopv+14nKYmdXlOd7VnAZ17Mk=
github.com/go-jose/go-jose/v3 v3.0.5 h1:BLLJWbC4nMZOfuPVxoZIxeYsn6Nl2r1fITaJ78UQlVQ=
github.com/go-jose/go-jose/v3 v3.0.5/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ=
github.com/go-jose/go-jose/v4 v4.1.4 h1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA=
github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
github.com/go-json-experiment/json v0.0.0-20260214004413-d219187c3433 h1:vymEbVwYFP/L05h5TKQxvkXoKxNvTpjxYKdF1Nlwuao=
github.com/go-json-experiment/json v0.0.0-20260214004413-d219187c3433/go.mod h1:tphK2c80bpPhMOI4v6bIc2xWywPfbqi1Z06+RcrMkDg=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
github.com/gobwas/ws v1.4.0 h1:CTaoG1tojrh4ucGPcoJFiAQUAsEWekEWvLy7GsVNqGs=
github.com/gobwas/ws v1.4.0/go.mod h1:G3gNqMNtPppf5XUz7O4shetPpcZ1VJ7zt18dlUeakrc=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/cel-go v0.27.0 h1:e7ih85+4qVrBuqQWTW4FKSqZYokVuc3HnhH5keboFTo=
github.com/google/cel-go v0.27.0/go.mod h1:tTJ11FWqnhw5KKpnWpvW9CJC3Y9GK4EIS0WXnBbebzw=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/martian/v3 v3.3.3 h1:DIhPTQrbPkgs2yJYdXU/eNACCG5DVQjySNRNlflZ9Fc=
github.com/google/martian/v3 v3.3.3/go.mod h1:iEPrYcgCF7jA9OtScMFQyAlZZ4YXTKEtJ1E6RWzmBA0=
github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0=
github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/enterprise-certificate-proxy v0.3.6 h1:GW/XbdyBFQ8Qe+YAmFU9uHLo7OnF5tL52HFAgMmyrf4=
github.com/googleapis/enterprise-certificate-proxy v0.3.6/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA=
github.com/googleapis/gax-go/v2 v2.14.2 h1:eBLnkZ9635krYIPD+ag1USrOAI0Nr0QYF3+/3GqO0k0=
github.com/googleapis/gax-go/v2 v2.14.2/go.mod h1:ON64QhlJkhVtSqp4v1uaK92VyZ2gmvDQsweuyLV+8+w=
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo=
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA=
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k=
github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU=
github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk=
github.com/jellydator/ttlcache/v3 v3.4.0 h1:YS4P125qQS0tNhtL6aeYkheEaB/m8HCqdMMP4mnWdTY=
github.com/jellydator/ttlcache/v3 v3.4.0/go.mod h1:Hw9EgjymziQD3yGsQdf1FqFdpp7YjFMd4Srg5EJlgD4=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/jxskiss/base62 v1.1.0 h1:A5zbF8v8WXx2xixnAKD2w+abC+sIzYJX+nxmhA6HWFw=
github.com/jxskiss/base62 v1.1.0/go.mod h1:HhWAlUXvxKThfOlZbcuFzsqwtF5TcqS9ru3y5GfjWAc=
github.com/klauspost/compress v1.18.4 h1:RPhnKRAQ4Fh8zU2FY/6ZFDwTVTxgJ/EMydqSTzE9a2c=
github.com/klauspost/compress v1.18.4/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80 h1:6Yzfa6GP0rIo/kULo2bwGEkFvCePZ3qHDDTC3/J9Swo=
github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs=
github.com/linkdata/deadlock v0.5.5 h1:d6O+rzEqasSfamGDA8u7bjtaq7hOX8Ha4Zn36Wxrkvo=
github.com/linkdata/deadlock v0.5.5/go.mod h1:tXb28stzAD3trzEEK0UJWC+rZKuobCoPktPYzebb1u0=
github.com/lithammer/shortuuid/v4 v4.2.0 h1:LMFOzVB3996a7b8aBuEXxqOBflbfPQAiVzkIcHO0h8c=
github.com/lithammer/shortuuid/v4 v4.2.0/go.mod h1:D5noHZ2oFw/YaKCfGy0YxyE7M0wMbezmMjPdhyEFe6Y=
github.com/livekit/gst-go v0.0.0-20250701011214-e7f61abd14cb h1:1Vjk6NaXJZQiCvXGlKv38ossk4mNKHy5ob+eZygewdw=
github.com/livekit/gst-go v0.0.0-20250701011214-e7f61abd14cb/go.mod h1:pyCgY9XFSG0CAnJzoJ84R5XWn8rEj849EYJOwnAdB8k=
github.com/livekit/livekit-server v1.9.12 h1:VsPJAL2EbiBKt5SIhZWazNUSkEUZi/8P8kttGXUE1zw=
github.com/livekit/livekit-server v1.9.12/go.mod h1:Xh2ocHdH+z/D2u00GulDVVIDdgAlck1miHT0Ab2Skvg=
github.com/livekit/mageutil v0.0.0-20250511045019-0f1ff63f7731 h1:9x+U2HGLrSw5ATTo469PQPkqzdoU7be46ryiCDO3boc=
github.com/livekit/mageutil v0.0.0-20250511045019-0f1ff63f7731/go.mod h1:Rs3MhFwutWhGwmY1VQsygw28z5bWcnEYmS1OG9OxjOQ=
github.com/livekit/media-sdk v0.0.0-20260422170315-2c3eed337496 h1:yIEbXERsObyjGGoTnv7Bf37pQfAHrxRmPAN//tgzwJU=
github.com/livekit/media-sdk v0.0.0-20260422170315-2c3eed337496/go.mod h1:7ssWiG+U4xnbvLih9WiZbhQP6zIKMjgXdUtIE1bm/E8=
github.com/livekit/mediatransportutil v0.0.0-20260113174415-2e8ba344fca3 h1:v1Xc/q/547TjLX7Nw5y2vXNnmV0XYFAbhTJrtErQeDA=
github.com/livekit/mediatransportutil v0.0.0-20260113174415-2e8ba344fca3/go.mod h1:QBx/KHV6Vv00ggibg/WrOlqrkTciEA2Hc9DGWYr3Q9U=
github.com/livekit/protocol v1.45.6 h1:E+wKxs8ckKNYYTNyHm5nR1ShGLJ5DmA+WCEb5AJG11A=
github.com/livekit/protocol v1.45.6/go.mod h1:e6QdWDkfot+M2nRh0eitJUS0ZLuwvKCsfiz2pWWSG3s=
github.com/livekit/psrpc v0.7.1 h1:ms37az0QTD3UXIWuUC5D/SkmKOlRMVRsI261eBWu/Vw=
github.com/livekit/psrpc v0.7.1/go.mod h1:bZ4iHFQptTkbPnB0LasvRNu/OBYXEu1NA6O5BMFo9kk=
github.com/livekit/server-sdk-go/v2 v2.16.2-0.20260401161108-50e969e2961f h1:xSUtbUe3wBIFG/Ki3KEIsmjkOcfbpSOYJh2xxwJEllg=
github.com/livekit/server-sdk-go/v2 v2.16.2-0.20260401161108-50e969e2961f/go.mod h1:oQbYijcbPzfjBAOzoq7tz9Ktqur8JNRCd923VP8xOQQ=
github.com/livekit/storage v0.0.0-20251113154014-aa1f4d0ce057 h1:6XTEL0cSGkDPWYl1nAS/3cNOK1QoIo11C/O4pc4vPMg=
github.com/livekit/storage v0.0.0-20251113154014-aa1f4d0ce057/go.mod h1:m+EDdiNremMNJbggvfj5mY8w7nbzVGtZka5Jhj4pg0g=
github.com/llehouerou/go-mp3 v1.2.0 h1:2WN/bjCGhfPZAQbSSF35DxNKS/+HPnJ76TakA7Kyscs=
github.com/llehouerou/go-mp3 v1.2.0/go.mod h1:/Rl7E/VQpWTQDTJgr69iYVSkS1BZEh4X/ABV1XvIpHA=
github.com/mackerelio/go-osstat v0.2.6 h1:gs4U8BZeS1tjrL08tt5VUliVvSWP26Ai2Ob8Lr7f2i0=
github.com/mackerelio/go-osstat v0.2.6/go.mod h1:lRy8V9ZuHpuRVZh+vyTkODeDPl3/d5MgXHtLSaqG8bA=
github.com/magefile/mage v1.15.0 h1:BvGheCMAsG3bWUDbZ8AyXXpCNwU9u5CB6sM+HNb9HYg=
github.com/magefile/mage v1.15.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-ieproxy v0.0.1/go.mod h1:pYabZ6IHcRpFh7vIaLfK7rdcWgFEb3SFJ6/gNWuh88E=
github.com/mattn/go-ieproxy v0.0.12 h1:OZkUFJC3ESNZPQ+6LzC3VJIFSnreeFLQyqvBWtvfL2M=
github.com/mattn/go-ieproxy v0.0.12/go.mod h1:Vn+N61199DAnVeTgaF8eoB9PvLO8P3OBnG95ENh7B7c=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/maxbrunsfeld/counterfeiter/v6 v6.12.1 h1:D4O2wLxB384TS3ohBJMfolnxb4qGmoZ1PnWNtit8LYo=
github.com/maxbrunsfeld/counterfeiter/v6 v6.12.1/go.mod h1:RuJdxo0oI6dClIaMzdl3hewq3a065RH65dofJP03h8I=
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
github.com/moby/moby/api v1.52.0 h1:00BtlJY4MXkkt84WhUZPRqt5TvPbgig2FZvTbe3igYg=
github.com/moby/moby/api v1.52.0/go.mod h1:8mb+ReTlisw4pS6BRzCMts5M49W5M7bKt1cJy/YbAqc=
github.com/moby/moby/client v0.1.0 h1:nt+hn6O9cyJQqq5UWnFGqsZRTS/JirUqzPjEl0Bdc/8=
github.com/moby/moby/client v0.1.0/go.mod h1:O+/tw5d4a1Ha/ZA/tPxIZJapJRUS6LNZ1wiVRxYHyUE=
github.com/moby/sys/user v0.3.0 h1:9ni5DlcW5an3SvRSx4MouotOygvzaXbaSrc/wGDFWPo=
github.com/moby/sys/user v0.3.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs=
github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ=
github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/nats-io/nats.go v1.48.0 h1:pSFyXApG+yWU/TgbKCjmm5K4wrHu86231/w84qRVR+U=
github.com/nats-io/nats.go v1.48.0/go.mod h1:iRWIPokVIFbVijxuMQq4y9ttaBTMe0SFdlZfMDd+33g=
github.com/nats-io/nkeys v0.4.15 h1:JACV5jRVO9V856KOapQ7x+EY8Jo3qw1vJt/9Jpwzkk4=
github.com/nats-io/nkeys v0.4.15/go.mod h1:CpMchTXC9fxA5zrMo4KpySxNjiDVvr8ANOSZdiNfUrs=
github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
github.com/onsi/gomega v1.38.2 h1:eZCjf2xjZAqe+LeWvKb5weQ+NcPwX84kqJ0cZNxok2A=
github.com/onsi/gomega v1.38.2/go.mod h1:W2MJcYxRGV63b418Ai34Ud0hEdTVXq9NW9+Sx6uXf3k=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=
github.com/opencontainers/runc v1.3.3 h1:qlmBbbhu+yY0QM7jqfuat7M1H3/iXjju3VkP9lkFQr4=
github.com/opencontainers/runc v1.3.3/go.mod h1:D7rL72gfWxVs9cJ2/AayxB0Hlvn9g0gaF1R7uunumSI=
github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde h1:x0TT0RDC7UhAVbbWWBzr41ElhJx5tXPWkIHA2HWPRuw=
github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0=
github.com/ory/dockertest/v3 v3.12.0 h1:3oV9d0sDzlSQfHtIaB5k6ghUCVMVLpAY8hwrqoCyRCw=
github.com/ory/dockertest/v3 v3.12.0/go.mod h1:aKNDTva3cp8dwOWwb9cWuX84aH5akkxXRvO7KCwWVjE=
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 h1:onHthvaw9LFnH4t2DcNVpwGmV9E1BkGknEliJkfwQj0=
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58/go.mod h1:DXv8WO4yhMYhSNPKjeNKa5WY9YCIEBRbNzFFPJbWO6Y=
github.com/petermattis/goid v0.0.0-20250813065127-a731cc31b4fe h1:vHpqOnPlnkba8iSxU4j/CvDSS9J4+F4473esQsYLGoE=
github.com/petermattis/goid v0.0.0-20250813065127-a731cc31b4fe/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4=
github.com/pion/datachannel v1.6.0 h1:XecBlj+cvsxhAMZWFfFcPyUaDZtd7IJvrXqlXD/53i0=
github.com/pion/datachannel v1.6.0/go.mod h1:ur+wzYF8mWdC+Mkis5Thosk+u/VOL287apDNEbFpsIk=
github.com/pion/dtls/v3 v3.1.2 h1:gqEdOUXLtCGW+afsBLO0LtDD8GnuBBjEy6HRtyofZTc=
github.com/pion/dtls/v3 v3.1.2/go.mod h1:Hw/igcX4pdY69z1Hgv5x7wJFrUkdgHwAn/Q/uo7YHRo=
github.com/pion/ice/v4 v4.2.0 h1:jJC8S+CvXCCvIQUgx+oNZnoUpt6zwc34FhjWwCU4nlw=
github.com/pion/ice/v4 v4.2.0/go.mod h1:EgjBGxDgmd8xB0OkYEVFlzQuEI7kWSCFu+mULqaisy4=
github.com/pion/interceptor v0.1.44 h1:sNlZwM8dWXU9JQAkJh8xrarC0Etn8Oolcniukmuy0/I=
github.com/pion/interceptor v0.1.44/go.mod h1:4atVlBkcgXuUP+ykQF0qOCGU2j7pQzX2ofvPRFsY5RY=
github.com/pion/logging v0.2.4 h1:tTew+7cmQ+Mc1pTBLKH2puKsOvhm32dROumOZ655zB8=
github.com/pion/logging v0.2.4/go.mod h1:DffhXTKYdNZU+KtJ5pyQDjvOAh/GsNSyv1lbkFbe3so=
github.com/pion/mdns/v2 v2.1.0 h1:3IJ9+Xio6tWYjhN6WwuY142P/1jA0D5ERaIqawg/fOY=
github.com/pion/mdns/v2 v2.1.0/go.mod h1:pcez23GdynwcfRU1977qKU0mDxSeucttSHbCSfFOd9A=
github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA=
github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8=
github.com/pion/rtcp v1.2.16 h1:fk1B1dNW4hsI78XUCljZJlC4kZOPk67mNRuQ0fcEkSo=
github.com/pion/rtcp v1.2.16/go.mod h1:/as7VKfYbs5NIb4h6muQ35kQF/J0ZVNz2Z3xKoCBYOo=
github.com/pion/rtp v1.10.1 h1:xP1prZcCTUuhO2c83XtxyOHJteISg6o8iPsE2acaMtA=
github.com/pion/rtp v1.10.1/go.mod h1:rF5nS1GqbR7H/TCpKwylzeq6yDM+MM6k+On5EgeThEM=
github.com/pion/sctp v1.9.2 h1:HxsOzEV9pWoeggv7T5kewVkstFNcGvhMPx0GvUOUQXo=
github.com/pion/sctp v1.9.2/go.mod h1:OTOlsQ5EDQ6mQ0z4MUGXt2CgQmKyafBEXhUVqLRB6G8=
github.com/pion/sdp/v3 v3.0.18 h1:l0bAXazKHpepazVdp+tPYnrsy9dfh7ZbT8DxesH5ZnI=
github.com/pion/sdp/v3 v3.0.18/go.mod h1:ZREGo6A9ZygQ9XkqAj5xYCQtQpif0i6Pa81HOiAdqQ8=
github.com/pion/srtp/v3 v3.0.10 h1:tFirkpBb3XccP5VEXLi50GqXhv5SKPxqrdlhDCJlZrQ=
github.com/pion/srtp/v3 v3.0.10/go.mod h1:3mOTIB0cq9qlbn59V4ozvv9ClW/BSEbRp4cY0VtaR7M=
github.com/pion/stun/v3 v3.1.1 h1:CkQxveJ4xGQjulGSROXbXq94TAWu8gIX2dT+ePhUkqw=
github.com/pion/stun/v3 v3.1.1/go.mod h1:qC1DfmcCTQjl9PBaMa5wSn3x9IPmKxSdcCsxBcDBndM=
github.com/pion/transport/v3 v3.1.1 h1:Tr684+fnnKlhPceU+ICdrw6KKkTms+5qHMgw6bIkYOM=
github.com/pion/transport/v3 v3.1.1/go.mod h1:+c2eewC5WJQHiAA46fkMMzoYZSuGzA/7E2FPrOYHctQ=
github.com/pion/transport/v4 v4.0.1 h1:sdROELU6BZ63Ab7FrOLn13M6YdJLY20wldXW2Cu2k8o=
github.com/pion/transport/v4 v4.0.1/go.mod h1:nEuEA4AD5lPdcIegQDpVLgNoDGreqM/YqmEx3ovP4jM=
github.com/pion/turn/v4 v4.1.4 h1:EU11yMXKIsK43FhcUnjLlrhE4nboHZq+TXBIi3QpcxQ=
github.com/pion/turn/v4 v4.1.4/go.mod h1:ES1DXVFKnOhuDkqn9hn5VJlSWmZPaRJLyBXoOeO/BmQ=
github.com/pion/webrtc/v4 v4.2.7 h1:NAdsMXzQk/2yN1uV06SGxXqqVrkpDmNe09st/u16rrY=
github.com/pion/webrtc/v4 v4.2.7/go.mod h1:IzslI8Dkj2FFIre/Ua4TU86aXi+oC8g/nP1CW6Yuw34=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo=
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v1.23.0 h1:ust4zpdl9r4trLY/gSjlm07PuiBq2ynaXXlptpfy8Uc=
github.com/prometheus/client_golang v1.23.0/go.mod h1:i/o0R9ByOnHX0McrTMTyhYvKE4haaf2mW08I+jGAjEE=
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
github.com/prometheus/common v0.67.5 h1:pIgK94WWlQt1WLwAC5j2ynLaBRDiinoAb86HZHTUGI4=
github.com/prometheus/common v0.67.5/go.mod h1:SjE/0MzDEEAyrdr5Gqc6G+sXI67maCxzaT3A2+HqjUw=
github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws=
github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw=
github.com/puzpuzpuz/xsync/v3 v3.5.1 h1:GJYJZwO6IdxN/IKbneznS6yPkVC+c3zyY/j19c++5Fg=
github.com/puzpuzpuz/xsync/v3 v3.5.1/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA=
github.com/redis/go-redis/v9 v9.17.3 h1:fN29NdNrE17KttK5Ndf20buqfDZwGNgoUr9qjl1DQx4=
github.com/redis/go-redis/v9 v9.17.3/go.mod h1:u410H11HMLoB+TP67dz8rL9s6QW2j76l0//kSOd3370=
github.com/rodaine/protogofakeit v0.1.1 h1:ZKouljuRM3A+TArppfBqnH8tGZHOwM/pjvtXe9DaXH8=
github.com/rodaine/protogofakeit v0.1.1/go.mod h1:pXn/AstBYMaSfc1/RqH3N82pBuxtWgejz1AlYpY1mI0=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/sclevine/spec v1.4.0 h1:z/Q9idDcay5m5irkZ28M7PtQM4aOISzOpj4bUPkDee8=
github.com/sclevine/spec v1.4.0/go.mod h1:LvpgJaFyvQzRvc1kaDs0bulYwzC70PbiYjC4QnFHkOM=
github.com/shoenig/test v1.7.0 h1:eWcHtTXa6QLnBvm0jgEabMRN/uJ4DMV3M8xUGgRkZmk=
github.com/shoenig/test v1.7.0/go.mod h1:UxJ6u/x2v/TNs/LoLxBNJRV9DiwBBKYxXSyczsBHFoI=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/spiffe/go-spiffe/v2 v2.6.0 h1:l+DolpxNWYgruGQVV0xsfeya3CsC7m8iBzDnMpsbLuo=
github.com/spiffe/go-spiffe/v2 v2.6.0/go.mod h1:gm2SeUoMZEtpnzPNs2Csc0D/gX33k1xIx7lEzqblHEs=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/twitchtv/twirp v8.1.3+incompatible h1:+F4TdErPgSUbMZMwp13Q/KgDVuI7HJXP61mNV3/7iuU=
github.com/twitchtv/twirp v8.1.3+incompatible/go.mod h1:RRJoFSAmTEh2weEqWtpPE3vFK5YBhA6bqp2l1kfCC5A=
github.com/urfave/cli/v3 v3.3.9 h1:54roEDJcTWuucl6MSQ3B+pQqt1ePh/xOQokhEYl5Gfs=
github.com/urfave/cli/v3 v3.3.9/go.mod h1:FJSKtM/9AiiTOJL4fJ6TbMUkxBXn7GO9guZqoZtpYpo=
github.com/wlynxg/anet v0.0.5 h1:J3VJGi1gvo0JwZ/P1/Yc/8p63SoW98B5dHkYDmpgvvU=
github.com/wlynxg/anet v0.0.5/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA=
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo=
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ=
github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=
github.com/zeebo/xxh3 v1.1.0 h1:s7DLGDK45Dyfg7++yxI0khrfwq9661w9EN78eP/UZVs=
github.com/zeebo/xxh3 v1.1.0/go.mod h1:IisAie1LELR4xhVinxWS5+zf1lA4p0MW4T+w+W07F5s=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/contrib/detectors/gcp v1.39.0 h1:kWRNZMsfBHZ+uHjiH4y7Etn2FK26LAGkNFw7RHv1DhE=
go.opentelemetry.io/contrib/detectors/gcp v1.39.0/go.mod h1:t/OGqzHBa5v6RHZwrDBJ2OirWc+4q/w2fTbLZwAKjTk=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 h1:YH4g8lQroajqUwWbq/tr2QX1JFmEXaDLgG+ew9bLMWo=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0/go.mod h1:fvPi2qXDqFs8M4B4fmJhE92TyQs9Ydjlg3RvfUp+NbQ=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q=
go.opentelemetry.io/otel v1.40.0 h1:oA5YeOcpRTXq6NN7frwmwFR0Cn3RhTVZvXsP4duvCms=
go.opentelemetry.io/otel v1.40.0/go.mod h1:IMb+uXZUKkMXdPddhwAHm6UfOwJyh4ct1ybIlV14J0g=
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.36.0 h1:rixTyDGXFxRy1xzhKrotaHy3/KXdPhlWARrCgK+eqUY=
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.36.0/go.mod h1:dowW6UsM9MKbJq5JTz2AMVp3/5iW5I/TStsk8S+CfHw=
go.opentelemetry.io/otel/metric v1.40.0 h1:rcZe317KPftE2rstWIBitCdVp89A2HqjkxR3c11+p9g=
go.opentelemetry.io/otel/metric v1.40.0/go.mod h1:ib/crwQH7N3r5kfiBZQbwrTge743UDc7DTFVZrrXnqc=
go.opentelemetry.io/otel/sdk v1.40.0 h1:KHW/jUzgo6wsPh9At46+h4upjtccTmuZCFAc9OJ71f8=
go.opentelemetry.io/otel/sdk v1.40.0/go.mod h1:Ph7EFdYvxq72Y8Li9q8KebuYUr2KoeyHx0DRMKrYBUE=
go.opentelemetry.io/otel/sdk/metric v1.40.0 h1:mtmdVqgQkeRxHgRv4qhyJduP3fYJRMX4AtAlbuWdCYw=
go.opentelemetry.io/otel/sdk/metric v1.40.0/go.mod h1:4Z2bGMf0KSK3uRjlczMOeMhKU2rhUqdWNoKcYrtcBPg=
go.opentelemetry.io/otel/trace v1.40.0 h1:WA4etStDttCSYuhwvEa8OP8I5EWu24lkOzp+ZYblVjw=
go.opentelemetry.io/otel/trace v1.40.0/go.mod h1:zeAhriXecNGP/s2SEG3+Y8X9ujcJOTqQ5RgdEJcawiA=
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go.uber.org/zap/exp v0.3.0 h1:6JYzdifzYkGmTdRR59oYH+Ng7k49H9qVpWwNSsGJj3U=
go.uber.org/zap/exp v0.3.0/go.mod h1:5I384qq7XGxYyByIhHm6jg5CHkGY0nsTfbDLgDDlgJQ=
go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=
go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
golang.org/x/exp v0.0.0-20260212183809-81e46e3db34a h1:ovFr6Z0MNmU7nH8VaX5xqw+05ST2uO1exVfZPVqRC5o=
golang.org/x/exp v0.0.0-20260212183809-81e46e3db34a/go.mod h1:K79w1Vqn7PoiZn+TkNpx3BUWUQksGO3JcVX6qIjytmA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8=
golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191112182307-2180aed22343/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60=
golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM=
golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw=
golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191112214154-59a1497f0cea/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k=
golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
google.golang.org/api v0.238.0 h1:+EldkglWIg/pWjkq97sd+XxH7PxakNYoe/rkSTbnvOs=
google.golang.org/api v0.238.0/go.mod h1:cOVEm2TpdAGHL2z+UwyS+kmlGr3bVWQQ6sYEqkKje50=
google.golang.org/genproto v0.0.0-20250505200425-f936aa4a68b2 h1:1tXaIXCracvtsRxSBsYDiSBN0cuJvM7QYW+MrpIRY78=
google.golang.org/genproto v0.0.0-20250505200425-f936aa4a68b2/go.mod h1:49MsLSx0oWMOZqcpB3uL8ZOkAh1+TndpJ8ONoCBWiZk=
google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57 h1:JLQynH/LBHfCTSbDWl+py8C+Rg/k1OVH3xfcaiANuF0=
google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:kSJwQxqmFXeo79zOmbrALdflXQeAYcUbgS7PbpMknCY=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 h1:mWPCjDEyshlQYzBpMNHaEof6UX1PmHcaUODUywQ0uac=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE=
google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=


================================================
FILE: magefile.go
================================================
// Copyright 2023 LiveKit, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//go:build mage

package main

import (
	"context"
	"encoding/json"
	"fmt"
	"os"
	"path"
	"runtime"
	"strings"

	"github.com/livekit/egress/version"
	"github.com/livekit/mageutil"
)

const (
	gstVersion      = "1.24.12"
	libniceVersion  = "0.1.21"
	chromiumVersion = "146.0.7680.177-1"
	dockerBuild     = "docker build"
	dockerBuildX    = "docker buildx build --push --platform linux/amd64,linux/arm64"
)

type packageInfo struct {
	Dir string
}

func Proto() error {
	ctx := context.Background()
	fmt.Println("generating protobuf")

	// parse go mod output
	pkgOut, err := mageutil.Out(ctx, "go list -json -m github.com/livekit/protocol")
	if err != nil {
		return err
	}
	pi := packageInfo{}
	if err = json.Unmarshal(pkgOut, &pi); err != nil {
		return err
	}

	psrpcOut, err := mageutil.Out(ctx, "go list -json -m github.com/livekit/psrpc")
	if err != nil {
		return err
	}
	psrpcInfo := packageInfo{}
	if err = json.Unmarshal(psrpcOut, &psrpcInfo); err != nil {
		return err
	}

	_, err = mageutil.GetToolPath("protoc")
	if err != nil {
		return err
	}
	protocGoPath, err := mageutil.GetToolPath("protoc-gen-go")
	if err != nil {
		return err
	}
	protocGrpcGoPath, err := mageutil.GetToolPath("protoc-gen-go-grpc")
	if err != nil {
		return err
	}

	// generate grpc-related protos
	return mageutil.RunDir(ctx, "pkg/ipc", fmt.Sprintf(
		"protoc"+
			" --go_out ."+
			" --go-grpc_out ."+
			" --go_opt=paths=source_relative"+
			" --go-grpc_opt=paths=source_relative"+
			" --plugin=go=%s"+
			" --plugin=go-grpc=%s"+
			" -I%s -I%s -I=. ipc.proto",
		protocGoPath, protocGrpcGoPath, pi.Dir+"/protobufs", psrpcInfo.Dir+"/protoc-gen-psrpc/options",
	))
}

func EnsureMediaSamples() error {
	ctx := context.Background()

	const script = "build/test/fetch-media-samples.sh"
	if _, err := os.Stat(script); err != nil {
		return fmt.Errorf("missing %s: %w", script, err)
	}

	if err := mageutil.Run(ctx, script); err != nil {
		return err
	}

	if entries, _ := os.ReadDir("media-samples"); len(entries) == 0 {
		return fmt.Errorf("media-samples is empty after %s", script)
	}
	return nil
}

func Integration(configFile string) error {
	if err := EnsureMediaSamples(); err != nil {
		return err
	}

	ctx := context.Background()
	os.Setenv("DOCKER_BUILDKIT", "1")
	defer os.Unsetenv("DOCKER_BUILDKIT")

	if err := mageutil.Run(ctx,
		fmt.Sprintf("docker build --build-arg TEMPLATE_TAG=%s --build-arg DEADLOCK=1 -t egress-test -f build/test/Dockerfile .", version.TemplateVersion),
	); err != nil {
		return err
	}
	return Retest(configFile)
}

func Retest(configFile string) error {
	if configFile != "" {
		if strings.HasPrefix(configFile, "test/") {
			configFile = configFile[5:]
		} else {
			oldLocation := configFile
			idx := strings.LastIndex(configFile, "/")
			if idx != -1 {
				configFile = configFile[idx+1:]
			}
			if err := os.Rename(oldLocation, "test/"+configFile); err != nil {
				return err
			}
		}

		configFile = "/out/" + configFile
	}

	defer Dotfiles()
	defer func() {
		// for some reason, these can't be deleted from within the docker container
		files, _ := os.ReadDir("test/output")
		for _, file := range files {
			if file.IsDir() {
				d, _ := os.ReadDir(path.Join("test/output", file.Name()))
				if len(d) == 0 {
					_ = os.RemoveAll(path.Join("test/output", file.Name()))
				}
			}
		}
	}()

	dir, err := os.Getwd()
	if err != nil {
		return err
	}

	return mageutil.Run(context.Background(),
		fmt.Sprintf("docker run --rm -e EGRESS_CONFIG_FILE=%s -v %s/test:/out egress-test", configFile, dir),
	)
}

func Build() error {
	return mageutil.Run(context.Background(),
		fmt.Sprintf("docker pull livekit/chrome-installer:%s", chromiumVersion),
		fmt.Sprintf("docker pull livekit/gstreamer:%s-dev", gstVersion),
		fmt.Sprintf("docker build -t livekit/egress:latest --build-arg TEMPLATE_TAG=%s -f build/egress/Dockerfile .", version.TemplateVersion),
	)
}

func BuildTemplate() error {
	return mageutil.Run(context.Background(),
		"docker pull ubuntu:24.04",
		"docker build -t livekit/egress-templates -f ./build/template/Dockerfile .",
	)
}

func BuildGStreamer() error {
	return buildGstreamer(dockerBuild)
}

func buildGstreamer(cmd string) error {
	commands := []string{"docker pull ubuntu:23.10"}
	for _, build := range []string{"base", "dev", "prod", "prod-rs"} {
		commands = append(commands, fmt.Sprintf("%s"+
			" --build-arg GSTREAMER_VERSION=%s"+
			" --build-arg LIBNICE_VERSION=%s"+
			" -t livekit/gstreamer:%s-%s"+
			" -t livekit/gstreamer:%s-%s-%s"+
			" -f build/gstreamer/Dockerfile-%s"+
			" ./build/gstreamer",
			cmd, gstVersion, libniceVersion, gstVersion, build, gstVersion, build, runtime.GOARCH, build,
		))
	}

	return mageutil.Run(context.Background(), commands...)
}

func Dotfiles() error {
	files, err := os.ReadDir("test/output")
	if err != nil {
		return err
	}

	dots := make(map[string]bool)
	pngs := make(map[string]bool)
	for _, file := range files {
		name := file.Name()
		if strings.HasSuffix(name, ".dot") {
			dots[name[:len(name)-4]] = true
		} else if strings.HasSuffix(file.Name(), ".png") {
			pngs[name[:len(name)-4]] = true
		}
	}

	for name := range dots {
		if !pngs[name] {
			if err := mageutil.Run(context.Background(), fmt.Sprintf(
				"dot -Tpng test/output/%s.dot -o test/output/%s.png",
				name, name,
			)); err != nil {
				return err
			}
		}
	}

	return nil
}


================================================
FILE: pkg/config/base.go
================================================
// Copyright 2023 LiveKit, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package config

import (
	"os"
	"strings"
	"time"

	"github.com/livekit/protocol/logger"
	"github.com/livekit/protocol/logger/medialogutils"
	"github.com/livekit/protocol/redis"
	lksdk "github.com/livekit/server-sdk-go/v2"

	"github.com/livekit/egress/pkg/types"
)

const TmpDir = "/home/egress/tmp"

type BaseConfig struct {
	NodeID string // do not supply - will be overwritten

	// required
	Redis     *redis.RedisConfig `yaml:"redis"`      // redis config
	ApiKey    string             `yaml:"api_key"`    // (env LIVEKIT_API_KEY)
	ApiSecret string             `yaml:"api_secret"` // (env LIVEKIT_API_SECRET)
	WsUrl     string             `yaml:"ws_url"`     // (env LIVEKIT_WS_URL)

	// optional
	Logging              *logger.Config `yaml:"logging"`                // logging config
	TemplateBase         string         `yaml:"template_base"`          // custom template base url
	ClusterID            string         `yaml:"cluster_id"`             // cluster this instance belongs to
	EnableChromeSandbox  bool           `yaml:"enable_chrome_sandbox"`  // enable Chrome sandbox, requires extra docker configuration
	MaxUploadQueue       int            `yaml:"max_upload_queue"`       // maximum upload queue size, in minutes
	DisallowLocalStorage bool           `yaml:"disallow_local_storage"` // require an upload config for all requests
	IOCreateTimeout      time.Duration  `yaml:"io_create_timeout"`      // timeout for CreateEgress calls
	IOUpdateTimeout      time.Duration  `yaml:"io_update_timeout"`      // timeout for UpdateEgress calls
	IOSelectionTimeout   time.Duration  `yaml:"io_selection_timeout"`   // timeout for affinity stage of IO RPC
	IOWorkers            int            `yaml:"io_workers"`             // number of IO update workers

	SessionLimits          `yaml:"session_limits"` // session duration limits
	StorageConfig          *StorageConfig          `yaml:"storage,omitempty"`          // storage config
	BackupConfig           *StorageConfig          `yaml:"backup,omitempty"`           // backup config, for storage failures
	S3AssumeRoleKey        string                  `yaml:"s3_assume_role_key"`         // if set, this key is used for S3 uploads to assume the role defined in the assume_role_arn field of the S3 config
	S3AssumeRoleSecret     string                  `yaml:"s3_assume_role_secret"`      // if set, this secret is used for S3 uploads to assume the role defined in the assume_role_arn field of the S3 config
	S3AssumeRoleArn        string                  `yaml:"s3_assume_role_arn"`         // if set, this arn is used by default for S3 uploads
	S3AssumeRoleExternalID string                  `yaml:"s3_assume_role_external_id"` // if set, this external ID is used by default for S3 uploads

	// advanced
	Insecure                      bool                                `yaml:"insecure"`                           // allow chrome to connect to an insecure websocket, bypasses chrome LNA checks
	Debug                         DebugConfig                         `yaml:"debug"`                              // create dot file on internal error
	ChromeFlags                   map[string]interface{}              `yaml:"chrome_flags"`                       // additional flags to pass to Chrome
	Latency                       LatencyConfig                       `yaml:"latency"`                            // gstreamer latencies, modifying these may break the service
	LatencyOverrides              map[types.RequestType]LatencyConfig `yaml:"latency_overrides"`                  // latency overrides for different request types, experimental only, will be removed
	EnableOneShotSenderReportSync bool                                `yaml:"enable_one_shot_sender_report_sync"` // temporary rollout flag enabling one-shot sender report correction for room composite / track requests that previously used audio PTS adjustment disabling
	AudioTempoController          AudioTempoController                `yaml:"audio_tempo_controller"`             // audio tempo controller
	TestOverrides                 TestOverrides                       `yaml:"test_overrides"`                     // set of config overrides for testing purposes
}

type SessionLimits struct {
	FileOutputMaxDuration    time.Duration `yaml:"file_output_max_duration"`
	FileOutputMaxSize        int64         `yaml:"file_output_max_size"` // max on-disk size in bytes before stopping; 0 to disable
	StreamOutputMaxDuration  time.Duration `yaml:"stream_output_max_duration"`
	SegmentOutputMaxDuration time.Duration `yaml:"segment_output_max_duration"`
	ImageOutputMaxDuration   time.Duration `yaml:"image_output_max_duration"`
}

type DebugConfig struct {
	EnableProfiling     bool             `yaml:"enable_profiling"`      // create dot file and pprof on internal error
	EnableTrackLogging  bool             `yaml:"enable_track_logging"`  // log packets and keyframes for each track
	EnableStreamLogging bool             `yaml:"enable_stream_logging"` // log bytes and keyframes for each stream
	EnableChromeLogging bool             `yaml:"enable_chrome_logging"` // log all chrome console events
	StorageConfig       `yaml:",inline"` // upload config (S3, Azure, GCP, or AliOSS)
}

type LatencyConfig struct {
	JitterBufferLatency             time.Duration `yaml:"jitter_buffer_latency"`                         // jitter buffer max latency for sdk egress
	AudioMixerLatency               time.Duration `yaml:"audio_mixer_latency"`                           // audio mixer latency, must be greater than jitter buffer latency
	PipelineLatency                 time.Duration `yaml:"pipeline_latency"`                              // max latency for the entire pipeline
	RTPMaxAllowedTsDiff             time.Duration `ymal:"rtp_max_allowed_ts_diff"`                       // max allowed PTS discont. for a RTP stream, before applying PTS alignment
	RTPMaxDriftAdjustment           time.Duration `ymal:"rtp_max_drift_adjustment,omitempty"`            // max allowed drift adjustment for a RTP stream
	RTPDriftAdjustmentWindowPercent float64       `ymal:"rtp_drift_adjustment_window_percent,omitempty"` // how much to throttle drift adjustment, 0.0 disables it
	OldPacketThreshold              time.Duration `yaml:"old_packet_threshold,omitempty"`                // syncrhonizer drops packets older than this, 0 to disable packet drops
}

type AudioTempoController struct {
	Enabled        bool    `yaml:"enabled"`         // enable audio tempo adjustments for compensating PTS drift
	AdjustmentRate float64 `yaml:"adjustment_rate"` // rate at which to adjust the tempo to compensate for PTS drift
}

func (c *BaseConfig) initLogger(values ...interface{}) error {
	_, exists := os.LookupEnv("GST_DEBUG")

	// If GST_DEBUG is not set, use pre-defined values based on logging level
	if !exists {
		var gstDebug []string
		switch c.Logging.Level {
		case "debug":
			gstDebug = []string{"3"}
		case "info", "warn":
			gstDebug = []string{"2"}
		case "error":
			gstDebug = []string{"1"}
		}
		gstDebug = append(gstDebug,
			"rtmpclient:4",
			"srtlib:1",
		)

		if err := os.Setenv("GST_DEBUG", strings.Join(gstDebug, ",")); err != nil {
			return err
		}
	}

	zl, err := logger.NewZapLogger(c.Logging)
	if err != nil {
		return err
	}

	l := zl.WithValues(values...)

	logger.SetLogger(l, "egress")
	lksdk.SetLogger(medialogutils.NewOverrideLogger(l.WithComponent("lksdk")))
	return nil
}

func (c *BaseConfig) getLatencyConfig(requestType types.RequestType) LatencyConfig {
	if override, ok := c.LatencyOverrides[requestType]; ok {
		return override
	}
	return c.Latency
}


================================================
FILE: pkg/config/config_test.go
================================================
// Copyright 2023 LiveKit, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package config

import (
	"os"
	"testing"

	"github.com/stretchr/testify/require"

	"github.com/livekit/egress/pkg/types"
	"github.com/livekit/protocol/livekit"
)

func TestSegmentNaming(t *testing.T) {
	t.Cleanup(func() {
		_ = os.RemoveAll("conf_test/")
	})

	for _, test := range []struct {
		filenamePrefix               string
		playlistName                 string
		livePlaylistName             string
		expectedStorageDir           string
		expectedPlaylistFilename     string
		expectedLivePlaylistFilename string
		expectedSegmentPrefix        string
	}{
		{
			filenamePrefix: "", playlistName: "playlist", livePlaylistName: "",
			expectedStorageDir: "", expectedPlaylistFilename: "playlist.m3u8", expectedLivePlaylistFilename: "", expectedSegmentPrefix: "playlist",
		},
		{
			filenamePrefix: "", playlistName: "conf_test/playlist", livePlaylistName: "conf_test/live_playlist",
			expectedStorageDir: "conf_test/", expectedPlaylistFilename: "playlist.m3u8", expectedLivePlaylistFilename: "live_playlist.m3u8", expectedSegmentPrefix: "playlist",
		},
		{
			filenamePrefix: "filename", playlistName: "", livePlaylistName: "live_playlist2.m3u8",
			expectedStorageDir: "", expectedPlaylistFilename: "filename.m3u8", expectedLivePlaylistFilename: "live_playlist2.m3u8", expectedSegmentPrefix: "filename",
		},
		{
			filenamePrefix: "filename", playlistName: "playlist", livePlaylistName: "",
			expectedStorageDir: "", expectedPlaylistFilename: "playlist.m3u8", expectedLivePlaylistFilename: "", expectedSegmentPrefix: "filename",
		},
		{
			filenamePrefix: "filename", playlistName: "conf_test/", livePlaylistName: "",
			expectedStorageDir: "conf_test/", expectedPlaylistFilename: "filename.m3u8", expectedLivePlaylistFilename: "", expectedSegmentPrefix: "filename",
		},
		{
			filenamePrefix: "filename", playlistName: "conf_test/playlist", livePlaylistName: "",
			expectedStorageDir: "conf_test/", expectedPlaylistFilename: "playlist.m3u8", expectedLivePlaylistFilename: "", expectedSegmentPrefix: "filename",
		},
		{
			filenamePrefix: "conf_test/", playlistName: "playlist", livePlaylistName: "",
			expectedStorageDir: "conf_test/", expectedPlaylistFilename: "playlist.m3u8", expectedLivePlaylistFilename: "", expectedSegmentPrefix: "playlist",
		},
		{
			filenamePrefix: "conf_test/filename", playlistName: "playlist", livePlaylistName: "",
			expectedStorageDir: "conf_test/", expectedPlaylistFilename: "playlist.m3u8", expectedLivePlaylistFilename: "", expectedSegmentPrefix: "filename",
		},
		{
			filenamePrefix: "conf_test/filename", playlistName: "conf_test/playlist", livePlaylistName: "",
			expectedStorageDir: "conf_test/", expectedPlaylistFilename: "playlist.m3u8", expectedLivePlaylistFilename: "", expectedSegmentPrefix: "filename",
		},
		{
			filenamePrefix: "conf_test_2/filename", playlistName: "conf_test/playlist", livePlaylistName: "",
			expectedStorageDir: "conf_test/", expectedPlaylistFilename: "playlist.m3u8", expectedLivePlaylistFilename: "", expectedSegmentPrefix: "conf_test_2/filename",
		},
	} {
		p := &PipelineConfig{Info: &livekit.EgressInfo{EgressId: "egress_ID"}}
		seg := &livekit.SegmentedFileOutput{
			FilenamePrefix:   test.filenamePrefix,
			PlaylistName:     test.playlistName,
			LivePlaylistName: test.livePlaylistName,
		}
		o, err := p.getSegmentConfig(seg, seg)
		require.NoError(t, err)

		require.Equal(t, test.expectedStorageDir, o.StorageDir)
		require.Equal(t, test.expectedPlaylistFilename, o.PlaylistFilename)
		require.Equal(t, test.expectedLivePlaylistFilename, o.LivePlaylistFilename)
		require.Equal(t, test.expectedSegmentPrefix, o.SegmentPrefix)
	}
}

func TestValidateAndUpdateOutputParamsRejectsHLSMP3(t *testing.T) {
	p := &PipelineConfig{
		Outputs: map[types.EgressType][]OutputConfig{
			types.EgressTypeSegments: {
				&SegmentConfig{outputConfig: outputConfig{OutputType: types.OutputTypeHLS}},
			},
		},
	}

	p.AudioEnabled = true
	p.VideoEnabled = false
	p.AudioOutCodec = types.MimeTypeMP3
	p.Info = &livekit.EgressInfo{}

	err := p.validateAndUpdateOutputParams()
	require.Error(t, err)
	require.ErrorContains(t, err, "format application/x-mpegurl incompatible with codec audio/mpeg")
}

func TestValidateAndUpdateOutputParamsRejectsVideoFileMP3(t *testing.T) {
	p := &PipelineConfig{
		Outputs: map[types.EgressType][]OutputConfig{
			types.EgressTypeFile: {
				&FileConfig{outputConfig: outputConfig{OutputType: types.OutputTypeMP3}},
			},
		},
	}

	p.AudioEnabled = true
	p.VideoEnabled = true
	p.AudioOutCodec = types.MimeTypeMP3
	p.VideoOutCodec = types.MimeTypeH264
	p.Info = &livekit.EgressInfo{}

	err := p.validateAndUpdateOutputParams()
	require.Error(t, err)
	require.ErrorContains(t, err, "format audio/mpeg incompatible with codec video/h264")
}


================================================
FILE: pkg/config/encoding.go
================================================
// Copyright 2023 LiveKit, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package config

import (
	"github.com/livekit/egress/pkg/errors"
	"github.com/livekit/egress/pkg/types"
	"github.com/livekit/protocol/livekit"
)

func (p *PipelineConfig) applyPreset(preset livekit.EncodingOptionsPreset) {
	switch preset {
	case livekit.EncodingOptionsPreset_H264_720P_30:
		p.Width = 1280
		p.Height = 720
		p.Framerate = 30
		p.VideoBitrate = 3000

	case livekit.EncodingOptionsPreset_H264_720P_60:
		p.Width = 1280
		p.Height = 720
		p.Framerate = 60
		p.VideoBitrate = 4500

	case livekit.EncodingOptionsPreset_H264_1080P_30:
		p.Width = 1920
		p.Height = 1080
		p.Framerate = 30
		p.VideoBitrate = 4500

	case livekit.EncodingOptionsPreset_H264_1080P_60:
		p.Width = 1920
		p.Height = 1080
		p.Framerate = 60
		p.VideoBitrate = 6000

	case livekit.EncodingOptionsPreset_PORTRAIT_H264_720P_30:
		p.Width = 720
		p.Height = 1280
		p.Framerate = 30
		p.VideoBitrate = 3000

	case livekit.EncodingOptionsPreset_PORTRAIT_H264_720P_60:
		p.Width = 720
		p.Height = 1280
		p.Framerate = 60
		p.VideoBitrate = 4500

	case livekit.EncodingOptionsPreset_PORTRAIT_H264_1080P_30:
		p.Width = 1080
		p.Height = 1920
		p.Framerate = 30
		p.VideoBitrate = 4500

	case livekit.EncodingOptionsPreset_PORTRAIT_H264_1080P_60:
		p.Width = 1080
		p.Height = 1920
		p.Framerate = 60
		p.VideoBitrate = 6000
	}
}

func (p *PipelineConfig) applyAdvanced(advanced *livekit.EncodingOptions) error {
	// audio
	switch advanced.AudioCodec {
	case livekit.AudioCodec_OPUS:
		p.AudioOutCodec = types.MimeTypeOpus
	case livekit.AudioCodec_AAC:
		p.AudioOutCodec = types.MimeTypeAAC
	case livekit.AudioCodec_AC_MP3:
		p.AudioOutCodec = types.MimeTypeMP3
	}

	if advanced.AudioBitrate != 0 {
		p.AudioBitrate = advanced.AudioBitrate
	}
	if advanced.AudioFrequency != 0 {
		p.AudioFrequency = advanced.AudioFrequency
	}

	// video
	switch advanced.VideoCodec {
	case livekit.VideoCodec_H264_BASELINE:
		p.VideoOutCodec = types.MimeTypeH264
		p.VideoProfile = types.ProfileBaseline

	case livekit.VideoCodec_H264_MAIN:
		p.VideoOutCodec = types.MimeTypeH264

	case livekit.VideoCodec_H264_HIGH:
		p.VideoOutCodec = types.MimeTypeH264
		p.VideoProfile = types.ProfileHigh
	}

	if advanced.Width > 0 {
		if advanced.Width < 16 || advanced.Width%2 == 1 {
			return errors.ErrInvalidInput("width")
		}
		p.Width = advanced.Width
	}

	if advanced.Height > 0 {
		if advanced.Height < 16 || advanced.Height%2 == 1 {
			return errors.ErrInvalidInput("height")
		}
		p.Height = advanced.Height
	}

	switch advanced.Depth {
	case 0:
	case 8, 16, 24:
		p.Depth = advanced.Depth
	default:
		return errors.ErrInvalidInput("depth")
	}

	if advanced.Framerate != 0 {
		p.Framerate = advanced.Framerate
	}
	if advanced.VideoBitrate != 0 {
		p.VideoBitrate = advanced.VideoBitrate
	}
	if advanced.KeyFrameInterval != 0 {
		p.KeyFrameInterval = advanced.KeyFrameInterval
	}

	return nil
}


================================================
FILE: pkg/config/manifest.go
================================================
// Copyright 2023 LiveKit, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package config

import (
	"bytes"
	"encoding/json"
	"time"

	"github.com/linkdata/deadlock"
)

type Manifest struct {
	EgressID          string `json:"egress_id,omitempty"`
	RoomID            string `json:"room_id,omitempty"`
	RoomName          string `json:"room_name,omitempty"`
	Url               string `json:"url,omitempty"`
	StartedAt         int64  `json:"started_at,omitempty"`
	EndedAt           int64  `json:"ended_at,omitempty"`
	PublisherIdentity string `json:"publisher_identity,omitempty"`
	TrackID           string `json:"track_id,omitempty"`
	TrackKind         string `json:"track_kind,omitempty"`
	TrackSource       string `json:"track_source,omitempty"`
	AudioTrackID      string `json:"audio_track_id,omitempty"`
	VideoTrackID      string `json:"video_track_id,omitempty"`

	mu        deadlock.Mutex
	Files     []*File     `json:"files,omitempty"`
	Playlists []*Playlist `json:"playlists,omitempty"`
	Images    []*Image    `json:"images,omitempty"`
}

type File struct {
	Filename string `json:"filename,omitempty"`
	Location string `json:"location,omitempty"`
}

type Playlist struct {
	mu       deadlock.Mutex
	Location string     `json:"location,omitempty"`
	Segments []*Segment `json:"segments,omitempty"`
}

type Segment struct {
	Filename string `json:"filename,omitempty"`
	Location string `json:"location,omitempty"`
}

type Image struct {
	Filename  string    `json:"filename,omitempty"`
	Timestamp time.Time `json:"timestamp,omitempty"`
	Location  string    `json:"location,omitempty"`
}

func (p *PipelineConfig) initManifest() {
	if p.shouldCreateManifest() {
		p.Manifest = &Manifest{
			EgressID:          p.Info.EgressId,
			RoomID:            p.Info.RoomId,
			RoomName:          p.Info.RoomName,
			Url:               p.WebUrl,
			StartedAt:         p.Info.StartedAt,
			PublisherIdentity: p.Identity,
			TrackID:           p.TrackID,
			TrackKind:         p.TrackKind,
			TrackSource:       p.TrackSource,
			AudioTrackID:      p.AudioTrackID,
			VideoTrackID:      p.VideoTrackID,
		}
	}
}

func (p *PipelineConfig) shouldCreateManifest() bool {
	if p.BackupConfig != nil {
		return true
	}
	if fc := p.GetFileConfig(); fc != nil && !fc.DisableManifest {
		return true
	}
	if sc := p.GetSegmentConfig(); sc != nil && !sc.DisableManifest {
		return true
	}
	for _, ic := range p.GetImageConfigs() {
		if !ic.DisableManifest {
			return true
		}
	}
	return false
}

func (m *Manifest) AddFile(filename, location string) {
	m.mu.Lock()
	m.Files = append(m.Files, &File{
		Filename: filename,
		Location: location,
	})
	m.mu.Unlock()
}

func (m *Manifest) AddPlaylist() *Playlist {
	p := &Playlist{}

	m.mu.Lock()
	m.Playlists = append(m.Playlists, p)
	m.mu.Unlock()

	return p
}

func (p *Playlist) UpdateLocation(location string) {
	p.mu.Lock()
	p.Location = location
	p.mu.Unlock()
}

func (p *Playlist) AddSegment(filename, location string) {
	p.mu.Lock()
	p.Segments = append(p.Segments, &Segment{
		Filename: filename,
		Location: location,
	})
	p.mu.Unlock()
}

func (m *Manifest) AddImage(filename string, ts time.Time, location string) {
	m.mu.Lock()
	m.Images = append(m.Images, &Image{
		Filename:  filename,
		Timestamp: ts,
		Location:  location,
	})
	m.mu.Unlock()
}

func (m *Manifest) Close(endedAt int64) ([]byte, error) {
	m.EndedAt = endedAt

	buf := bytes.NewBuffer(nil)
	enc := json.NewEncoder(buf)
	enc.SetEscapeHTML(false)
	if err := enc.Encode(m); err != nil {
		return nil, err
	}

	return buf.Bytes(), nil
}


================================================
FILE: pkg/config/output.go
================================================
// Copyright 2023 LiveKit, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed un
Download .txt
gitextract_ye_8hhme/

├── .github/
│   ├── CODEOWNERS
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug_report.md
│   │   └── feature_request.md
│   └── workflows/
│       ├── publish-chrome.yaml
│       ├── publish-egress.yaml
│       ├── publish-gstreamer-base.yaml
│       ├── publish-gstreamer.yaml
│       ├── publish-template-sdk.yaml
│       ├── publish-template.yaml
│       ├── slack-notifier.yaml
│       ├── test-cleanup.yaml
│       ├── test-integration.yaml
│       └── test-template.yaml
├── .gitignore
├── .golangci.yaml
├── LICENSE
├── NOTICE
├── README.md
├── bootstrap.sh
├── build/
│   ├── chrome/
│   │   ├── Dockerfile
│   │   ├── README.md
│   │   ├── install-chrome
│   │   └── scripts/
│   │       ├── amd64.sh
│   │       ├── arm64.sh
│   │       └── driver.sh
│   ├── egress/
│   │   ├── Dockerfile
│   │   └── entrypoint.sh
│   ├── gstreamer/
│   │   ├── Dockerfile-base
│   │   ├── Dockerfile-dev
│   │   ├── Dockerfile-prod
│   │   ├── Dockerfile-prod-rs
│   │   ├── compile
│   │   ├── compile-rs
│   │   ├── install-dependencies
│   │   └── tag.sh
│   ├── template/
│   │   └── Dockerfile
│   └── test/
│       ├── Dockerfile
│       ├── entrypoint.sh
│       └── fetch-media-samples.sh
├── chrome-sandboxing-seccomp-profile.json
├── cmd/
│   ├── server/
│   │   ├── http.go
│   │   └── main.go
│   └── template_version/
│       └── main.go
├── go.mod
├── go.sum
├── magefile.go
├── pkg/
│   ├── config/
│   │   ├── base.go
│   │   ├── config_test.go
│   │   ├── encoding.go
│   │   ├── manifest.go
│   │   ├── output.go
│   │   ├── output_file.go
│   │   ├── output_image.go
│   │   ├── output_segment.go
│   │   ├── output_stream.go
│   │   ├── pipeline.go
│   │   ├── retry_test.go
│   │   ├── service.go
│   │   ├── storage.go
│   │   ├── test_overrides.go
│   │   ├── urls.go
│   │   └── urls_test.go
│   ├── errors/
│   │   └── errors.go
│   ├── gstreamer/
│   │   ├── bin.go
│   │   ├── builder.go
│   │   ├── callbacks.go
│   │   ├── pads.go
│   │   ├── pipeline.go
│   │   ├── queue_monitor.go
│   │   ├── state.go
│   │   └── time_provider.go
│   ├── handler/
│   │   ├── handler.go
│   │   ├── handler_ipc.go
│   │   └── handler_rpc.go
│   ├── info/
│   │   └── io.go
│   ├── ipc/
│   │   ├── conn.go
│   │   ├── ipc.pb.go
│   │   ├── ipc.proto
│   │   └── ipc_grpc.pb.go
│   ├── logging/
│   │   ├── csv.go
│   │   ├── handler.go
│   │   └── s3.go
│   ├── pipeline/
│   │   ├── builder/
│   │   │   ├── audio.go
│   │   │   ├── file.go
│   │   │   ├── image.go
│   │   │   ├── muxer.go
│   │   │   ├── muxer_test.go
│   │   │   ├── pts_fixer.go
│   │   │   ├── segment.go
│   │   │   ├── stream.go
│   │   │   ├── video.go
│   │   │   ├── vp9_probe.go
│   │   │   └── websocket.go
│   │   ├── controller.go
│   │   ├── debug.go
│   │   ├── sink/
│   │   │   ├── file.go
│   │   │   ├── image.go
│   │   │   ├── m3u8/
│   │   │   │   ├── writer.go
│   │   │   │   └── writer_test.go
│   │   │   ├── segments.go
│   │   │   ├── sink.go
│   │   │   ├── stream.go
│   │   │   ├── uploader/
│   │   │   │   ├── uploader.go
│   │   │   │   └── uploader_test.go
│   │   │   └── websocket.go
│   │   ├── source/
│   │   │   ├── pulse/
│   │   │   │   └── pactl.go
│   │   │   ├── sdk/
│   │   │   │   ├── appwriter.go
│   │   │   │   └── translator.go
│   │   │   ├── sdk.go
│   │   │   ├── source.go
│   │   │   ├── tracer.go
│   │   │   ├── track_worker.go
│   │   │   ├── track_worker_test.go
│   │   │   └── web.go
│   │   ├── tempo/
│   │   │   ├── controller.go
│   │   │   └── controller_test.go
│   │   └── watch.go
│   ├── server/
│   │   ├── integration.go
│   │   ├── server.go
│   │   ├── server_ipc.go
│   │   └── server_rpc.go
│   ├── service/
│   │   ├── debug.go
│   │   ├── metrics.go
│   │   ├── process.go
│   │   └── servicefakes/
│   │       └── fake_process_manager.go
│   ├── stats/
│   │   ├── handler.go
│   │   ├── monitor.go
│   │   ├── monitor_memory_test.go
│   │   └── monitor_prom.go
│   └── types/
│       ├── types.go
│       └── types_test.go
├── renovate.json
├── template-default/
│   ├── .gitignore
│   ├── .prettierrc
│   ├── README.md
│   ├── eslint.config.js
│   ├── index.html
│   ├── package.json
│   ├── public/
│   │   ├── manifest.json
│   │   └── robots.txt
│   ├── src/
│   │   ├── App.css
│   │   ├── App.tsx
│   │   ├── Room.tsx
│   │   ├── SingleSpeakerLayout.tsx
│   │   ├── SpeakerLayout.tsx
│   │   ├── common.ts
│   │   ├── index.css
│   │   ├── index.tsx
│   │   └── vite-env.d.ts
│   ├── tsconfig.app.json
│   ├── tsconfig.json
│   ├── tsconfig.node.json
│   └── vite.config.ts
├── template-sdk/
│   ├── .gitignore
│   ├── .npmignore
│   ├── .prettierrc
│   ├── README.md
│   ├── package.json
│   ├── src/
│   │   └── index.ts
│   └── tsconfig.json
├── test/
│   ├── agents/
│   │   ├── .gitignore
│   │   ├── guest.py
│   │   ├── host.py
│   │   └── requirements.txt
│   ├── agents.go
│   ├── builder.go
│   ├── config-sample.yaml
│   ├── content_checks.go
│   ├── download.go
│   ├── edge.go
│   ├── ffprobe.go
│   ├── file.go
│   ├── flags.go
│   ├── images.go
│   ├── integration.go
│   ├── integration_test.go
│   ├── ioserver.go
│   ├── multi.go
│   ├── publish.go
│   ├── runner.go
│   ├── segments.go
│   ├── stream.go
│   └── test_content.go
└── version/
    └── version.go
Download .txt
SYMBOL INDEX (1325 symbols across 112 files)

FILE: cmd/server/http.go
  type httpHandler (line 24) | type httpHandler struct
    method ServeHTTP (line 28) | func (h *httpHandler) ServeHTTP(w http.ResponseWriter, _ *http.Request) {

FILE: cmd/server/main.go
  function main (line 48) | func main() {
  function runService (line 91) | func runService(_ context.Context, c *cli.Command) error {
  function runHandler (line 161) | func runHandler(_ context.Context, c *cli.Command) error {

FILE: cmd/template_version/main.go
  function main (line 23) | func main() {

FILE: magefile.go
  constant gstVersion (line 33) | gstVersion      = "1.24.12"
  constant libniceVersion (line 34) | libniceVersion  = "0.1.21"
  constant chromiumVersion (line 35) | chromiumVersion = "146.0.7680.177-1"
  constant dockerBuild (line 36) | dockerBuild     = "docker build"
  constant dockerBuildX (line 37) | dockerBuildX    = "docker buildx build --push --platform linux/amd64,lin...
  type packageInfo (line 40) | type packageInfo struct
  function Proto (line 44) | func Proto() error {
  function EnsureMediaSamples (line 94) | func EnsureMediaSamples() error {
  function Integration (line 112) | func Integration(configFile string) error {
  function Retest (line 129) | func Retest(configFile string) error {
  function Build (line 171) | func Build() error {
  function BuildTemplate (line 179) | func BuildTemplate() error {
  function BuildGStreamer (line 186) | func BuildGStreamer() error {
  function buildGstreamer (line 190) | func buildGstreamer(cmd string) error {
  function Dotfiles (line 207) | func Dotfiles() error {

FILE: pkg/config/base.go
  constant TmpDir (line 30) | TmpDir = "/home/egress/tmp"
  type BaseConfig (line 32) | type BaseConfig struct
    method initLogger (line 103) | func (c *BaseConfig) initLogger(values ...interface{}) error {
    method getLatencyConfig (line 139) | func (c *BaseConfig) getLatencyConfig(requestType types.RequestType) L...
  type SessionLimits (line 72) | type SessionLimits struct
  type DebugConfig (line 80) | type DebugConfig struct
  type LatencyConfig (line 88) | type LatencyConfig struct
  type AudioTempoController (line 98) | type AudioTempoController struct

FILE: pkg/config/config_test.go
  function TestSegmentNaming (line 27) | func TestSegmentNaming(t *testing.T) {
  function TestValidateAndUpdateOutputParamsRejectsHLSMP3 (line 98) | func TestValidateAndUpdateOutputParamsRejectsHLSMP3(t *testing.T) {
  function TestValidateAndUpdateOutputParamsRejectsVideoFileMP3 (line 117) | func TestValidateAndUpdateOutputParamsRejectsVideoFileMP3(t *testing.T) {

FILE: pkg/config/encoding.go
  method applyPreset (line 23) | func (p *PipelineConfig) applyPreset(preset livekit.EncodingOptionsPrese...
  method applyAdvanced (line 75) | func (p *PipelineConfig) applyAdvanced(advanced *livekit.EncodingOptions...

FILE: pkg/config/manifest.go
  type Manifest (line 25) | type Manifest struct
    method AddFile (line 103) | func (m *Manifest) AddFile(filename, location string) {
    method AddPlaylist (line 112) | func (m *Manifest) AddPlaylist() *Playlist {
    method AddImage (line 137) | func (m *Manifest) AddImage(filename string, ts time.Time, location st...
    method Close (line 147) | func (m *Manifest) Close(endedAt int64) ([]byte, error) {
  type File (line 45) | type File struct
  type Playlist (line 50) | type Playlist struct
    method UpdateLocation (line 122) | func (p *Playlist) UpdateLocation(location string) {
    method AddSegment (line 128) | func (p *Playlist) AddSegment(filename, location string) {
  type Segment (line 56) | type Segment struct
  type Image (line 61) | type Image struct
  method initManifest (line 67) | func (p *PipelineConfig) initManifest() {
  method shouldCreateManifest (line 85) | func (p *PipelineConfig) shouldCreateManifest() bool {

FILE: pkg/config/output.go
  constant StreamKeyframeInterval (line 26) | StreamKeyframeInterval = 4.0
  type OutputConfig (line 28) | type OutputConfig interface
  type outputConfig (line 32) | type outputConfig struct
    method GetOutputType (line 36) | func (o outputConfig) GetOutputType() types.OutputType {
  method updateEncodedOutputs (line 40) | func (p *PipelineConfig) updateEncodedOutputs(req egress.EncodedOutput) ...
  method updateOutputs (line 220) | func (p *PipelineConfig) updateOutputs(req *livekit.ExportReplayRequest)...
  method updateDirectOutput (line 412) | func (p *PipelineConfig) updateDirectOutput(req *livekit.TrackEgressRequ...

FILE: pkg/config/output_file.go
  type FileConfig (line 29) | type FileConfig struct
    method updateFilepath (line 127) | func (o *FileConfig) updateFilepath(p *PipelineConfig, identifier stri...
  method GetFileConfig (line 40) | func (p *PipelineConfig) GetFileConfig() *FileConfig {
  method getEncodedFileConfig (line 48) | func (p *PipelineConfig) getEncodedFileConfig(file *livekit.EncodedFileO...
  method getDirectFileConfig (line 52) | func (p *PipelineConfig) getDirectFileConfig(file *livekit.DirectFileOut...
  function fileTypeToOutputType (line 56) | func fileTypeToOutputType(ft livekit.EncodedFileType) types.OutputType {
  method getFileConfig (line 69) | func (p *PipelineConfig) getFileConfig(outputType types.OutputType, file...
  method getFilenameInfo (line 105) | func (p *PipelineConfig) getFilenameInfo() (string, map[string]string) {
  function clean (line 165) | func clean(filepath string) string {

FILE: pkg/config/output_image.go
  type ImageConfig (line 30) | type ImageConfig struct
    method updatePrefix (line 129) | func (o *ImageConfig) updatePrefix(p *PipelineConfig) error {
  method GetImageConfigs (line 51) | func (p *PipelineConfig) GetImageConfigs() []*ImageConfig {
  method getImageConfig (line 62) | func (p *PipelineConfig) getImageConfig(images *livekit.ImageOutput, upl...
  function getMimeTypes (line 157) | func getMimeTypes(imageCodec livekit.ImageCodec) (types.MimeType, types....

FILE: pkg/config/output_segment.go
  type SegmentConfig (line 30) | type SegmentConfig struct
    method updatePrefixAndPlaylist (line 117) | func (o *SegmentConfig) updatePrefixAndPlaylist(p *PipelineConfig) err...
  method GetSegmentConfig (line 46) | func (p *PipelineConfig) GetSegmentConfig() *SegmentConfig {
  method getSegmentConfig (line 55) | func (p *PipelineConfig) getSegmentConfig(segments *livekit.SegmentedFil...
  function removeKnownExtension (line 105) | func removeKnownExtension(filename string) string {

FILE: pkg/config/output_stream.go
  type StreamConfig (line 27) | type StreamConfig struct
  type Stream (line 36) | type Stream struct
    method UpdateEndTime (line 86) | func (s *Stream) UpdateEndTime(endedAt int64) {
    method ShouldSendRetryUpdate (line 98) | func (s *Stream) ShouldSendRetryUpdate(now time.Time, minInterval time...
  method GetStreamConfig (line 46) | func (p *PipelineConfig) GetStreamConfig() *StreamConfig {
  method GetWebsocketConfig (line 54) | func (p *PipelineConfig) GetWebsocketConfig() *StreamConfig {
  method getStreamConfig (line 62) | func (p *PipelineConfig) getStreamConfig(outputType types.OutputType, ur...

FILE: pkg/config/pipeline.go
  type PipelineConfig (line 43) | type PipelineConfig struct
    method IsReplay (line 67) | func (p *PipelineConfig) IsReplay() bool {
    method Update (line 196) | func (p *PipelineConfig) Update(request *rpc.StartEgressRequest) error {
    method validateAndUpdateOutputParams (line 624) | func (p *PipelineConfig) validateAndUpdateOutputParams() error {
    method validateAndUpdateOutputCodecs (line 671) | func (p *PipelineConfig) validateAndUpdateOutputCodecs() (compatibleAu...
    method updateOutputType (line 718) | func (p *PipelineConfig) updateOutputType(compatibleAudioCodecs map[ty...
    method UpdateInfoFromSDK (line 754) | func (p *PipelineConfig) UpdateInfoFromSDK(identifier string, replacem...
    method GetEncodedOutputs (line 805) | func (p *PipelineConfig) GetEncodedOutputs() []OutputConfig {
  type StorageObserver (line 71) | type StorageObserver interface
  type SourceConfig (line 79) | type SourceConfig struct
  type WebSourceParams (line 85) | type WebSourceParams struct
  type SDKSourceParams (line 94) | type SDKSourceParams struct
  type AudioRouteConfig (line 108) | type AudioRouteConfig struct
  type AudioRouteMatch (line 113) | type AudioRouteMatch struct
  type TrackSource (line 119) | type TrackSource struct
  type AudioConfig (line 132) | type AudioConfig struct
  type VideoConfig (line 141) | type VideoConfig struct
  function NewPipelineConfig (line 155) | func NewPipelineConfig(confString string, req *rpc.StartEgressRequest) (...
  function GetValidatedPipelineConfig (line 182) | func GetValidatedPipelineConfig(conf *ServiceConfig, req *rpc.StartEgres...
  function ShouldUseSDKSource (line 616) | func ShouldUseSDKSource(req interface {
  function isHttp (line 815) | func isHttp(parsedUrl *url.URL) bool {
  function stringReplace (line 819) | func stringReplace(s string, replacements map[string]string) string {

FILE: pkg/config/retry_test.go
  function TestFileOutputRetrySafety (line 27) | func TestFileOutputRetrySafety(t *testing.T) {
  function TestSegmentOutputRetrySafety (line 104) | func TestSegmentOutputRetrySafety(t *testing.T) {

FILE: pkg/config/service.go
  constant roomCompositeCpuCost (line 32) | roomCompositeCpuCost      = 4
  constant audioRoomCompositeCpuCost (line 33) | audioRoomCompositeCpuCost = 1
  constant webCpuCost (line 34) | webCpuCost                = 4
  constant audioWebCpuCost (line 35) | audioWebCpuCost           = 1
  constant participantCpuCost (line 36) | participantCpuCost        = 2
  constant trackCompositeCpuCost (line 37) | trackCompositeCpuCost     = 1
  constant trackCpuCost (line 38) | trackCpuCost              = 0.5
  constant maxCpuUtilization (line 39) | maxCpuUtilization         = 0.8
  constant maxUploadQueue (line 40) | maxUploadQueue            = 60
  constant defaultTemplatePort (line 42) | defaultTemplatePort         = 7980
  constant defaultTemplateBaseTemplate (line 43) | defaultTemplateBaseTemplate = "http://localhost:%d/"
  constant defaultIOCreateTimeout (line 45) | defaultIOCreateTimeout = time.Second * 15
  constant defaultIOUpdateTimeout (line 46) | defaultIOUpdateTimeout = time.Second * 30
  constant defaultIOWorkers (line 47) | defaultIOWorkers       = 5
  constant defaultJitterBufferLatency (line 49) | defaultJitterBufferLatency   = time.Second * 2
  constant defaultAudioMixerLatency (line 50) | defaultAudioMixerLatency     = time.Millisecond * 2750
  constant defaultPipelineLatency (line 51) | defaultPipelineLatency       = time.Second * 3
  constant defaultRTPMaxDriftAdjustment (line 52) | defaultRTPMaxDriftAdjustment = time.Millisecond * 5
  constant defaultOldPacketThreshold (line 53) | defaultOldPacketThreshold    = 2200 * time.Millisecond
  constant defaultRTPMaxAllowedTsDiff (line 54) | defaultRTPMaxAllowedTsDiff   = time.Second * 5
  constant defaultAudioTempoControllerAdjustmentRate (line 56) | defaultAudioTempoControllerAdjustmentRate = 0.05
  constant defaultMaxPulseClients (line 58) | defaultMaxPulseClients = 60
  type ServiceConfig (line 61) | type ServiceConfig struct
    method InitDefaults (line 131) | func (c *ServiceConfig) InitDefaults() {
  type MemorySource (line 73) | type MemorySource
  constant MemorySourceProcRSS (line 77) | MemorySourceProcRSS MemorySource = "proc_rss"
  constant MemorySourceCgroup (line 79) | MemorySourceCgroup MemorySource = "cgroup"
  type CPUCostConfig (line 82) | type CPUCostConfig struct
  function NewServiceConfig (line 100) | func NewServiceConfig(confString string) (*ServiceConfig, error) {
  function applyLatencyDefaults (line 208) | func applyLatencyDefaults(latency *LatencyConfig) {

FILE: pkg/config/storage.go
  type StorageConfig (line 26) | type StorageConfig struct
    method IsLocal (line 140) | func (c *StorageConfig) IsLocal() bool {
  method getStorageConfig (line 36) | func (p *PipelineConfig) getStorageConfig(req egress.UploadRequest) (*St...
  function resolveStorageConfig (line 147) | func resolveStorageConfig(outputStorage, requestStorage *livekit.Storage...

FILE: pkg/config/test_overrides.go
  type TestOverrides (line 4) | type TestOverrides struct

FILE: pkg/config/urls.go
  method AddStream (line 39) | func (o *StreamConfig) AddStream(rawUrl string, outputType types.OutputT...
  method ValidateUrl (line 62) | func (o *StreamConfig) ValidateUrl(rawUrl string, outputType types.Outpu...
  method GetStream (line 110) | func (o *StreamConfig) GetStream(rawUrl string) (*Stream, error) {
  method updateTwitchURL (line 140) | func (o *StreamConfig) updateTwitchURL(key string) (string, error) {
  method updateTwitchTemplate (line 148) | func (o *StreamConfig) updateTwitchTemplate() error {
  function redactStreamKey (line 184) | func redactStreamKey(url string) (string, string, bool) {

FILE: pkg/config/urls_test.go
  function TestValidateUrl (line 27) | func TestValidateUrl(t *testing.T) {
  function TestGetUrl (line 77) | func TestGetUrl(t *testing.T) {

FILE: pkg/errors/errors.go
  function New (line 24) | func New(err string) error {
  function Is (line 28) | func Is(err, target error) bool {
  function As (line 32) | func As(err error, target any) bool {
  type ErrArray (line 36) | type ErrArray struct
    method AppendErr (line 40) | func (e *ErrArray) AppendErr(err error) {
    method Check (line 44) | func (e *ErrArray) Check(err error) {
    method ToError (line 50) | func (e *ErrArray) ToError() psrpc.Error {
  function ErrPadLinkFailed (line 83) | func ErrPadLinkFailed(src, sink, status string) error {
  function ErrGstPipelineError (line 87) | func ErrGstPipelineError(err error) error {
  function ErrProcessFailed (line 91) | func ErrProcessFailed(process string, err error) error {
  function ChromeError (line 95) | func ChromeError(err error) error {
  function PageLoadError (line 114) | func PageLoadError(err string) error {
  function TemplateError (line 119) | func TemplateError(err string) error {
  function ErrCouldNotParseConfig (line 123) | func ErrCouldNotParseConfig(err error) error {
  function ErrNotSupported (line 127) | func ErrNotSupported(feature string) error {
  function ErrIncompatible (line 131) | func ErrIncompatible(format, codec interface{}) error {
  function ErrInvalidInput (line 135) | func ErrInvalidInput(field string) error {
  function ErrInvalidUrl (line 139) | func ErrInvalidUrl(url string, reason string) error {
  function ErrUploadFailed (line 143) | func ErrUploadFailed(location string, err error) error {
  function ErrParticipantNotFound (line 147) | func ErrParticipantNotFound(identity string) error {
  function ErrStreamNotFound (line 151) | func ErrStreamNotFound(url string) error {
  function ErrTrackNotFound (line 155) | func ErrTrackNotFound(trackID string) error {
  function ErrFeatureDisabled (line 159) | func ErrFeatureDisabled(feature string) error {
  function ErrCPUExhausted (line 163) | func ErrCPUExhausted(usage float64) error {
  function ErrOOM (line 167) | func ErrOOM(usage float64) error {

FILE: pkg/gstreamer/bin.go
  constant removeSourceBinTimeout (line 33) | removeSourceBinTimeout = 3 * time.Second
  type Bin (line 55) | type Bin struct
    method NewBin (line 79) | func (b *Bin) NewBin(name string) *Bin {
    method GetName (line 90) | func (b *Bin) GetName() string {
    method AddSourceBin (line 95) | func (b *Bin) AddSourceBin(src *Bin) error {
    method AddSinkBin (line 101) | func (b *Bin) AddSinkBin(sink *Bin) error {
    method addBin (line 106) | func (b *Bin) addBin(bin *Bin, direction gst.PadDirection) error {
    method AddElement (line 160) | func (b *Bin) AddElement(e *gst.Element) error {
    method AddElements (line 173) | func (b *Bin) AddElements(elements ...*gst.Element) error {
    method ForceRemoveSourceBin (line 187) | func (b *Bin) ForceRemoveSourceBin(name string) error {
    method RemoveSourceBin (line 239) | func (b *Bin) RemoveSourceBin(name string) error {
    method RemoveSinkBin (line 244) | func (b *Bin) RemoveSinkBin(name string) error {
    method removeSourceLocked (line 249) | func (b *Bin) removeSourceLocked(name string) *Bin {
    method removeBin (line 259) | func (b *Bin) removeBin(name string, direction gst.PadDirection) error {
    method probeRemoveSource (line 303) | func (b *Bin) probeRemoveSource(src *Bin) {
    method probeRemoveSink (line 375) | func (b *Bin) probeRemoveSink(sink *Bin) {
    method SetState (line 445) | func (b *Bin) SetState(state gst.State) error {
    method SetLinkFunc (line 462) | func (b *Bin) SetLinkFunc(f func([]*gst.Element) error) {
    method SetShouldLink (line 469) | func (b *Bin) SetShouldLink(f func(string) bool) {
    method SetGetSrcPad (line 477) | func (b *Bin) SetGetSrcPad(f func(srcName string) *gst.Pad) {
    method SetGetSinkPad (line 485) | func (b *Bin) SetGetSinkPad(f func(sinkName string) *gst.Pad) {
    method SetEOSFunc (line 493) | func (b *Bin) SetEOSFunc(f func() bool) {
    method sendEOS (line 500) | func (b *Bin) sendEOS() {
    method AddOnEOSReceived (line 526) | func (b *Bin) AddOnEOSReceived(f func()) error {
    method link (line 560) | func (b *Bin) link() error {
    method queueLinkPeersLocked (line 672) | func (b *Bin) queueLinkPeersLocked(src, sink *Bin) error {
  function detachSourceBin (line 408) | func detachSourceBin(src *Bin, srcGhostPad, sinkGhostPad *gst.GhostPad, ...
  function deleteGhostPadsLocked (line 428) | func deleteGhostPadsLocked(src, sink *Bin) (*gst.GhostPad, *gst.GhostPad...
  function linkPeersLocked (line 633) | func linkPeersLocked(src, sink *Bin) error {
  function getPeerSrcs (line 701) | func getPeerSrcs(srcs []*Bin) []*Bin {
  function getPeerSinks (line 713) | func getPeerSinks(sinks []*Bin) []*Bin {

FILE: pkg/gstreamer/builder.go
  function BuildQueue (line 25) | func BuildQueue(name string, latency time.Duration, leaky bool) (*gst.El...
  function BuildAudioRate (line 49) | func BuildAudioRate(name string, tolerance time.Duration) (*gst.Element,...

FILE: pkg/gstreamer/callbacks.go
  type Callbacks (line 24) | type Callbacks struct
    method SetOnError (line 45) | func (c *Callbacks) SetOnError(f func(error)) {
    method OnError (line 51) | func (c *Callbacks) OnError(err error) {
    method SetOnDebugDotRequest (line 61) | func (c *Callbacks) SetOnDebugDotRequest(f func(string)) {
    method OnDebugDotRequest (line 67) | func (c *Callbacks) OnDebugDotRequest(reason string) {
    method PipelinePaused (line 77) | func (c *Callbacks) PipelinePaused() <-chan struct{} {
    method OnPipelinePaused (line 81) | func (c *Callbacks) OnPipelinePaused() {
    method AddOnStop (line 85) | func (c *Callbacks) AddOnStop(f func() error) {
    method OnStop (line 91) | func (c *Callbacks) OnStop() error {
    method AddOnTrackAdded (line 103) | func (c *Callbacks) AddOnTrackAdded(f func(*config.TrackSource)) {
    method OnTrackAdded (line 109) | func (c *Callbacks) OnTrackAdded(ts *config.TrackSource) {
    method AddOnTrackMuted (line 119) | func (c *Callbacks) AddOnTrackMuted(f func(string)) {
    method OnTrackMuted (line 125) | func (c *Callbacks) OnTrackMuted(trackID string) {
    method AddOnTrackUnmuted (line 135) | func (c *Callbacks) AddOnTrackUnmuted(f func(string)) {
    method OnTrackUnmuted (line 141) | func (c *Callbacks) OnTrackUnmuted(trackID string) {
    method AddOnTrackRemoved (line 151) | func (c *Callbacks) AddOnTrackRemoved(f func(string)) {
    method OnTrackRemoved (line 157) | func (c *Callbacks) OnTrackRemoved(trackID string) {
    method AddOnSourceBinReset (line 167) | func (c *Callbacks) AddOnSourceBinReset(f func(*config.TrackSource) er...
    method OnSourceBinReset (line 177) | func (c *Callbacks) OnSourceBinReset(ts *config.TrackSource) error {
    method SetOnEOSSent (line 190) | func (c *Callbacks) SetOnEOSSent(f func()) {
    method OnEOSSent (line 196) | func (c *Callbacks) OnEOSSent() {

FILE: pkg/gstreamer/pads.go
  type padTemplate (line 28) | type padTemplate struct
    method toPad (line 35) | func (p *padTemplate) toPad() *gst.Pad {
    method findDirectMatch (line 42) | func (p *padTemplate) findDirectMatch(others []*padTemplate) *padTempl...
    method findAnyMatch (line 58) | func (p *padTemplate) findAnyMatch(others []*padTemplate) *padTemplate {
  function createGhostPadsLocked (line 70) | func createGhostPadsLocked(src, sink *Bin, queue *gst.Element) (*gst.Gho...
  function matchPadsLocked (line 109) | func matchPadsLocked(src, sink *Bin) (*gst.Pad, *gst.Pad, error) {
  method getPadTemplatesLocked (line 149) | func (b *Bin) getPadTemplatesLocked(direction gst.PadDirection) []*padTe...
  method getTypesLocked (line 203) | func (b *Bin) getTypesLocked(direction gst.PadDirection) (map[string]str...

FILE: pkg/gstreamer/pipeline.go
  constant stateChangeTimeout (line 29) | stateChangeTimeout = time.Second * 15
  type Pipeline (line 32) | type Pipeline struct
    method AddSourceBin (line 62) | func (p *Pipeline) AddSourceBin(src *Bin) error {
    method AddSinkBin (line 70) | func (p *Pipeline) AddSinkBin(sink *Bin) error {
    method AddElement (line 78) | func (p *Pipeline) AddElement(e *gst.Element) error {
    method AddElements (line 86) | func (p *Pipeline) AddElements(elements ...*gst.Element) error {
    method Link (line 94) | func (p *Pipeline) Link() error {
    method SetWatch (line 98) | func (p *Pipeline) SetWatch(watch func(msg *gst.Message) bool) {
    method SetState (line 102) | func (p *Pipeline) SetState(state gst.State) error {
    method Run (line 123) | func (p *Pipeline) Run() error {
    method SendEOS (line 134) | func (p *Pipeline) SendEOS() {
    method Stop (line 145) | func (p *Pipeline) Stop() {
    method DebugBinToDotData (line 166) | func (p *Pipeline) DebugBinToDotData(details gst.DebugGraphDetails) st...
    method RunningTime (line 171) | func (p *Pipeline) RunningTime() (time.Duration, bool) {
    method PlayheadPosition (line 203) | func (p *Pipeline) PlayheadPosition() (time.Duration, bool) {
  function NewPipeline (line 43) | func NewPipeline(name string, latency time.Duration, callbacks *Callback...

FILE: pkg/gstreamer/queue_monitor.go
  type LeakyQueueMonitor (line 27) | type LeakyQueueMonitor struct
    method postEOSStats (line 75) | func (m *LeakyQueueMonitor) postEOSStats() {
    method Name (line 118) | func (m *LeakyQueueMonitor) Name() string {
  function NewLeakyQueueMonitor (line 37) | func NewLeakyQueueMonitor(name string, queue *gst.Element) {
  constant LeakyQueueStatsMessage (line 73) | LeakyQueueStatsMessage = "LeakyQueueStats"

FILE: pkg/gstreamer/state.go
  type State (line 24) | type State
    method String (line 80) | func (s State) String() string {
  constant StateBuilding (line 27) | StateBuilding State = iota
  constant StateStarted (line 28) | StateStarted
  constant StateRunning (line 29) | StateRunning
  constant StateEOS (line 30) | StateEOS
  constant StateStopping (line 31) | StateStopping
  constant StateFinished (line 32) | StateFinished
  type StateManager (line 35) | type StateManager struct
    method GetState (line 40) | func (s *StateManager) GetState() State {
    method GetStateLocked (line 47) | func (s *StateManager) GetStateLocked() State {
    method LockState (line 51) | func (s *StateManager) LockState() {
    method UnlockState (line 55) | func (s *StateManager) UnlockState() {
    method LockStateShared (line 59) | func (s *StateManager) LockStateShared() {
    method UnlockStateShared (line 63) | func (s *StateManager) UnlockStateShared() {
    method UpgradeState (line 67) | func (s *StateManager) UpgradeState(state State) (State, bool) {

FILE: pkg/gstreamer/time_provider.go
  type TimeProvider (line 22) | type TimeProvider interface
  type nopTimeProvider (line 29) | type nopTimeProvider struct
    method RunningTime (line 36) | func (n *nopTimeProvider) RunningTime() (time.Duration, bool) {
    method PlayheadPosition (line 40) | func (n *nopTimeProvider) PlayheadPosition() (time.Duration, bool) {
  function NopTimeProvider (line 32) | func NopTimeProvider() TimeProvider {

FILE: pkg/handler/handler.go
  type Handler (line 40) | type Handler struct
    method Run (line 103) | func (h *Handler) Run() {
    method Kill (line 169) | func (h *Handler) Kill() {
    method shouldInjectEgressFailure (line 177) | func (h *Handler) shouldInjectEgressFailure() bool {
  function NewHandler (line 56) | func NewHandler(conf *config.PipelineConfig, bus psrpc.MessageBus) (*Han...
  type ipcStorageObserver (line 187) | type ipcStorageObserver struct
    method OnStorageEvent (line 191) | func (o *ipcStorageObserver) OnStorageEvent(egressID, operation, path ...

FILE: pkg/handler/handler_ipc.go
  method GetPipelineDot (line 34) | func (h *Handler) GetPipelineDot(ctx context.Context, _ *ipc.GstPipeline...
  method GetPProf (line 54) | func (h *Handler) GetPProf(ctx context.Context, req *ipc.PProfRequest) (...
  method GetMetrics (line 75) | func (h *Handler) GetMetrics(ctx context.Context, _ *ipc.MetricsRequest)...
  method GenerateMetrics (line 89) | func (h *Handler) GenerateMetrics(_ context.Context) (string, error) {
  function renderMetrics (line 103) | func renderMetrics(metrics []*dto.MetricFamily) (string, error) {
  method KillEgress (line 121) | func (h *Handler) KillEgress(ctx context.Context, req *ipc.KillEgressReq...

FILE: pkg/handler/handler_rpc.go
  method UpdateStream (line 24) | func (h *Handler) UpdateStream(ctx context.Context, req *livekit.UpdateS...
  method UpdateEgress (line 40) | func (h *Handler) UpdateEgress(ctx context.Context, req *livekit.UpdateE...
  method StopEgress (line 56) | func (h *Handler) StopEgress(ctx context.Context, _ *livekit.StopEgressR...

FILE: pkg/info/io.go
  constant numWorkers (line 38) | numWorkers                     = 5
  constant maxBackoff (line 39) | maxBackoff                     = time.Minute * 1
  constant unhealthyShutdownWatchdogDelay (line 40) | unhealthyShutdownWatchdogDelay = 20 * time.Second
  type SessionReporter (line 43) | type SessionReporter interface
  type sessionReporter (line 52) | type sessionReporter struct
    method CreateEgress (line 118) | func (c *sessionReporter) CreateEgress(ctx context.Context, info *live...
    method UpdateEgress (line 146) | func (c *sessionReporter) UpdateEgress(ctx context.Context, info *live...
    method UpdateMetrics (line 170) | func (c *sessionReporter) UpdateMetrics(_ context.Context, _ *rpc.Upda...
    method SetWatchdogHandler (line 174) | func (c *sessionReporter) SetWatchdogHandler(w func()) {
    method IsHealthy (line 181) | func (c *sessionReporter) IsHealthy() bool {
    method Drain (line 188) | func (c *sessionReporter) Drain() {
    method runWorker (line 193) | func (c *sessionReporter) runWorker(w *worker) {
    method getWorker (line 213) | func (c *sessionReporter) getWorker(egressID string) *worker {
    method handleUpdate (line 231) | func (c *sessionReporter) handleUpdate(w *worker, egressID string) {
    method setHealthy (line 285) | func (c *sessionReporter) setHealthy(isHealthy bool) bool {
  type worker (line 69) | type worker struct
    method submit (line 219) | func (w *worker) submit(u *update) error {
  type update (line 76) | type update struct
  function NewSessionReporter (line 81) | func NewSessionReporter(conf *config.BaseConfig, bus psrpc.MessageBus) (...
  function isRetryableError (line 307) | func isRetryableError(err error) bool {

FILE: pkg/ipc/conn.go
  constant network (line 28) | network        = "unix"
  constant handlerAddress (line 29) | handlerAddress = "handler_ipc.sock"
  constant serviceAddress (line 30) | serviceAddress = "service_ipc.sock"
  type EgressHandlerClientWrapper (line 33) | type EgressHandlerClientWrapper struct
    method Close (line 94) | func (c EgressHandlerClientWrapper) Close() error {
  function StartServiceListener (line 38) | func StartServiceListener(ipcServer *grpc.Server, serviceTmpDir string) ...
  function NewHandlerClient (line 53) | func NewHandlerClient(handlerTmpDir string) (*EgressHandlerClientWrapper...
  function StartHandlerListener (line 66) | func StartHandlerListener(ipcServer *grpc.Server, handlerTmpDir string) ...
  function NewServiceClient (line 81) | func NewServiceClient(serviceTmpDir string) (EgressServiceClient, error) {

FILE: pkg/ipc/ipc.pb.go
  constant _ (line 36) | _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
  constant _ (line 38) | _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
  type HandlerReadyRequest (line 41) | type HandlerReadyRequest struct
    method Reset (line 48) | func (x *HandlerReadyRequest) Reset() {
    method String (line 55) | func (x *HandlerReadyRequest) String() string {
    method ProtoMessage (line 59) | func (*HandlerReadyRequest) ProtoMessage() {}
    method ProtoReflect (line 61) | func (x *HandlerReadyRequest) ProtoReflect() protoreflect.Message {
    method Descriptor (line 74) | func (*HandlerReadyRequest) Descriptor() ([]byte, []int) {
    method GetEgressId (line 78) | func (x *HandlerReadyRequest) GetEgressId() string {
  type HandlerFinishedRequest (line 85) | type HandlerFinishedRequest struct
    method Reset (line 94) | func (x *HandlerFinishedRequest) Reset() {
    method String (line 101) | func (x *HandlerFinishedRequest) String() string {
    method ProtoMessage (line 105) | func (*HandlerFinishedRequest) ProtoMessage() {}
    method ProtoReflect (line 107) | func (x *HandlerFinishedRequest) ProtoReflect() protoreflect.Message {
    method Descriptor (line 120) | func (*HandlerFinishedRequest) Descriptor() ([]byte, []int) {
    method GetEgressId (line 124) | func (x *HandlerFinishedRequest) GetEgressId() string {
    method GetMetrics (line 131) | func (x *HandlerFinishedRequest) GetMetrics() string {
    method GetInfo (line 138) | func (x *HandlerFinishedRequest) GetInfo() *livekit.EgressInfo {
  type StorageEventRequest (line 145) | type StorageEventRequest struct
    method Reset (line 156) | func (x *StorageEventRequest) Reset() {
    method String (line 163) | func (x *StorageEventRequest) String() string {
    method ProtoMessage (line 167) | func (*StorageEventRequest) ProtoMessage() {}
    method ProtoReflect (line 169) | func (x *StorageEventRequest) ProtoReflect() protoreflect.Message {
    method Descriptor (line 182) | func (*StorageEventRequest) Descriptor() ([]byte, []int) {
    method GetEgressId (line 186) | func (x *StorageEventRequest) GetEgressId() string {
    method GetOperation (line 193) | func (x *StorageEventRequest) GetOperation() string {
    method GetPath (line 200) | func (x *StorageEventRequest) GetPath() string {
    method GetSize (line 207) | func (x *StorageEventRequest) GetSize() int64 {
    method GetLifetimeDays (line 214) | func (x *StorageEventRequest) GetLifetimeDays() int64 {
  type GstPipelineDebugDotRequest (line 221) | type GstPipelineDebugDotRequest struct
    method Reset (line 227) | func (x *GstPipelineDebugDotRequest) Reset() {
    method String (line 234) | func (x *GstPipelineDebugDotRequest) String() string {
    method ProtoMessage (line 238) | func (*GstPipelineDebugDotRequest) ProtoMessage() {}
    method ProtoReflect (line 240) | func (x *GstPipelineDebugDotRequest) ProtoReflect() protoreflect.Messa...
    method Descriptor (line 253) | func (*GstPipelineDebugDotRequest) Descriptor() ([]byte, []int) {
  type GstPipelineDebugDotResponse (line 257) | type GstPipelineDebugDotResponse struct
    method Reset (line 264) | func (x *GstPipelineDebugDotResponse) Reset() {
    method String (line 271) | func (x *GstPipelineDebugDotResponse) String() string {
    method ProtoMessage (line 275) | func (*GstPipelineDebugDotResponse) ProtoMessage() {}
    method ProtoReflect (line 277) | func (x *GstPipelineDebugDotResponse) ProtoReflect() protoreflect.Mess...
    method Descriptor (line 290) | func (*GstPipelineDebugDotResponse) Descriptor() ([]byte, []int) {
    method GetDotFile (line 294) | func (x *GstPipelineDebugDotResponse) GetDotFile() string {
  type PProfRequest (line 301) | type PProfRequest struct
    method Reset (line 310) | func (x *PProfRequest) Reset() {
    method String (line 317) | func (x *PProfRequest) String() string {
    method ProtoMessage (line 321) | func (*PProfRequest) ProtoMessage() {}
    method ProtoReflect (line 323) | func (x *PProfRequest) ProtoReflect() protoreflect.Message {
    method Descriptor (line 336) | func (*PProfRequest) Descriptor() ([]byte, []int) {
    method GetProfileName (line 340) | func (x *PProfRequest) GetProfileName() string {
    method GetTimeout (line 347) | func (x *PProfRequest) GetTimeout() int32 {
    method GetDebug (line 354) | func (x *PProfRequest) GetDebug() int32 {
  type PProfResponse (line 361) | type PProfResponse struct
    method Reset (line 368) | func (x *PProfResponse) Reset() {
    method String (line 375) | func (x *PProfResponse) String() string {
    method ProtoMessage (line 379) | func (*PProfResponse) ProtoMessage() {}
    method ProtoReflect (line 381) | func (x *PProfResponse) ProtoReflect() protoreflect.Message {
    method Descriptor (line 394) | func (*PProfResponse) Descriptor() ([]byte, []int) {
    method GetPprofFile (line 398) | func (x *PProfResponse) GetPprofFile() []byte {
  type MetricsRequest (line 405) | type MetricsRequest struct
    method Reset (line 411) | func (x *MetricsRequest) Reset() {
    method String (line 418) | func (x *MetricsRequest) String() string {
    method ProtoMessage (line 422) | func (*MetricsRequest) ProtoMessage() {}
    method ProtoReflect (line 424) | func (x *MetricsRequest) ProtoReflect() protoreflect.Message {
    method Descriptor (line 437) | func (*MetricsRequest) Descriptor() ([]byte, []int) {
  type MetricsResponse (line 441) | type MetricsResponse struct
    method Reset (line 448) | func (x *MetricsResponse) Reset() {
    method String (line 455) | func (x *MetricsResponse) String() string {
    method ProtoMessage (line 459) | func (*MetricsResponse) ProtoMessage() {}
    method ProtoReflect (line 461) | func (x *MetricsResponse) ProtoReflect() protoreflect.Message {
    method Descriptor (line 474) | func (*MetricsResponse) Descriptor() ([]byte, []int) {
    method GetMetrics (line 478) | func (x *MetricsResponse) GetMetrics() string {
  type KillEgressRequest (line 485) | type KillEgressRequest struct
    method Reset (line 492) | func (x *KillEgressRequest) Reset() {
    method String (line 499) | func (x *KillEgressRequest) String() string {
    method ProtoMessage (line 503) | func (*KillEgressRequest) ProtoMessage() {}
    method ProtoReflect (line 505) | func (x *KillEgressRequest) ProtoReflect() protoreflect.Message {
    method Descriptor (line 518) | func (*KillEgressRequest) Descriptor() ([]byte, []int) {
    method GetError (line 522) | func (x *KillEgressRequest) GetError() string {
  constant file_ipc_proto_rawDesc (line 531) | file_ipc_proto_rawDesc = "" +
  function file_ipc_proto_rawDescGZIP (line 580) | func file_ipc_proto_rawDescGZIP() []byte {
  function init (line 631) | func init() { file_ipc_proto_init() }
  function file_ipc_proto_init (line 632) | func file_ipc_proto_init() {

FILE: pkg/ipc/ipc_grpc.pb.go
  constant _ (line 36) | _ = grpc.SupportPackageIsVersion9
  constant EgressService_HandlerReady_FullMethodName (line 39) | EgressService_HandlerReady_FullMethodName    = "/ipc.EgressService/Handl...
  constant EgressService_HandlerUpdate_FullMethodName (line 40) | EgressService_HandlerUpdate_FullMethodName   = "/ipc.EgressService/Handl...
  constant EgressService_HandlerFinished_FullMethodName (line 41) | EgressService_HandlerFinished_FullMethodName = "/ipc.EgressService/Handl...
  constant EgressService_ReplayReady_FullMethodName (line 42) | EgressService_ReplayReady_FullMethodName     = "/ipc.EgressService/Repla...
  constant EgressService_StorageEvent_FullMethodName (line 43) | EgressService_StorageEvent_FullMethodName    = "/ipc.EgressService/Stora...
  type EgressServiceClient (line 49) | type EgressServiceClient interface
  type egressServiceClient (line 57) | type egressServiceClient struct
    method HandlerReady (line 65) | func (c *egressServiceClient) HandlerReady(ctx context.Context, in *Ha...
    method HandlerUpdate (line 75) | func (c *egressServiceClient) HandlerUpdate(ctx context.Context, in *l...
    method HandlerFinished (line 85) | func (c *egressServiceClient) HandlerFinished(ctx context.Context, in ...
    method ReplayReady (line 95) | func (c *egressServiceClient) ReplayReady(ctx context.Context, in *rpc...
    method StorageEvent (line 105) | func (c *egressServiceClient) StorageEvent(ctx context.Context, in *St...
  function NewEgressServiceClient (line 61) | func NewEgressServiceClient(cc grpc.ClientConnInterface) EgressServiceCl...
  type EgressServiceServer (line 118) | type EgressServiceServer interface
  type UnimplementedEgressServiceServer (line 132) | type UnimplementedEgressServiceServer struct
    method HandlerReady (line 134) | func (UnimplementedEgressServiceServer) HandlerReady(context.Context, ...
    method HandlerUpdate (line 137) | func (UnimplementedEgressServiceServer) HandlerUpdate(context.Context,...
    method HandlerFinished (line 140) | func (UnimplementedEgressServiceServer) HandlerFinished(context.Contex...
    method ReplayReady (line 143) | func (UnimplementedEgressServiceServer) ReplayReady(context.Context, *...
    method StorageEvent (line 146) | func (UnimplementedEgressServiceServer) StorageEvent(context.Context, ...
    method mustEmbedUnimplementedEgressServiceServer (line 149) | func (UnimplementedEgressServiceServer) mustEmbedUnimplementedEgressSe...
    method testEmbeddedByValue (line 150) | func (UnimplementedEgressServiceServer) testEmbeddedByValue()         ...
  type UnsafeEgressServiceServer (line 155) | type UnsafeEgressServiceServer interface
  function RegisterEgressServiceServer (line 159) | func RegisterEgressServiceServer(s grpc.ServiceRegistrar, srv EgressServ...
  function _EgressService_HandlerReady_Handler (line 170) | func _EgressService_HandlerReady_Handler(srv interface{}, ctx context.Co...
  function _EgressService_HandlerUpdate_Handler (line 188) | func _EgressService_HandlerUpdate_Handler(srv interface{}, ctx context.C...
  function _EgressService_HandlerFinished_Handler (line 206) | func _EgressService_HandlerFinished_Handler(srv interface{}, ctx context...
  function _EgressService_ReplayReady_Handler (line 224) | func _EgressService_ReplayReady_Handler(srv interface{}, ctx context.Con...
  function _EgressService_StorageEvent_Handler (line 242) | func _EgressService_StorageEvent_Handler(srv interface{}, ctx context.Co...
  constant EgressHandler_GetPipelineDot_FullMethodName (line 293) | EgressHandler_GetPipelineDot_FullMethodName = "/ipc.EgressHandler/GetPip...
  constant EgressHandler_GetPProf_FullMethodName (line 294) | EgressHandler_GetPProf_FullMethodName       = "/ipc.EgressHandler/GetPProf"
  constant EgressHandler_GetMetrics_FullMethodName (line 295) | EgressHandler_GetMetrics_FullMethodName     = "/ipc.EgressHandler/GetMet...
  constant EgressHandler_KillEgress_FullMethodName (line 296) | EgressHandler_KillEgress_FullMethodName     = "/ipc.EgressHandler/KillEg...
  type EgressHandlerClient (line 302) | type EgressHandlerClient interface
  type egressHandlerClient (line 309) | type egressHandlerClient struct
    method GetPipelineDot (line 317) | func (c *egressHandlerClient) GetPipelineDot(ctx context.Context, in *...
    method GetPProf (line 327) | func (c *egressHandlerClient) GetPProf(ctx context.Context, in *PProfR...
    method GetMetrics (line 337) | func (c *egressHandlerClient) GetMetrics(ctx context.Context, in *Metr...
    method KillEgress (line 347) | func (c *egressHandlerClient) KillEgress(ctx context.Context, in *Kill...
  function NewEgressHandlerClient (line 313) | func NewEgressHandlerClient(cc grpc.ClientConnInterface) EgressHandlerCl...
  type EgressHandlerServer (line 360) | type EgressHandlerServer interface
  type UnimplementedEgressHandlerServer (line 373) | type UnimplementedEgressHandlerServer struct
    method GetPipelineDot (line 375) | func (UnimplementedEgressHandlerServer) GetPipelineDot(context.Context...
    method GetPProf (line 378) | func (UnimplementedEgressHandlerServer) GetPProf(context.Context, *PPr...
    method GetMetrics (line 381) | func (UnimplementedEgressHandlerServer) GetMetrics(context.Context, *M...
    method KillEgress (line 384) | func (UnimplementedEgressHandlerServer) KillEgress(context.Context, *K...
    method mustEmbedUnimplementedEgressHandlerServer (line 387) | func (UnimplementedEgressHandlerServer) mustEmbedUnimplementedEgressHa...
    method testEmbeddedByValue (line 388) | func (UnimplementedEgressHandlerServer) testEmbeddedByValue()         ...
  type UnsafeEgressHandlerServer (line 393) | type UnsafeEgressHandlerServer interface
  function RegisterEgressHandlerServer (line 397) | func RegisterEgressHandlerServer(s grpc.ServiceRegistrar, srv EgressHand...
  function _EgressHandler_GetPipelineDot_Handler (line 408) | func _EgressHandler_GetPipelineDot_Handler(srv interface{}, ctx context....
  function _EgressHandler_GetPProf_Handler (line 426) | func _EgressHandler_GetPProf_Handler(srv interface{}, ctx context.Contex...
  function _EgressHandler_GetMetrics_Handler (line 444) | func _EgressHandler_GetMetrics_Handler(srv interface{}, ctx context.Cont...
  function _EgressHandler_KillEgress_Handler (line 462) | func _EgressHandler_KillEgress_Handler(srv interface{}, ctx context.Cont...

FILE: pkg/logging/csv.go
  type TrackStats (line 26) | type TrackStats struct
  type StreamStats (line 39) | type StreamStats struct
  type CSVLogger (line 49) | type CSVLogger struct
  function NewCSVLogger (line 53) | func NewCSVLogger[T any](filename string) (*CSVLogger[T], error) {
  method Write (line 76) | func (l *CSVLogger[T]) Write(value *T) {
  method Close (line 88) | func (l *CSVLogger[T]) Close() {

FILE: pkg/logging/handler.go
  constant channelSize (line 16) | channelSize     = 4096
  constant dropLogThrottle (line 17) | dropLogThrottle = 10 * time.Second
  type HandlerLogger (line 20) | type HandlerLogger struct
    method Write (line 40) | func (h *HandlerLogger) Write(p []byte) (int, error) {
    method Close (line 61) | func (h *HandlerLogger) Close() error {
    method drain (line 67) | func (h *HandlerLogger) drain() {
    method processLine (line 98) | func (h *HandlerLogger) processLine(line string, panicBuf *[]string) {
    method isPanicContinuation (line 142) | func (h *HandlerLogger) isPanicContinuation(line string) bool {
    method flushPanic (line 155) | func (h *HandlerLogger) flushPanic(panicBuf *[]string) {
  function NewHandlerLogger (line 28) | func NewHandlerLogger(handlerID, egressID string) *HandlerLogger {

FILE: pkg/logging/s3.go
  type S3Logger (line 28) | type S3Logger struct
    method Logf (line 40) | func (l *S3Logger) Logf(classification logging.Classification, format ...
    method WriteLogs (line 50) | func (l *S3Logger) WriteLogs() {
  function NewS3Logger (line 34) | func NewS3Logger() *S3Logger {

FILE: pkg/pipeline/builder/audio.go
  constant leakyQueue (line 36) | leakyQueue    = true
  constant blockingQueue (line 37) | blockingQueue = false
  constant audioRateTolerance (line 39) | audioRateTolerance = 3 * time.Millisecond
  constant audioBinName (line 40) | audioBinName       = "audio"
  type AudioBin (line 43) | type AudioBin struct
    method onTrackAdded (line 160) | func (b *AudioBin) onTrackAdded(ts *config.TrackSource) {
    method onTrackRemoved (line 174) | func (b *AudioBin) onTrackRemoved(trackID string) {
    method buildWebInput (line 193) | func (b *AudioBin) buildWebInput() error {
    method buildSDKInput (line 217) | func (b *AudioBin) buildSDKInput() error {
    method addAudioAppSrcBin (line 240) | func (b *AudioBin) addAudioAppSrcBin(ts *config.TrackSource) error {
    method addAudioAppSrcBinLocked (line 247) | func (b *AudioBin) addAudioAppSrcBinLocked(ts *config.TrackSource) err...
    method onSourceBinReset (line 366) | func (b *AudioBin) onSourceBinReset(ts *config.TrackSource) error {
    method resetAudioAppSrcBin (line 373) | func (b *AudioBin) resetAudioAppSrcBin(ts *config.TrackSource) error {
    method getChannelLocked (line 406) | func (b *AudioBin) getChannelLocked(ts *config.TrackSource) livekit.Au...
    method addAudioTestSrcBin (line 433) | func (b *AudioBin) addAudioTestSrcBin() error {
    method addMixer (line 466) | func (b *AudioBin) addMixer() error {
    method addEncoder (line 488) | func (b *AudioBin) addEncoder() error {
    method installPitchProbes (line 563) | func (b *AudioBin) installPitchProbes() {
    method addAudioConvertWithPitch (line 601) | func (b *AudioBin) addAudioConvertWithPitch(bin *gstreamer.Bin, p *con...
  type driftProcessNotifier (line 55) | type driftProcessNotifier interface
  type audioPacer (line 59) | type audioPacer struct
    method start (line 67) | func (a *audioPacer) start(drift time.Duration) {
    method observeProcessedDuration (line 95) | func (a *audioPacer) observeProcessedDuration(d time.Duration) {
    method stop (line 107) | func (a *audioPacer) stop() {
  function BuildAudioBin (line 116) | func BuildAudioBin(pipeline *gstreamer.Pipeline, p *config.PipelineConfi...
  function addAudioConverter (line 534) | func addAudioConverter(b *gstreamer.Bin, p *config.PipelineConfig, chann...
  function newAudioFloatCapsFilter (line 656) | func newAudioFloatCapsFilter(p *config.PipelineConfig, channel livekit.A...
  function newAudioCapsFilter (line 679) | func newAudioCapsFilter(p *config.PipelineConfig, channel livekit.AudioC...
  function subscribeForQoS (line 714) | func subscribeForQoS(mixer *gst.Element) {

FILE: pkg/pipeline/builder/file.go
  function BuildFileBin (line 26) | func BuildFileBin(pipeline *gstreamer.Pipeline, p *config.PipelineConfig...

FILE: pkg/pipeline/builder/image.go
  constant imageQueueLatency (line 31) | imageQueueLatency = 200 * time.Millisecond
  function BuildImageBin (line 34) | func BuildImageBin(c *config.ImageConfig, pipeline *gstreamer.Pipeline, ...

FILE: pkg/pipeline/builder/muxer.go
  type muxer (line 26) | type muxer interface
  type muxerImpl (line 32) | type muxerImpl struct
    method GetRequestPad (line 53) | func (m *muxerImpl) GetRequestPad(name string) *gst.Pad {
    method GetElement (line 57) | func (m *muxerImpl) GetElement() *gst.Element {
  function newMuxer (line 37) | func newMuxer(elementName string) (*muxerImpl, error) {
  type mp3Muxer (line 63) | type mp3Muxer struct
    method GetRequestPad (line 83) | func (m *mp3Muxer) GetRequestPad(_ string) *gst.Pad {
  function newMP3Muxer (line 70) | func newMP3Muxer() (*mp3Muxer, error) {

FILE: pkg/pipeline/builder/muxer_test.go
  function initGStreamer (line 14) | func initGStreamer(t *testing.T) {
  function TestNewMuxer_KnownMuxers (line 19) | func TestNewMuxer_KnownMuxers(t *testing.T) {
  function TestNewMuxer_InvalidMuxer (line 32) | func TestNewMuxer_InvalidMuxer(t *testing.T) {
  function TestNewMP3Muxer (line 40) | func TestNewMP3Muxer(t *testing.T) {

FILE: pkg/pipeline/builder/pts_fixer.go
  type ptsFixer (line 13) | type ptsFixer struct
    method onBuffer (line 44) | func (f *ptsFixer) onBuffer(_ *gst.Pad, info *gst.PadProbeInfo) gst.Pa...
  function newPTSFixer (line 22) | func newPTSFixer(elementName, context string) (*ptsFixer, error) {

FILE: pkg/pipeline/builder/segment.go
  type FirstSampleMetadata (line 31) | type FirstSampleMetadata struct
  function BuildSegmentBin (line 35) | func BuildSegmentBin(pipeline *gstreamer.Pipeline, p *config.PipelineCon...

FILE: pkg/pipeline/builder/stream.go
  type StreamBin (line 33) | type StreamBin struct
    method BuildStream (line 115) | func (sb *StreamBin) BuildStream(stream *config.Stream, framerate int3...
  type Stream (line 40) | type Stream struct
    method Reset (line 234) | func (s *Stream) Reset(streamErr error) (bool, error) {
    method Stats (line 274) | func (s *Stream) Stats() (*logging.StreamStats, bool) {
  function BuildStreamBin (line 52) | func BuildStreamBin(pipeline *gstreamer.Pipeline, p *config.PipelineConf...
  constant outBytesTotal (line 267) | outBytesTotal = "out-bytes-total"
  constant outBytesAcked (line 268) | outBytesAcked = "out-bytes-acked"
  constant inBytesTotal (line 269) | inBytesTotal  = "in-bytes-total"
  constant inBytesAcked (line 270) | inBytesAcked  = "in-bytes-acked"
  constant srtBytesSent (line 271) | srtBytesSent  = "bytes-sent-total"
  function tryUInt64 (line 306) | func tryUInt64(stats map[string]interface{}, key string) uint64 {

FILE: pkg/pipeline/builder/video.go
  constant videoTestSrcName (line 35) | videoTestSrcName = "video_test_src"
  type VideoBin (line 38) | type VideoBin struct
    method buildVideoQueue (line 55) | func (b *VideoBin) buildVideoQueue(name string) (*gst.Element, error) {
    method onTrackAdded (line 128) | func (b *VideoBin) onTrackAdded(ts *config.TrackSource) {
    method onTrackRemoved (line 142) | func (b *VideoBin) onTrackRemoved(trackID string) {
    method onTrackMuted (line 170) | func (b *VideoBin) onTrackMuted(trackID string) {
    method onTrackUnmuted (line 186) | func (b *VideoBin) onTrackUnmuted(trackID string) {
    method onSourceBinReset (line 202) | func (b *VideoBin) onSourceBinReset(ts *config.TrackSource) error {
    method resetVideoAppSrcBin (line 209) | func (b *VideoBin) resetVideoAppSrcBin(ts *config.TrackSource) error {
    method buildWebInput (line 271) | func (b *VideoBin) buildWebInput() error {
    method buildSDKInput (line 323) | func (b *VideoBin) buildSDKInput() error {
    method addAppSrcBin (line 359) | func (b *VideoBin) addAppSrcBin(ts *config.TrackSource) error {
    method buildAppSrcBin (line 383) | func (b *VideoBin) buildAppSrcBin(ts *config.TrackSource, name string)...
    method addVideoTestSrcBin (line 536) | func (b *VideoBin) addVideoTestSrcBin() error {
    method addSelector (line 572) | func (b *VideoBin) addSelector() error {
    method addEncoder (line 599) | func (b *VideoBin) addEncoder() error {
    method addDecodedVideoSink (line 714) | func (b *VideoBin) addDecodedVideoSink() error {
    method addVideoConverter (line 734) | func (b *VideoBin) addVideoConverter(bin *gstreamer.Bin) error {
    method newVideoCapsFilter (line 772) | func (b *VideoBin) newVideoCapsFilter(includeFramerate bool) (*gst.Ele...
    method getSrcPad (line 794) | func (b *VideoBin) getSrcPad(name string) *gst.Pad {
    method createSrcPad (line 801) | func (b *VideoBin) createSrcPad(trackID, name string) {
    method createSrcPadLocked (line 808) | func (b *VideoBin) createSrcPadLocked(trackID, name string) {
    method createTestSrcPad (line 827) | func (b *VideoBin) createTestSrcPad() {
    method setSelectorPad (line 847) | func (b *VideoBin) setSelectorPad(name string) error {
    method setSelectorPadLocked (line 854) | func (b *VideoBin) setSelectorPadLocked(name string) error {
  function BuildVideoBin (line 63) | func BuildVideoBin(pipeline *gstreamer.Pipeline, p *config.PipelineConfi...

FILE: pkg/pipeline/builder/vp9_probe.go
  constant keyframeHistorySize (line 16) | keyframeHistorySize     = 10
  constant keyframeRequestInterval (line 17) | keyframeRequestInterval = 200 * time.Millisecond
  type vp9ParseProbe (line 23) | type vp9ParseProbe struct
    method Close (line 78) | func (p *vp9ParseProbe) Close() {
    method onSrcBuffer (line 93) | func (p *vp9ParseProbe) onSrcBuffer(_ *gst.Pad, info *gst.PadProbeInfo...
    method onSinkBuffer (line 113) | func (p *vp9ParseProbe) onSinkBuffer(_ *gst.Pad, info *gst.PadProbeInf...
    method handleMissingPTS (line 142) | func (p *vp9ParseProbe) handleMissingPTS() {
    method handleValidPTS (line 162) | func (p *vp9ParseProbe) handleValidPTS(buffer *gst.Buffer, pts time.Du...
    method trackKeyframe (line 179) | func (p *vp9ParseProbe) trackKeyframe(pts time.Duration) {
    method requestKeyframeIfDue (line 199) | func (p *vp9ParseProbe) requestKeyframeIfDue() {
    method keyframeStats (line 225) | func (p *vp9ParseProbe) keyframeStats() (time.Duration, int, bool) {
    method logKeyframeHistory (line 237) | func (p *vp9ParseProbe) logKeyframeHistory(reason string) {
  function newVP9ParseProbe (line 51) | func newVP9ParseProbe(trackID string, parse *gst.Element, onSignal func(...
  function clockTimeToDuration (line 218) | func clockTimeToDuration(ct gst.ClockTime) (time.Duration, bool) {
  type missingPadError (line 256) | type missingPadError struct
    method Error (line 265) | func (e missingPadError) Error() string {
  function newMissingPadError (line 261) | func newMissingPadError(element, pad string) error {

FILE: pkg/pipeline/builder/websocket.go
  function BuildWebsocketBin (line 25) | func BuildWebsocketBin(pipeline *gstreamer.Pipeline, appSinkCallbacks *a...

FILE: pkg/pipeline/controller.go
  constant pipelineName (line 49) | pipelineName = "pipeline"
  constant eosTimeout (line 50) | eosTimeout   = time.Second * 30
  constant streamRetryUpdateInterval (line 52) | streamRetryUpdateInterval = time.Minute
  type Controller (line 55) | type Controller struct
    method Callbacks (line 156) | func (c *Controller) Callbacks() *gstreamer.Callbacks {
    method BuildPipeline (line 188) | func (c *Controller) BuildPipeline() error {
    method SetReplayTiming (line 244) | func (c *Controller) SetReplayTiming(startAt, durationMs int64) {
    method Run (line 249) | func (c *Controller) Run(ctx context.Context) *livekit.EgressInfo {
    method UpdateStream (line 361) | func (c *Controller) UpdateStream(ctx context.Context, req *livekit.Up...
    method UpdateEgress (line 418) | func (c *Controller) UpdateEgress(ctx context.Context, req *livekit.Up...
    method streamFinished (line 449) | func (c *Controller) streamFinished(ctx context.Context, stream *confi...
    method streamFailed (line 473) | func (c *Controller) streamFailed(ctx context.Context, stream *config....
    method trackStreamRetry (line 498) | func (c *Controller) trackStreamRetry(ctx context.Context, stream *con...
    method onEOSSent (line 513) | func (c *Controller) onEOSSent() {
    method onStorageLimitReached (line 522) | func (c *Controller) onStorageLimitReached() {
    method SendEOS (line 529) | func (c *Controller) SendEOS(ctx context.Context, reason string) {
    method sendEOS (line 570) | func (c *Controller) sendEOS() {
    method OnError (line 601) | func (c *Controller) OnError(err error) {
    method Close (line 615) | func (c *Controller) Close() {
    method startSessionLimitTimer (line 672) | func (c *Controller) startSessionLimitTimer(ctx context.Context) {
    method startOutputSizeMonitor (line 709) | func (c *Controller) startOutputSizeMonitor() {
    method stopOutputSizeMonitor (line 721) | func (c *Controller) stopOutputSizeMonitor() {
    method monitorOutputDirSize (line 728) | func (c *Controller) monitorOutputDirSize(ctx context.Context) {
    method getOutputDirStats (line 796) | func (c *Controller) getOutputDirStats() (int64, []outputFileStat, err...
    method logOutputFileSizes (line 842) | func (c *Controller) logOutputFileSizes(files []outputFileStat, limit ...
    method updateStartTime (line 856) | func (c *Controller) updateStartTime(startedAt int64) {
    method updateStreamStartTime (line 893) | func (c *Controller) updateStreamStartTime(streamID string) {
    method streamUpdated (line 908) | func (c *Controller) streamUpdated(ctx context.Context) {
    method sendHandlerUpdate (line 930) | func (c *Controller) sendHandlerUpdate(ctx context.Context, info *live...
    method updateEndTime (line 936) | func (c *Controller) updateEndTime() {
    method uploadManifest (line 985) | func (c *Controller) uploadManifest() {
    method getStreamSink (line 1031) | func (c *Controller) getStreamSink() *sink.StreamSink {
    method getSegmentSink (line 1040) | func (c *Controller) getSegmentSink() *sink.SegmentSink {
    method getImageSink (line 1049) | func (c *Controller) getImageSink(name string) *sink.ImageSink {
  type controllerStats (line 87) | type controllerStats struct
  type SourceBuilder (line 102) | type SourceBuilder
  function New (line 108) | func New(ctx context.Context, conf *config.PipelineConfig, ipcServiceCli...
  function NewWithSource (line 121) | func NewWithSource(
  function newController (line 160) | func newController(conf *config.PipelineConfig, ipcServiceClient ipc.Egr...
  type outputFileStat (line 791) | type outputFileStat struct

FILE: pkg/pipeline/debug.go
  method GetGstPipelineDebugDot (line 35) | func (c *Controller) GetGstPipelineDebugDot() (string, error) {
  function sanitizeDebugFilenameComponent (line 49) | func sanitizeDebugFilenameComponent(s string) string {
  method writeDotFile (line 64) | func (c *Controller) writeDotFile(filename, contents string) {
  method generateDotFile (line 74) | func (c *Controller) generateDotFile(reason string) {
  method generatePProf (line 100) | func (c *Controller) generatePProf() {
  method uploadDebugFiles (line 123) | func (c *Controller) uploadDebugFiles() {

FILE: pkg/pipeline/sink/file.go
  type FileSink (line 30) | type FileSink struct
    method Start (line 65) | func (s *FileSink) Start() error {
    method UploadManifest (line 69) | func (s *FileSink) UploadManifest(filepath string) (string, bool, erro...
    method Close (line 83) | func (s *FileSink) Close() error {
  function newFileSink (line 38) | func newFileSink(

FILE: pkg/pipeline/sink/image.go
  type ImageSink (line 36) | type ImageSink struct
    method Start (line 92) | func (s *ImageSink) Start() error {
    method handleNewImage (line 114) | func (s *ImageSink) handleNewImage(update *imageUpdate) error {
    method getImageTime (line 149) | func (s *ImageSink) getImageTime(pts uint64) time.Time {
    method NewImage (line 159) | func (s *ImageSink) NewImage(filepath string, ts uint64) error {
    method UploadManifest (line 174) | func (s *ImageSink) UploadManifest(filepath string) (string, bool, err...
    method Close (line 188) | func (s *ImageSink) Close() error {
  type imageUpdate (line 52) | type imageUpdate struct
  function newImageSink (line 57) | func newImageSink(

FILE: pkg/pipeline/sink/m3u8/writer.go
  type PlaylistType (line 27) | type PlaylistType
  constant PlaylistTypeLive (line 30) | PlaylistTypeLive  PlaylistType = ""
  constant PlaylistTypeEvent (line 31) | PlaylistTypeEvent PlaylistType = "EVENT"
  type PlaylistWriter (line 34) | type PlaylistWriter interface
  type basePlaylistWriter (line 39) | type basePlaylistWriter struct
    method createHeader (line 58) | func (p *basePlaylistWriter) createHeader(plType PlaylistType) string {
    method createSegmentEntry (line 74) | func (p *basePlaylistWriter) createSegmentEntry(dateTime time.Time, du...
  type eventPlaylistWriter (line 44) | type eventPlaylistWriter struct
    method Append (line 110) | func (p *eventPlaylistWriter) Append(dateTime time.Time, duration floa...
    method Close (line 122) | func (p *eventPlaylistWriter) Close() error {
  type livePlaylistWriter (line 48) | type livePlaylistWriter struct
    method Append (line 148) | func (p *livePlaylistWriter) Append(dateTime time.Time, duration float...
    method Close (line 167) | func (p *livePlaylistWriter) Close() error {
    method generatePlaylist (line 183) | func (p *livePlaylistWriter) generatePlaylist() string {
  function NewEventPlaylistWriter (line 88) | func NewEventPlaylistWriter(filename string, targetDuration int) (Playli...
  function NewLivePlaylistWriter (line 133) | func NewLivePlaylistWriter(filename string, targetDuration int, windowSi...

FILE: pkg/pipeline/sink/m3u8/writer_test.go
  function TestEventPlaylistWriter (line 26) | func TestEventPlaylistWriter(t *testing.T) {
  function TestLivePlaylistWriter (line 51) | func TestLivePlaylistWriter(t *testing.T) {

FILE: pkg/pipeline/sink/segments.go
  constant defaultLivePlaylistWindow (line 38) | defaultLivePlaylistWindow = 5
  type SegmentSink (line 41) | type SegmentSink struct
    method Start (line 150) | func (s *SegmentSink) Start() error {
    method handleClosedSegment (line 171) | func (s *SegmentSink) handleClosedSegment(update SegmentUpdate) {
    method handlePlaylistUpdates (line 199) | func (s *SegmentSink) handlePlaylistUpdates(update SegmentUpdate) error {
    method shouldUploadPlaylist (line 241) | func (s *SegmentSink) shouldUploadPlaylist() bool {
    method uploadPlaylist (line 247) | func (s *SegmentSink) uploadPlaylist() error {
    method uploadLivePlaylist (line 263) | func (s *SegmentSink) uploadLivePlaylist() error {
    method UpdateStartDate (line 273) | func (s *SegmentSink) UpdateStartDate(t time.Time) {
    method FragmentOpened (line 280) | func (s *SegmentSink) FragmentOpened(filepath string, startTime uint64...
    method FragmentClosed (line 303) | func (s *SegmentSink) FragmentClosed(filepath string, endTime uint64) ...
    method UploadManifest (line 325) | func (s *SegmentSink) UploadManifest(filepath string) (string, bool, e...
    method Close (line 339) | func (s *SegmentSink) Close() error {
  type SegmentUpdate (line 70) | type SegmentUpdate struct
  function newSegmentSink (line 76) | func newSegmentSink(

FILE: pkg/pipeline/sink/sink.go
  type Sink (line 28) | type Sink interface
  type base (line 36) | type base struct
    method AddEOSProbe (line 71) | func (s *base) AddEOSProbe() {
    method EOSReceived (line 80) | func (s *base) EOSReceived() bool {
  function NewSink (line 41) | func NewSink(

FILE: pkg/pipeline/sink/stream.go
  type StreamSink (line 32) | type StreamSink struct
    method Start (line 75) | func (s *StreamSink) Start() error {
    method AddStream (line 105) | func (s *StreamSink) AddStream(stream *config.Stream) error {
    method GetStream (line 127) | func (s *StreamSink) GetStream(name string) (*config.Stream, error) {
    method ResetStream (line 138) | func (s *StreamSink) ResetStream(stream *config.Stream, streamErr erro...
    method RemoveStream (line 149) | func (s *StreamSink) RemoveStream(stream *config.Stream) error {
    method UploadManifest (line 162) | func (s *StreamSink) UploadManifest(_ string) (string, bool, error) {
    method Close (line 166) | func (s *StreamSink) Close() error {
  function newStreamSink (line 44) | func newStreamSink(p *gstreamer.Pipeline, conf *config.PipelineConfig, o...

FILE: pkg/pipeline/sink/uploader/uploader.go
  constant presignedExpiration (line 33) | presignedExpiration = time.Hour * 24 * 7
  type Uploader (line 35) | type Uploader struct
    method Upload (line 113) | func (u *Uploader) Upload(
    method upload (line 166) | func (u *Uploader) upload(localFilepath string, storageFilepath string...
  type store (line 44) | type store struct
  function New (line 50) | func New(primary, backup *config.StorageConfig, monitor *stats.HandlerMo...
  function getUploader (line 75) | func getUploader(conf *config.StorageConfig) (*store, error) {

FILE: pkg/pipeline/sink/uploader/uploader_test.go
  function TestUploader (line 17) | func TestUploader(t *testing.T) {

FILE: pkg/pipeline/sink/websocket.go
  constant pingPeriod (line 39) | pingPeriod = time.Second * 30
  type WebsocketSink (line 41) | type WebsocketSink struct
    method Start (line 122) | func (s *WebsocketSink) Start() error {
    method Write (line 177) | func (s *WebsocketSink) Write(p []byte) (int, error) {
    method OnTrackMuted (line 187) | func (s *WebsocketSink) OnTrackMuted(_ string) {
    method OnTrackUnmuted (line 193) | func (s *WebsocketSink) OnTrackUnmuted(_ string) {
    method writeMutedMessage (line 203) | func (s *WebsocketSink) writeMutedMessage(muted bool) error {
    method UploadManifest (line 220) | func (s *WebsocketSink) UploadManifest(_ string) (string, bool, error) {
    method Close (line 224) | func (s *WebsocketSink) Close() error {
  function newWebsocketSink (line 50) | func newWebsocketSink(
  type textMessagePayload (line 199) | type textMessagePayload struct

FILE: pkg/pipeline/source/pulse/pactl.go
  function Clients (line 11) | func Clients() (int, error) {
  function List (line 19) | func List() (*PulseInfo, error) {
  type PulseInfo (line 32) | type PulseInfo struct
    method GetEgressInfo (line 124) | func (info *PulseInfo) GetEgressInfo() map[int]*EgressInfo {
  type Module (line 43) | type Module struct
  type Device (line 50) | type Device struct
  type IOBase (line 72) | type IOBase struct
  type SinkInput (line 90) | type SinkInput struct
  type SourceOutput (line 95) | type SourceOutput struct
  type Client (line 100) | type Client struct
  type Volume (line 107) | type Volume struct
  type Latency (line 113) | type Latency struct
  type EgressInfo (line 118) | type EgressInfo struct

FILE: pkg/pipeline/source/sdk.go
  constant subscriptionTimeout (line 40) | subscriptionTimeout = time.Second * 30
  type SDKSource (line 43) | type SDKSource struct
    method StartRecording (line 139) | func (s *SDKSource) StartRecording() <-chan struct{} {
    method EndRecording (line 143) | func (s *SDKSource) EndRecording() <-chan struct{} {
    method Playing (line 147) | func (s *SDKSource) Playing(trackID string) {
    method GetStartedAt (line 160) | func (s *SDKSource) GetStartedAt() int64 {
    method GetEndedAt (line 164) | func (s *SDKSource) GetEndedAt() int64 {
    method CloseWriters (line 168) | func (s *SDKSource) CloseWriters() {
    method StreamStopped (line 190) | func (s *SDKSource) StreamStopped(elementName string) {
    method Close (line 205) | func (s *SDKSource) Close() {
    method SetTimeProvider (line 209) | func (s *SDKSource) SetTimeProvider(tp gstreamer.TimeProvider) {
    method joinRoom (line 231) | func (s *SDKSource) joinRoom() error {
    method startAwaitingTracks (line 316) | func (s *SDKSource) startAwaitingTracks(expectedCount int) <-chan subs...
    method stopAwaitingTracks (line 323) | func (s *SDKSource) stopAwaitingTracks() {
    method completeInit (line 327) | func (s *SDKSource) completeInit() {
    method getInitResultChan (line 334) | func (s *SDKSource) getInitResultChan() chan<- subscriptionResult {
    method sendInitResult (line 342) | func (s *SDKSource) sendInitResult(ch chan<- subscriptionResult, track...
    method awaitRoomTracks (line 353) | func (s *SDKSource) awaitRoomTracks() error {
    method awaitMediaTracks (line 372) | func (s *SDKSource) awaitMediaTracks() (uint32, uint32, error) {
    method awaitParticipantTracks (line 436) | func (s *SDKSource) awaitParticipantTracks(identity string) (uint32, u...
    method awaitExpected (line 467) | func (s *SDKSource) awaitExpected(expected int) error {
    method getParticipant (line 492) | func (s *SDKSource) getParticipant(identity string, deadline time.Time...
    method awaitTrackPublication (line 504) | func (s *SDKSource) awaitTrackPublication(trackID string, deadline tim...
    method awaitTracks (line 518) | func (s *SDKSource) awaitTracks(expecting map[string]struct{}) (uint32...
    method subscribeToTracks (line 566) | func (s *SDKSource) subscribeToTracks(expecting map[string]struct{}, d...
    method subscribe (line 604) | func (s *SDKSource) subscribe(track lksdk.TrackPublication) error {
    method onTrackSubscribed (line 622) | func (s *SDKSource) onTrackSubscribed(track *webrtc.TrackRemote, pub *...
    method onTrackPublished (line 642) | func (s *SDKSource) onTrackPublished(pub *lksdk.RemoteTrackPublication...
    method shouldSubscribe (line 670) | func (s *SDKSource) shouldSubscribe(pub lksdk.TrackPublication) bool {
    method shouldSubscribeMedia (line 691) | func (s *SDKSource) shouldSubscribeMedia(pub lksdk.TrackPublication, r...
    method matchesAudioRoute (line 704) | func (s *SDKSource) matchesAudioRoute(pub lksdk.TrackPublication, rp *...
    method matchesMediaVideo (line 728) | func (s *SDKSource) matchesMediaVideo(pub lksdk.TrackPublication, rp *...
    method onTrackMuted (line 747) | func (s *SDKSource) onTrackMuted(pub lksdk.TrackPublication, _ lksdk.P...
    method onTrackUnmuted (line 756) | func (s *SDKSource) onTrackUnmuted(pub lksdk.TrackPublication, _ lksdk...
    method onTrackUnsubscribed (line 765) | func (s *SDKSource) onTrackUnsubscribed(_ *webrtc.TrackRemote, pub *lk...
    method onParticipantDisconnected (line 781) | func (s *SDKSource) onParticipantDisconnected(rp *lksdk.RemoteParticip...
    method onDisconnected (line 788) | func (s *SDKSource) onDisconnected() {
    method finished (line 793) | func (s *SDKSource) finished() {
    method shouldSkipTrackSubscriptions (line 797) | func (s *SDKSource) shouldSkipTrackSubscriptions() bool {
    method disconnectRoom (line 805) | func (s *SDKSource) disconnectRoom() {
    method shouldUseOneShotSenderReportSync (line 812) | func (s *SDKSource) shouldUseOneShotSenderReportSync() bool {
    method shouldEnableOneShotSenderReportSync (line 816) | func (s *SDKSource) shouldEnableOneShotSenderReportSync() bool {
    method shouldDisableAudioPTSAdjustment (line 820) | func (s *SDKSource) shouldDisableAudioPTSAdjustment() bool {
  type subscriptionResult (line 74) | type subscriptionResult struct
  function NewSDKSource (line 79) | func NewSDKSource(ctx context.Context, p *config.PipelineConfig, callbac...

FILE: pkg/pipeline/source/sdk/appwriter.go
  constant errBufferTooSmall (line 45) | errBufferTooSmall       = "buffer too small"
  constant discontinuityTolerance (line 46) | discontinuityTolerance  = 500 * time.Millisecond
  constant pipelineCheckInterval (line 47) | pipelineCheckInterval   = 5 * time.Second
  constant cSamplesQueueDepth (line 48) | cSamplesQueueDepth      = 100
  constant drainingTimeout (line 49) | drainingTimeout         = time.Second * 3
  constant unsubscribedGracePeriod (line 50) | unsubscribedGracePeriod = time.Second * 2
  constant flushingThreshold (line 54) | flushingThreshold = 100
  constant maxSrcResets (line 56) | maxSrcResets = 2
  type sampleItem (line 61) | type sampleItem struct
  type AppWriter (line 66) | type AppWriter struct
    method start (line 232) | func (w *AppWriter) start() {
    method readNext (line 291) | func (w *AppWriter) readNext() {
    method handleReadError (line 336) | func (w *AppWriter) handleReadError(err error) {
    method SetTimeProvider (line 398) | func (w *AppWriter) SetTimeProvider(tp gstreamer.TimeProvider) {
    method waitFor (line 407) | func (w *AppWriter) waitFor(ch <-chan struct{}) bool {
    method pipelineRunningTime (line 419) | func (w *AppWriter) pipelineRunningTime() (time.Duration, bool) {
    method pipelinePlayhead (line 426) | func (w *AppWriter) pipelinePlayhead() (time.Duration, bool) {
    method logTrackState (line 433) | func (w *AppWriter) logTrackState(event string) {
    method onKeyframeRequired (line 444) | func (w *AppWriter) onKeyframeRequired() {
    method notifyPushSamples (line 451) | func (w *AppWriter) notifyPushSamples() {
    method onPacket (line 457) | func (w *AppWriter) onPacket(sample []jitter.ExtPacket) {
    method pushSamples (line 487) | func (w *AppWriter) pushSamples() {
    method pushPacket (line 539) | func (w *AppWriter) pushPacket(pkt jitter.ExtPacket) error {
    method tryRecoverFromFlushing (line 614) | func (w *AppWriter) tryRecoverFromFlushing() bool {
    method maybeCheckPipelineLag (line 660) | func (w *AppWriter) maybeCheckPipelineLag(pts time.Duration) {
    method Playing (line 683) | func (w *AppWriter) Playing() {
    method Drain (line 688) | func (w *AppWriter) Drain(force bool) {
    method OnUnsubscribed (line 712) | func (w *AppWriter) OnUnsubscribed() {
    method Finished (line 718) | func (w *AppWriter) Finished() <-chan struct{} {
    method logStats (line 722) | func (w *AppWriter) logStats() {
    method getStats (line 743) | func (w *AppWriter) getStats() *logging.TrackStats {
    method updateDrift (line 759) | func (w *AppWriter) updateDrift(drift time.Duration) {
    method shouldHandleDiscontinuity (line 772) | func (w *AppWriter) shouldHandleDiscontinuity() bool {
    method TrackKind (line 776) | func (w *AppWriter) TrackKind() webrtc.RTPCodecType {
    method drainJitterBuffer (line 780) | func (w *AppWriter) drainJitterBuffer() {
    method shouldRemoveBeforeDrain (line 794) | func (w *AppWriter) shouldRemoveBeforeDrain() bool {
    method ensureRemovedBeforeDrain (line 799) | func (w *AppWriter) ensureRemovedBeforeDrain() {
  type appWriterStats (line 132) | type appWriterStats struct
  type DriftHandler (line 136) | type DriftHandler interface
  function NewAppWriter (line 141) | func NewAppWriter(
  function isDiscontinuity (line 790) | func isDiscontinuity(lastPTS time.Duration, pts time.Duration) bool {
  type G711Packet (line 805) | type G711Packet struct
    method Unmarshal (line 807) | func (p *G711Packet) Unmarshal(packet []byte) ([]byte, error) {
    method IsPartitionHead (line 815) | func (p *G711Packet) IsPartitionHead(_ []byte) bool {
    method IsPartitionTail (line 819) | func (p *G711Packet) IsPartitionTail(_ bool, _ []byte) bool {

FILE: pkg/pipeline/source/sdk/translator.go
  type Translator (line 27) | type Translator interface
  type VP8Translator (line 33) | type VP8Translator struct
    method Translate (line 48) | func (t *VP8Translator) Translate(pkt *rtp.Packet) {
  function NewVP8Translator (line 41) | func NewVP8Translator(logger logger.Logger) *VP8Translator {
  type NullTranslator (line 92) | type NullTranslator struct
    method Translate (line 98) | func (t *NullTranslator) Translate(_ *rtp.Packet) {}
  function NewNullTranslator (line 94) | func NewNullTranslator() Translator {

FILE: pkg/pipeline/source/source.go
  type Source (line 26) | type Source interface
  type TimeAware (line 34) | type TimeAware interface
  function New (line 38) | func New(ctx context.Context, p *config.PipelineConfig, callbacks *gstre...

FILE: pkg/pipeline/source/track_worker.go
  type TrackState (line 38) | type TrackState
    method String (line 46) | func (s TrackState) String() string {
  constant TrackStateIdle (line 41) | TrackStateIdle     TrackState = iota
  constant TrackStateActive (line 42) | TrackStateActive
  constant TrackStateCleaning (line 43) | TrackStateCleaning
  type OpType (line 60) | type OpType
    method String (line 71) | func (o OpType) String() string {
  constant OpSubscribe (line 63) | OpSubscribe       OpType = iota
  constant OpUnsubscribe (line 64) | OpUnsubscribe
  constant OpFinished (line 65) | OpFinished
  constant OpPlaying (line 66) | OpPlaying
  constant OpSetTimeProvider (line 67) | OpSetTimeProvider
  constant OpClose (line 68) | OpClose
  type Operation (line 91) | type Operation struct
  type workerState (line 103) | type workerState struct
  type trackWorker (line 111) | type trackWorker struct
  method getOrCreateWorker (line 118) | func (s *SDKSource) getOrCreateWorker(trackID string) *trackWorker {
  method runWorker (line 152) | func (s *SDKSource) runWorker(w *trackWorker) {
  method submitOp (line 168) | func (s *SDKSource) submitOp(trackID string, op Operation) {
  method reportSubscribeError (line 187) | func (s *SDKSource) reportSubscribeError(isPostInit bool, resultChan cha...
  method validateSubscription (line 195) | func (s *SDKSource) validateSubscription(op Operation) error {
  method updatePreInitStateLocked (line 206) | func (s *SDKSource) updatePreInitStateLocked(op Operation, ts *config.Tr...
  method handleSubscribe (line 261) | func (s *SDKSource) handleSubscribe(w *trackWorker, trackID string, stat...
  method processOp (line 320) | func (s *SDKSource) processOp(w *trackWorker, trackID string, ws *worker...
  method processIdleOp (line 338) | func (s *SDKSource) processIdleOp(w *trackWorker, trackID string, state ...
  method processActiveOp (line 357) | func (s *SDKSource) processActiveOp(_ *trackWorker, trackID string, stat...
  method startCleanup (line 399) | func (s *SDKSource) startCleanup(trackID string, state *workerState) {
  method doCleanup (line 415) | func (s *SDKSource) doCleanup(trackID string, state *workerState) {
  method createWriterForOp (line 454) | func (s *SDKSource) createWriterForOp(op Operation) (*sdk.AppWriter, *co...
  method handleOrphanedWriter (line 512) | func (s *SDKSource) handleOrphanedWriter(trackID string, writer *sdk.App...

FILE: pkg/pipeline/source/track_worker_test.go
  function testSDKSource (line 30) | func testSDKSource(t *testing.T) *SDKSource {
  function TestGetOrCreateWorker_ReturnsExistingWorker (line 56) | func TestGetOrCreateWorker_ReturnsExistingWorker(t *testing.T) {
  function TestGetOrCreateWorker_ReturnsNilWhenClosing (line 65) | func TestGetOrCreateWorker_ReturnsNilWhenClosing(t *testing.T) {
  function TestSubmitOp_DropsOpWhenClosing (line 74) | func TestSubmitOp_DropsOpWhenClosing(t *testing.T) {
  function TestStateTransitions_IdleState (line 88) | func TestStateTransitions_IdleState(t *testing.T) {
  function TestStateTransitions_ActiveState (line 123) | func TestStateTransitions_ActiveState(t *testing.T) {

FILE: pkg/pipeline/source/web.go
  constant startRecordingLog (line 46) | startRecordingLog = "START_RECORDING"
  constant endRecordingLog (line 47) | endRecordingLog   = "END_RECORDING"
  constant chromeFailedToStart (line 49) | chromeFailedToStart       = "chrome failed to start:"
  constant chromeCertVerifierChanged (line 50) | chromeCertVerifierChanged = "net::ERR_CERT_VERIFIER_CHANGED"
  constant chromeTimeout (line 52) | chromeTimeout = time.Second * 30
  constant chromeRetries (line 53) | chromeRetries = 3
  type WebSource (line 56) | type WebSource struct
    method StartRecording (line 103) | func (s *WebSource) StartRecording() <-chan struct{} {
    method EndRecording (line 107) | func (s *WebSource) EndRecording() <-chan struct{} {
    method GetStartedAt (line 111) | func (s *WebSource) GetStartedAt() int64 {
    method GetEndedAt (line 115) | func (s *WebSource) GetEndedAt() int64 {
    method Close (line 119) | func (s *WebSource) Close() {
    method createPulseSink (line 146) | func (s *WebSource) createPulseSink(ctx context.Context, p *config.Pip...
    method launchXvfb (line 175) | func (s *WebSource) launchXvfb(ctx context.Context, p *config.Pipeline...
    method launchChrome (line 202) | func (s *WebSource) launchChrome(ctx context.Context, p *config.Pipeli...
    method navigate (line 304) | func (s *WebSource) navigate(chromeCtx context.Context, chromeCancel c...
  function NewWebSource (line 69) | func NewWebSource(ctx context.Context, p *config.PipelineConfig) (*WebSo...
  function newChromeLogger (line 190) | func newChromeLogger(tmpDir string) *lumberjack.Logger {

FILE: pkg/pipeline/tempo/controller.go
  constant DefaultThreshold (line 10) | DefaultThreshold = 10 * time.Millisecond
  constant MaxDriftBudget (line 11) | MaxDriftBudget   = 2 * time.Second
  type Controller (line 14) | type Controller struct
    method EnqueueDrift (line 28) | func (tc *Controller) EnqueueDrift(drift time.Duration) {
    method DriftProcessed (line 55) | func (tc *Controller) DriftProcessed() {
    method OnDriftDetectedCallback (line 76) | func (tc *Controller) OnDriftDetectedCallback(cb func(time.Duration)) {
    method Processed (line 88) | func (tc *Controller) Processed() time.Duration {
  function NewController (line 24) | func NewController() *Controller { return &Controller{} }

FILE: pkg/pipeline/tempo/controller_test.go
  function TestEnqueueStartsWithinBudget (line 8) | func TestEnqueueStartsWithinBudget(t *testing.T) {
  function TestThresholdAccumulation (line 23) | func TestThresholdAccumulation(t *testing.T) {
  function TestDriftProcessedStartsNext (line 37) | func TestDriftProcessedStartsNext(t *testing.T) {
  function TestBudgetBlocksAndResumes (line 63) | func TestBudgetBlocksAndResumes(t *testing.T) {
  function TestImmediateCallbackOnRegister (line 96) | func TestImmediateCallbackOnRegister(t *testing.T) {
  function TestZeroDriftNoop (line 111) | func TestZeroDriftNoop(t *testing.T) {
  function TestSignedProcessedAccumulation (line 123) | func TestSignedProcessedAccumulation(t *testing.T) {

FILE: pkg/pipeline/watch.go
  constant msgWrongThread (line 36) | msgWrongThread = "Called from wrong thread"
  constant msgKeyframe (line 39) | msgKeyframe                    = "Could not request a keyframe. Files ma...
  constant msgLatencyQuery (line 40) | msgLatencyQuery                = "Latency query failed"
  constant msgTaps (line 41) | msgTaps                        = "can't find exact taps"
  constant msgInputDisappeared (line 42) | msgInputDisappeared            = "Can't copy metadata because input buff...
  constant msgSkippingSegment (line 43) | msgSkippingSegment             = "error reading data -1 (reason: Success...
  constant fnGstAudioResampleCheckDiscont (line 44) | fnGstAudioResampleCheckDiscont = "gst_audio_resample_check_discont"
  constant msgColorMatrix (line 47) | msgColorMatrix        = "Need to specify a color matrix when using YUV f...
  constant msgInvalidColorimetry (line 48) | msgInvalidColorimetry = "invalid colorimetry, using default"
  constant msgStreamStart (line 51) | msgStreamStart       = "stream-start event without group-id. Consider im...
  constant msgCreatingStream (line 52) | msgCreatingStream    = "Creating random stream-id, consider implementing...
  constant msgAggregateSubclass (line 53) | msgAggregateSubclass = "Subclass should call gst_aggregator_selected_sam...
  constant catRtmpClient (line 56) | catRtmpClient      = "rtmpclient"
  constant fnSendCreateStream (line 57) | fnSendCreateStream = "send_create_stream"
  method gstLog (line 88) | func (c *Controller) gstLog(
  method messageWatch (line 120) | func (c *Controller) messageWatch(msg *gst.Message) bool {
  constant msgClockProblem (line 158) | msgClockProblem = "GStreamer error: clock problem."
  method handleMessageWarning (line 161) | func (c *Controller) handleMessageWarning(gErr *gst.GError) error {
  constant elementGstAppSrc (line 185) | elementGstAppSrc       = "GstAppSrc"
  constant elementGstRtmp2Sink (line 186) | elementGstRtmp2Sink    = "GstRtmp2Sink"
  constant elementGstSplitMuxSink (line 187) | elementGstSplitMuxSink = "GstSplitMuxSink"
  constant elementGstSrtSink (line 188) | elementGstSrtSink      = "GstSRTSink"
  constant msgStreamingNotNegotiated (line 190) | msgStreamingNotNegotiated = "streaming stopped, reason not-negotiated (-4)"
  constant msgMuxer (line 191) | msgMuxer                  = ":muxer"
  method handleMessageError (line 195) | func (c *Controller) handleMessageError(gErr *gst.GError) error {
  method handleMessageStateChanged (line 257) | func (c *Controller) handleMessageStateChanged(msg *gst.Message) {
  constant msgFirstSampleMetadata (line 295) | msgFirstSampleMetadata = "FirstSampleMetadata"
  constant msgFragmentOpened (line 296) | msgFragmentOpened      = "splitmuxsink-fragment-opened"
  constant msgFragmentClosed (line 297) | msgFragmentClosed      = "splitmuxsink-fragment-closed"
  constant msgGstMultiFileSink (line 298) | msgGstMultiFileSink    = "GstMultiFileSink"
  method handleMessageElement (line 301) | func (c *Controller) handleMessageElement(msg *gst.Message) error {
  function parseLeakyQueueStats (line 377) | func parseLeakyQueueStats(s *gst.Structure) (queue string, dropped uint6...
  function normalizeUint64 (line 392) | func normalizeUint64(value interface{}) uint64 {
  method handleMessageQoS (line 416) | func (c *Controller) handleMessageQoS(msg *gst.Message) {
  method handleAudioMixerQoS (line 428) | func (c *Controller) handleAudioMixerQoS(qosValues *gst.QoSValues) {
  function parseDebugInfo (line 437) | func parseDebugInfo(gErr *gst.GError) (element, name, message string) {
  constant fragmentLocation (line 451) | fragmentLocation    = "location"
  constant fragmentRunningTime (line 452) | fragmentRunningTime = "running-time"
  function getSegmentParamsFromGstStructure (line 455) | func getSegmentParamsFromGstStructure(s *gst.Structure) (filepath string...
  function getFirstSampleMetadataFromGstStructure (line 477) | func getFirstSampleMetadataFromGstStructure(s *gst.Structure) (startDate...
  constant gstMultiFileSinkFilename (line 488) | gstMultiFileSinkFilename  = "filename"
  constant gstMultiFileSinkTimestamp (line 489) | gstMultiFileSinkTimestamp = "timestamp"
  function getImageInformationFromGstStructure (line 492) | func getImageInformationFromGstStructure(s *gst.Structure) (string, uint...
  function isQosForAudioMixer (line 515) | func isQosForAudioMixer(msg *gst.Message) bool {

FILE: pkg/server/integration.go
  method ReplayReady (line 26) | func (s *Server) ReplayReady(context.Context, *rpc.EgressReadyRequest) (...

FILE: pkg/server/server.go
  type Server (line 44) | type Server struct
    method StartTemplatesServer (line 128) | func (s *Server) StartTemplatesServer(fs fs.FS) error {
    method Run (line 148) | func (s *Server) Run() error {
    method Status (line 163) | func (s *Server) Status() ([]byte, error) {
    method IsIdle (line 171) | func (s *Server) IsIdle() bool {
    method IsDisabled (line 175) | func (s *Server) IsDisabled() bool {
    method IsTerminating (line 179) | func (s *Server) IsTerminating() bool {
    method Shutdown (line 183) | func (s *Server) Shutdown(terminating, kill bool) {
    method Drain (line 195) | func (s *Server) Drain() {
  function NewServer (line 64) | func NewServer(conf *config.ServiceConfig, bus psrpc.MessageBus, ioClien...

FILE: pkg/server/server_ipc.go
  method HandlerReady (line 29) | func (s *Server) HandlerReady(_ context.Context, req *ipc.HandlerReadyRe...
  method HandlerUpdate (line 39) | func (s *Server) HandlerUpdate(_ context.Context, info *livekit.EgressIn...
  method HandlerFinished (line 54) | func (s *Server) HandlerFinished(_ context.Context, req *ipc.HandlerFini...
  method StorageEvent (line 68) | func (s *Server) StorageEvent(_ context.Context, _ *ipc.StorageEventRequ...

FILE: pkg/server/server_rpc.go
  method StartEgress (line 46) | func (s *Server) StartEgress(ctx context.Context, req *rpc.StartEgressRe...
  method launchProcess (line 113) | func (s *Server) launchProcess(req *rpc.StartEgressRequest, info *liveki...
  method processEnded (line 165) | func (s *Server) processEnded(req *rpc.StartEgressRequest, info *livekit...
  method StartEgressAffinity (line 199) | func (s *Server) StartEgressAffinity(_ context.Context, req *rpc.StartEg...
  method ListActiveEgress (line 215) | func (s *Server) ListActiveEgress(ctx context.Context, _ *rpc.ListActive...

FILE: pkg/service/debug.go
  constant gstPipelineDotFileApp (line 33) | gstPipelineDotFileApp = "gst_pipeline"
  constant pprofApp (line 34) | pprofApp              = "pprof"
  type DebugService (line 37) | type DebugService struct
    method StartDebugHandlers (line 47) | func (s *DebugService) StartDebugHandlers(port int) {
    method handleGstPipelineDotFile (line 65) | func (s *DebugService) handleGstPipelineDotFile(w http.ResponseWriter,...
    method GetGstPipelineDotFile (line 81) | func (s *DebugService) GetGstPipelineDotFile(egressID string) (string,...
    method handlePProf (line 95) | func (s *DebugService) handlePProf(w http.ResponseWriter, r *http.Requ...
  function NewDebugService (line 41) | func NewDebugService(pm ProcessManager) *DebugService {
  function getErrorCode (line 148) | func getErrorCode(err error) int {

FILE: pkg/service/metrics.go
  type MetricsService (line 35) | type MetricsService struct
    method PromHandler (line 55) | func (s *MetricsService) PromHandler() http.Handler {
    method CreateGatherer (line 61) | func (s *MetricsService) CreateGatherer() prometheus.Gatherer {
    method StoreProcessEndedMetrics (line 84) | func (s *MetricsService) StoreProcessEndedMetrics(egressID string, met...
  function NewMetricsService (line 46) | func NewMetricsService(pm ProcessManager) *MetricsService {
  function deserializeMetrics (line 97) | func deserializeMetrics(egressID string, s string) ([]*dto.MetricFamily,...
  function applyDefaultLabel (line 111) | func applyDefaultLabel(families map[string]*dto.MetricFamily, egressID s...

FILE: pkg/service/process.go
  constant launchTimeout (line 38) | launchTimeout = 10 * time.Second
  type ProcessManager (line 42) | type ProcessManager interface
  type processManager (line 57) | type processManager struct
    method Launch (line 68) | func (pm *processManager) Launch(
    method GetContext (line 116) | func (pm *processManager) GetContext(egressID string) context.Context {
    method AlreadyExists (line 127) | func (pm *processManager) AlreadyExists(egressID string) bool {
    method HandlerStarted (line 135) | func (pm *processManager) HandlerStarted(egressID string) error {
    method GetActiveEgressIDs (line 147) | func (pm *processManager) GetActiveEgressIDs() []string {
    method GetStatus (line 159) | func (pm *processManager) GetStatus(info map[string]interface{}) {
    method GetGatherers (line 168) | func (pm *processManager) GetGatherers() []prometheus.Gatherer {
    method GetGRPCClient (line 180) | func (pm *processManager) GetGRPCClient(egressID string) (ipc.EgressHa...
    method KillAll (line 191) | func (pm *processManager) KillAll() {
    method AbortProcess (line 200) | func (pm *processManager) AbortProcess(egressID string, err error) {
    method KillProcess (line 214) | func (pm *processManager) KillProcess(egressID string, err error) {
    method ProcessFinished (line 226) | func (pm *processManager) ProcessFinished(egressID string) {
  function NewProcessManager (line 62) | func NewProcessManager() ProcessManager {
  type Process (line 242) | type Process struct
    method Gather (line 254) | func (p *Process) Gather() ([]*dto.MetricFamily, error) {
    method kill (line 268) | func (p *Process) kill(e error) {

FILE: pkg/service/servicefakes/fake_process_manager.go
  type FakeProcessManager (line 16) | type FakeProcessManager struct
    method AbortProcess (line 128) | func (fake *FakeProcessManager) AbortProcess(arg1 string, arg2 error) {
    method AbortProcessCallCount (line 142) | func (fake *FakeProcessManager) AbortProcessCallCount() int {
    method AbortProcessCalls (line 148) | func (fake *FakeProcessManager) AbortProcessCalls(stub func(string, er...
    method AbortProcessArgsForCall (line 154) | func (fake *FakeProcessManager) AbortProcessArgsForCall(i int) (string...
    method AlreadyExists (line 161) | func (fake *FakeProcessManager) AlreadyExists(arg1 string) bool {
    method AlreadyExistsCallCount (line 180) | func (fake *FakeProcessManager) AlreadyExistsCallCount() int {
    method AlreadyExistsCalls (line 186) | func (fake *FakeProcessManager) AlreadyExistsCalls(stub func(string) b...
    method AlreadyExistsArgsForCall (line 192) | func (fake *FakeProcessManager) AlreadyExistsArgsForCall(i int) string {
    method AlreadyExistsReturns (line 199) | func (fake *FakeProcessManager) AlreadyExistsReturns(result1 bool) {
    method AlreadyExistsReturnsOnCall (line 208) | func (fake *FakeProcessManager) AlreadyExistsReturnsOnCall(i int, resu...
    method GetActiveEgressIDs (line 222) | func (fake *FakeProcessManager) GetActiveEgressIDs() []string {
    method GetActiveEgressIDsCallCount (line 240) | func (fake *FakeProcessManager) GetActiveEgressIDsCallCount() int {
    method GetActiveEgressIDsCalls (line 246) | func (fake *FakeProcessManager) GetActiveEgressIDsCalls(stub func() []...
    method GetActiveEgressIDsReturns (line 252) | func (fake *FakeProcessManager) GetActiveEgressIDsReturns(result1 []st...
    method GetActiveEgressIDsReturnsOnCall (line 261) | func (fake *FakeProcessManager) GetActiveEgressIDsReturnsOnCall(i int,...
    method GetContext (line 275) | func (fake *FakeProcessManager) GetContext(arg1 string) context.Context {
    method GetContextCallCount (line 294) | func (fake *FakeProcessManager) GetContextCallCount() int {
    method GetContextCalls (line 300) | func (fake *FakeProcessManager) GetContextCalls(stub func(string) cont...
    method GetContextArgsForCall (line 306) | func (fake *FakeProcessManager) GetContextArgsForCall(i int) string {
    method GetContextReturns (line 313) | func (fake *FakeProcessManager) GetContextReturns(result1 context.Cont...
    method GetContextReturnsOnCall (line 322) | func (fake *FakeProcessManager) GetContextReturnsOnCall(i int, result1...
    method GetGRPCClient (line 336) | func (fake *FakeProcessManager) GetGRPCClient(arg1 string) (ipc.Egress...
    method GetGRPCClientCallCount (line 355) | func (fake *FakeProcessManager) GetGRPCClientCallCount() int {
    method GetGRPCClientCalls (line 361) | func (fake *FakeProcessManager) GetGRPCClientCalls(stub func(string) (...
    method GetGRPCClientArgsForCall (line 367) | func (fake *FakeProcessManager) GetGRPCClientArgsForCall(i int) string {
    method GetGRPCClientReturns (line 374) | func (fake *FakeProcessManager) GetGRPCClientReturns(result1 ipc.Egres...
    method GetGRPCClientReturnsOnCall (line 384) | func (fake *FakeProcessManager) GetGRPCClientReturnsOnCall(i int, resu...
    method GetGatherers (line 400) | func (fake *FakeProcessManager) GetGatherers() []prometheus.Gatherer {
    method GetGatherersCallCount (line 418) | func (fake *FakeProcessManager) GetGatherersCallCount() int {
    method GetGatherersCalls (line 424) | func (fake *FakeProcessManager) GetGatherersCalls(stub func() []promet...
    method GetGatherersReturns (line 430) | func (fake *FakeProcessManager) GetGatherersReturns(result1 []promethe...
    method GetGatherersReturnsOnCall (line 439) | func (fake *FakeProcessManager) GetGatherersReturnsOnCall(i int, resul...
    method GetStatus (line 453) | func (fake *FakeProcessManager) GetStatus(arg1 map[string]interface{}) {
    method GetStatusCallCount (line 466) | func (fake *FakeProcessManager) GetStatusCallCount() int {
    method GetStatusCalls (line 472) | func (fake *FakeProcessManager) GetStatusCalls(stub func(map[string]in...
    method GetStatusArgsForCall (line 478) | func (fake *FakeProcessManager) GetStatusArgsForCall(i int) map[string...
    method HandlerStarted (line 485) | func (fake *FakeProcessManager) HandlerStarted(arg1 string) error {
    method HandlerStartedCallCount (line 504) | func (fake *FakeProcessManager) HandlerStartedCallCount() int {
    method HandlerStartedCalls (line 510) | func (fake *FakeProcessManager) HandlerStartedCalls(stub func(string) ...
    method HandlerStartedArgsForCall (line 516) | func (fake *FakeProcessManager) HandlerStartedArgsForCall(i int) string {
    method HandlerStartedReturns (line 523) | func (fake *FakeProcessManager) HandlerStartedReturns(result1 error) {
    method HandlerStartedReturnsOnCall (line 532) | func (fake *FakeProcessManager) HandlerStartedReturnsOnCall(i int, res...
    method KillAll (line 546) | func (fake *FakeProcessManager) KillAll() {
    method KillAllCallCount (line 558) | func (fake *FakeProcessManager) KillAllCallCount() int {
    method KillAllCalls (line 564) | func (fake *FakeProcessManager) KillAllCalls(stub func()) {
    method KillProcess (line 570) | func (fake *FakeProcessManager) KillProcess(arg1 string, arg2 error) {
    method KillProcessCallCount (line 584) | func (fake *FakeProcessManager) KillProcessCallCount() int {
    method KillProcessCalls (line 590) | func (fake *FakeProcessManager) KillProcessCalls(stub func(string, err...
    method KillProcessArgsForCall (line 596) | func (fake *FakeProcessManager) KillProcessArgsForCall(i int) (string,...
    method Launch (line 603) | func (fake *FakeProcessManager) Launch(arg1 context.Context, arg2 stri...
    method LaunchCallCount (line 626) | func (fake *FakeProcessManager) LaunchCallCount() int {
    method LaunchCalls (line 632) | func (fake *FakeProcessManager) LaunchCalls(stub func(context.Context,...
    method LaunchArgsForCall (line 638) | func (fake *FakeProcessManager) LaunchArgsForCall(i int) (context.Cont...
    method LaunchReturns (line 645) | func (fake *FakeProcessManager) LaunchReturns(result1 error) {
    method LaunchReturnsOnCall (line 654) | func (fake *FakeProcessManager) LaunchReturnsOnCall(i int, result1 err...
    method ProcessFinished (line 668) | func (fake *FakeProcessManager) ProcessFinished(arg1 string) {
    method ProcessFinishedCallCount (line 681) | func (fake *FakeProcessManager) ProcessFinishedCallCount() int {
    method ProcessFinishedCalls (line 687) | func (fake *FakeProcessManager) ProcessFinishedCalls(stub func(string)) {
    method ProcessFinishedArgsForCall (line 693) | func (fake *FakeProcessManager) ProcessFinishedArgsForCall(i int) stri...
    method Invocations (line 700) | func (fake *FakeProcessManager) Invocations() map[string][][]interface...
    method recordInvocation (line 710) | func (fake *FakeProcessManager) recordInvocation(key string, args []in...

FILE: pkg/stats/handler.go
  type HandlerMonitor (line 21) | type HandlerMonitor struct
    method IncUploadCountSuccess (line 62) | func (m *HandlerMonitor) IncUploadCountSuccess(uploadType string, elap...
    method IncUploadCountFailure (line 68) | func (m *HandlerMonitor) IncUploadCountFailure(uploadType string, elap...
    method IncBackupStorageWrites (line 74) | func (m *HandlerMonitor) IncBackupStorageWrites(outputType string) {
    method RegisterSegmentsChannelSizeGauge (line 78) | func (m *HandlerMonitor) RegisterSegmentsChannelSizeGauge(nodeID, clus...
    method RegisterPlaylistChannelSizeGauge (line 90) | func (m *HandlerMonitor) RegisterPlaylistChannelSizeGauge(nodeID, clus...
  function NewHandlerMonitor (line 27) | func NewHandlerMonitor(nodeID, clusterID, egressID string) *HandlerMonit...

FILE: pkg/stats/monitor.go
  constant cpuHoldDuration (line 39) | cpuHoldDuration         = time.Second * 15
  constant defaultKillThreshold (line 40) | defaultKillThreshold    = 0.95
  constant minKillDuration (line 41) | minKillDuration         = 10
  constant gb (line 42) | gb                      = 1024.0 * 1024.0 * 1024.0
  constant pulseClientHold (line 43) | pulseClientHold         = 4
  constant memoryHeadroomGB (line 44) | memoryHeadroomGB        = 1.0
  constant memoryUsageDumpInterval (line 45) | memoryUsageDumpInterval = 10 * time.Minute
  type Service (line 48) | type Service interface
  type Monitor (line 55) | type Monitor struct
    method validateCPUConfig (line 134) | func (m *Monitor) validateCPUConfig() error {
    method CanAcceptRequest (line 173) | func (m *Monitor) CanAcceptRequest(req *rpc.StartEgressRequest) bool {
    method CanAcceptWebRequest (line 182) | func (m *Monitor) CanAcceptWebRequest() bool {
    method canAcceptRequestLocked (line 189) | func (m *Monitor) canAcceptRequestLocked(req *rpc.StartEgressRequest) ...
    method canAcceptWebLocked (line 294) | func (m *Monitor) canAcceptWebLocked() bool {
    method checkMemoryAdmissionLocked (line 304) | func (m *Monitor) checkMemoryAdmissionLocked() (bool, string) {
    method checkProcRSSMemoryAdmission (line 333) | func (m *Monitor) checkProcRSSMemoryAdmission(pendingMem, memoryCost, ...
    method AcceptRequest (line 341) | func (m *Monitor) AcceptRequest(req *rpc.StartEgressRequest) error {
    method UpdatePID (line 435) | func (m *Monitor) UpdatePID(egressID string, pid int) {
    method EgressStarted (line 459) | func (m *Monitor) EgressStarted(req *rpc.StartEgressRequest) {
    method EgressAborted (line 484) | func (m *Monitor) EgressAborted(req *rpc.StartEgressRequest) {
    method EgressEnded (line 499) | func (m *Monitor) EgressEnded(req *rpc.StartEgressRequest) (float64, f...
    method GetAvailableCPU (line 559) | func (m *Monitor) GetAvailableCPU() float64 {
    method getCPUUsageLocked (line 567) | func (m *Monitor) getCPUUsageLocked() (total, available, pending, used...
    method GetAvailableMemory (line 595) | func (m *Monitor) GetAvailableMemory() float64 {
    method updateEgressStats (line 606) | func (m *Monitor) updateEgressStats(stats *hwstats.ProcStats) {
    method maybeLogMemoryUsage (line 689) | func (m *Monitor) maybeLogMemoryUsage(memory map[int]*hwstats.GroupMem...
    method updateCgroupStats (line 711) | func (m *Monitor) updateCgroupStats() {
    method updateWouldRejectMetrics (line 738) | func (m *Monitor) updateWouldRejectMetrics() {
    method checkMemoryKill (line 757) | func (m *Monitor) checkMemoryKill(maxMemoryEgress string, maxMemoryGro...
  type processStats (line 87) | type processStats struct
  function NewMonitor (line 101) | func NewMonitor(conf *config.ServiceConfig, svc Service) (*Monitor, erro...

FILE: pkg/stats/monitor_memory_test.go
  function TestCheckMemoryAdmissionLocked_Legacy (line 25) | func TestCheckMemoryAdmissionLocked_Legacy(t *testing.T) {
  function TestCheckMemoryAdmissionLocked_CgroupWorkingSet (line 46) | func TestCheckMemoryAdmissionLocked_CgroupWorkingSet(t *testing.T) {
  function TestCheckMemoryAdmissionLocked_FallbackToProcRSS (line 68) | func TestCheckMemoryAdmissionLocked_FallbackToProcRSS(t *testing.T) {
  function TestCheckMemoryAdmissionLocked_NoMaxMemory (line 89) | func TestCheckMemoryAdmissionLocked_NoMaxMemory(t *testing.T) {
  function TestCheckMemoryAdmissionLocked_WithPendingMemory (line 103) | func TestCheckMemoryAdmissionLocked_WithPendingMemory(t *testing.T) {
  function TestCheckProcRSSMemoryAdmission (line 124) | func TestCheckProcRSSMemoryAdmission(t *testing.T) {

FILE: pkg/stats/monitor_prom.go
  method initPrometheus (line 24) | func (m *Monitor) initPrometheus() {
  method promIsIdle (line 109) | func (m *Monitor) promIsIdle() float64 {
  method promCanAcceptRequest (line 116) | func (m *Monitor) promCanAcceptRequest() float64 {
  method promIsDisabled (line 129) | func (m *Monitor) promIsDisabled() float64 {
  method promIsTerminating (line 136) | func (m *Monitor) promIsTerminating() float64 {

FILE: pkg/types/types.go
  type RequestType (line 17) | type RequestType
  type SourceType (line 18) | type SourceType
  type EgressType (line 19) | type EgressType
  type MimeType (line 20) | type MimeType
  type Profile (line 21) | type Profile
  type OutputType (line 22) | type OutputType
  type FileExtension (line 23) | type FileExtension
  constant RequestTypeTemplate (line 27) | RequestTypeTemplate = "template"
  constant RequestTypeWeb (line 28) | RequestTypeWeb      = "web"
  constant RequestTypeMedia (line 29) | RequestTypeMedia    = "media"
  constant RequestTypeRoomComposite (line 31) | RequestTypeRoomComposite  = "room_composite"
  constant RequestTypeParticipant (line 32) | RequestTypeParticipant    = "participant"
  constant RequestTypeTrackComposite (line 33) | RequestTypeTrackComposite = "track_composite"
  constant RequestTypeTrack (line 34) | RequestTypeTrack          = "track"
  constant SourceTypeWeb (line 37) | SourceTypeWeb SourceType = "web"
  constant SourceTypeSDK (line 38) | SourceTypeSDK SourceType = "sdk"
  constant EgressTypeStream (line 41) | EgressTypeStream    EgressType = "stream"
  constant EgressTypeWebsocket (line 42) | EgressTypeWebsocket EgressType = "websocket"
  constant EgressTypeFile (line 43) | EgressTypeFile      EgressType = "file"
  constant EgressTypeSegments (line 44) | EgressTypeSegments  EgressType = "segments"
  constant EgressTypeImages (line 45) | EgressTypeImages    EgressType = "images"
  constant MimeTypeAAC (line 48) | MimeTypeAAC      MimeType = "audio/aac"
  constant MimeTypeOpus (line 49) | MimeTypeOpus     MimeType = "audio/opus"
  constant MimeTypeRawAudio (line 50) | MimeTypeRawAudio MimeType = "audio/x-raw"
  constant MimeTypeH264 (line 51) | MimeTypeH264     MimeType = "video/h264"
  constant MimeTypeVP8 (line 52) | MimeTypeVP8      MimeType = "video/vp8"
  constant MimeTypeVP9 (line 53) | MimeTypeVP9      MimeType = "video/vp9"
  constant MimeTypeJPEG (line 54) | MimeTypeJPEG     MimeType = "image/jpeg"
  constant MimeTypeRawVideo (line 55) | MimeTypeRawVideo MimeType = "video/x-raw"
  constant MimeTypeMP3 (line 56) | MimeTypeMP3      MimeType = "audio/mpeg"
  constant MimeTypePCMU (line 57) | MimeTypePCMU     MimeType = "audio/pcmu"
  constant MimeTypePCMA (line 58) | MimeTypePCMA     MimeType = "audio/pcma"
  constant ProfileBaseline (line 61) | ProfileBaseline Profile = "baseline"
  constant ProfileMain (line 62) | ProfileMain     Profile = "main"
  constant ProfileHigh (line 63) | ProfileHigh     Profile = "high"
  constant OutputTypeUnknownFile (line 66) | OutputTypeUnknownFile OutputType = ""
  constant OutputTypeRaw (line 67) | OutputTypeRaw         OutputType = "audio/x-raw"
  constant OutputTypeOGG (line 68) | OutputTypeOGG         OutputType = "audio/ogg"
  constant OutputTypeMP3 (line 69) | OutputTypeMP3         OutputType = "audio/mpeg"
  constant OutputTypeIVF (line 70) | OutputTypeIVF         OutputType = "video/x-ivf"
  constant OutputTypeMP4 (line 71) | OutputTypeMP4         OutputType = "video/mp4"
  constant OutputTypeTS (line 72) | OutputTypeTS          OutputType = "video/mp2t"
  constant OutputTypeWebM (line 73) | OutputTypeWebM        OutputType = "video/webm"
  constant OutputTypeJPEG (line 74) | OutputTypeJPEG        OutputType = "image/jpeg"
  constant OutputTypeRTMP (line 75) | OutputTypeRTMP        OutputType = "rtmp"
  constant OutputTypeSRT (line 76) | OutputTypeSRT         OutputType = "srt"
  constant OutputTypeHLS (line 77) | OutputTypeHLS         OutputType = "application/x-mpegurl"
  constant OutputTypeJSON (line 78) | OutputTypeJSON        OutputType = "application/json"
  constant OutputTypeBlob (line 79) | OutputTypeBlob        OutputType = "application/octet-stream"
  constant FileExtensionRaw (line 82) | FileExtensionRaw  = ".raw"
  constant FileExtensionOGG (line 83) | FileExtensionOGG  = ".ogg"
  constant FileExtensionMP3 (line 84) | FileExtensionMP3  = ".mp3"
  constant FileExtensionIVF (line 85) | FileExtensionIVF  = ".ivf"
  constant FileExtensionMP4 (line 86) | FileExtensionMP4  = ".mp4"
  constant FileExtensionTS (line 87) | FileExtensionTS   = ".ts"
  constant FileExtensionWebM (line 88) | FileExtensionWebM = ".webm"
  constant FileExtensionM3U8 (line 89) | FileExtensionM3U8 = ".m3u8"
  constant FileExtensionJPEG (line 90) | FileExtensionJPEG = ".jpeg"
  function GetOutputTypeCompatibleWithCodecs (line 237) | func GetOutputTypeCompatibleWithCodecs(types []OutputType, audioCodecs m...
  function IsOutputTypeCompatibleWithCodecs (line 253) | func IsOutputTypeCompatibleWithCodecs(ot OutputType, codecs map[MimeType...
  function GetMapIntersection (line 262) | func GetMapIntersection[K comparable](mapA map[K]bool, mapB map[K]bool) ...

FILE: pkg/types/types_test.go
  function TestGetMapIntersection (line 23) | func TestGetMapIntersection(t *testing.T) {
  function TestGetOutputTypesCompatibleWithCodecs (line 38) | func TestGetOutputTypesCompatibleWithCodecs(t *testing.T) {

FILE: template-default/src/App.tsx
  function App (line 23) | function App() {

FILE: template-default/src/Room.tsx
  constant FRAME_DECODE_TIMEOUT (line 31) | const FRAME_DECODE_TIMEOUT = 5000;
  type RoomPageProps (line 33) | interface RoomPageProps {
  function RoomPage (line 39) | function RoomPage({ url, token, layout }: RoomPageProps) {
  type CompositeTemplateProps (line 52) | interface CompositeTemplateProps {
  function CompositeTemplate (line 56) | function CompositeTemplate({ layout: initialLayout }: CompositeTemplateP...

FILE: template-default/src/common.ts
  type LayoutProps (line 19) | interface LayoutProps {

FILE: template-sdk/src/index.ts
  method getLiveKitURL (line 24) | getLiveKitURL(): string {
  method getAccessToken (line 36) | getAccessToken(): string {
  method getLayout (line 48) | getLayout(): string {
  method setRoom (line 60) | setRoom(room: Room) {
  method startRecording (line 74) | startRecording() {
  method endRecording (line 82) | endRecording() {
  method onLayoutChanged (line 91) | onLayoutChanged(f: (layout: string) => void) {
  type TemplateState (line 102) | interface TemplateState {
  function onMetadataChanged (line 106) | function onMetadataChanged() {
  function getURLParam (line 118) | function getURLParam(name: string): string | null {

FILE: test/agents.go
  method launchAgents (line 31) | func (r *Runner) launchAgents(t *testing.T) {

FILE: test/agents/guest.py
  function entrypoint (line 15) | async def entrypoint(ctx: JobContext):

FILE: test/agents/host.py
  function entrypoint (line 15) | async def entrypoint(ctx: JobContext):

FILE: test/builder.go
  constant webUrl (line 32) | webUrl       = "https://download.blender.org/peach/bigbuckbunny_movies/B...
  constant setAtRuntime (line 33) | setAtRuntime = "set-at-runtime"
  type testCase (line 36) | type testCase struct
    method isV2 (line 373) | func (test *testCase) isV2() bool {
  type publishOptions (line 58) | type publishOptions struct
  type fileOptions (line 85) | type fileOptions struct
  type streamOptions (line 91) | type streamOptions struct
  type segmentOptions (line 98) | type segmentOptions struct
  type imageOptions (line 105) | type imageOptions struct
  type v2OutputOptions (line 110) | type v2OutputOptions struct
  method build (line 115) | func (r *Runner) build(test *testCase) *rpc.StartEgressRequest {
  method buildFileOutputs (line 275) | func (r *Runner) buildFileOutputs(o *fileOptions) []*livekit.EncodedFile...
  method buildStreamOutputs (line 300) | func (r *Runner) buildStreamOutputs(o *streamOptions) []*livekit.StreamO...
  method buildSegmentOutputs (line 317) | func (r *Runner) buildSegmentOutputs(o *segmentOptions) []*livekit.Segme...
  method buildImageOutputs (line 346) | func (r *Runner) buildImageOutputs(o *imageOptions) []*livekit.ImageOutp...
  method getUploadConfig (line 356) | func (r *Runner) getUploadConfig() interface{} {
  method buildRequest (line 384) | func (r *Runner) buildRequest(test *testCase) *rpc.StartEgressRequest {
  method getV2StorageConfig (line 391) | func (r *Runner) getV2StorageConfig() *livekit.StorageConfig {
  method buildV2Outputs (line 409) | func (r *Runner) buildV2Outputs(test *testCase) []*livekit.Output {
  method buildV2 (line 487) | func (r *Runner) buildV2(test *testCase) *rpc.StartEgressRequest {

FILE: test/content_checks.go
  method fullContentCheck (line 33) | func (r *Runner) fullContentCheck(t *testing.T, file string, _ *FFProbeI...
  method videoOnlyContentCheck (line 70) | func (r *Runner) videoOnlyContentCheck(t *testing.T, file string, info *...
  method audioOnlyContentCheck (line 89) | func (r *Runner) audioOnlyContentCheck(t *testing.T, file string, _ *FFP...
  method fullContentCheckWithVideoUnpublishAt10AndRepublishAt20 (line 120) | func (r *Runner) fullContentCheckWithVideoUnpublishAt10AndRepublishAt20(...
  method streamKeyframeContentCheck (line 157) | func (r *Runner) streamKeyframeContentCheck(expectedInterval float64) fu...
  function requireKeyframeInterval (line 164) | func requireKeyframeInterval(t *testing.T, input string, expectedInterva...
  function ffprobeKeyframeTimestamps (line 190) | func ffprobeKeyframeTimestamps(input string, expectedInterval float64) (...

FILE: test/download.go
  function loadManifest (line 44) | func loadManifest(t *testing.T, c *config.StorageConfig, localFilepath, ...
  function download (line 58) | func download(t *testing.T, c *config.StorageConfig, localFilepath, stor...
  function downloadS3 (line 73) | func downloadS3(t *testing.T, conf *lkstorage.S3Config, localFilepath, s...
  function downloadAzure (line 112) | func downloadAzure(t *testing.T, conf *lkstorage.AzureConfig, localFilep...
  function downloadGCP (line 152) | func downloadGCP(t *testing.T, conf *lkstorage.GCPConfig, localFilepath,...

FILE: test/edge.go
  method testEdgeCases (line 35) | func (r *Runner) testEdgeCases(t *testing.T) {
  method testRoomCompositeLateTrackDuration (line 214) | func (r *Runner) testRoomCompositeLateTrackDuration(t *testing.T, test *...
  method testAgents (line 261) | func (r *Runner) testAgents(t *testing.T, test *testCase) {
  method testAudioMixing (line 272) | func (r *Runner) testAudioMixing(t *testing.T, test *testCase) {
  method testParticipantNoPublish (line 310) | func (r *Runner) testParticipantNoPublish(t *testing.T, test *testCase) {
  method testRoomCompositeStaysOpen (line 334) | func (r *Runner) testRoomCompositeStaysOpen(t *testing.T, test *testCase) {
  method testRoomCompositeDisconnectDuration (line 363) | func (r *Runner) testRoomCompositeDisconnectDuration(t *testing.T, test ...
  method testStorageLimit (line 447) | func (r *Runner) testStorageLimit(t *testing.T, test *testCase) {
  method testRtmpFailure (line 487) | func (r *Runner) testRtmpFailure(t *testing.T, test *testCase) {
  method testSrtFailure (line 518) | func (r *Runner) testSrtFailure(t *testing.T, test *testCase) {
  method testTrackDisconnection (line 537) | func (r *Runner) testTrackDisconnection(t *testing.T, test *testCase) {
  method testEmptyStreamBin (line 542) | func (r *Runner) testEmptyStreamBin(t *testing.T, test *testCase) {

FILE: test/ffprobe.go
  constant maxRetries (line 42) | maxRetries = 5
  constant minDelay (line 43) | minDelay   = time.Millisecond * 100
  constant maxDelay (line 44) | maxDelay   = time.Second * 5
  type FFProbeInfo (line 51) | type FFProbeInfo struct
  function ffprobe (line 81) | func ffprobe(input string) (*FFProbeInfo, error) {
  function verify (line 117) | func verify(t *testing.T, in string, p *config.PipelineConfig, res *live...
  function parseFFProbeDuration (line 316) | func parseFFProbeDuration(s string) (time.Duration, error) {
  function verifyXingHeader (line 354) | func verifyXingHeader(t *testing.T, filepath string, sampleRate int, ffp...

FILE: test/file.go
  method testFile (line 32) | func (r *Runner) testFile(t *testing.T) {
  method runFileTest (line 530) | func (r *Runner) runFileTest(t *testing.T, test *testCase) {
  method verifyFile (line 558) | func (r *Runner) verifyFile(t *testing.T, tc *testCase, p *config.Pipeli...

FILE: test/flags.go
  constant runRoom (line 22) | runRoom           = 0b1 << 0
  constant runWeb (line 23) | runWeb            = 0b1 << 1
  constant runParticipant (line 24) | runParticipant    = 0b1 << 2
  constant runTrackComposite (line 25) | runTrackComposite = 0b1 << 3
  constant runTrack (line 26) | runTrack          = 0b1 << 4
  constant runTemplate (line 27) | runTemplate       = 0b1 << 5
  constant runMedia (line 28) | runMedia          = 0b1 << 6
  constant runAllRequests (line 30) | runAllRequests = 0b1111111
  constant runFile (line 32) | runFile     = 0b1 << 31
  constant runStream (line 33) | runStream   = 0b1 << 30
  constant runSegments (line 34) | runSegments = 0b1 << 29
  constant runImages (line 35) | runImages   = 0b1 << 28
  constant runMulti (line 36) | runMulti    = 0b1 << 27
  constant runEdge (line 37) | runEdge     = 0b1 << 26
  constant runAllOutputs (line 39) | runAllOutputs = 0b111111 << 26
  method updateFlagset (line 52) | func (r *Runner) updateFlagset() {
  method should (line 90) | func (r *Runner) should(runFlag uint) bool {

FILE: test/images.go
  method testImages (line 32) | func (r *Runner) testImages(t *testing.T) {
  method runImagesTest (line 96) | func (r *Runner) runImagesTest(t *testing.T, test *testCase) {
  method verifyImages (line 117) | func (r *Runner) verifyImages(t *testing.T, p *config.PipelineConfig, re...

FILE: test/integration.go
  method RunTests (line 38) | func (r *Runner) RunTests(t *testing.T) {
  method run (line 48) | func (r *Runner) run(t *testing.T, test *testCase, f func(*testing.T, *t...
  method ensureRoomForTest (line 98) | func (r *Runner) ensureRoomForTest(t *testing.T, test *testCase) {
  method awaitIdle (line 121) | func (r *Runner) awaitIdle(t *testing.T) {
  method startEgress (line 137) | func (r *Runner) startEgress(t *testing.T, req *rpc.StartEgressRequest) ...
  method sendRequest (line 155) | func (r *Runner) sendRequest(t *testing.T, req *rpc.StartEgressRequest) ...
  method checkUpdate (line 179) | func (r *Runner) checkUpdate(t *testing.T, egressID string, status livek...
  method checkStreamUpdate (line 188) | func (r *Runner) checkStreamUpdate(t *testing.T, egressID string, expect...
  method getUpdate (line 213) | func (r *Runner) getUpdate(t *testing.T, egressID string) *livekit.Egres...
  method getStatus (line 229) | func (r *Runner) getStatus(t *testing.T) map[string]interface{} {
  method createDotFile (line 240) | func (r *Runner) createDotFile(t *testing.T, egressID string) {
  method stopEgress (line 254) | func (r *Runner) stopEgress(t *testing.T, egressID string) *livekit.Egre...

FILE: test/integration_test.go
  function TestEgress (line 37) | func TestEgress(t *testing.T) {

FILE: test/ioserver.go
  type ioTestServer (line 30) | type ioTestServer struct
    method CreateEgress (line 48) | func (s *ioTestServer) CreateEgress(_ context.Context, info *livekit.E...
    method UpdateEgress (line 53) | func (s *ioTestServer) UpdateEgress(_ context.Context, info *livekit.E...
    method UpdateMetrics (line 59) | func (s *ioTestServer) UpdateMetrics(_ context.Context, _ *rpc.UpdateM...
  function newIOTestServer (line 36) | func newIOTestServer(bus psrpc.MessageBus, updates chan *livekit.EgressI...

FILE: test/multi.go
  method testMulti (line 31) | func (r *Runner) testMulti(t *testing.T) {
  method runMultiTest (line 119) | func (r *Runner) runMultiTest(t *testing.T, test *testCase) {

FILE: test/publish.go
  method publishSample (line 48) | func (r *Runner) publishSample(t *testing.T, codec types.MimeType, publi...
  method publishSampleWithDisconnection (line 99) | func (r *Runner) publishSampleWithDisconnection(t *testing.T, codec type...
  method publish (line 110) | func (r *Runner) publish(t *testing.T, p *lksdk.LocalParticipant, codec ...

FILE: test/runner.go
  type Runner (line 41) | type Runner struct
    method connectRoom (line 211) | func (r *Runner) connectRoom(t *testing.T, roomName string, codecs []l...
    method StartServer (line 234) | func (r *Runner) StartServer(t *testing.T, svc Server, bus psrpc.Messa...
  type Server (line 84) | type Server interface
  function NewRunner (line 95) | func NewRunner(t *testing.T) *Runner {

FILE: test/segments.go
  method testSegments (line 35) | func (r *Runner) testSegments(t *testing.T) {
  method runSegmentsTest (line 178) | func (r *Runner) runSegmentsTest(t *testing.T, test *testCase) {
  method verifySegments (line 201) | func (r *Runner) verifySegments(
  type segmentPlaylist (line 234) | type segmentPlaylist struct
  method verifySegmentOutput (line 241) | func (r *Runner) verifySegmentOutput(
  function verifyPlaylistProgramDateTime (line 280) | func verifyPlaylistProgramDateTime(t *testing.T, filenameSuffix livekit....
  type Playlist (line 318) | type Playlist struct
  type Segment (line 326) | type Segment struct
  function readPlaylist (line 332) | func readPlaylist(filename string) (*Playlist, error) {

FILE: test/stream.go
  constant badRtmpUrl1 (line 41) | badRtmpUrl1         = "rtmp://localhost:1936/wrong/stream"
  constant badRtmpUrl1Redacted (line 42) | badRtmpUrl1Redacted = "rtmp://localhost:1936/wrong/{st...am}"
  constant badRtmpUrl2 (line 43) | badRtmpUrl2         = "rtmp://localhost:1936/live/stream"
  constant badRtmpUrl2Redacted (line 44) | badRtmpUrl2Redacted = "rtmp://localhost:1936/live/{st...am}"
  constant badSrtUrl1 (line 45) | badSrtUrl1          = "srt://localhost:8891?streamid=publish:wrongport&p...
  constant badSrtUrl2 (line 46) | badSrtUrl2          = "srt://localhost:8891?streamid=publish:badstream&p...
  method testStream (line 84) | func (r *Runner) testStream(t *testing.T) {
  method runStreamTest (line 229) | func (r *Runner) runStreamTest(t *testing.T, test *testCase) {
  method verifyStreams (line 324) | func (r *Runner) verifyStreams(t *testing.T, tc *testCase, p *config.Pip...
  method runWebsocketTest (line 333) | func (r *Runner) runWebsocketTest(t *testing.T, test *testCase) {
  type websocketTestServer (line 356) | type websocketTestServer struct
    method handleWebsocket (line 370) | func (s *websocketTestServer) handleWebsocket(w http.ResponseWriter, r...
    method close (line 423) | func (s *websocketTestServer) close() {
  function newTestWebsocketServer (line 363) | func newTestWebsocketServer(filepath string) *websocketTestServer {

FILE: test/test_content.go
  constant testSampleSilenceLevel (line 41) | testSampleSilenceLevel = -38
  constant testSampleBeepLevel (line 42) | testSampleBeepLevel    = -30.0
  function ffmpegVideoStats (line 51) | func ffmpegVideoStats(videoPath, statsFile string) error {
  function ffmpegAudioStats (line 76) | func ffmpegAudioStats(audioPath, statsFile string) error {
  function ffmpegSilenceStats (line 100) | func ffmpegSilenceStats(audioPath string, noiseLevel int, minDuration fl...
  function extractFlashTimestamps (line 125) | func extractFlashTimestamps(videoPath, outPath string) ([]time.Duration,...
  function extractBeepTimestamps (line 173) | func extractBeepTimestamps(audioPath string, beepThreshold float64, outP...
  type silenceRange (line 222) | type silenceRange struct
  function detectSilence (line 229) | func detectSilence(audioPath string, noiseLevel int, minDuration time.Du...
  function secondsToDuration (line 268) | func secondsToDuration(f float64) time.Duration {
  function parsePTSSecondsToDuration (line 272) | func parsePTSSecondsToDuration(s string) (time.Duration, error) {
  function averageSpacing (line 280) | func averageSpacing(ts []time.Duration) (time.Duration, error) {
  function requireDurationInDelta (line 302) | func requireDurationInDelta(t *testing.T, expected, actual, delta time.D...

FILE: version/version.go
  constant Version (line 18) | Version         = "1.12.0"
  constant TemplateVersion (line 19) | TemplateVersion = "sha-594b3b1"
Condensed preview — 184 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (991K chars).
[
  {
    "path": ".github/CODEOWNERS",
    "chars": 19,
    "preview": "* @livekit/cs-devs\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "chars": 434,
    "preview": "---\nname: Bug report\nabout: Report an egress issue\ntitle: \"[BUG]\"\nlabels: bug\nassignees: frostbyte73\n\n---\n\n**Describe th"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "chars": 626,
    "preview": "---\nname: Feature request\nabout: Suggest an idea for this project\ntitle: \"[FEATURE]\"\nlabels: enhancement, help wanted\nas"
  },
  {
    "path": ".github/workflows/publish-chrome.yaml",
    "chars": 10547,
    "preview": "name: Publish Chrome\n\non:\n  workflow_dispatch:\n    inputs:\n      chrome_version:\n        description: \"Version of Chrome"
  },
  {
    "path": ".github/workflows/publish-egress.yaml",
    "chars": 2482,
    "preview": "# Copyright 2023 LiveKit, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use thi"
  },
  {
    "path": ".github/workflows/publish-gstreamer-base.yaml",
    "chars": 2470,
    "preview": "on:\n  workflow_call:\n    inputs:\n      version:\n        required: true\n        type: string\n      buildjet-runs-on:\n    "
  },
  {
    "path": ".github/workflows/publish-gstreamer.yaml",
    "chars": 1391,
    "preview": "name: Publish GStreamer\n\non:\n  workflow_dispatch:\n    inputs:\n      version:\n        description: \"GStreamer version to "
  },
  {
    "path": ".github/workflows/publish-template-sdk.yaml",
    "chars": 1371,
    "preview": "# Copyright 2023 LiveKit, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use thi"
  },
  {
    "path": ".github/workflows/publish-template.yaml",
    "chars": 2745,
    "preview": "# Copyright 2023 LiveKit, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use thi"
  },
  {
    "path": ".github/workflows/slack-notifier.yaml",
    "chars": 593,
    "preview": "name: PR Slack Notifier\n\non:\n  pull_request:\n    types: [review_requested, reopened, closed, synchronize]\n  pull_request"
  },
  {
    "path": ".github/workflows/test-cleanup.yaml",
    "chars": 2653,
    "preview": "name: Cleanup Integration Images\n\non:\n  schedule:\n    - cron: '0 6 * * *'\n  workflow_dispatch:\n\npermissions: {}\n\njobs:\n "
  },
  {
    "path": ".github/workflows/test-integration.yaml",
    "chars": 4008,
    "preview": "# Copyright 2023 LiveKit, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use thi"
  },
  {
    "path": ".github/workflows/test-template.yaml",
    "chars": 1221,
    "preview": "# Copyright 2023 LiveKit, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use thi"
  },
  {
    "path": ".gitignore",
    "chars": 129,
    "preview": ".idea/\n.DS_Store\n\n.github/workflows/config.yaml\nbuild/plugins/\nmedia-samples/\ntest/output/*\ntest/*.yaml\n!test/config-sam"
  },
  {
    "path": ".golangci.yaml",
    "chars": 1841,
    "preview": "version: \"2\"\nrun:\n  build-tags:\n    - deadlock\n    - integration\n  tests: true\nlinters:\n  default: none\n  enable:\n    - "
  },
  {
    "path": "LICENSE",
    "chars": 11357,
    "preview": "                                 Apache License\n                           Version 2.0, January 2004\n                   "
  },
  {
    "path": "NOTICE",
    "chars": 553,
    "preview": "Copyright 2023 LiveKit, Inc.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file "
  },
  {
    "path": "README.md",
    "chars": 16402,
    "preview": "<!--BEGIN_BANNER_IMAGE-->\n\n<picture>\n  <source media=\"(prefers-color-scheme: dark)\" srcset=\"/.github/banner_dark.png\">\n "
  },
  {
    "path": "bootstrap.sh",
    "chars": 872,
    "preview": "#!/bin/bash\n# Copyright 2023 LiveKit, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may"
  },
  {
    "path": "build/chrome/Dockerfile",
    "chars": 762,
    "preview": "# Copyright 2023 LiveKit, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use thi"
  },
  {
    "path": "build/chrome/README.md",
    "chars": 1004,
    "preview": "# Chrome installer\n\nThis dockerfile is used to install chrome on ubuntu amd64 and arm64.\n\nThere is no official or availa"
  },
  {
    "path": "build/chrome/install-chrome",
    "chars": 1295,
    "preview": "#!/bin/bash\nset -euxo pipefail\n\nif [ \"$1\" = \"linux/arm64\" ]\nthen\n  apt-get update\n  apt-get install -y \\\n    ca-certific"
  },
  {
    "path": "build/chrome/scripts/amd64.sh",
    "chars": 270,
    "preview": "#!/bin/bash\nset -xeuo pipefail\n\nwget https://dl.google.com/linux/chrome/deb/pool/main/g/google-chrome-stable/google-chro"
  },
  {
    "path": "build/chrome/scripts/arm64.sh",
    "chars": 2320,
    "preview": "#!/bin/bash\nset -xeuo pipefail\n\nsudo apt-get update\nsudo apt-get install -y \\\n  apt-utils \\\n  build-essential \\\n  curl \\"
  },
  {
    "path": "build/chrome/scripts/driver.sh",
    "chars": 348,
    "preview": "#!/bin/bash\nset -xeuo pipefail\n\nwget https://storage.googleapis.com/chrome-for-testing-public/\"$1\"/linux64/chromedriver-"
  },
  {
    "path": "build/egress/Dockerfile",
    "chars": 2531,
    "preview": "# Copyright 2023 LiveKit, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use thi"
  },
  {
    "path": "build/egress/entrypoint.sh",
    "chars": 894,
    "preview": "#!/usr/bin/env bash\n# Copyright 2023 LiveKit, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n#"
  },
  {
    "path": "build/gstreamer/Dockerfile-base",
    "chars": 1144,
    "preview": "FROM ubuntu:24.04\n\nARG GSTREAMER_VERSION\n\nARG LIBNICE_VERSION\n\nCOPY install-dependencies /\n\nRUN /install-dependencies\n\nE"
  },
  {
    "path": "build/gstreamer/Dockerfile-dev",
    "chars": 301,
    "preview": "ARG GSTREAMER_VERSION\n\nFROM livekit/gstreamer:${GSTREAMER_VERSION}-base-${TARGETARCH}\n\nENV DEBUG=true\nENV OPTIMIZATIONS="
  },
  {
    "path": "build/gstreamer/Dockerfile-prod",
    "chars": 2532,
    "preview": "ARG GSTREAMER_VERSION\n\nFROM livekit/gstreamer:${GSTREAMER_VERSION}-base-${TARGETARCH}\n\nENV DEBUG=false\nENV OPTIMIZATIONS"
  },
  {
    "path": "build/gstreamer/Dockerfile-prod-rs",
    "chars": 405,
    "preview": "ARG GSTREAMER_VERSION\n\nFROM livekit/gstreamer:${GSTREAMER_VERSION}-base-${TARGETARCH}\n\nFROM livekit/gstreamer:${GSTREAME"
  },
  {
    "path": "build/gstreamer/compile",
    "chars": 1474,
    "preview": "#!/bin/bash\nset -euxo pipefail\n\nfor repo in gstreamer libnice gst-plugins-base gst-plugins-good gst-plugins-bad gst-plug"
  },
  {
    "path": "build/gstreamer/compile-rs",
    "chars": 1016,
    "preview": "#!/bin/bash\nset -euxo pipefail\n\n: \"${CARGO_BUILD_JOBS:=4}\"\nexport CARGO_BUILD_JOBS\n\nfor repo in gst-plugins-rs; do\n  pus"
  },
  {
    "path": "build/gstreamer/install-dependencies",
    "chars": 2893,
    "preview": "#!/bin/bash\nset -euxo pipefail\n\nexport DEBIAN_FRONTEND=noninteractive\n\napt-get update\napt-get dist-upgrade -y\napt-get in"
  },
  {
    "path": "build/gstreamer/tag.sh",
    "chars": 694,
    "preview": "#!/bin/bash\n\nimage_suffix=(base dev prod prod-rs)\narchs=(amd64 arm64)\ngst_version=$1\n\nfor suffix in ${image_suffix[*]}\nd"
  },
  {
    "path": "build/template/Dockerfile",
    "chars": 865,
    "preview": "# Copyright 2023 LiveKit, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use thi"
  },
  {
    "path": "build/test/Dockerfile",
    "chars": 4804,
    "preview": "# Copyright 2023 LiveKit, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use thi"
  },
  {
    "path": "build/test/entrypoint.sh",
    "chars": 1136,
    "preview": "#!/usr/bin/env bash\n# Copyright 2023 LiveKit, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n#"
  },
  {
    "path": "build/test/fetch-media-samples.sh",
    "chars": 985,
    "preview": "#!/usr/bin/env bash\nset -euo pipefail\n\nREPO=\"livekit/media-samples\"\nDEST=\"media-samples\"\nREF=\"${1:-main}\"\n\nexport GIT_TE"
  },
  {
    "path": "chrome-sandboxing-seccomp-profile.json",
    "chars": 12086,
    "preview": "{\n\t\"defaultAction\": \"SCMP_ACT_ERRNO\",\n\t\"defaultErrnoRet\": 1,\n\t\"archMap\": [\n\t\t{\n\t\t\t\"architecture\": \"SCMP_ARCH_X86_64\",\n\t\t"
  },
  {
    "path": "cmd/server/http.go",
    "chars": 1003,
    "preview": "// Copyright 2023 LiveKit, Inc.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
  },
  {
    "path": "cmd/server/main.go",
    "chars": 4840,
    "preview": "// Copyright 2023 LiveKit, Inc.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
  },
  {
    "path": "cmd/template_version/main.go",
    "chars": 716,
    "preview": "// Copyright 2025 LiveKit, Inc.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
  },
  {
    "path": "go.mod",
    "chars": 8796,
    "preview": "module github.com/livekit/egress\n\nreplace github.com/go-gst/go-gst => github.com/livekit/gst-go v0.0.0-20250701011214-e7"
  },
  {
    "path": "go.sum",
    "chars": 49059,
    "preview": "buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.11-20260209202127-80ab13bee0bf.1 h1:PMmTMyvHScV9Mn8wc6A"
  },
  {
    "path": "magefile.go",
    "chars": 5946,
    "preview": "// Copyright 2023 LiveKit, Inc.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
  },
  {
    "path": "pkg/config/base.go",
    "chars": 8178,
    "preview": "// Copyright 2023 LiveKit, Inc.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
  },
  {
    "path": "pkg/config/config_test.go",
    "chars": 5351,
    "preview": "// Copyright 2023 LiveKit, Inc.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
  },
  {
    "path": "pkg/config/encoding.go",
    "chars": 3449,
    "preview": "// Copyright 2023 LiveKit, Inc.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
  },
  {
    "path": "pkg/config/manifest.go",
    "chars": 4059,
    "preview": "// Copyright 2023 LiveKit, Inc.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
  },
  {
    "path": "pkg/config/output.go",
    "chars": 12572,
    "preview": "// Copyright 2023 LiveKit, Inc.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
  },
  {
    "path": "pkg/config/output_file.go",
    "chars": 5364,
    "preview": "// Copyright 2023 LiveKit, Inc.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
  },
  {
    "path": "pkg/config/output_image.go",
    "chars": 4465,
    "preview": "// Copyright 2023 LiveKit, Inc.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
  },
  {
    "path": "pkg/config/output_segment.go",
    "chars": 5956,
    "preview": "// Copyright 2023 LiveKit, Inc.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
  },
  {
    "path": "pkg/config/output_stream.go",
    "chars": 2684,
    "preview": "// Copyright 2023 LiveKit, Inc.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
  },
  {
    "path": "pkg/config/pipeline.go",
    "chars": 22545,
    "preview": "// Copyright 2023 LiveKit, Inc.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
  },
  {
    "path": "pkg/config/retry_test.go",
    "chars": 4989,
    "preview": "// Copyright 2026 LiveKit, Inc.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
  },
  {
    "path": "pkg/config/service.go",
    "chars": 7546,
    "preview": "// Copyright 2023 LiveKit, Inc.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
  },
  {
    "path": "pkg/config/storage.go",
    "chars": 4626,
    "preview": "// Copyright 2023 LiveKit, Inc.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
  },
  {
    "path": "pkg/config/test_overrides.go",
    "chars": 286,
    "preview": "package config\n\n// TestOverrides is used to override the default configuration for testing purposes.\ntype TestOverrides "
  },
  {
    "path": "pkg/config/urls.go",
    "chars": 5083,
    "preview": "// Copyright 2023 LiveKit, Inc.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
  },
  {
    "path": "pkg/config/urls_test.go",
    "chars": 2944,
    "preview": "// Copyright 2023 LiveKit, Inc.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
  },
  {
    "path": "pkg/errors/errors.go",
    "chars": 5493,
    "preview": "// Copyright 2023 LiveKit, Inc.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
  },
  {
    "path": "pkg/gstreamer/bin.go",
    "chars": 18720,
    "preview": "// Copyright 2023 LiveKit, Inc.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
  },
  {
    "path": "pkg/gstreamer/builder.go",
    "chars": 1898,
    "preview": "// Copyright 2023 LiveKit, Inc.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
  },
  {
    "path": "pkg/gstreamer/callbacks.go",
    "chars": 4449,
    "preview": "// Copyright 2023 LiveKit, Inc.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
  },
  {
    "path": "pkg/gstreamer/pads.go",
    "chars": 7221,
    "preview": "// Copyright 2023 LiveKit, Inc.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
  },
  {
    "path": "pkg/gstreamer/pipeline.go",
    "chars": 4643,
    "preview": "// Copyright 2023 LiveKit, Inc.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
  },
  {
    "path": "pkg/gstreamer/queue_monitor.go",
    "chars": 3464,
    "preview": "// Copyright 2026 LiveKit, Inc.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
  },
  {
    "path": "pkg/gstreamer/state.go",
    "chars": 1880,
    "preview": "// Copyright 2023 LiveKit, Inc.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
  },
  {
    "path": "pkg/gstreamer/time_provider.go",
    "chars": 1219,
    "preview": "// Copyright 2025 LiveKit, Inc.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
  },
  {
    "path": "pkg/handler/handler.go",
    "chars": 5704,
    "preview": "// Copyright 2023 LiveKit, Inc.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
  },
  {
    "path": "pkg/handler/handler_ipc.go",
    "chars": 3721,
    "preview": "// Copyright 2023 LiveKit, Inc.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
  },
  {
    "path": "pkg/handler/handler_rpc.go",
    "chars": 1875,
    "preview": "// Copyright 2023 LiveKit, Inc.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
  },
  {
    "path": "pkg/info/io.go",
    "chars": 7241,
    "preview": "// Copyright 2023 LiveKit, Inc.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
  },
  {
    "path": "pkg/ipc/conn.go",
    "chars": 2529,
    "preview": "// Copyright 2023 LiveKit, Inc.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
  },
  {
    "path": "pkg/ipc/ipc.pb.go",
    "chars": 20621,
    "preview": "// Copyright 2023 LiveKit, Inc.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
  },
  {
    "path": "pkg/ipc/ipc.proto",
    "chars": 2196,
    "preview": "// Copyright 2023 LiveKit, Inc.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
  },
  {
    "path": "pkg/ipc/ipc_grpc.pb.go",
    "chars": 20639,
    "preview": "// Copyright 2023 LiveKit, Inc.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
  },
  {
    "path": "pkg/logging/csv.go",
    "chars": 2077,
    "preview": "// Copyright 2025 LiveKit, Inc.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
  },
  {
    "path": "pkg/logging/handler.go",
    "chars": 2975,
    "preview": "package logging\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/frostbyte73/core\"\n\t\"go.uber.org/atomic\"\n\n\t\"gi"
  },
  {
    "path": "pkg/logging/s3.go",
    "chars": 1438,
    "preview": "// Copyright 2025 LiveKit, Inc.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
  },
  {
    "path": "pkg/pipeline/builder/audio.go",
    "chars": 19382,
    "preview": "// Copyright 2023 LiveKit, Inc.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
  },
  {
    "path": "pkg/pipeline/builder/file.go",
    "chars": 2101,
    "preview": "// Copyright 2023 LiveKit, Inc.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
  },
  {
    "path": "pkg/pipeline/builder/image.go",
    "chars": 3709,
    "preview": "// Copyright 2023 LiveKit, Inc.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
  },
  {
    "path": "pkg/pipeline/builder/muxer.go",
    "chars": 2458,
    "preview": "// Copyright 2025 LiveKit, Inc.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
  },
  {
    "path": "pkg/pipeline/builder/muxer_test.go",
    "chars": 937,
    "preview": "package builder\n\nimport (\n\t\"strings\"\n\t\"sync\"\n\t\"testing\"\n\n\t\"github.com/go-gst/go-gst/gst\"\n\t\"github.com/stretchr/testify/r"
  },
  {
    "path": "pkg/pipeline/builder/pts_fixer.go",
    "chars": 1611,
    "preview": "package builder\n\nimport (\n\t\"github.com/go-gst/go-gst/gst\"\n\n\t\"github.com/livekit/egress/pkg/errors\"\n\t\"github.com/livekit/"
  },
  {
    "path": "pkg/pipeline/builder/segment.go",
    "chars": 3412,
    "preview": "// Copyright 2023 LiveKit, Inc.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
  },
  {
    "path": "pkg/pipeline/builder/stream.go",
    "chars": 8458,
    "preview": "// Copyright 2023 LiveKit, Inc.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
  },
  {
    "path": "pkg/pipeline/builder/video.go",
    "chars": 22599,
    "preview": "// Copyright 2023 LiveKit, Inc.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
  },
  {
    "path": "pkg/pipeline/builder/vp9_probe.go",
    "chars": 6366,
    "preview": "package builder\n\nimport (\n\t\"fmt\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"github.com/go-gst/go-gst/gst\"\n\t\"github.com/linkdata/deadlock\""
  },
  {
    "path": "pkg/pipeline/builder/websocket.go",
    "chars": 1243,
    "preview": "// Copyright 2023 LiveKit, Inc.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
  },
  {
    "path": "pkg/pipeline/controller.go",
    "chars": 26292,
    "preview": "// Copyright 2023 LiveKit, Inc.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
  },
  {
    "path": "pkg/pipeline/debug.go",
    "chars": 3989,
    "preview": "// Copyright 2023 LiveKit, Inc.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
  },
  {
    "path": "pkg/pipeline/sink/file.go",
    "chars": 2522,
    "preview": "// Copyright 2023 LiveKit, Inc.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
  },
  {
    "path": "pkg/pipeline/sink/image.go",
    "chars": 4613,
    "preview": "// Copyright 2023 LiveKit, Inc.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
  },
  {
    "path": "pkg/pipeline/sink/m3u8/writer.go",
    "chars": 4655,
    "preview": "// Copyright 2023 LiveKit, Inc.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
  },
  {
    "path": "pkg/pipeline/sink/m3u8/writer_test.go",
    "chars": 3165,
    "preview": "// Copyright 2023 LiveKit, Inc.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
  },
  {
    "path": "pkg/pipeline/sink/segments.go",
    "chars": 9700,
    "preview": "// Copyright 2023 LiveKit, Inc.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
  },
  {
    "path": "pkg/pipeline/sink/sink.go",
    "chars": 2208,
    "preview": "// Copyright 2023 LiveKit, Inc.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
  },
  {
    "path": "pkg/pipeline/sink/stream.go",
    "chars": 3978,
    "preview": "// Copyright 2025 LiveKit, Inc.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
  },
  {
    "path": "pkg/pipeline/sink/uploader/uploader.go",
    "chars": 4989,
    "preview": "// Copyright 2023 LiveKit, Inc.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
  },
  {
    "path": "pkg/pipeline/sink/uploader/uploader_test.go",
    "chars": 1437,
    "preview": "package uploader\n\nimport (\n\t\"io\"\n\t\"net/http\"\n\t\"os\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n\n\t\"gith"
  },
  {
    "path": "pkg/pipeline/sink/websocket.go",
    "chars": 5466,
    "preview": "// Copyright 2023 LiveKit, Inc.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
  },
  {
    "path": "pkg/pipeline/source/pulse/pactl.go",
    "chars": 4508,
    "preview": "package pulse\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"os/exec\"\n\n\t\"github.com/livekit/egress/pkg/errors\"\n)\n\nfunc Clients() "
  },
  {
    "path": "pkg/pipeline/source/sdk/appwriter.go",
    "chars": 21668,
    "preview": "// Copyright 2023 LiveKit, Inc.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
  },
  {
    "path": "pkg/pipeline/source/sdk/translator.go",
    "chars": 2324,
    "preview": "// Copyright 2023 LiveKit, Inc.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
  },
  {
    "path": "pkg/pipeline/source/sdk.go",
    "chars": 21860,
    "preview": "// Copyright 2023 LiveKit, Inc.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
  },
  {
    "path": "pkg/pipeline/source/source.go",
    "chars": 1327,
    "preview": "// Copyright 2023 LiveKit, Inc.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
  },
  {
    "path": "pkg/pipeline/source/tracer.go",
    "chars": 721,
    "preview": "// Copyright 2025 LiveKit, Inc.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
  },
  {
    "path": "pkg/pipeline/source/track_worker.go",
    "chars": 14181,
    "preview": "// Copyright 2026 LiveKit, Inc.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
  },
  {
    "path": "pkg/pipeline/source/track_worker_test.go",
    "chars": 4694,
    "preview": "// Copyright 2026 LiveKit, Inc.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
  },
  {
    "path": "pkg/pipeline/source/web.go",
    "chars": 11050,
    "preview": "// Copyright 2023 LiveKit, Inc.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
  },
  {
    "path": "pkg/pipeline/tempo/controller.go",
    "chars": 2206,
    "preview": "package tempo\n\nimport (\n\t\"time\"\n\n\t\"github.com/linkdata/deadlock\"\n)\n\nconst (\n\tDefaultThreshold = 10 * time.Millisecond //"
  },
  {
    "path": "pkg/pipeline/tempo/controller_test.go",
    "chars": 4055,
    "preview": "package tempo\n\nimport (\n\t\"testing\"\n\t\"time\"\n)\n\nfunc TestEnqueueStartsWithinBudget(t *testing.T) {\n\ttc := NewController()\n"
  },
  {
    "path": "pkg/pipeline/watch.go",
    "chars": 14528,
    "preview": "// Copyright 2023 LiveKit, Inc.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
  },
  {
    "path": "pkg/server/integration.go",
    "chars": 903,
    "preview": "// Copyright 2026 LiveKit, Inc.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
  },
  {
    "path": "pkg/server/server.go",
    "chars": 4718,
    "preview": "// Copyright 2023 LiveKit, Inc.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
  },
  {
    "path": "pkg/server/server_ipc.go",
    "chars": 2504,
    "preview": "// Copyright 2023 LiveKit, Inc.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
  },
  {
    "path": "pkg/server/server_rpc.go",
    "chars": 5955,
    "preview": "// Copyright 2023 LiveKit, Inc.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
  },
  {
    "path": "pkg/service/debug.go",
    "chars": 3978,
    "preview": "// Copyright 2023 LiveKit, Inc.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
  },
  {
    "path": "pkg/service/metrics.go",
    "chars": 3830,
    "preview": "// Copyright 2023 LiveKit, Inc.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
  },
  {
    "path": "pkg/service/process.go",
    "chars": 7254,
    "preview": "// Copyright 2023 LiveKit, Inc.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
  },
  {
    "path": "pkg/service/servicefakes/fake_process_manager.go",
    "chars": 21337,
    "preview": "// Code generated by counterfeiter. DO NOT EDIT.\npackage servicefakes\n\nimport (\n\t\"context\"\n\t\"os/exec\"\n\t\"sync\"\n\n\t\"github."
  },
  {
    "path": "pkg/stats/handler.go",
    "chars": 3980,
    "preview": "// Copyright 2023 LiveKit, Inc.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
  },
  {
    "path": "pkg/stats/monitor.go",
    "chars": 21965,
    "preview": "// Copyright 2023 LiveKit, Inc.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
  },
  {
    "path": "pkg/stats/monitor_memory_test.go",
    "chars": 3885,
    "preview": "// Copyright 2026 LiveKit, Inc.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
  },
  {
    "path": "pkg/stats/monitor_prom.go",
    "chars": 4351,
    "preview": "// Copyright 2023 LiveKit, Inc.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
  },
  {
    "path": "pkg/types/types.go",
    "chars": 6970,
    "preview": "// Copyright 2023 LiveKit, Inc.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
  },
  {
    "path": "pkg/types/types_test.go",
    "chars": 2154,
    "preview": "// Copyright 2023 LiveKit, Inc.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
  },
  {
    "path": "renovate.json",
    "chars": 705,
    "preview": "{\n  \"$schema\": \"https://docs.renovatebot.com/renovate-schema.json\",\n  \"extends\": [\n    \"config:base\"\n  ],\n  \"commitBody\""
  },
  {
    "path": "template-default/.gitignore",
    "chars": 254,
    "preview": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\nnode_modules\nbuild\ndi"
  },
  {
    "path": "template-default/.prettierrc",
    "chars": 152,
    "preview": "{\n  \"singleQuote\": true,\n  \"trailingComma\": \"all\",\n  \"semi\": true,\n  \"tabWidth\": 2,\n  \"printWidth\": 100,\n  \"plugins\": []"
  },
  {
    "path": "template-default/README.md",
    "chars": 274,
    "preview": "# Default LiveKit Recording Templates\n\nThis repo contains the default recording template used with LiveKit Egress. The t"
  },
  {
    "path": "template-default/eslint.config.js",
    "chars": 611,
    "preview": "import js from '@eslint/js'\nimport globals from 'globals'\nimport reactHooks from 'eslint-plugin-react-hooks'\nimport reac"
  },
  {
    "path": "template-default/index.html",
    "chars": 1248,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\" />\n    <link rel=\"icon\" href=\"favicon.ico\" />\n    <m"
  },
  {
    "path": "template-default/package.json",
    "chars": 960,
    "preview": "{\n  \"name\": \"livekit-egress-web\",\n  \"homepage\": \"https://livekit.io\",\n  \"description\": \"Default templates for RoomCompos"
  },
  {
    "path": "template-default/public/manifest.json",
    "chars": 413,
    "preview": "{\n  \"short_name\": \"livekit-egress-web\",\n  \"name\": \"Web template for LiveKit Egress\",\n  \"icons\": [\n    {\n      \"src\": \"fa"
  },
  {
    "path": "template-default/public/robots.txt",
    "chars": 67,
    "preview": "# https://www.robotstxt.org/robotstxt.html\nUser-agent: *\nDisallow:\n"
  },
  {
    "path": "template-default/src/App.css",
    "chars": 1337,
    "preview": "/**\n * Copyright 2023 LiveKit, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not"
  },
  {
    "path": "template-default/src/App.tsx",
    "chars": 1100,
    "preview": "/**\n * Copyright 2023 LiveKit, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not"
  },
  {
    "path": "template-default/src/Room.tsx",
    "chars": 5316,
    "preview": "/**\n * Copyright 2023 LiveKit, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not"
  },
  {
    "path": "template-default/src/SingleSpeakerLayout.tsx",
    "chars": 1045,
    "preview": "/**\n * Copyright 2023 LiveKit, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not"
  },
  {
    "path": "template-default/src/SpeakerLayout.tsx",
    "chars": 1515,
    "preview": "/**\n * Copyright 2023 LiveKit, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not"
  },
  {
    "path": "template-default/src/common.ts",
    "chars": 720,
    "preview": "/**\n * Copyright 2023 LiveKit, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not"
  },
  {
    "path": "template-default/src/index.css",
    "chars": 965,
    "preview": "/**\n * Copyright 2023 LiveKit, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not"
  },
  {
    "path": "template-default/src/index.tsx",
    "chars": 928,
    "preview": "/**\n * Copyright 2023 LiveKit, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not"
  },
  {
    "path": "template-default/src/vite-env.d.ts",
    "chars": 38,
    "preview": "/// <reference types=\"vite/client\" />\n"
  },
  {
    "path": "template-default/tsconfig.app.json",
    "chars": 668,
    "preview": "{\n  \"compilerOptions\": {\n    \"tsBuildInfoFile\": \"./node_modules/.tmp/tsconfig.app.tsbuildinfo\",\n    \"target\": \"ES2022\",\n"
  },
  {
    "path": "template-default/tsconfig.json",
    "chars": 119,
    "preview": "{\n  \"files\": [],\n  \"references\": [\n    { \"path\": \"./tsconfig.app.json\" },\n    { \"path\": \"./tsconfig.node.json\" }\n  ]\n}\n"
  },
  {
    "path": "template-default/tsconfig.node.json",
    "chars": 596,
    "preview": "{\n  \"compilerOptions\": {\n    \"tsBuildInfoFile\": \"./node_modules/.tmp/tsconfig.node.tsbuildinfo\",\n    \"target\": \"ES2023\","
  },
  {
    "path": "template-default/vite.config.ts",
    "chars": 219,
    "preview": "import { defineConfig } from 'vite'\nimport react from '@vitejs/plugin-react'\n\n// https://vite.dev/config/\nexport default"
  },
  {
    "path": "template-sdk/.gitignore",
    "chars": 19,
    "preview": "node_modules/\ndist/"
  },
  {
    "path": "template-sdk/.npmignore",
    "chars": 47,
    "preview": ".github\nnode_modules\ntsconfig.json\n.prettierrc\n"
  },
  {
    "path": "template-sdk/.prettierrc",
    "chars": 152,
    "preview": "{\n  \"singleQuote\": true,\n  \"trailingComma\": \"all\",\n  \"semi\": true,\n  \"tabWidth\": 2,\n  \"printWidth\": 100,\n  \"plugins\": []"
  },
  {
    "path": "template-sdk/README.md",
    "chars": 214,
    "preview": "# Egress Recording Template SDK\n\nThis lightweight SDK makes it simple to build your own Room Composite templates.\n\n## Do"
  },
  {
    "path": "template-sdk/package.json",
    "chars": 567,
    "preview": "{\n  \"name\": \"@livekit/egress-sdk\",\n  \"version\": \"0.2.1\",\n  \"description\": \"A lightweight SDK for developing RoomComposit"
  },
  {
    "path": "template-sdk/src/index.ts",
    "chars": 3233,
    "preview": "/**\n * Copyright 2023 LiveKit, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not"
  },
  {
    "path": "template-sdk/tsconfig.json",
    "chars": 1015,
    "preview": "{\n  \"compilerOptions\": {\n    \"target\": \"es2015\",                          /* Specify ECMAScript target version: 'ES3' (d"
  },
  {
    "path": "test/agents/.gitignore",
    "chars": 11,
    "preview": "venv/\n.env\n"
  },
  {
    "path": "test/agents/guest.py",
    "chars": 991,
    "preview": "from dotenv import load_dotenv\nfrom livekit.agents import (\n    Agent,\n    AgentSession,\n    JobContext,\n    WorkerOptio"
  },
  {
    "path": "test/agents/host.py",
    "chars": 1117,
    "preview": "from dotenv import load_dotenv\nfrom livekit.agents import (\n    Agent,\n    AgentSession,\n    JobContext,\n    WorkerOptio"
  },
  {
    "path": "test/agents/requirements.txt",
    "chars": 236,
    "preview": "livekit-agents>=1.0.0\nlivekit-plugins-deepgram>=1.0.0\nlivekit-plugins-elevenlabs>=1.0.0\nlivekit-plugins-openai>=1.0.0\nli"
  },
  {
    "path": "test/agents.go",
    "chars": 1967,
    "preview": "// Copyright 2025 LiveKit, Inc.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
  },
  {
    "path": "test/builder.go",
    "chars": 16330,
    "preview": "// Copyright 2024 LiveKit, Inc.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
  },
  {
    "path": "test/config-sample.yaml",
    "chars": 384,
    "preview": "log_level: error\nredis:\n  address: 192.168.65.2:6379\napi_key: '****'\napi_secret: '****'\nws_url: 'wss://your.livekit.url'"
  },
  {
    "path": "test/content_checks.go",
    "chars": 7510,
    "preview": "// Copyright 2023 LiveKit, Inc.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
  },
  {
    "path": "test/download.go",
    "chars": 5552,
    "preview": "// Copyright 2023 LiveKit, Inc.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
  },
  {
    "path": "test/edge.go",
    "chars": 17630,
    "preview": "// Copyright 2023 LiveKit, Inc.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
  },
  {
    "path": "test/ffprobe.go",
    "chars": 10863,
    "preview": "// Copyright 2023 LiveKit, Inc.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
  },
  {
    "path": "test/file.go",
    "chars": 16966,
    "preview": "// Copyright 2023 LiveKit, Inc.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
  },
  {
    "path": "test/flags.go",
    "chars": 2395,
    "preview": "// Copyright 2023 LiveKit, Inc.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
  },
  {
    "path": "test/images.go",
    "chars": 3405,
    "preview": "// Copyright 2023 LiveKit, Inc.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
  },
  {
    "path": "test/integration.go",
    "chars": 7486,
    "preview": "// Copyright 2023 LiveKit, Inc.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
  },
  {
    "path": "test/integration_test.go",
    "chars": 1396,
    "preview": "// Copyright 2023 LiveKit, Inc.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
  },
  {
    "path": "test/ioserver.go",
    "chars": 1804,
    "preview": "// Copyright 2023 LiveKit, Inc.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
  },
  {
    "path": "test/multi.go",
    "chars": 4433,
    "preview": "// Copyright 2023 LiveKit, Inc.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
  },
  {
    "path": "test/publish.go",
    "chars": 3748,
    "preview": "// Copyright 2023 LiveKit, Inc.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
  },
  {
    "path": "test/runner.go",
    "chars": 8043,
    "preview": "// Copyright 2023 LiveKit, Inc.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
  },
  {
    "path": "test/segments.go",
    "chars": 10733,
    "preview": "// Copyright 2023 LiveKit, Inc.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
  },
  {
    "path": "test/stream.go",
    "chars": 11623,
    "preview": "// Copyright 2023 LiveKit, Inc.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
  },
  {
    "path": "test/test_content.go",
    "chars": 8658,
    "preview": "// Copyright 2025 LiveKit, Inc.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
  },
  {
    "path": "version/version.go",
    "chars": 679,
    "preview": "// Copyright 2023 LiveKit, Inc.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
  }
]

About this extraction

This page contains the full source code of the livekit/egress GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 184 files (881.4 KB), approximately 263.2k tokens, and a symbol index with 1325 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!