Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Project FOV: Personalize Your Streaming Experience

in short

FOV (Flexible Output View) is a next-generation streaming solution designed to give viewers ultimate control over their viewing experience.


What is FOV?

Have you ever watched a live stream where the music was too loud, the webcam was blocking the game, or the chat interface was in the way? Traditionally, viewers are stuck with whatever layout the streamer chooses.

FOV solves this by providing a multi-track streaming solution. Instead of receiving one flat video, viewers receive separate, synchronized layers (such as the game feed, webcam, and individual audio tracks) that they can independently control and arrange.

Key Features for Viewers

Our platform delivers a modern streaming experience equipped with advanced interactive capabilities:

  • 🎛️ Custom Layouts: Move, resize, or hide individual elements like the streamer’s camera or chat window.
  • 🔊 Audio Mixing: Adjust the volume of the game, background music, and the streamer’s voice independently.
  • 💾 Personalized Settings: Save your favorite layouts so you never have to reconfigure them manually.
  • 🔄 One-Click Reset: Instantly return to the streamer’s default layout whenever needed.

Want to Try FOV?

Ready to dive in? Choose your path below:

  • 💻 Watching a Stream: Experience the future of interactive viewing firsthand on our public demo instance at https://fovapp.live.
  • 📡 Streaming to FOV: Ready to broadcast? Follow our detailed User Guide to set up multi-track streaming.

How It Works

Traditional streaming software flattens every element into a single video stream before broadcasting. FOV reimagines this pipeline:

  1. 🎬 Broadcasting: Using a custom version of OBS Studio, the streamer transmits multiple isolated video and audio tracks simultaneously.
  2. 🌐 Delivery: The FOV Backend ingests these tracks, synchronizes them dynamically, and converts them into optimized browser-ready streams.
  3. 👀 Viewing: The FOV Web Platform receives these separate layers, empowering the viewer’s browser to render and arrange them in real-time.

The FOV Ecosystem

The project is built on three core pillars:

ComponentDescription
FOV SoftwareA modified fork of OBS Studio, the world’s leading streaming software optimized for multi-track isolation.
Backend APIThe “brain” of the operation that manages users, categories, and the complex media pipelines required for multi-track delivery.
Angular FrontendAn intuitive website where spectators discover content and interact with the player.

Advanced Technical Architecture

  • ⚙️ Custom OBS Integration: Built on a modified C/C++ OBS fork featuring a custom output module that packages and transmits isolated tracks via the SRT protocol.
  • ⚡ Backend Processing Pipeline: Dynamically synchronizes incoming streams and converts them into segments for robust cross-browser playback.
  • 📐 Angular Web Platform: A responsive, component-driven frontend providing a real-time interactive player for full layer customization.

Performance Metrics & Targets

  • ⏱️ Ultra-Low Latency: Optimized for an inter-track synchronization delay under 50 ms and an overall viewer latency of ~5 seconds.
  • 🌱 Resource Optimization: Implements advanced backend performance tuning to minimize CPU, memory, and bandwidth consumption.
  • 🛡️ Quality Assurance: Backed by automated GitHub Actions CI/CD pipelines running unit tests, static code analysis, and multi-platform builds.

Documentation

This repository contains the complete documentation for the FOV project.

want to contribute?

Please check out the pinned GitHub discussion on our organization’s page and our contribution guidelines to get started!

Frequently Asked Questions (Q&A)

  • ❓ What is fovapp.live for?

    • fovapp.live is provided primarily as a demo environment to showcase project capabilities. It does not have the server capacity to operate as a full-scale, Twitch-sized production platform. Please use it responsibly, respect applicable laws, and avoid abusive or unauthorized behavior.
  • 🎓 What is the context of this project?

    • This project is developed as part of the EPITECH EIP curriculum for Promo 2027, Toulouse, France.
  • 🐛 Where should I report bugs or request features?

    • You can open an issue on the relevant repository or head over to our GitHub Discussions page.
  • 📦 Can I use or deploy FOV?

    • Yes! You are free to use, fork, modify, and deploy FOV, provided you adhere to the project’s licenses: our web stack is licensed under MIT, and the OBS fork (obs-studio-fov) is licensed under GPLv2 (requiring any distributed modifications or forks to remain open-source under GPLv2).

Showcase

Using FOV Software (our custom OBS fork) to stream a multi-track scene.

Watching the live stream and freely moving/resizing individual video tracks on fovapp.live.

User Guide

Welcome to the FOV User Guide! This section provides everything you need to set up your environment, configure the custom FOV OBS Studio build, and start broadcasting interactive multi-track streams.


Table of Contents

  • Installation Step-by-step instructions to download, set up, and configure the custom FOV software required for broadcasting.

  • Start a Stream A comprehensive walkthrough covering technical constraints, service configuration, hardware encoder choices, and going live on fovapp.live (or your self-hosted instance).


note

New to FOV? If you are discovering the project for the first time, we recommend checking out the Project Presentation for a high-level overview. From there, head to the Installation guide and review the technical considerations in Start a Stream before launching your first broadcast.

Install FOV Software

We provide pre-built packages for Windows (x64), macOS (ARM), and Linux (x64).

You can download all official versions from the obs-studio-fov GitHub Releases page.


Linux (x64)

important

Flatpak Prerequisite: Flatpak must be installed and configured on your system to run the Linux package. Some distributions like Ubuntu require you to install Flatpak manually if it is not already present. Refer to the official Flatpak setup guide for installation instructions specific to your Linux distribution.

1. Download

Download the Flatpak package from the releases page:

2. Install & Run

  • Via GUI: Open the downloaded file using your desktop’s package manager.
  • Via Terminal: Run the following command to install it (remove --user for a global install):
    flatpak --user install FOV-linux-x64-release.flatpak
    

You can launch the application from your desktop’s application menu or by running:

flatpak run com.flexible_output_view.FOV

macOS (ARM)

important

Apple Silicon (M) Required & Unsigned: This build is compiled natively for ARM64 macOS architectures (Intel Macs are not supported).

Because the binary is unsigned, macOS Gatekeeper will block it by default on the first launch.

1. Download

Download the disk image from the releases page:

2. Run (Installation Optional)

  1. Mount the DMG: Double-click the downloaded .dmg file to open it.
  2. Run or Move: Installation is completely optional. You can run the application directly from the mounted disk image, or drag it to your Applications folder (or any folder like your Desktop) if you prefer to keep a local copy.
  3. Bypassing the Security Warning: Because the app is unsigned, macOS will block it on the first launch. If a direct open is blocked, you can authorize it through your system preferences:
    • Try to open the app once (it will display a warning dialog that it cannot be opened).
    • Open your Mac’s System Settings and go to Privacy & Security.
    • Scroll down the page until you find the notice stating that FOV was blocked from use.
    • Click the “Open Anyway” button.
    • Confirm your choice by clicking Open on the final prompt. You only need to perform this action the very first time you launch the application.

Windows (x64)

important

Unsigned Portable Build: This release is packaged as a portable ZIP archive and is unsigned.

Windows SmartScreen may block the application on first launch.

1. Download

Download the portable ZIP archive from the releases page:

2. Run (No Installation Required)

  1. Extract: Extract the contents of the ZIP archive anywhere on your system (e.g., your Desktop or a custom tools folder).
  2. Launch: Open the application using the executable located at:
    • Release\bin\64bit\obs64.exe
  3. Windows SmartScreen: Because the binary is unsigned, Windows may display a blue “Windows protected your PC” popup when you first run it.
    • How to open it: Click on the “More info” link in the warning text, and then click the “Run anyway” button that appears at the bottom.
    • You only need to do this the first time you launch the application.

Start a stream

Welcome! This guide will walk you through setting up and broadcasting your first stream using FOV.

Prerequisites

Before you begin, ensure you have the FOV software installed and running.

Technical Considerations & Operational Constraints

Using FOV introduces a fundamentally different architectural model compared to standard OBS streaming by routing each source as an independent track. Because multi-track encoding imposes strict resource, structural, and network demands, keep the following guidelines in mind before going live:

important

Bandwidth Multiplication, Encoders & Codecs

  • Bandwidth Scaling: Each source is transmitted as an independent video or audio track, resulting in significantly higher bandwidth demands than a regular OBS setup. Your target bitrate configuration applies to every individual track. If your output bitrate is set to 2500 kbps and your scene contains 4 video layers, your total bandwidth allocation scales linearly up to 10,000 kbps upload overhead (excluding audio). Plan your network capacity accordingly.

  • CPU Load & Hardware Encoding: Your encoder settings apply universally to every track. Hardware-accelerated encoding (NVENC, AMF, or QuickSync) is strongly recommended to handle the multi-track workload efficiently. While software-based encoders (x264) can be used, running multiple concurrent streams places a heavy burden on your processor and may lead to high CPU usage or frame drops.

Supported Codecs

  • Video: H.264 (h264), H.265 (h265)
  • Audio: AAC (aac), Opus (opus)

warning

Single-Scene & Layout Lock

  • FOV operates strictly within a single-scene setup. Scene switching, as well as adding, removing, or configuring sources, is disabled while streaming. Finalize all scene structures completely before starting your broadcast.

note

Automated Track Isolation & Audio Rules

  • Automated Tracking: Track isolation is fully automated by FOVSystem. You do not need to manually configure tracks or checkboxes inside the Advanced Audio Properties window; any source currently visible and active in your active scene automatically spawns its own independent stream track.

  • Fixed Audio Mapping: Manual adjustments inside the Advanced Audio Properties panel are automatically overwritten or ignored. Audio sources map on a strict 1:1 basis based on their order of addition.

Quickstart

Follow these steps to configure your broadcast:

1. Configure the Platform Service

  1. Launch the application binary.
  2. Open Settings from the main dashboard control interface and select the Stream tab on the left margin.
  3. Set the service type dropdown to FOV - Multitrack.

2. Enter Infrastructure Target Parameters

Populate your connection details:

  • Server URL: Specify your target platform API endpoint. Use https://api.fovapp.live for the public service, or enter your custom backend API URL if you are self-hosting (see the developer documentation).
  • Stream Key: Insert your authentication token string.

3. Configure Your Scene

Add multiple audio and video sources to your current single active scene just like you would in regular OBS.

4. Go Live and Watch the Stream

  1. Click Start Streaming in the workspace console interface.
  2. Depending on your configuration, your interactive multi-track stream will be available on your self-hosted instance or on https://fovapp.live.

You can now watch your stream and enjoy the unique functionalities provided by FOV, such as resizing and moving video tracks around dynamically.

Project Roadmap & Overview

Updated: September 24th, 2026

Introduction

Welcome to the public roadmap for the Flexible Output View (FOV) project. This document outlines our core objectives, ongoing development pillars, and high-level milestones as we elevate the platform to a production-grade, professional ecosystem.

Our current core features are fully operational, including starting and stopping streams, navigating the web interface to view streams, dynamically positioning and resizing video elements, and independently adjusting audio track volumes.

1. Development Pillars & Objectives

To achieve a professional standard of quality and performance, our development efforts are structured around several key pillars:

  • Stability & Performance:

    • Minimize inter-track latency to ensure precise audio and video synchronization.
    • Resolve bugs and reinforce the overall stability of both the desktop application and the web platform.
    • Implement regular performance benchmarks.
  • Social Features & Authentication:

    • Develop a comprehensive user authentication and account management system.
    • Build interactive and social features, including real-time chat and live statistics.
  • Ergonomics, Accessibility, and Mobile Support:

    • Ensure full compliance with web accessibility standards (WCAG).
    • Optimize layouts for mobile devices, particularly for the multi-stream viewing experience.
    • Add user convenience features such as fullscreen mode and default stream layouts.
    • Refine the desktop client (OBS Studio fork) interface for seamless synergy with the web platform.
  • Documentation & Community:

    • Provide extensive, comprehensive technical and user documentation.
    • Foster an open environment that encourages external community contributions and involvement.
  • Software Quality & Security:

    • Expand automated testing suites (regression, validation, integration) and optimize the continuous integration (CI) pipeline.
    • Rigorously validate user inputs and maintain a strong focus on security.

2. Public Roadmap Timeline

🔴 High Priority — September to October 2026

  • API & Data: Complete the replacement of all remaining mock data with the production API.
  • Player UX: Automatically persist and restore user-selected stream layouts.
  • Player UX: Optimize client performance to minimize stream load times.
  • Responsive Design: Ensure global site responsiveness, focusing heavily on mobile views for the multi-stream player.
  • Documentation: Clean up repository README files and establish a centralized, publicly accessible documentation hub.
  • Desktop Client (OBS): Implement source selection capabilities to easily show or hide specific output streams.
  • R&D / Prototyping: Explore and test cutting-edge technologies (such as Media Over QUIC) to address technical bottlenecks.
    • Open to contributions

🟡 Medium Priority — October to December 2026

  • Accounts & Sessions: Implement user registration and login workflows.
  • Social Features: Deploy a comprehensive follower system.
  • Navigation: Dynamically link categories to active live streams.
  • Navigation: Introduce user profile pages and dedicated user stream spaces.
  • Quality & Accessibility: Finalize frontend accessibility compliance for primary user journeys.
  • Social Features: Release real-time live chat functionality.
  • Security: Vulnerability audit across our project.
    • Open to contributions (see #63)

🟢 Low Priority — January / February 2027

  • Advanced Social Features: Roll out advanced live statistics and deep social interactions.
  • Recommendation Engine: Implement personalized content discovery algorithms (“You might also like…”).
  • Industrialization: Expand automated frontend/backend testing coverage and thoroughly document the codebase.
    • Open to contributions (see #62 and #61)
  • Milestone Review: Conduct final performance benchmarks and polish all technical and user-facing documentation.
  • Language Localization: Translate the website and FOV Software UI.
    • Open to contributions (see #64 and #28)

Contribution Guidelines & Project Organization

Welcome to the contribution guidelines! This document outlines the standards, workflows, team structure, governance, and repositories for the Flexible Output View (FOV) ecosystem.

Scope

This document applies to all organization repositories and to every team member or contributor working on the project.


Table of Contents


Team & Organization

Core Team

FOV is developed and maintained by a 3-person core team:

GitHub Organization & Repositories

All project code and documentation are hosted under the official Flexible Output View GitHub Organization. The project is structured across three primary repositories:

  1. obs-studio-fov
    • Language: C
    • License: GPLv2
    • Description: A custom fork of obsproject/obs-studio built specifically to add robust multi-track functionality and source isolation for the FOV ecosystem.
  2. web-fov
    • Language: JavaScript
    • License: MIT
    • Description: Repository hosting the Flexible Output View web stack and platform infrastructure.
  3. documentation-fov
    • License: CC BY-SA 4.0
    • Description: Public documentation repository containing user guides, developer docs, and mdBook source files.

Community, Discussions & Feedback

  • GitHub Discussions: We actively use GitHub Discussions to collect user feedback, discuss architectural decisions, answer questions, and interact with the community.
  • Building & Feedback: We strongly encourage users to share their feedback, exchange thoughts, and build alternative or complementary software solutions utilizing our platform.

GitHub Project Board

The main internal GitHub project board is currently private (managed under the Epitech EIP framework) and accessible exclusively to the FOV team.

note

We plan to release a public mini GitHub project with a global roadmap to the public soon. Keeping the internal project board up-to-date remains mandatory for all active team contributors.


Releases

The FOV core team periodically creates tagged GitHub releases containing stable software packages.

  • Trigger Criteria: A new release is triggered when sufficient improvements, bug fixes, and features have been accumulated and merged into master to deliver a significantly enhanced experience to users.

Contribution & Feature Policy

Opening Issues & Bug Reports

We strongly encourage users and contributors to open issues on GitHub for bug reports, feature requests, or general feedback. When creating an issue, please adhere to the following guidelines:

  • Type & Labels: Properly categorize your issue by selecting the correct issue type and assigning relevant labels.
  • Precise Description: Provide a clear and thorough explanation of your request or problem.
  • Steps to Reproduce: If you are reporting a bug, always include precise, step-by-step instructions to reproduce the issue, along with relevant error logs, screenshots, or environment details.

Feature Submissions & Workflow

  • Contributors are free to develop features or fixes and open a pull request directly whenever they are ready.
  • Feature Requests & Questions: You can discuss potential feature requests or ask questions directly to the team via GitHub Discussions before or during implementation.
  • The FOV core team begins evaluating and reviewing contributions starting from the moment a pull request is opened.

What Will and Won’t Be Accepted

  • What Will Be Accepted:
    • Well-tested bug fixes and performance improvements.
    • Clear, modular features that align with the core architectural goals of the FOV ecosystem.
    • Documentation updates with accurate, clear explanations.
    • Contributions that successfully pass automated CI checks and peer reviews.
  • What Won’t Be Accepted:
    • Out-of-scope architectural rewrites or features introduced without prior discussion or issue alignment.
    • Code lacking proper documentation, formatting, or test coverage.
    • Contributions failing automated continuous integration pipelines or introducing unresolved security/stability risks.

Development & Branching Strategy

Git Branch Rules

Our repository structure relies on two primary long-lived branches alongside feature branches:

  • dev (Integration Branch): All active development happens here. Every pull request must target and merge into dev.
  • main / master (Production Branch): Stable, production-ready code. This branch only receives commits by merging dev into it. Direct commits or PRs into main/master are strictly prohibited.

Feature Branches & Cleanup

  • Create dedicated Git branches for every feature or bug fix (ideally using GitHub’s “Create a branch” button directly from your assigned issue).
  • Cleanup: Merged branches must be deleted once the pull request is closed.

Coding Conventions

To maintain a clean and readable codebase across repositories, please adhere to the following baseline conventions:

  • Language Standards: Follow established language-specific styles (e.g., standard JavaScript style guidelines for web-fov, and clean C practices for obs-studio-fov).
  • Automatic Formatting: Contributors must use automatic code formatting tools prior to submission:
    • Use clang-format for all obs-studio-fov C code.
    • Use eslint or prettier for the frontend and backend codebase in web-fov.
  • Comments & Clarity: Write self-documenting code and add clear comments for complex logic or business logic blocks.
    • Use Doxygen comments for obs-studio-fov.

Commit Message Format

Commit messages must follow a clean, standardized format:

PREFIX: Short description
(Optional detailed description)

Where PREFIX is:

  • ADD: Adding a new feature.
  • UPDATE: Updating an existing feature.
  • FIX: Fixing a bug.
  • RM: Removing a functionality or file.
  • DOC: Adding or updating documentation.
  • REFACT: Refactoring code without altering functionality.
  • TEST: Adding or updating tests.
  • MERGE: Merging a branch via pull request.

Pull Requests & How to Submit a Change

Pull requests (PRs) are mandatory for introducing changes into dev.

Step-by-Step Submission Workflow:

  1. Fork & Branch: Create a dedicated feature branch off dev.
  2. Implement & Test: Code your changes following project conventions, apply formatters, and perform local testing.
  3. Commit: Commit your changes using the standardized commit message format.
  4. Open PR: Submit a Pull Request targeting the dev branch, referencing any related issues or discussions.

PR Requirements & Review Policy

  • Mandatory Review: All PRs must be reviewed and approved by at least one member of the team before merging.
  • Manual & Local Testing: The FOV team tests every pull request manually and locally to ensure our quality standards are met.
  • CI Validation: The automated CI pipeline must complete successfully.

Response Time & Review SLA

  • As this project is developed as part of our 5th-year Epitech curriculum, the core team is primarily active and working on the repositories during Thursdays and Fridays.
  • Review feedback, issue triage, and PR merges are concentrated around these project days. We appreciate your patience outside of these windows!

Governance & Community Guidelines

  • Disagreements: Any disagreements or conflicts within the community will be handled and resolved by majority decision.
  • Constructive Collaboration: We value respectful, community-driven development and encourage open discussions via GitHub channels.

Developer Documentation

Welcome to the FOV Developer Documentation! This section provides comprehensive technical information for developers and contributors working on the project, covering web stack architecture, frontend/backend specifications, deployment protocols, and custom OBS integration modules.


Table of Contents


note

Looking to broadcast? If you are a content creator trying to set up your stream rather than develop the platform, please head over to the User Guide instead.

FOV Web Documentation

Welcome to the documentation for the FOV Web Stack. The web platform is composed of two main components:

  • Frontend: An Angular-based user interface and custom multi-stream player.
  • Backend: A Node.js + Express server managing stream metadata, user authentication, and routing.

Documentation Sections

  • Frontend Documentation Guides, setup instructions, and component references for the Angular frontend application.

  • Backend Documentation Comprehensive documentation for the Node.js backend, covering architecture, API testing, deployment, security, and developer guidelines.

  • Continuous Integration Overview of the CI/CD pipelines, workflows, and automated checks used for the web repository.

  • Deploying with docker A guide on how to deploy the entire web stack using Docker.


Web Platform Architecture Overview

Core Technologies

  • Frontend:

    • Angular Framework: Utilized for its robust component architecture, team familiarity, and strong support for managing complex, interactive user interfaces.
    • Custom Video Player: Handles multi-stream layouts, stream synchronization, and interactive layout customization.
  • Backend:

    • Node.js & Express: Powers the core API, user authentication systems, and stream metadata handling (operating similarly to standard broadcasting platforms like Twitch).
    • Database: Postgres used for persistent data storage.

Infrastructure & Requirements

  • Development Setup: Service isolation achieved via Docker containers.
  • Viewer Requirements: Modern web browsers (Chrome, Firefox, Edge) for smooth multi-stream layout rendering and low-latency playback.

FOV Frontend Documentation

FOV Backend - README

Project Overview

The FOV Backend is an Express.js-based REST API and media streaming server that powers the FOV video streaming platform. It handles user management, stream metadata, categories, and serves HLS (HTTP Live Streaming) content for live and on-demand video delivery.

Key Features:

  • RESTful API for users, streams, and categories
  • MySQL database integration with connection pooling
  • HLS streaming support via SRT ingest
  • CORS-enabled for multi-origin requests
  • Comprehensive error handling and logging
  • Docker-ready deployment

Table of Contents

  1. Installation
  2. Configuration
  3. Project Structure
  4. Running the Server
  5. API Endpoints
  6. Testing
  7. Linting & Code Quality
  8. Deployment
  9. Documentation
  10. Troubleshooting

Installation

Prerequisites

  • Node.js: v18+ (v20 recommended)
  • npm: v8+
  • MySQL: v8.0+ (for development and production)
  • FFmpeg: Required for media transcoding (optional, if using media server features)

Steps

  1. Clone and navigate to the backend:

    cd backend
    
  2. Install dependencies:

    npm install
    
  3. Set up environment variables:

    cp .env.example .env
    # Edit .env with your database and server configuration
    
  4. Verify database connection:

    node test-db.js
    

Configuration

Environment Variables

Create a .env file in the backend/ directory. See .env.example for template.

VariableTypeDefaultDescription
PORTint4000Express server port
NODE_ENVstringdevelopmentEnvironment (development/production)
DB_HOSTstringlocalhostMySQL host
DB_USERstringadminMySQL user
DB_PASSWORDstring(required)MySQL password
DB_NAMEstringfovwebdbDatabase name
MEDIA_ROOTstring./mediaPath to HLS and media files
FFMPEG_PATHstringauto-detectedPath to FFmpeg binary
CORS_ORIGINstring*CORS allowed origin

Production Configuration

For production, create .env.production based on .env.production.example:

cp .env.production.example .env.production
# Update with production credentials and endpoints

Project Structure

backend/
├── src/
│   ├── index.js                 # Express app initialization & server startup
│   ├── db.js                    # MySQL connection pool & helper methods
│   ├── mediaServer.mjs          # SRT server & HLS transcoding setup
│   └── routes/
│       ├── index.js             # Route aggregator
│       ├── users.js             # User CRUD operations
│       ├── streams.js           # Stream metadata & HLS playlists
│       └── categories.js        # Category management
├── src/__tests__/               # Unit tests
│   ├── db.test.js
│   └── users.test.js
├── media/
│   ├── hls/                     # HLS segments & manifests
│   └── .gitkeep
├── .eslintrc.json               # ESLint configuration
├── jest.config.js               # Jest test configuration
├── package.json                 # Dependencies & scripts
├── .env.example                 # Environment template
├── .env.production.example      # Production environment template
├── Dockerfile                   # Docker image definition
├── readme.md                    # This file
├── ARCHITECTURE.md              # System design & justification
├── DEVELOPMENT.md               # Development guide
├── DEPLOYMENT.md                # Deployment instructions
├── CHANGELOG.md                 # Version history
└── SECURITY.md                  # Security policies & practices

Running the Server

Development

Start with automatic restart on file changes:

npm run dev

Server will be available at http://localhost:4000

Production

npm start

Ensure environment variables are properly set in .env or the environment.


API Endpoints

Users

  • GET /api/users/:id - Get user by ID
  • POST /api/users - Create new user

Example:

# Create user
curl -X POST http://localhost:4000/api/users \
  -H "Content-Type: application/json" \
  -d '{"username":"john_doe","display_name":"John Doe"}'

# Get user
curl http://localhost:4000/api/users/1

Streams

  • GET /api/streams - List all streams
  • GET /api/streams/:id - Get stream details & HLS playlist
  • POST /api/streams - Create new stream

Categories

  • GET /api/categories - List all categories
  • POST /api/categories - Create new category

Media (HLS)

  • GET /hls/:stream_id/playlist.m3u8 - Master HLS playlist
  • GET /hls/:stream_id/:segment.ts - Video segments

Testing

Run All Tests

npm test

This runs all tests in src/__tests__/ and generates a coverage report.

Run Tests in Watch Mode

npm run test:watch

Useful during development to re-run tests on file changes.

View Coverage Report

After running tests, open coverage/lcov-report/index.html in your browser.

Test Structure

Tests are organized following the module structure:

  • db.test.js - Database connection and query testing
  • users.test.js - User route tests (CRUD operations)

Linting & Code Quality

Run Linter

Check for code style issues:

npm run lint

Auto-Fix Linting Issues

npm run lint:fix

Code Standards

The project enforces:

  • Indentation: 4 spaces
  • Quotes: Single quotes
  • Semicolons: Always required
  • Variable Declaration: const preferred, no var
  • Equality: Strict equality (===)
  • Naming: camelCase for variables/functions, PascalCase for classes

See .eslintrc.json for complete rules.


Deployment

Docker

Build and run using Docker:

# Build image
docker build -t fov-backend:latest .

# Run container
docker run -p 4000:4000 \
  -e DB_HOST=mysql-host \
  -e DB_USER=admin \
  -e DB_PASSWORD=xxxx \
  -e DB_NAME=fovwebdb \
  fov-backend:latest

Docker Compose

docker-compose up -d

See DEPLOYMENT.md for detailed deployment instructions.

GitHub Actions CI/CD

The project includes automated testing and linting on push/PR:

  • File: .github/workflows/backend-tests.yml
  • Triggers: Push to backend/ or PR to main
  • Steps: Install → Lint → Test

Additional Documentation

  • ARCHITECTURE.md - System design, technology stack justification, data flows
  • DEVELOPMENT.md - Development setup, debugging, contribution guidelines
  • DEPLOYMENT.md - Production deployment, scaling, backup strategies
  • CHANGELOG.md - Version history and feature updates
  • SECURITY.md - Security practices, authentication, data protection

Troubleshooting

Database Connection Failed

Unable to connect to DB Error: ECONNREFUSED 127.0.0.1:3306

Solutions:

  1. Verify MySQL is running: mysql -u admin -p
  2. Check .env credentials match your MySQL setup
  3. Ensure database exists: CREATE DATABASE fovwebdb;

Port Already in Use

Error: listen EADDRINUSE :::4000

Solutions:

  1. Change PORT in .env: PORT=5000
  2. Kill existing process: lsof -ti:4000 | xargs kill -9

Tests Failing

Run with verbose output:

npm test -- --verbose

Check test database connectivity in .env.

Linting Errors

Auto-fix most issues:

npm run lint:fix

Support & Contributing

For issues, improvements, or questions:

  1. Review DEVELOPMENT.md for contribution guidelines
  2. Check existing issues/documentation
  3. Submit bug reports with test cases and logs

Last Updated: April 2026 Maintainer: FOV Development Team

FOV Backend - Architecture & Design

1. System Overview

The FOV Backend is a modular Express.js REST API with media streaming capabilities. It separates concerns into:

  • API Layer: RESTful routes for data management
  • Database Layer: MySQL connection pooling
  • Media Layer: HLS video streaming (SRT ingest)
  • Middleware Layer: CORS, logging, error handling
┌─────────────────────────────────────┐
│         Client Applications          │
│    (Angular Frontend, Mobile, etc)   │
└──────────────┬──────────────────────┘
               │ HTTPS/HTTP
┌──────────────▼──────────────────────┐
│     Express.js REST API Server       │
│  (Port 4000 - Main API)              │
├──────────────────────────────────────┤
│  Routes: /api/users, /streams, etc   │
│  Middleware: CORS, Morgan, JSON      │
│  Error Handling: Centralized         │
└──────────────┬──────────────────────┘
               │
       ┌───────┴────────┐
       │                │
┌──────▼─────┐   ┌──────▼─────────┐
│ MySQL Pool │   │ Media Server    │
│ (Port 3306)│   │ SRT/HLS        │
│            │   │ (Port 9999)     │
└────────────┘   └─────────────────┘
       │                │
       │                │
┌──────▼──────┐  ┌──────▼──────────┐
│  Database   │  │  Media Files    │
│  fovwebdb   │  │  /media/hls/    │
│             │  │  (segments, m3u)│
└─────────────┘  └─────────────────┘

2. Technology Stack & Justification

Core Framework: Express.js

Why Express?

  • Lightweight and performant
  • Large ecosystem & community support
  • Flexible routing and middleware system
  • Minimal setup overhead (perfect for microservices)
  • Battle-tested in production environments

Alternatives Considered:

  • Fastify: Faster but smaller ecosystem; Express sufficient for our throughput
  • NestJS: Excellent but heavier; unnecessary for our current scope
  • Koa: Modern but less mature; Express more stable

Database: MySQL 8.0+

Why MySQL?

  • Mature, stable, widely-hosted (AWS RDS, DigitalOcean, Linode)
  • ACID compliance ensures data integrity
  • Good performance for structured data (users, streams, categories)
  • Connection pooling support (mysql2/promise)
  • Cost-effective at scale

Schema Highlights:

  • Users: id, username, display_name, created_at
  • Streams: id, streamer, title, category_id, viewers, thumbnail_url, avatar_url, is_live
  • Categories: id, name, viewers, image_url

Media Streaming: SRT → HLS

Why SRT Ingest + HLS Delivery?

ComponentPurposeAlternativeWhy Not
SRT InputSecure, low-latency protocol (OBS, FFmpeg)RTMP, RTSPSuperior reliability and latency performance
HLS OutputHTTP-based deliveryDASH, RTMPMost compatible (Safari, iOS native, widely supported)
FFmpegTranscoding/segmentation & format conversionNode-media-server onlyBetter quality control, codec flexibility, extensive format support

Media Pipeline:

Encoder → SRT Server → FFmpeg → Segmentation → HLS Segments → CDN/Client
(OBS)     (Port 9999) (Transcode) (/media/hls/) (.m3u8, .ts files)

Connection Management: mysql2/promise Pool

Why Pooling?

  • Reuses connections, reduces overhead
  • Prevents “too many connections” errors
  • Auto-reconnection on failure
  • Default: 5 connections (configurable)

3. Module Structure & Responsibility

src/index.js - Application Entry Point

Responsibility:

  • Initialize Express app
  • Configure middleware (CORS, Morgan, JSON parser)
  • Mount routes and media server
  • Start HTTP listener
  • Handle application-level errors

Key Design:

  • Error handler at bottom (Express middleware order)
  • Database validation before server start
  • Graceful error messages

src/db.js - Database Access Layer

Responsibility:

  • Create and manage connection pool
  • Provide query interface
  • Handle connection cleanup

Design Pattern: Singleton Pool

const pool = mysql.createPool({...})
export default { pool, getConnection(), query() }

Why:

  • Single pool instance across app lifetime
  • All modules share connections
  • Automatic cleanup via connection release

src/routes/index.js - Route Aggregator

Responsibility:

  • Mount sub-routers at /api prefix
  • Provide root health check

Separation of Concerns:

/api/users → routes/users.js
/api/streams → routes/streams.js
/api/categories → routes/categories.js

src/routes/users.js - User API

Operations:

  • GET /:id - Fetch user by ID
  • POST / - Create user (minimal validation)

Implementation Notes:

  • Uses async/await for DB calls
  • Error passed to next() middleware
  • Input validation basic; strengthen in security review

src/routes/streams.js - Stream Management

Operations:

  • HLS playlist validation & serving
  • Stream status checking
  • Segment counting for health monitoring

Key Functions:

  • isPlaylistReady() - Ensures 2+ segments before serving (reliability)
  • getSegmentCount() - Monitors growth (debugging)

src/mediaServer.mjs - Media Server

Features:

  • SRT server (ingest from encoders on port 9999)
  • SRT → HLS transcoding via FFmpeg
  • Stream monitoring and segment management
  • Automatic process lifecycle management

Why Separate File:

  • Complex live-streaming logic isolated
  • Future migration to dedicated media service easier

4. Data Flow Architecture

API Request Flow

Client Request (JSON)
    ↓
Express Middleware (CORS, JSON parse, logging)
    ↓
Route Handler (async)
    ↓
Database Query (mysql2/promise)
    ↓
Response (JSON)
    ↓
Error Handler (if exception)

Live Stream Ingestion Flow

EncodeSRT (Port 9999)
SRT Server (mediaServer.mjs)
    ↓ Transcode via FFmpeg
FFmpeg Process
    ↓ Segment & Outpuss
    ↓ Segment
/media/hls/ (m3u8 + .ts files)
    ↓ HTTP GET
Client HLS Player

5. Error Handling Strategy

Layers:

  1. Application Level (src/index.js):

    app.use((err, req, res, next) => {
        console.error(err);
        res.status(err.status || 500).json({ error: err.message });
    });
    
  2. Route Level (src/routes/*.js):

    try {
        // DB operation
    } catch (err) {
        next(err); // Pass to app handler
    }
    
  3. Database Level (src/db.js):

    • Finally block ensures connection release
    • Pool handles reconnection

Future Improvements:

  • Circuit breaker for DB failover
  • Structured logging (Winston, Pino)
  • Error tracking (Sentry)

6. Security Architecture

Current State:

  • ✅ CORS enabled (configurable)
  • ✅ Morgan request logging
  • ⚠️ No authentication (to be implemented)
  • ⚠️ Basic input validation
  • ⚠️ No rate limiting

Roadmap:

  • JWT authentication for protected endpoints
  • Input sanitization (SQL injection prevention)
  • Rate limiting (Express-limiter)
  • HTTPS enforcement in production
  • Database credentials rotation

See SECURITY.md for detailed policies.


7. Scalability Considerations

Current Limitations

ComponentBottleneckSolution
Single ExpressCPU-bound workCluster module / Load balancer
Single DB PoolConnection limitRead replicas, sharding
Local Media FilesDisk I/OS3/Object storage + CDN

Scaling Strategy

Phase 1 (Current):

  • Single region, containerized

Phase 2:

  • Load balancer (Nginx)
  • Dedicated media server (Wowza/SRT Live Server)

Phase 3:

  • Database replication (Master-Slave)
  • Media on S3 + CloudFront CDN

8. Deployment Architecture

Development

Laptop → npm run dev → localhost:4000

Production (Docker)

Dockerfile → Image → Container → Port 4000
    ↓
  Volume Mounts: /media/hls
    ↓
  Docker Network: postgres, frontend

See DEPLOYMENT.md for full instructions.


9. Module Dependencies

index.js
  ├── express
  ├── cors
  ├── morgan
  ├── db.js
  │   └── mysql2/promise
  ├── routes/
  │   ├── users.js → db.js
  │   ├── streams.js → db.js, fs, path
  │   └── categories.js → db.js
  └── mediaServer.mjs

jest.config.js (Testing)
supertest (API testing)

10. Code Quality Standards

Enforced by ESLint:

  • Indentation: 4 spaces
  • Quotes: Single
  • Semicolons: Always
  • Variable: prefer const
  • Equality: Strict (===)

Testing:

  • Unit tests for critical modules (DB, routes)
  • Mocked external dependencies
  • 50%+ code coverage threshold

11. Version Control & Releases

Current Version: 0.1.0

Versioning: Semantic Versioning (MAJOR.MINOR.PATCH)

  • MAJOR: Breaking API changes
  • MINOR: New features (backwards compatible)
  • PATCH: Bug fixes

See CHANGELOG.md for version history.


Document Version: 1.0
Last Updated: April 2026

FOV Backend - Development Guide

Getting Started

Prerequisites

  • Node.js v20+
  • npm v8+
  • MySQL 8.0+ (for testing against real DB)
  • Git
  • A code editor (VS Code recommended)

Initial Setup

# 1. Clone and enter backend directory
cd backend

# 2. Install dependencies
npm install

# 3. Copy environment template
cp .env.example .env

# 4. Update .env with your local database
#    DB_HOST=localhost
#    DB_USER=root (or your user)
#    DB_PASSWORD=your_password
#    DB_NAME=fovwebdb

# 5. Verify database connection
node test-db.js
# Expected output: ✅ Connected! ✅ Query result: [...]

# 6. Start development server
npm run dev
# Expected output: 🚀 Server listening on http://localhost:4000

Development Workflow

Running the Server

Development (with hot-reload):

npm run dev

Uses nodemon to auto-restart on file changes. Server runs on http://localhost:4000

Production Build:

npm start

Single process, requires manual restart on code changes.

Testing

Run all tests:

npm test

Generates coverage report in coverage/ directory.

Watch mode (recommended during development):

npm run test:watch

Re-runs affected tests on file save.

Example output:

PASS  src/__tests__/users.test.js
  Users Routes
    GET /:id
      ✓ should return user by id (45ms)
      ✓ should return 404 when user not found (12ms)
      ✓ should handle database errors (8ms)
    POST /
      ✓ should create a new user (38ms)
      ✓ should return 400 when username is missing (5ms)
      ✓ should handle database errors on create (6ms)

Test Suites: 1 passed, 1 total
Tests:       6 passed, 6 total
Coverage: 62% statements, 48% branches

Code Quality

Check linting:

npm run lint

Detects code style violations.

Auto-fix issues:

npm run lint:fix

Automatically corrects most ESLint violations (indentation, quotes, semicolons, etc).

Example violations:

/backend/src/users.js
  5:4  error  Unexpected var, use const or let  no-var
  12:1 error  Missing semicolon                 semi
  18:5 warning Unexpected console statement     no-console

Project Structure Guide

Adding a New Route

Example: Add a new /api/comments endpoint

1. Create route file:

touch src/routes/comments.js

2. Implement handler:

// src/routes/comments.js
import express from 'express';
const router = express.Router();
import db from '../db.js';

router.get('/:streamId', async (req, res, next) => {
    try {
        const rows = await db.query(
            'SELECT * FROM comments WHERE stream_id = ? ORDER BY created_at DESC',
            [req.params.streamId]
        );
        res.json({ data: rows });
    } catch (err) {
        next(err); // Pass to error handler
    }
});

export default router;

3. Register in main routes file:

// src/routes/index.js
import comments from './comments.js';

router.use('/comments', comments); // This line

4. Test your endpoint:

curl http://localhost:4000/api/comments/1

5. Add tests:

# src/__tests__/comments.test.js
import request from 'supertest';
import app from '../index.js';
import db from '../db.js';

jest.mock('../db.js');

describe('Comments Routes', () => {
    // ... write tests
});

Modifying Database Queries

Current practice:

const rows = await db.query(
    'SELECT id, username FROM users WHERE id = ?',
    [id]
);

Best practices:

  • Always use parameterized queries (prevent SQL injection)
  • Use ? placeholders, never string concatenation
  • Handle null returns explicitly
  • Release connections properly (handled by pool)

Adding Environment Variables

1. Define in .env:

MY_NEW_VAR=value

2. Use in code:

const myVar = process.env.MY_NEW_VAR || 'default_value';

3. Document in .env.example:

# My new configuration
MY_NEW_VAR=default_value

Debugging

Enable Verbose Logging

Option 1: Add console statements

console.log('Debug:', variableName);

Option 2: Use Node debugger

node --inspect-brk src/index.js
# Then open DevTools in Chrome: chrome://inspect

Common Issues

“Cannot find module ‘express’”

npm install

“Port 4000 is already in use”

# Find process using port
lsof -i :4000
# Kill it
kill -9 <PID>
# Or change in .env: PORT=5000

Database connection hangs

  • Check MySQL is running: mysql -u admin -p
  • Verify credentials in .env
  • Check MySQL version: mysql --version

Tests fail with “Cannot find module”

# Make sure jest.config.js exists and is correct
npm test -- --detectOpenHandles

Code Standards

Style Guide

Variables:

const USER_LIMIT = 100;           // Constants: UPPER_SNAKE_CASE
const userName = 'John';          // Variables: camelCase
const getUserById = () => {};     // Functions: camelCase
class UserManager {}              // Classes: PascalCase

Async/Await:

// ✅ Good
async function fetchUser(id) {
    try {
        const user = await db.query('SELECT * FROM users WHERE id = ?', [id]);
        return user[0];
    } catch (err) {
        console.error('Failed to fetch user:', err);
        throw err;
    }
}

// ❌ Bad (uses callback)
function fetchUser(id, callback) {
    db.query('...', [id], (err, result) => {
        // Harder to read, easy to forget error handling
    });
}

Error Handling:

// ✅ Good
try {
    const result = await db.query(sql, params);
} catch (err) {
    next(err); // Route handlers
    // or
    res.status(500).json({ error: err.message }); // Direct response
}

// ❌ Bad
db.query(sql, params); // Ignores errors silently

Naming Conventions

TypePatternExample
VariablecamelCaseuserName, streamCount
ConstantUPPER_SNAKE_CASEDB_LIMIT, HLS_PORT
FunctioncamelCasegetUserById(), validateEmail()
ClassPascalCaseUserService, StreamManager
Filekebab-case or camelCaseuser-routes.js or userRoutes.js
Databasesnake_caseuser_id, created_at

Testing Guidelines

Writing Tests

Structure:

describe('Feature to test', () => {
    beforeEach(() => {
        // Setup before each test
    });

    test('should do something', async () => {
        // Arrange
        const input = 'test';
        
        // Act
        const result = await functionToTest(input);
        
        // Assert
        expect(result).toBe('expected');
    });

    afterEach(() => {
        // Cleanup after each test
        jest.clearAllMocks();
    });
});

Mocking Database:

jest.mock('../db.js');

db.query.mockResolvedValue([{id: 1, name: 'Test'}]);
// OR
db.query.mockRejectedValue(new Error('DB Error'));

Coverage Goals

Current threshold (in jest.config.js):

coverageThreshold: {
    global: {
        branches: 50,        // 50% of branch paths
        functions: 50,       // 50% of functions
        lines: 50,          // 50% of lines
        statements: 50      // 50% of statements
    }
}

To view coverage report:

npm test
open coverage/lcov-report/index.html

Contributing

Before Committing

  1. Run linter:

    npm run lint:fix
    
  2. Run tests:

    npm test
    
  3. Check coverage hasn’t dropped:

    npm test
    # View coverage/lcov-report/index.html
    
  4. Commit with meaningful message:

    git commit -m "feat: add user authentication routes"
    

Commit Message Format

<type>(<scope>): <subject>

<body>

<footer>

Types:

  • feat: New feature
  • fix: Bug fix
  • docs: Documentation
  • style: Code style (lint, formatting)
  • test: Tests
  • chore: Dependencies, config

Examples:

feat(users): add password hashing
fix(streams): handle missing HLS segments
docs(readme): clarify installation steps
test(db): increase coverage to 75%

Performance Optimization

Database Query Optimization

Use indexes:

CREATE INDEX idx_user_id ON streams(user_id);
CREATE INDEX idx_created_at ON comments(created_at DESC);

Monitor slow queries:

# In MySQL
SET GLOBAL slow_query_log='ON';
SET GLOBAL long_query_time=2;

Connection Pooling Tuning

Adjust in src/db.js:

const pool = mysql.createPool({
    connectionLimit: 10,  // Increase if hitting limits
    enableKeepAlive: true,
    keepAliveInitialDelayMs: 0,
});

Useful Commands

CommandPurpose
npm run devStart dev server with hot-reload
npm testRun tests with coverage
npm run test:watchWatch mode for tests
npm run lintCheck code style
npm run lint:fixAuto-fix linting issues
npm startProduction start
node test-db.jsTest database connection

Resources


Last Updated: April 2026

FOV Backend - Deployment Guide

Overview

This document covers deploying FOV Backend to production environments using Docker, Docker Compose, and cloud platforms.


Table of Contents

  1. Docker Deployment
  2. Docker Compose
  3. Environment Configuration
  4. Database Setup
  5. Monitoring & Logging
  6. Backup & Recovery
  7. Troubleshooting

Docker Deployment

Building the Docker Image

Build command:

docker build -t fov-backend:1.0.0 .

With platform specification (M1/M2 Mac compatibility):

docker buildx build --platform linux/amd64 -t fov-backend:1.0.0 .

Running as Docker Container

Basic launch:

docker run -d \
  -p 4000:4000 \
  -e DB_HOST=mysql-server \
  -e DB_USER=admin \
  -e DB_PASSWORD=secure_password \
  -e DB_NAME=fovwebdb \
  -e NODE_ENV=production \
  --name fov-backend \
  fov-backend:1.0.0

With volume mounts (for media storage and SRT ingest):

docker run -d \
  -p 4000:4000 \
  -p 9999:9999/udp \
  -v /var/fov/media:/app/media \
  -e DB_HOST=mysql-server \
  -e DB_USER=admin \
  -e DB_PASSWORD=secure_password \
  -e DB_NAME=fovwebdb \
  -e NODE_ENV=production \
  --name fov-backend \
  fov-backend:1.0.0

View logs:

docker logs -f fov-backend

Stop container:

docker stop fov-backend
docker rm fov-backend

Docker Compose

Single Server Setup

File: docker-compose.yml (already exists)

docker-compose up --build

Check services:

docker-compose ps

View logs:

docker-compose logs -f backend

Stop services:

docker-compose down

Production Setup with External Database

File: docker-compose.prod.yml (already exists)

For deployments where MySQL runs separately (e.g., AWS RDS):

Edit environment in compose:

services:
  backend:
    build: ./backend
    ports:
      - "4000:4000"
    environment:
      DB_HOST: your-rds.amazonaws.com
      DB_USER: produser
      DB_PASSWORD: ${DB_PASSWORD}  # From .env file
      DB_NAME: fovwebdb
      NODE_ENV: production

Start with .env file:

docker-compose -f docker-compose.prod.yml up -d

Environment Configuration

Production .env Setup

Create .env.production:

cp .env.production.example .env.production

Critical variables:

NODE_ENV=production
PORT=4000

# Database (use managed service)
DB_HOST=prod-db.example.com
DB_USER=produser
DB_PASSWORD=<use_secrets_manager>
DB_NAME=fovwebdb

# Media Storage
MEDIA_ROOT=/mnt/media-volume

# Security
CORS_ORIGIN=https://yourdomain.com

Secrets Management

Using Docker Secrets (Swarm):

echo "secure_password" | docker secret create db_password -

Using AWS Secrets Manager:

aws secretsmanager create-secret \
  --name fov/db/password \
  --secret-string "secure_password"

Using environment variables in Docker Compose:

environment:
  DB_PASSWORD: ${DB_PASSWORD}

Then run:

export DB_PASSWORD=secure_password
docker-compose up

Database Setup

Initial Database Creation

On production MySQL server:

CREATE DATABASE IF NOT EXISTS fovwebdb;
CREATE USER 'produser'@'%' IDENTIFIED BY 'secure_password';
GRANT ALL PRIVILEGES ON fovwebdb.* TO 'produser'@'%';
FLUSH PRIVILEGES;

Schema Migration

If using migration files (future enhancement):

npm run migrate

Manual setup (current):

mysql -h your-db.com -u produser -p fovwebdb < schema.sql

Backup Strategy

Daily backup (cron job):

# AWS RDS: Use automated backups (7+ days retention)
aws rds modify-db-instance \
  --db-instance-identifier fov-db \
  --backup-retention-period 30 \
  --apply-immediately

Manual backup to S3:

mysqldump -h your-db.com -u produser -p fovwebdb | \
  gzip | \
  aws s3 cp - s3://fov-backups/db-$(date +%Y%m%d).sql.gz

Restore from backup:

aws s3 cp s3://fov-backups/db-20240101.sql.gz - | gunzip | \
  mysql -h your-db.com -u produser -p fovwebdb

Monitoring & Logging

Container Health Checks

In Dockerfile:

HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
  CMD node -e "require('http').get('http://localhost:4000/', (r) => {if (r.statusCode !== 200) throw new Error(r.statusCode)})"

Check health:

docker inspect --format='{{.State.Health.Status}}' fov-backend

Application Logging

Current: Morgan request logging

Production improvement: Use structured logging (Winston)

Redirect logs to file:

docker run ... fov-backend:1.0.0 > /var/log/fov-backend.log 2>&1 &

With log rotation (logrotate):

/var/log/fov-backend.log {
    daily
    rotate 14
    compress
    delaycompress
    notifempty
    create 0640 nobody nobody
    sharedscripts
}

Performance Monitoring

Database query monitoring:

-- Enable slow query log
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 2;

Memory usage:

docker stats fov-backend

Port monitoring:

lsof -i :4000  # Check port 4000

Backup & Recovery

Media Files Backup

S3 sync (daily):

aws s3 sync /var/fov/media s3://fov-media-backup/daily/ --delete

With versioning enabled on S3 bucket:

aws s3api put-bucket-versioning \
  --bucket fov-media-backup \
  --versioning-configuration Status=Enabled

Complete System Backup

Weekly snapshot (AWS):

# Create EBS snapshot
aws ec2 create-snapshot \
  --volume-id vol-12345678 \
  --description "FOV Backend weekly backup"

Disaster Recovery Plan

ComponentRTORPOMethod
Database4 hours1 hourAWS RDS automated backups
Media Files2 hours30 minS3 versioning + daily sync
Application Code30 min0 minGit repository

Recovery Steps:

  1. Spin up new EC2 instance with latest AMI
  2. Restore RDS from snapshot
  3. Restore media from S3 backup
  4. Deploy latest backend container
  5. Run health checks

Scaling

Horizontal Scaling (Multiple Instances)

Using Docker Swarm:

# Initialize swarm
docker swarm init

# Deploy service with replicas
docker service create \
  --name fov-backend \
  --replicas 3 \
  -p 4000:4000 \
  -e DB_HOST=mysql-server \
  fov-backend:1.0.0

Using Kubernetes:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: fov-backend
spec:
  replicas: 3
  selector:
    matchLabels:
      app: fov-backend
  template:
    metadata:
      labels:
        app: fov-backend
    spec:
      containers:
      - name: backend
        image: fov-backend:1.0.0
        ports:
        - containerPort: 4000
        env:
        - name: DB_HOST
          value: mysql-service

Load Balancing

Nginx as reverse proxy:

upstream backend {
    server backend-1:4000;
    server backend-2:4000;
    server backend-3:4000;
}

server {
    listen 80;
    location / {
        proxy_pass http://backend;
        proxy_set_header Host $host;
    }
}

Troubleshooting

Container Won’t Start

# Check logs
docker logs fov-backend

# Common issues:
# - Port already in use: Change PORT or kill existing process
# - Database unreachable: Check DB_HOST, credentials, firewall
# - Out of memory: Increase container memory limit

High CPU Usage

# Identify bottleneck
docker stats fov-backend

# Check slow queries
mysql -h $DB_HOST -u $DB_USER -p $DB_NAME -e "SHOW PROCESSLIST;"

Media Files Not Accessible

# Verify volume mount
docker inspect fov-backend | grep -A 10 Mounts

# Check permissions
ls -la /var/fov/media/hls/

# Verify Docker user has access
docker exec fov-backend ls -la /app/media/

Last Updated: April 2026

FOV Backend - API Testing & Examples

Quick reference for testing the FOV Backend API endpoints.


API Base URL

Development:  http://localhost:4000
Production:   https://api.yourdomain.com

Health Check

Check Server Status

Request:

curl http://localhost:4000/

Response (200 OK):

{
  "ok": true,
  "message": "FOV backend running"
}

Users Endpoints

Get User by ID (GET)

Request:

curl http://localhost:4000/api/users/1

Response (200 OK):

{
  "data": {
    "id": 1,
    "username": "john_doe",
    "display_name": "John Doe",
    "created_at": "2026-01-15T10:30:00Z"
  }
}

Response (404 Not Found):

{
  "error": "User not found"
}

Create User (POST)

Request:

curl -X POST http://localhost:4000/api/users \
  -H "Content-Type: application/json" \
  -d '{
    "username": "jane_smith",
    "display_name": "Jane Smith"
  }'

Response (201 Created):

{
  "id": 42
}

Error (400 Bad Request):

# Missing username
curl -X POST http://localhost:4000/api/users \
  -H "Content-Type: application/json" \
  -d '{"display_name": "No Username"}'

Response:

{
  "error": "username required"
}

Categories Endpoints

List All Categories (GET)

Request:

curl http://localhost:4000/api/categories

Response (200 OK):

[
  {
    "id": 1,
    "name": "Gaming",
    "viewers": 1250,
    "image_url": "http://localhost:4000/images/gaming.jpg"
  },
  {
    "id": 2,
    "name": "Music",
    "viewers": 890,
    "image_url": "http://localhost:4000/images/music.jpg"
  }
]

Get Category by ID (GET)

Request:

curl http://localhost:4000/api/categories/1

Response (200 OK):

{
  "id": 1,
  "name": "Gaming",
  "viewers": 1250,
  "image_url": "http://localhost:4000/images/gaming.jpg"
}

Streams Endpoints

List All Streams (GET)

Request:

curl http://localhost:4000/api/streams

Response (200 OK):

{
  "data": [
[
  {
    "id": 1,
    "streamer": "john_doe",
    "title": "Epic Gaming Session",
    "category_id": 1,
    "viewers": 245,
    "thumbnail_url": "http://localhost:4000/images/stream1.jpg",
    "avatar_url": "http://localhost:4000/images/avatar1.jpg",
    "is_live": 1
  },
  {
    "id": 2,
    "streamer": "jane_smith",
    "title": "Music Production Stream",
    "category_id": 2,
    "viewers": 128,
    "thumbnail_url": "http://localhost:4000/images/stream2.jpg",
    "avatar_url": "http://localhost:4000/images/avatar2.jpg",
    "is_live": 1
  }
]

Get Stream by ID (GET)

Request:

curl http://localhost:4000/api/streams/1

Response (200 OK):

{
  "id": 1,
  "streamer": "john_doe",
  "title": "Epic Gaming Session",
  "category_id": 1,
  "viewers": 245,
  "thumbnail_url": "http://localhost:4000/images/stream1.jpg",
  "avatar_url": "http://localhost:4000/images/avatar1.jpg",
  "is_live": 1
}

Get Available Streams with HLS Tracks (GET)

Request:

curl http://localhost:4000/api/streams/available

Response (200 OK):

[
  {
    "streamId": "1",
    "trackCount": 2,
    "tracks": [
      {
        "trackId": "v:0",
        "videoUrl": "http://localhost:4000/hls/1/v:0/playlist.m3u8"
      },
      {
        "trackId": "v:1",
        "videoUrl": "http://localhost:4000/hls/1/v:1/playlist.m3u8"
      }
    ],
    "title": "Epic Gaming Session",
    "category": "Gaming",
    "viewers": 245,
    "avatarUrl": "http://localhost:4000/images/avatar1.jpg",
    "thumbnailUrl": "http://localhost:4000/images/stream1.jpg"
  }
]

Get Stream HLS URL (GET)

Request:

curl http://localhost:4000/api/streams/1/hls

Response (200 OK):

{
  "hls": "http://localhost:4000/hls/live/1/playlist.m3u8"
}

Get HLS Playlist

Request:

curl http://localhost:4000/hls/1/v:0/playlist.m3u8

Response (200 OK):

#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:2
#EXTINF:2.0,
seg00000.ts
#EXTINF:2.0,
seg

---

## Testing with cURL

### Using cURL Variables

**Save base URL:**
```bash
BASE_URL="http://localhost:4000"

# Create user
curl -X POST $BASE_URL/api/users \
  -H "Content-Type: application/json" \
  -d '{"username":"testuser"}'

# Get user
curl $BASE_URL/api/users/1

Debugging with Verbose Output

Show request and response headers:

curl -v http://localhost:4000/api/users/1

Show only headers:

curl -i http://localhost:4000/api/users/1

Testing with JavaScript/Node.js

Using Fetch API

// Create user
const response = await fetch('http://localhost:4000/api/users', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    username: 'testuser',
    display_name: 'Test User'
  })
});

const data = await response.json();
console.log('Created user ID:', data.id);

// Get user
const userResponse = await fetch('http://localhost:4000/api/users/1');
const user = await userResponse.json();
console.log('User:', user.data);

Using axios

const axios = require('axios');

const api = axios.create({
  baseURL: 'http://localhost:4000'
});

// Create user
const createResponse = await api.post('/api/users', {
  username: 'testuser',
  display_name: 'Test User'
});
console.log('User ID:', createResponse.data.id);

// Get user
const getUserResponse = await api.get('/api/users/1');
console.log('User:', getUserResponse.data);

Automated Testing

Run Test Suite

# Run all tests
npm test

# Run specific test file
npm test -- users.test.js

# Watch mode
npm run test:watch

# With coverage
npm test -- --coverage

Example Test Run

$ npm test

 PASS  src/__tests__/users.test.js
  Users Routes
    GET /:id
      ✓ should return user by id (45ms)
      ✓ should return 404 when user not found (12ms)
      ✓ should handle database errors (8ms)
    POST /
      ✓ should create a new user (38ms)
      ✓ should return 400 when username is missing (5ms)
      ✓ should handle database errors on create (6ms)

Test Suites: 1 passed, 1 total
Tests:       6 passed, 6 total
Time:        2.345s

Coverage summary:
  Statements   : 62% ( 123/198 )
  Branches     : 48% ( 72/150 )
  Functions    : 70% ( 35/50 )
  Lines        : 65% ( 128/197 )

Error Testing

Test Error Scenarios

# Missing required field
curl -X POST http://localhost:4000/api/users \
  -H "Content-Type: application/json" \
  -d '{}'

# Response (400)
# {"error":"username required"}

# Non-existent resource
curl http://localhost:4000/api/users/99999

# Response (404)
# {"error":"User not found"}

# Invalid method
curl -X DELETE http://localhost:4000/api/users/1

# Response (405)
# Method not allowed

# Server error simulation
# (Manually stop database)
curl http://localhost:4000/api/users/1

# Response (500)
# {"error":"Unable to connect to DB..."}

Performance Testing

Response Time Measurement

# Measure response time
time curl -s http://localhost:4000/api/users/1 > /dev/null

# Example output:
# real    0m0.125s
# user    0m0.032s
# sys     0m0.024s

# Should be < 500ms for good performance

Database Query Performance

Enable MySQL slow query log:

SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 0.5;  -- 500ms threshold

-- Tail the log
TAIL /var/log/mysql/slow.log

Debugging

Enable Verbose Logging

In development:

NODE_ENV=development npm run dev

In code:

console.log('Debug info:', variable);

View Server Logs

# Docker
docker logs -f fov-backend

# Local process
tail -f ~/.pm2/logs/backend-out.log

# Check specific endpoint
curl -v http://localhost:4000/api/users/1

Testing Checklist

Before considering API ready:

  • Health Check: GET / returns 200
  • Create User: POST /api/users returns 201 with ID
  • Get User: GET /api/users/:id returns 200
  • Not Found: GET /api/users/999 returns 404
  • Validation: Missing fields return 400
  • Errors: Database errors return 500 (not stacktrace)
  • Categories: CRUD operations work
  • Streams: All endpoints respond
  • Performance: Responses < 500ms
  • Tests Pass: npm test shows all green
  • Linting Passes: npm run lint shows no errors

Last Updated: April 2026

FOV Backend - Security Policy

Overview

This document outlines security practices, vulnerabilities, and the roadmap for securing the FOV Backend. Current Status: Beta - Security hardening in progress.


Current Status

✅ Implemented

  • CORS middleware (configurable origin)
  • HTTP request logging (Morgan)
  • Environment variable separation (no hardcoded secrets)
  • Connection pooling (prevents connection exhaustion)
  • Basic error handling (avoids stack trace leaks)

⚠️ Partially Implemented

  • Input validation (basic, not comprehensive)
  • Error messages (may expose internals in some cases)

❌ NOT Implemented (High Priority)

  • Authentication (JWT, OAuth2)
  • Input sanitization (SQL injection prevention)
  • Rate limiting
  • Password hashing
  • HTTPS enforcement
  • CORS proper configuration
  • Security headers (HSTS, CSP, etc.)

Vulnerability Assessment

High Risk 🔴

1. No Authentication

Impact: Anyone can access all endpoints Current Mitigation: VPC/firewall in production Fix: Implement JWT authentication

// Planned in v0.2.0
const verifyToken = (req, res, next) => {
    const token = req.headers.authorization;
    // Validate JWT
};

2. SQL Injection Risk (Low due to parameterized queries)

Current Status: Using parameterized queries ? Review Needed: Audit all queries for string concatenation

// ✅ Safe
db.query('SELECT * FROM users WHERE id = ?', [userId])

// ❌ Unsafe (don't do this)
db.query(`SELECT * FROM users WHERE id = ${userId}`)

3. No Rate Limiting

Impact: Vulnerable to brute force and DoS Fix: Implement rate limiting middleware

// Planned
import rateLimit from 'express-limit';
app.use(rateLimit({windowMs: 15*60*1000, max: 100}));

Medium Risk 🟡

1. Basic Input Validation

Current: Only checks for required fields Needed: Type validation, length limits, sanitization

// Planned
import joi from 'joi';
const userSchema = joi.object({
    username: joi.string().alphanum().min(3).max(30).required(),
    email: joi.string().email().required(),
    password: joi.string().min(8).required()
});

2. No Password Hashing

Current: Passwords stored in database (if implemented) Fix: Use bcrypt

import bcrypt from 'bcrypt';
const hashedPassword = await bcrypt.hash(password, 10);

3. Error Messages Expose System Details

Example: Current error responses may reveal database/table names Fix: Generic error messages in production

// Production error response
res.status(500).json({ error: 'Internal server error' });
// Log detailed error for admins only
logger.error('Detailed error:', err);

Low Risk 🟢

1. No HTTPS Enforcement

Note: Application code is HTTP, rely on reverse proxy for TLS Implementation: Use hsts middleware or Nginx config

app.use(helmet.hsts({ maxAge: 31536000, includeSubDomains: true }));

2. CORS Not Strictly Configured

Current: CORS_ORIGIN=* in development Fix: Whitelist specific origins in production

app.use(cors({
    origin: process.env.CORS_ORIGIN || ['https://yourdomain.com'],
    credentials: true
}));

Security Best Practices

1. Database Credentials

✅ DO:

  • Store in environment variables
  • Use different credentials for dev/prod
  • Rotate passwords quarterly
  • Use managed database (AWS RDS) when possible

❌ DON’T:

  • Hardcode credentials in code
  • Commit .env files
  • Use default MySQL root password
  • Share database password in Slack/email

2. API Authentication

Current: None (TO DO) Recommended: JWT + Refresh Tokens

// Planned implementation
const jwt = require('jsonwebtoken');

function createToken(userId) {
    return jwt.sign({ userId }, process.env.JWT_SECRET, { expiresIn: '1h' });
}

function verifyToken(token) {
    return jwt.verify(token, process.env.JWT_SECRET);
}

3. Input Validation

Example Attack:

POST /api/users
{"username": "'; DROP TABLE users; --"}

Prevention:

// Validate before use
if (!username || username.length > 30 || !/^[a-zA-Z0-9_]+$/.test(username)) {
    return res.status(400).json({ error: 'Invalid username' });
}

// And use parameterized queries
db.query('INSERT INTO users (username) VALUES (?)', [username])

4. Logging & Monitoring

DO log:

  • Authentication attempts (success/failure)
  • Failed validation attempts
  • Database errors (generic)
  • Security-sensitive operations

DON’T log:

  • Passwords
  • API keys
  • Personal data
  • Sensitive database content

5. Secrets Management

Development:

  • .env file locally (never commit)
  • .env.example as template

Production:

  • AWS Secrets Manager
  • Docker Secrets (if using Swarm)
  • Environment variables from CI/CD

Example:

# AWS Secrets Manager
aws secretsmanager create-secret \
  --name fov/db/password \
  --secret-string "securepassword123"

# Retrieve in app
const secret = aws.secretsmanager.getSecretValue({SecretId: 'fov/db/password'});

6. HTTPS/TLS

Ensure in production:

# Nginx reverse proxy
server {
    listen 443 ssl;
    ssl_certificate /path/to/cert.pem;
    ssl_certificate_key /path/to/key.pem;
    ssl_protocols TLSv1.2 TLSv1.3;
    
    location / {
        proxy_pass http://backend:4000;
    }
}

7. File Upload Security (Future)

If implementing file uploads (media):

// Validate file type
const ALLOWED_TYPES = ['video/mp4', 'video/x-msvideo'];
if (!ALLOWED_TYPES.includes(file.mimetype)) {
    return res.status(400).json({ error: 'Invalid file type' });
}

// Scan for malware (future)
// Use virus scanning service (ClamAV, etc.)

Incident Response

Data Breach Protocol

  1. Detect: Monitor logs, alerts for suspicious activity
  2. Contain: Disable affected accounts, revoke tokens
  3. Investigate: Review logs, identify scope
  4. Remediate: Patch vulnerability, force password resets
  5. Communicate: Notify users, regulatory bodies (if required)
  6. Document: Post-mortem, process improvements

Example Response:

Breach Detected: Unauthorized database access on 2024-04-15 10:30 UTC
├─ Impact: All user data exposed (names, usernames, created_at only - no passwords)
├─ Scope: ~500 user records
├─ Response:
│  ├─ Disabled public API endpoints (2 hours)
│  ├─ Rotated database credentials
│  ├─ Reviewed container logs for other access
│  ├─ Deployed security patch
│  └─ Notified users via email
└─ Root Cause: Unsecured admin debugging endpoint
   Future: Remove debug endpoints in production

Security Headers

Recommended headers to add (via middleware):

app.use((req, res, next) => {
    res.setHeader('X-Content-Type-Options', 'nosniff');
    res.setHeader('X-Frame-Options', 'DENY');
    res.setHeader('X-XSS-Protection', '1; mode=block');
    res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
    res.setHeader('Content-Security-Policy', "default-src 'self'");
    next();
});

Or use helmet middleware:

import helmet from 'helmet';
app.use(helmet());

Penetration Testing Checklist

To Do Before v1.0.0

  • OWASP Top 10 review
  • SQL injection testing
  • XSS testing (if web interface)
  • Authentication bypass attempts
  • Rate limiting testing
  • CORS misconfiguration review
  • API parameter fuzzing
  • Secrets exposure scan (git history)
  • Dependencies vulnerability scan
  • Load testing (DoS resistance)

Tools

# Dependency scanning
npm audit

# OWASP ZAP
docker run -t owasp/zap2docker-stable zap-baseline.py -t http://localhost:4000

# Burp Suite (manual testing)
# - Download: https://portswigger.net/burp

# git-secrets (prevent credentials in commits)
brew install git-secrets
git secrets --install

Compliance & Standards

GDPR (if European users)

  • ✅ Environment variable protection
  • ❌ Data export endpoint (TODO)
  • ❌ Data deletion / right to be forgotten (TODO)
  • ❌ Consent management (TODO)

PCI DSS (if handling payments - future)

  • Encryption at rest and in transit
  • Access controls with authentication
  • Regular security testing
  • Vulnerability management

SOC 2 Type II (if enterprise)

  • Audit logging
  • Access controls
  • Change management
  • Incident response

Roadmap


Reporting Security Issues

Please DO NOT file public GitHub issues for security vulnerabilities.

Instead, email security issues to: security@fov-project.io (placeholder)

Include:

  • Description of vulnerability
  • Steps to reproduce
  • Potential impact
  • Suggested fix (if any)

Response time: Within 48 hours


References


Document Version: 1.0
Last Updated: April 2026 Status: Active Review Required Before Production

FOV Backend - Architecture Diagrams & Flows

Visual representations of the FOV Backend system architecture and workflows.


System Architecture

┌─────────────────────────────────────────────────────────────────┐
│                     Client Applications                         │
│         (Angular Frontend, Mobile Apps, Web Clients)            │
└────┬────────────────────────────────────────────────────────────┘
     │ HTTPS/HTTP Requests
     ▼
┌─────────────────────────────────────────────────────────────────┐
│                  Reverse Proxy / Load Balancer                  │
│                      (Nginx in production)                      │
└────┬────────────────────────────────────────────────────────────┘
     │
┌────▼────────────────────────────────────────────────────────────┐
│             FOV Backend - Express.js (Port 4000)                │
├─────────────────────────────────────────────────────────────────┤
│  Middleware Layer:                                              │
│  ├─ CORS (Cross-Origin Resource Sharing)                       │
│  ├─ Morgan (HTTP Request Logging)                              │
│  ├─ Express.json (JSON Parser)                                 │
│  └─ Error Handler (Centralized)                                │
├─────────────────────────────────────────────────────────────────┤
│  Routes Layer:                                                  │
│  ├─ GET  /                    (Health check)                   │
│  ├─ GET  /api/                (API status)                     │
│  ├─ GET  /api/users/:id       (Fetch user)                    │
│  ├─ POST /api/users           (Create user)                    │
│  ├─ GET  /api/categories      (List categories)                │
│  ├─ POST /api/categories      (Create category)                │
│  ├─ GET  /api/streams         (List streams)                   │
│  ├─ GET  /hls/:id/playlist    (HLS streaming)                 │
│  └─ GET  /hls/:id/:segment    (Video segments)                │
├─────────────────────────────────────────────────────────────────┤
│  Business Logic Layer:                                          │
│  ├─ Route Handlers (Async/await)                               │
│  ├─ Input Validation                                           │
│  ├─ Error Handling                                             │
│  └─ Database Queries                                           │
└────┬────────────────────────────────────────────────────────────┘
     │
     ├──────────────────────────────┬───────────────────────────────┐
     ▼                              ▼                               ▼
┌─────────────────┐    ┌────────────────────────┐    ┌──────────────────┐
│  MySQL Database │    │  Media Server (SRT)    │    │  File System     │
│  (Port 3306)    │    │  (Port 9999)           │    │  (/media/hls)    │
├─────────────────┤    ├────────────────────────┤    ├──────────────────┤
│ Connection Pool │    │  ├─ SRT Ingest        │    │ ├─ Segments      │
│ ├─ users        │    │  ├─ FFmpeg Trans.    │    │ ├─ Playlists     │
│ ├─ streams      │    │  └─ HLS Output       │    │ └─ Metadata      │
│ └─ categories   │    │                       │    │                  │
└─────────────────┘    │ Encoder Input:        │    │ Client Playback: │
                       │ OBS Studio            │    │ HLS Players      │
                       │ FFmpeg                │    │ (Safari, iOS)    │
         │             │ StreamYard            │    │                  │
         │             │ etc.                  │    │                  │
         │             └────────────────────────┘    └──────────────────┘
         │
         └─────────────────────────────────────────────────────────────
              Persistent Data Storage & Recovery

Module Dependency Graph

app (index.js)
│
├─ Express.js
│  ├─ Router (routes/index.js)
│  │  ├─ Users Route (routes/users.js)
│  │  │  └─ Database (db.js)
│  │  │     └─ MySQL Pool (mysql2/promise)
│  │  ├─ Streams Route (routes/streams.js)
│  │  │  ├─ Database (db.js)
│  │  │  ├─ File System (fs)
│  │  │  └─ Path utilities
│  │  └─ Categories Route (routes/categories.js)
│  │     └─ Database (db.js)
│  ├─ CORS (cors)
│  ├─ Morgan (morgan - logging)
│  └─ JSON Parser (express.json)
│
├─ Media Server (mediaServer.mjs)
│  ├─ Node Media Server
│  ├─ FFmpeg
│  └─ File System
│
└─ Environment Config (dotenv)
   └─ .env file

Request Response Flow

┌─────────────────────────────────────────────────────────────────┐
│ CLIENT                                                          │
│ curl -X POST /api/users -d {username: 'john'}                 │
└────────────────┬────────────────────────────────────────────────┘
                 │
                 ▼
┌─────────────────────────────────────────────────────────────────┐
│ EXPRESS MIDDLEWARE STACK                                        │
│ ├─ CORS Middleware       → Allow origin                         │
│ ├─ Morgan Middleware     → Log request                          │
│ ├─ JSON Parser           → Parse body JSON                      │
│ └─ Route Matching        → Find handler                         │
└────────────────┬────────────────────────────────────────────────┘
                 │
                 ▼
┌─────────────────────────────────────────────────────────────────┐
│ ROUTE HANDLER (Async)                                           │
│ ├─ Extract params/body                                         │
│ ├─ Validate input                                              │
│ │  └─ If invalid → res.status(400).json({error: '...'})       │
│ └─ Proceed to try/catch                                        │
└────────────────┬────────────────────────────────────────────────┘
                 │
                 ▼
┌─────────────────────────────────────────────────────────────────┐
│ DATABASE OPERATION                                              │
│ ├─ Get connection from pool                                    │
│ ├─ Execute parameterized query                                │
│ │  └─ INSERT INTO users (username) VALUES (?)                 │
│ └─ Release connection back to pool                             │
└────────────────┬────────────────────────────────────────────────┘
                 │
        ┌────────┴────────┐
        │                 │
        ▼                 ▼
    SUCCESS           ERROR
    └─ Catch           └─ Catch Error
       ├─ Get insertId  ├─ Log error internally
       ├─ res.status    ├─ Generic message to client
       │  (201)         ├─ res.status(500)
       └─ Send JSON     └─ next(error)
          {id: 42}           │
                             ▼
                   ┌──────────────────────┐
                   │ Global Error Handler │
                   │ res.status(500)      │
                   │ .json({error: msg})  │
                   └──────────────────────┘

Testing Flow

┌──────────────────────────────────────────────────────────────┐
│ DEVELOPER WRITES TEST                                        │
│                                                              │
│ describe('Users', () => {                                   │
│   test('should create user', async () => {                 │
│     db.query.mockResolveValue({insertId: 42})             │
│     const res = await request(app).post('/api/users')     │
│     expect(res.status).toBe(201)                          │
│   })                                                        │
│ })                                                          │
└────────────────┬─────────────────────────────────────────────┘
                 │
                 ▼
┌──────────────────────────────────────────────────────────────┐
│ npm test (or: npm run test:watch)                            │
│                                                              │
│ Jest reads jest.config.js                                   │
│   ├─ testMatch: '**/__tests__/**/*.test.js'                │
│   ├─ collectCoverageFrom: src/**/*.js                       │
│   └─ coverageThreshold: 50%                                 │
└────────────────┬─────────────────────────────────────────────┘
                 │
                 ▼
┌──────────────────────────────────────────────────────────────┐
│ TEST EXECUTION                                               │
│                                                              │
│ For each test file:                                         │
│ ├─ Mock external dependencies (db.js)                       │
│ ├─ Run beforeEach hooks                                     │
│ ├─ Execute test code                                        │
│ │  ├─ Setup test data (Arrange)                            │
│ │  ├─ Call function under test (Act)                       │
│ │  └─ Verify results (Assert)                              │
│ ├─ Run afterEach hooks (cleanup)                            │
│ └─ Record pass/fail status                                  │
└────────────────┬─────────────────────────────────────────────┘
                 │
                 ▼
┌──────────────────────────────────────────────────────────────┐
│ COVERAGE ANALYSIS                                            │
│                                                              │
│ Instrument code and track execution:                        │
│ ├─ Statements: 62% (123/198 lines)                         │
│ ├─ Branches: 48% (72/150 decision paths)                   │
│ ├─ Functions: 70% (35/50 functions)                        │
│ └─ Lines: 65% (128/197 lines)                              │
│                                                              │
│ If coverage < 50%:                                          │
│ └─ ❌ Tests FAIL (threshold not met)                        │
└────────────────┬─────────────────────────────────────────────┘
                 │
        ┌────────┴────────┐
        │                 │
        ▼                 ▼
    ✅ PASS           ❌ FAIL
    │                 │
    ├─ All tests      ├─ Some tests failed
    ├─ Coverage OK    ├─ Or coverage too low
    └─ Coverage       └─ Show errors
       report         └─ Developer fixes
       displayed

Linting Flow

┌────────────────────────────────────────────────────────────┐
│ DEVELOPER SOURCE CODE                                      │
│                                                            │
│ var username = req.body.username;  // Bad style           │
│ var name='John'                    // Bad style           │
│ if(auth==true) {                   // Bad style           │
│   console.log('debug')             // Bad practice        │
│ }                                                          │
└────────────────┬───────────────────────────────────────────┘
                 │
                 ▼
┌────────────────────────────────────────────────────────────┐
│ npm run lint (or: npm run lint:fix)                        │
│                                                            │
│ Reads .eslintrc.json:                                     │
│ ├─ indent: ["error", 4]                                  │
│ ├─ quotes: ["error", "single"]                           │
│ ├─ semi: ["error", "always"]                             │
│ ├─ no-var: "error"                                       │
│ ├─ eqeqeq: ["error", "always"]                           │
│ ├─ no-console: ["warn"]                                  │
│ └─ prefer-const: "error"                                 │
└────────────────┬───────────────────────────────────────────┘
                 │
                 ▼
┌────────────────────────────────────────────────────────────┐
│ ESLINT ANALYSIS                                            │
│                                                            │
│ For each .js file in src/:                               │
│ ├─ Parse code into AST                                   │
│ ├─ Check against rules                                   │
│ │  ├─ "var" found → ERROR: no-var                       │
│ │  ├─ Double quotes → ERROR: quotes                     │
│ │  ├─ No semicolon → ERROR: semi                        │
│ │  ├─ console.log → WARNING: no-console                 │
│ │  └─ == operator → ERROR: eqeqeq                       │
│ └─ Collect violations                                    │
└────────────────┬───────────────────────────────────────────┘
                 │
        ┌────────┴────────────────────┐
        │                             │
        ▼                             ▼
   npm run lint              npm run lint:fix
   (Show violations)         (Auto-fix violations)
   │                         │
   ├─ /src/users.js          ├─ var → const
   │  5:4 error: no-var      ├─ '' → ""
   │  12:1 error: semi       ├─ == → ===
   │  ...                    └─ Add semicolons
   │
   └─ Developer fixes        Creates fixed code:
      manually

                             const username = req.body.username;
                             const name = 'John';
                             if (auth === true) {
                               // console removed or kept depending on rule
                             }

CI/CD Pipeline (GitHub Actions)

┌─────────────────────────────────────────────────────────────┐
│ DEVELOPER PUSH                                              │
│ git push origin feature/my-feature                          │
└────────────────┬────────────────────────────────────────────┘
                 │
                 ▼
┌─────────────────────────────────────────────────────────────┐
│ GITHUB WEBHOOK                                              │
│                                                             │
│ Detects push to "backend/**" directory                      │
│ Triggers: .github/workflows/backend-tests.yml              │
└────────────────┬────────────────────────────────────────────┘
                 │
                 ▼
┌─────────────────────────────────────────────────────────────┐
│ GitHub Actions Runner (Ubuntu Latest)                       │
│ ├─ Step 1: Checkout Code                                  │
│ ├─ Step 2: Setup Node.js v20                              │
│ ├─ Step 3: npm ci (clean install)                         │
│ ├─ Step 4: npm run lint (check code style)                │
│ ├─ Step 5: npm test (run tests with coverage)             │
│ └─ Step 6: Upload coverage report                         │
└────────────────┬────────────────────────────────────────────┘
                 │
        ┌────────┴────────┐
        │                 │
        ▼                 ▼
    ✅ ALL PASS       ❌ ANY FAIL
    │                 │
    ├─ Green check   ├─ Red X
    ├─ Can merge     ├─ Cannot merge
    │   (if branch   │   until fixed
    │   protection)  │
    └─ PR shows ✓    └─ PR shows ✗
       All checks       Check failed
       passed           Details link


Create Pull Request (Push triggers CI)
         │
         ▼
    Wait for Actions
         │
      ┌──┴──┐
      │     │
      ▼     ▼
    PASS  FAIL
    │     │
    ├─OK  ├─Fix code
    │     │  └─ npm run lint:fix
    │     │  └─ npm test
    │     │  └─ git add . && git push
    │     │  └─ CI runs again
    │     │  └─ (repeat until PASS)
    │     │
    ▼     └────┐
   Request      │
   Review       │
      │        │
   Approve     │
      │        │
    Merge ◄────┘
      │
      ▼
   ✅ MERGED

Deployment Flow

┌──────────────────────────────────────────────────┐
│ SOURCE CODE                                      │
│ (backend/ directory)                             │
└────────────┬─────────────────────────────────────┘
             │
             ▼
┌──────────────────────────────────────────────────┐
│ DOCKERFILE                                       │
│                                                  │
│ FROM node:20                                    │
│ WORKDIR /app                                    │
│ COPY package*.json .                            │
│ RUN npm ci --only=production                    │
│ COPY src/ src/                                  │
│ EXPOSE 4000                                     │
│ CMD ["npm", "start"]                            │
└────────────┬─────────────────────────────────────┘
             │
             ▼
┌──────────────────────────────────────────────────┐
│ BUILD IMAGE                                      │
│ docker build -t fov-backend:1.0.0 .              │
│                                                  │
│ Image created: fov-backend:1.0.0                │
└────────────┬─────────────────────────────────────┘
             │
             ▼
┌──────────────────────────────────────────────────┐
│ PUSH TO REGISTRY                                 │
│ docker push registry.io/fov-backend:1.0.0        │
└────────────┬─────────────────────────────────────┘
             │
        ┌────┴────┐
        │          │
        ▼          ▼
   DOCKER      DOCKER
   COMPOSE     SWARM / K8S
   │           │
   ├─docker-   ├─Pull image
   │  compose  ├─Create service
   │  up       ├─Deploy replicas
   │           └─Load balancing
   │
   └─Start    └─Health checks
             │
             ▼
        ┌──────────────────────────────────┐
        │ RUNNING CONTAINER(S)             │
        │ fov-backend:1.0.0                │
        │                                  │
        │ Port 4000 exposed                │
        │ Mounted volumes:                 │
        │ ├─ /media (persistent)          │
        │ ├─ .env (secrets)                │
        │ └─ Logs                          │
        │                                  │
        │ Health checks                    │
        │ ├─ GET / (every 30s)            │
        │ └─ Restart if fails              │
        └──────────────────────────────────┘

        Health Check Loop:
        ├─ Success → Running ✅
        ├─ Failure → Retry
        │ └─ Still fails → Restart container
        └─ Persistent failure → Alert on-call

Database Changes Flow

┌────────────────────────────────────────────┐
│ ADD NEW DATABASE FEATURE                   │
│ (e.g., new column in users table)         │
└────────────┬───────────────────────────────┘
             │
             ▼
┌────────────────────────────────────────────┐
│ UPDATE CODE                                │
│ ├─ Write SQL query with new column        │
│ ├─ Update route handler                   │
│ ├─ Add tests for new functionality        │
│ └─ Update documentation                   │
└────────────┬───────────────────────────────┘
             │
             ▼
┌────────────────────────────────────────────┐
│ LOCAL TESTING                              │
│ ├─ Update local .env if needed            │
│ ├─ Run migrations (manual SQL)             │
│ ├─ npm run lint:fix && npm test           │
│ └─ Manual endpoint testing                 │
└────────────┬───────────────────────────────┘
             │
             ▼
┌────────────────────────────────────────────┐
│ COMMIT & PUSH                              │
│ └─ Triggers GitHub Actions                 │
└────────────┬───────────────────────────────┘
             │
             ▼
┌────────────────────────────────────────────┐
│ PRODUCTION DEPLOYMENT                      │
│ (After PR merged)                          │
│                                            │
│ 1. Backup database                         │
│ 2. Build new Docker image                  │
│ 3. Run schema migrations                   │
│ 4. Deploy new container(s)                 │
│ 5. Health checks                           │
│ 6. Monitor for issues                      │
│ 7. Keep backup for rollback                │
└────────────────────────────────────────────┘

Error Handling Flow

Client Request
     │
     ▼
Express Route Handler
     │
  try/catch
     │
  ┌──┴──┐
  │     │
  ▼     ▼
SUCCESS ERROR
│       │
├─   ├─ Caught exception
│      │
│      ├─ Log error (internal)
│      │  └─ console.error or Winston
│      │
│      ├─ Generic response
│      │  └─ res.status(500)
│      │     .json({error: "Internal server error"})
│      │
│      └─ next(error)
│         │
│         ▼
│    Global Error Handler
│         │
│         ├─ Log error with context
│         ├─ Ensure no stacktrace exposed
│         └─ Send generic response
│
└─ res.status(200/201/etc)
   .json(data)

Database Connection:
  pool.getConnection()
    └─ try/finally
       └─ Always release connection
          (even if error)

Last Updated: April 2026

FOV Backend - Quality Assurance Checklist

This checklist ensures backend quality before commits and deployments.

Pre-Commit Checklist

Run these commands before committing code:

Code Quality

  • Linting: npm run lint passes with no errors
  • Auto-fix issues: npm run lint:fix applied
  • Code Review: Manual review of changes
  • Comments: Added for complex logic

Testing

  • Unit Tests: npm test passes with no failures
  • Coverage: Coverage report shows ≥ 50%
  • Edge Cases: Tests cover error scenarios
  • Mocking: External dependencies properly mocked

Database

  • SQL Queries: All use parameterized queries (no string interpolation)
  • Connection: Properly released/returned to pool
  • Null Handling: Edge cases handled
  • No N+1: Queries optimized

Security

  • No Secrets: No API keys, passwords in code
  • Input Validation: Basic validation present
  • Error Messages: Don’t expose system details
  • CORS: Properly configured for environment

Documentation

  • Function Comments: Public functions documented
  • Parameter Types: Function parameters described
  • Return Values: Return values documented
  • Error Handling: Exception behavior documented

Pre-Deployment Checklist

Before deploying to production:

Code Review

  • Peer Review: At least one approval
  • No TODOs: Remove debug comments
  • Console Logs: Only for important events
  • Dead Code: Remove unused functions

Testing

  • Full Test Suite: npm test passes
  • Coverage: Maintained or improved (≥50%)
  • Integration Tests: Tested with real database
  • Manual Testing: Features tested manually

Performance

  • No Memory Leaks: Check with --max-old-space-size=2048
  • Database Queries: Indexed and optimized
  • Response Times: API responses < 1 second
  • Load Testing: Tested with 100+ concurrent requests

Security

  • Secrets Scanning: No hardcoded credentials
  • Dependency Audit: npm audit passes
  • Input Validation: Comprehensive validation
  • Authentication: Verified if required
  • HTTPS: Configured in reverse proxy
  • Rate Limiting: Implemented if needed

Configuration

  • Environment Variables: All required vars defined
  • Database: Production database verified
  • Error Handling: No stack traces in responses
  • Logging: Structured and secure

Deployment

  • Backup: Database backup taken
  • Version Tag: Git tag created
  • CHANGELOG: Updated with version
  • Runbook: Rollback procedure documented
  • Health Check: Endpoint verified

Continuous Integration (CI)

The GitHub Actions workflow automatically runs on push and PR:

.github/workflows/backend-tests.yml triggers:

on:
  push:
    paths:
      - "backend/**"
  pull_request:
    branches: [main]

Steps:

  1. Checkout code
  2. Setup Node.js v20
  3. Install dependencies (npm ci)
  4. Lint code (npm run lint)
  5. Run tests with coverage (npm test)
  6. Upload coverage report as artifact

Checking CI Results

GitHub:

  1. Go to Pull Request
  2. Scroll to “Checks” section
  3. Review “Backend Tests & Lint” workflow
  4. Green checkmark = all tests passed

Failed CI?

# Reproduce locally
npm run lint:fix
npm test

# Commit and push again
git add .
git commit -m "fix: linting issues"
git push

Test Coverage Analysis

Current Coverage Targets

// jest.config.js
coverageThreshold: {
    global: {
        branches: 50,      // 50% of decision points
        functions: 50,     // 50% of functions
        lines: 50,        // 50% of code lines
        statements: 50    // 50% of statements
    }
}

View Coverage Report

npm test

# Open in browser
open coverage/lcov-report/index.html

Improving Coverage

Add missing tests:

# Identify uncovered lines
npm test -- --coverage --verbose

# Find files with lowest coverage
# Edit coverage/lcov-report/index.html

# Add tests for that module
touch src/__tests__/module.test.js

Example:

// If `getSegmentCount()` shows only 60% coverage
describe('getSegmentCount', () => {
    test('should handle file system errors', () => {
        // Test error case
    });
    test('should return 0 for invalid content', () => {
        // Test edge case
    });
});

Common QA Tasks

Test Everything Before Commit

#!/bin/bash
# save as scripts/pre-commit.sh

echo "🧹 Fixing lint issues..."
npm run lint:fix || exit 1

echo "🧪 Running tests..."
npm test || exit 1

echo "✅ All checks passed!"
echo "Ready to commit: git add . && git commit"

Make executable:

chmod +x scripts/pre-commit.sh
./scripts/pre-commit.sh

Generate Coverage Report

npm test

# Create summary
coverage_percentage=$(cat coverage/coverage-summary.json | grep -o '"lines":[^}]*' | grep -o '[0-9.]*')

echo "Coverage: ${coverage_percentage}%"
if (( $(echo "$coverage_percentage < 50" | bc -l) )); then
    echo "⚠️ Coverage below 50% threshold"
    exit 1
fi

Profile Performance

# With profiling
node --prof src/index.js

# Generate analysis
# Hit endpoints, then Ctrl+C
node --prof-process isolate-*.log > profiling-result.txt

# View results
cat profiling-result.txt

Automated Quality Checks

GitHub Branch Protection

Set up in GitHub repo settings:

Settings → Branches → Add rule for main:

  • ✅ Require status checks to pass
    • Backend Tests & Lint
  • ✅ Require branches to be up to date
  • ✅ Require code review approval (1 person)
  • ✅ Dismiss stale reviews
  • ✅ Delete head branches

This prevents merging without passing tests and review.

Pre-commit Hook (Local)

Install Husky:

npm install husky --save-dev
npx husky install

Add pre-commit hook:

npx husky add .husky/pre-commit "npm run lint && npm test"

Now tests run automatically before each commit.


Monitoring Post-Deployment

Health Checks

# Check API is responding
curl http://localhost:4000/
# Expected: {"ok":true,"message":"FOV backend running"}

# Check specific endpoints
curl http://localhost:4000/api/
# Expected: {"ok":true,"api":true}

Error Monitoring

Watch logs:

docker logs -f fov-backend

# Or if not containerized
tail -f ~/.pm2/logs/backend-out.log

Look for:

  • ❌ Errors or exceptions
  • ⚠️ Warnings about connection issues
  • 🔴 Database connection failures

Performance Monitoring

Response time:

# Simple request timer
time curl http://localhost:4000/api/

# Should complete in < 1 second

Memory leak detection:

# Monitor memory usage over time
watch -n 5 'ps aux | grep "node src/index"'

# Memory should remain stable, not growing infinitely

Incident Response

If Tests Are Failing

  1. Reproduce locally:

    npm test
    
  2. Check recent changes:

    git log -1 --stat
    git diff HEAD~1
    
  3. Run specific test for debugging:

    npm test -- users.test.js
    npm test -- --verbose
    
  4. Check dependencies:

    npm audit
    npm list --depth=0
    
  5. Revert if critical:

    git revert HEAD
    git push
    

If Linting Fails

npm run lint       # See issues
npm run lint:fix   # Auto-fix most
npm run lint       # Verify fixed

If Coverage Drops

  1. Run coverage report:

    npm test -- --coverage
    open coverage/lcov-report/index.html
    
  2. Identify new uncovered code

  3. Write tests for those lines

  4. Commit with message: test: improve coverage for module-name


Resources


Checklist Version: 1.0
Last Updated: April 2026

FOV Backend - Quality Assurance Setup Summary

Complete documentation of the backend testing, linting, and quality infrastructure created.


📋 Overview

The FOV Backend now includes a comprehensive quality assurance infrastructure with:

  • ✅ Automated Testing (Jest with 50%+ coverage requirements)
  • ✅ Code Quality Enforcement (ESLint with strict rules)
  • ✅ Continuous Integration (GitHub Actions on every push/PR)
  • ✅ Complete Documentation (Architecture, deployment, security, development guides)
  • ✅ API Testing Guides (cURL, JavaScript, Postman examples)

📁 Files Created

Configuration Files

FilePurpose
.eslintrc.jsonESLint code style rules (4-space indent, single quotes, strict equality, etc.)
jest.config.jsJest testing framework configuration with 50% coverage threshold
package.jsonUpdated with test/lint scripts and dev dependencies
.env.exampleEnvironment template for development
.env.production.exampleProduction environment template

Test Files

FileCoverageTests
src/__tests__/db.test.jsDatabase moduleConnection pool, query methods
src/__tests__/users.test.jsUser routesGET, POST, error handling
src/__tests__/categories.test.jsCategory routesCRUD operations, validation
src/__tests__/streams-utils.test.jsStream utilitiesPlaylist validation, segment counting

Total Tests: 18 test cases covering critical modules

Documentation Files

FileContent
readme.mdComplete project guide - Overview, installation, configuration, API endpoints, deployment
ARCHITECTURE.mdSystem design - Technology stack justification, module structure, data flows, scaling strategy
DEVELOPMENT.mdDeveloper guide - Setup, workflow, debugging, code standards, testing guidelines
DEPLOYMENT.mdProduction deployment - Docker, Docker Compose, cloud platforms, monitoring, backups
SECURITY.mdSecurity policies - Vulnerabilities, best practices, roadmap, compliance considerations
CHANGELOG.mdVersion history - Changes, roadmap, dependencies, breaking changes
QA-CHECKLIST.mdQuality assurance - Pre-commit, pre-deployment, CI/CD, coverage analysis
API-TESTING.mdAPI reference - Endpoints, examples, cURL commands, Postman collection

CI/CD Files

FilePurpose
.github/workflows/backend-tests.ymlGitHub Actions - Auto linting & testing on push/PR

🚀 Quick Start

Install Dependencies

cd backend
npm install

Run Tests

npm test                 # Run all tests with coverage
npm run test:watch      # Watch mode for development

Check Code Quality

npm run lint             # Check for style violations
npm run lint:fix         # Auto-fix most issues

Start Development Server

npm run dev              # With nodemon auto-restart
npm start                # Production mode

📊 Quality Metrics

Test Coverage

Current Thresholds (by module):

{
    branches: 50,      // Decision paths
    functions: 50,     // Function coverage
    lines: 50,         // Line coverage
    statements: 50     // Statement coverage
}

View Coverage Report:

npm test
open coverage/lcov-report/index.html

Code Quality

ESLint Rules Enforced:

  • ✅ 4-space indentation
  • ✅ Single quotes
  • ✅ Semicolons required
  • ✅ const preferred over var
  • ✅ Strict equality (===)
  • ✅ No unused variables
  • ✅ No console.log production code

🧪 Testing Overview

Test Files & Coverage

src/__tests__/
├── db.test.js              # Database module (pool, queries)
├── users.test.js           # User CRUD API routes
├── categories.test.js      # Category management
└── streams-utils.test.js   # HLS playlist utilities

Total: 18 test cases

Running Tests

# All tests with coverage
npm test

# Specific test file
npm test -- users.test.js

# Watch mode (auto-rerun on changes)
npm run test:watch

# Verbose output
npm test -- --verbose

# Update snapshots
npm test -- -u

Test Structure

describe('Feature Group', () => {
    beforeEach(() => {
        // Setup before each test
    });

    test('should do something', () => {
        // Arrange, Act, Assert
        expect(result).toBe(expected);
    });

    afterEach(() => {
        jest.clearAllMocks();
    });
});

🔍 Linting & Code Quality

ESLint Configuration

File: .eslintrc.json

Key Rules:

{
    indent: ["error", 4],                  // 4 spaces
    quotes: ["error", "single"],           // Single quotes
    semi: ["error", "always"],             // Always semicolons
    no-var: "error",                       // Use const/let
    eqeqeq: ["error", "always"],           // Strict equality
    prefer-const: "error",                 // Prefer const
    no-unused-vars: ["error"],             // No dead vars
    no-console: ["warn", {allow: [...]}]   // Limited console
}

Running Linter

npm run lint             # Show violations
npm run lint:fix         # Auto-fix issues

# Example violations:
# /backend/src/users.js
#  5:4  error  Unexpected var, use const or let  no-var
#  12:1 error  Missing semicolon                  semi

🔄 Continuous Integration

GitHub Actions Workflow

File: .github/workflows/backend-tests.yml

Triggers:

  • On push to backend/ directory
  • On pull request to main branch

Steps:

  1. Checkout code
  2. Setup Node.js v20
  3. Install dependencies (npm ci)
  4. Run linter (npm run lint)
  5. Run tests with coverage (npm test)
  6. Upload coverage report as artifact

Status Badge (add to README):

![Backend Tests](https://github.com/username/web-fov/actions/workflows/backend-tests.yml/badge.svg)

📖 Documentation Highlights

readme.md

  • Project overview and features
  • Installation & configuration steps
  • API endpoints reference
  • Deployment instructions
  • Troubleshooting guide

ARCHITECTURE.md

  • System diagram and design
  • Technology stack justification
  • Module descriptions
  • Data flow architecture
  • Scaling considerations

DEVELOPMENT.md

  • Development environment setup
  • Adding new routes (step-by-step)
  • Debugging techniques
  • Code standards & naming conventions
  • Testing guidelines

DEPLOYMENT.md

  • Docker image building
  • Docker Compose setup
  • Cloud platform deployments (AWS, DigitalOcean, Heroku)
  • Database configuration
  • Monitoring & logging
  • Backup & recovery strategies

SECURITY.md

  • Vulnerability assessment (High/Medium/Low risk)
  • Current security gaps (authentication, rate limiting, etc.)
  • Security best practices
  • Incident response protocol
  • Penetration testing checklist

QA-CHECKLIST.md

  • Pre-commit checklist
  • Pre-deployment checklist
  • CI/CD verification
  • Coverage analysis
  • Common QA tasks

API-TESTING.md

  • API endpoint examples
  • cURL command templates
  • JavaScript/Node.js examples
  • Postman collection
  • Stress testing tools

🛠️ Development Workflow

Before Committing

# 1. Auto-fix style issues
npm run lint:fix

# 2. Run all tests
npm test

# 3. Check coverage hasn't dropped
# (View coverage/lcov-report/index.html)

# 4. Commit with meaningful message
git add .
git commit -m "feat: add user authentication"

Pull Request

  1. Push to feature branch
  2. GitHub Actions automatically runs:
    • Linter ($npm run lint`)
    • Tests (npm test)
  3. Wait for ✅ all checks pass
  4. Request review from team
  5. Merge after approval

Deployment

# 1. Update version
# (Edit package.json: "version": "0.2.0")

# 2. Update CHANGELOG.md
# (Document changes)

# 3. Tag release
git tag -a v0.2.0 -m "Release v0.2.0"
git push origin v0.2.0

# 4. Deploy
docker build -t fov-backend:0.2.0 .
docker push registry/fov-backend:0.2.0

📋 Dependency List

Production Dependencies

PackageVersionPurpose
express^4.22.1REST API framework
cors^2.8.5Cross-origin resource sharing
morgan^1.10.0HTTP request logging
mysql2^3.6.5MySQL database driver
dotenv^16.0.0Environment variables
node-media-server2.2.0SRT ingest and streaming capability

Development Dependencies

PackageVersionPurpose
eslint^8.56.0Code linting
jest^29.7.0Testing framework
supertest^6.3.3HTTP testing
jest-mock-extended^3.0.5Advanced mocking
nodemon^3.1.11Auto-restart on changes

✅ Quality Standards

Code Quality Gate

Pass all of:

  • ✅ ESLint with no errors
  • ✅ Jest with 50%+ coverage
  • ✅ All tests passing
  • ✅ No hardcoded secrets
  • ✅ Parameterized SQL queries only
  • ✅ Proper error handling

Performance Standards

  • ✅ API response time < 500ms
  • ✅ No memory leaks
  • ✅ Database queries optimized (indexed)
  • ✅ Connection pooling enabled

Security Standards

  • ✅ Environment variables for secrets
  • ✅ Parameterized SQL queries
  • ✅ Input validation
  • ✅ Error messages don’t expose internals
  • ⚠️ Authentication (todo for v0.2.0)
  • ⚠️ Rate limiting (todo for v0.2.0)

🔐 Security Considerations

Current Implementation

  • ✅ CORS middleware
  • ✅ Connection pooling (prevents exhaustion)
  • ✅ Basic input validation
  • ✅ Error handling (no stacktraces)

Missing (Priority)

  1. JWT Authentication - All endpoints currently public
  2. Input Sanitization - Prevent SQL injection
  3. Rate Limiting - Prevent DDoS
  4. HTTPS - Use in production behind reverse proxy
  5. Password Hashing - If storing passwords

See SECURITY.md for detailed roadmap.


📞 Support & Resources

Documentation

Commands

npm run dev              # Start development server with hot-reload
npm start                # Production server
npm test                 # Run all tests
npm run test:watch      # Watch mode tests
npm run lint             # Check code style
npm run lint:fix         # Auto-fix style issues

Testing

npm test                                    # Full test suite
npm test -- users.test.js                  # Specific test
npm test -- --coverage --verbose           # Detailed coverage
npm run test:watch                         # Watch mode

🎯 Next Steps (Roadmap)

v0.2.0 (Next Release)

  • Implement JWT authentication
  • Add comprehensive input validation (joi/yup)
  • Implement rate limiting
  • Add Swagger/OpenAPI documentation
  • Increase coverage to 75%+
  • Security headers middleware

v0.3.0

  • Database migrations system
  • Structured logging (Winston)
  • Stream quality analytics
  • Real-time notifications (Socket.io)
  • User role-based access control

v1.0.0

  • Third-party security audit
  • Production-ready deployment
  • WebRTC streaming support
  • Advanced transcoding
  • Multi-region support

📝 Summary

The FOV Backend now has:

✅ Complete Testing Setup

  • 18 unit tests across 4 test files
  • 50%+ coverage requirement
  • Automated testing in CI/CD

✅ Code Quality Enforcement

  • ESLint with strict style rules
  • Auto-fix capability
  • Pre-commit hooks ready

✅ Comprehensive Documentation

  • 9 markdown documentation files
  • API examples and guides
  • Security guidelines
  • Deployment instructions

✅ Continuous Integration

  • GitHub Actions workflow
  • Automatic linting and testing
  • Coverage reporting

✅ Developer Experience

  • Clear setup instructions
  • Development workflow guide
  • Debugging techniques
  • Code standards reference

Setup Completed: April 28, 2026 Backend Version: 0.1.0
Node.js Version: v20 recommended
Total Documentation Files: 9
Total Test Files: 4
Configuration Files: 5

FOV Backend - Quick Reference Card

Getting Started

cd backend
npm install
npm run dev              # Start server with hot-reload

Visit: http://localhost:4000


Essential Commands

# Development
npm run dev              # Start with auto-restart (Ctrl+C to stop)

# Testing
npm test                 # Run all tests + coverage
npm run test:watch      # Auto-rerun on file changes

# Code Quality
npm run lint             # Check for violations
npm run lint:fix         # Auto-fix violations
npm start                # Production server

# Database
node test-db.js          # Test database connection

Testing a Feature Quickly

# 1. Make your code change
# 2. Check linting
npm run lint:fix

# 3. Run tests
npm test

# 4. If tests pass, commit!
git add .
git commit -m "feat: description"

Common Issues

IssueSolution
Port 4000 in usenpm run dev and change PORT in .env
DB connection failedCheck .env credentials with node test-db.js
Tests failingRun npm test -- --verbose to see details
Lint errorsRun npm run lint:fix then npm test

API Endpoints

# Health check
curl http://localhost:4000/

# Create user
curl -X POST http://localhost:4000/api/users \
  -H "Content-Type: application/json" \
  -d '{"username":"john","display_name":"John"}'

# Get user
curl http://localhost:4000/api/users/1

# List categories
curl http://localhost:4000/api/categories

Key Files

FilePurpose
src/index.jsApp startup
src/db.jsDatabase connection
src/routes/API endpoints
.envConfiguration (don’t commit!)
.eslintrc.jsonCode style rules
jest.config.jsTest configuration
package.jsonDependencies & scripts

File Structure

backend/
├── src/
│   ├── index.js              # Main app
│   ├── db.js                 # Database
│   ├── mediaServer.mjs       # Streaming
│   ├── routes/               # API routes
│   │   ├── users.js
│   │   ├── streams.js
│   │   ├── categories.js
│   │   └── index.js
│   └── __tests__/            # Tests
├── media/                    # HLS files
├── .env.example              # Env template
├── .eslintrc.json            # Lint rules
├── jest.config.js            # Test config
├── package.json              # Dependencies
├── readme.md                 # Full guide
└── [DOCS]                    # Other docs

Pre-Commit Checklist

# Before doing git commit:

[ ] npm run lint:fix
[ ] npm test
[ ] Check coverage didn't drop
[ ] Git commit with meaningful message

Environment Setup

Development Configuration (.env):

NODE_ENV=development
PORT=4000
DB_HOST=localhost
DB_USER=admin
DB_PASSWORD=your_password
DB_NAME=fovwebdb
MEDIA_ROOT=./media

Test Coverage

Run: npm test

View: open coverage/lcov-report/index.html

Current threshold: 50%

Database

# Test connection
node test-db.js

# Create database (first time)
mysql -u admin -p
> CREATE DATABASE fovwebdb;
> CREATE USER 'admin'@'localhost' IDENTIFIED BY 'password';
> GRANT ALL ON fovwebdb.* TO 'admin'@'localhost';

Documentation

DocContent
readme.mdOverview, setup, API docs
ARCHITECTURE.mdSystem design
DEVELOPMENT.mdDev guide
DEPLOYMENT.mdProduction setup
SECURITY.mdSecurity Guidelines
API-TESTING.mdAPI examples
QA-CHECKLIST.mdTesting process

GitHub Workflow

# 1. Feature branch
git checkout -b feature/my-feature

# 2. Make changes + test
npm run lint:fix
npm test

# 3. Commit & push
git add .
git commit -m "feat: add feature"
git push

# 4. Wait for GitHub Actions ✅
# 5. Create Pull Request
# 6. Get review approval
# 7. Merge

Deployment

# Docker
docker build -t fov-backend:1.0 .
docker run -p 4000:4000 -e DB_HOST=host fov-backend:1.0

# Docker Compose
docker-compose up -d

# See: DEPLOYMENT.md for full guide

Quick API Calls

Create User:

curl -X POST http://localhost:4000/api/users \
  -H "Content-Type: application/json" \
  -d '{"username":"testuser"}'

Get User:

curl http://localhost:4000/api/users/1

List Categories:

curl http://localhost:4000/api/categories

Help & Support

Can’t remember a command?

Test won’t run?

  • npm test -- --verbose
  • Check .env database connection
  • Run npm audit for dependency issues

Deployment help?


Performance Tips

# Find slow queries
npm test -- --logHeapUsage

# Monitor memory
docker stats fov-backend

# Check database performance
# Enable slow query log in MySQL
SET GLOBAL slow_query_log='ON';
SET GLOBAL long_query_time=1;

Bookmark this page!
Last updated: April 2026

FOV Backend - Changelog

All notable changes to the FOV Backend project will be documented in this file.

The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.


[Unreleased]

Planned Features

  • JWT authentication for protected endpoints
  • Input validation & sanitization middleware
  • Rate limiting per IP
  • Comprehensive API documentation (Swagger/OpenAPI)
  • Database migration system
  • Structured logging (Winston)
  • WebSocket support for real-time notifications
  • Stream quality analytics
  • User subscription tiers

Security Improvements Needed

  • Implement password hashing (bcrypt)
  • Add HTTPS enforcement
  • SQL injection prevention review
  • CORS configuration hardening
  • API key authentication

[0.1.0] - 2026-04-14

Added

Core Features

  • Express.js REST API with modular route structure
    • User CRUD endpoints: GET /api/users/:id, POST /api/users
    • Stream management endpoints: GET /api/streams, POST /api/streams
    • Category endpoints: GET /api/categories, POST /api/categories
    • Health check: GET /

Database

  • MySQL 8.0+ connection pooling via mysql2/promise
  • Connection pool with configurable limits (default: 5 connections)
  • Auto-reconnection on failure
  • Database module with singleton pattern

Media Streaming

  • SRT server for secure, low-latency encoder ingest (port 9999)
  • FFmpeg-based transcoding for HLS segmentation and delivery
  • Stream playlist validation (isPlaylistReady, getSegmentCount)
  • Media server integration with process lifecycle management

Development Tools

  • ESLint configuration for code quality

    • 4-space indentation enforcement
    • Single quote requirement
    • Strict equality enforcement
    • No var declarations
    • Unused variable detection
  • Jest testing framework

    • Unit tests for database module
    • User route API tests with mocking
    • Coverage reporting (50% threshold)
    • Watch mode for development
  • Nodemon for automatic restart on file changes

Documentation

  • Comprehensive readme.md with installation, configuration, and API documentation
  • ARCHITECTURE.md explaining system design and technology justification
  • DEVELOPMENT.md with setup, workflow, and contribution guidelines
  • DEPLOYMENT.md covering Docker, Docker Compose, and cloud platforms
  • SECURITY.md with security policies and roadmap
  • CHANGELOG.md (this file)

Configuration Files

  • .eslintrc.json - Code style rules
  • jest.config.js - Test configuration with coverage thresholds
  • .env.example - Environment template
  • .env.production.example - Production environment template
  • Dockerfile - Container image definition

Package Management

  • Updated package.json with:
    • Core dependencies: express, cors, morgan, mysql2, dotenv
    • Dev dependencies: eslint, jest, supertest, nodemon
    • Scripts: dev, start, test, test:watch, lint, lint:fix

CI/CD

  • GitHub Actions workflow (.github/workflows/backend-tests.yml)
    • Automated linting on push/PR
    • Automated testing on push/PR
    • Coverage reporting

Changed

  • Enhanced error handling with centralized error handler middleware
  • Improved environment variable structure and documentation

Fixed

  • (N/A - Initial release)

Security

  • ⚠️ CRITICAL - Not Yet Implemented:
    • JWT authentication
    • Input validation & sanitization
    • Rate limiting
    • Password hashing
    • HTTPS enforcement

Roadmap


Know Issues

Current Limitations

  1. No Authentication: All endpoints are public (security risk for production)
  2. Basic Input Validation: No comprehensive validation/sanitization
  3. Local Media Storage: No CDN or object storage integration
  4. Single Database Connection Pool: Limited across instances
  5. No Rate Limiting: Vulnerable to DDoS/abuse
  6. No Error Tracking: Errors logged to console only

Workarounds

  • Implement VPC/firewall rules for production
  • Use reverse proxy (Nginx) for rate limiting until implemented
  • Deploy behind API Gateway with authentication
  • Use S3 + CloudFront for media delivery

Dependencies

Production

PackageVersionPurpose
express^4.22.1Web framework
cors^2.8.5CORS middleware
morgan^1.10.0HTTP request logging
mysql2^3.6.5Database driver
dotenv^16.0.0Environment variables
node-media-server2.2.0Media server foundation (SRT ingest capability)
srt^0.0.3SRT protocol support

Development

PackageVersionPurpose
eslint^8.56.0Code linting
jest^29.7.0Testing framework
supertest^6.3.3HTTP testing
nodemon^3.1.11Auto-restart on changes

Breaking Changes

None in current version (0.1.0).


Migration Guide

From 0.0.0 to 0.1.0

This is the initial release. No migration needed.

For existing deployments:

  1. Back up database and media files
  2. Update .env with new variables if needed
  3. Run npm install to get new dependencies
  4. Redeploy or restart container

Contributors

  • FOV Development Team

License

See LICENSE file.


Format Version: 1.0
Last Updated: April 28, 2026
Document Version: 1.0

Continuous Integration

To ensure the code quality of the FOV web stack, we use several GitHub Actions workflows and code analysis tools.

This document presents the Continuous Integration (CI) setup used by our repository.

Commit Lint

Verifies that every commit message adheres to our formatting guidelines.

  • Triggers: Runs on every push across all branches.
  • Runner: GitHub-hosted runners.
  • Target: Checks compliance with our commit standard.
  • Results:
    • Success: Commit messages follow the required format.
    • Failure: Commit messages violate the standard and the job fails.

Backend Lint

Performs a backend analysis using ESLint.

  • Triggers: Pull requests targeting the main branch.
  • Runner: GitHub-hosted runners.
  • Results: Inline warnings and error notices are added directly to the affected files in the GitHub pull request view.

Backend Tests

Runs all backend tests with coverage.

  • Triggers: Pull requests targeting the main branch.
  • Runner: GitHub-hosted runners.
  • Results: Coverage report uploaded as a GitHub artifact.

Angular Tests

Runs all frontend tests with coverage.

  • Triggers: Pull requests targeting the main branch.
  • Runner: GitHub-hosted runners.
  • Results: Coverage report uploaded as a GitHub artifact.

Development Deployment

Deploys the web application to our internal self-hosted test server.

  • Triggers: Runs on every push to dev.
  • Runner: Self-hosted VM.
  • Results: Reports the success state of the deployment.

Production Deployment

Deploys the web application to the public website https://fovapp.live.

  • Triggers: Runs on every push to main.
  • Runner: Public-hosted VM.
  • Results: Reports the success state of the deployment.

Deploy the Web Stack Using Docker

Date: September 23rd, 2026

This guide outlines how to deploy the web application stack locally for development or in a production environment using Docker and Docker Compose.

Prerequisites

1. Docker Installed

Ensure you have Docker and Docker Compose installed on your system.

2. Configure the Environment

At the root of the repository, you will find a .env.example file. This file contains the default environment variables required by the application.

VariableDescriptionDefault Value
DB_PASSWORDSecure root password for the PostgreSQL database container.your_secure_db_password_here
SRT_URLHostname or IP address for SRT video streaming endpoints.localhost
API_URLFull publicly accessible URL pointing to the backend API.http://localhost:4000/api
JWT_SECRETYour JWT secret.your_jwt_secret_here
JWT_EXPIRES_INThe JWT expiration configuration7d

To set up your environment:

  1. Copy the template file: cp .env.example .env
  2. Open the newly created .env file and customize the values according to your environment.

3. Internal Architecture (Docker Compose)

In addition to environment variables and build arguments, the docker-compose.yml file configures internal variables, persistent storage volumes, and network isolation to wire the architecture together safely:

Backend Container Variables

VariableDescriptionValue / Default
PORTThe internal port that the Node.js backend listens on.4000
DB_HOSTThe internal Docker network hostname used by the backend to reach the database container.fov-db
DB_PORTThe port number used for database communication.5432
DB_USERThe database user profile name.root
SRT_PORTThe primary base port allocated for incoming SRT stream ingestion.9999
DB_NAMEThe exact target database name.fovwebdb
FFMPEG_PATHThe absolute path inside the container where FFmpeg is installed, essential for processing video feeds./usr/local/bin/ffmpeg
MEDIA_ROOTThe container directory mapped to persistent storage where media assets and HLS segments are saved./var/media

Database Container (fov-db) Variables

VariableDescriptionValue / Default
POSTGRES_DBInstructs the official PostgreSQL container to automatically create a database upon initial startup.fovwebdb
POSTGRES_USERDefines the administrative user account created for the database instance.root

Frontend Build Arguments

ArgumentDescriptionSource / Context
API_URLThe full URL pointing to the backend API, injected into the Angular frontend container during build time.Passed from ${API_URL} in .env

Volumes & Storage

Volume / MountTypeTarget ContainerPurpose
./backend/media:/var/mediaBind MountBackendStores persistent media assets and HLS segments generated by the backend.
fov-db:/var/lib/postgresqlNamed VolumeDatabase (fov-db)Ensures PostgreSQL database records persist safely across container restarts.
./fovwebdb.sql:/docker-entrypoint-initdb.d/init.sqlBind MountDatabase (fov-db)Automatically seeds the database schema and initial data on first-time container initialization.

Networking

Network NameDriver / TypePurpose
frontendBridgeConnects the frontend container to the backend API service to allow user web traffic communication.
backendBridge (internal: true)An isolated internal network ensuring the PostgreSQL database is completely hidden from external access and reachable exclusively by the backend service.
nginx-proxy-networkExternal Bridge(Production only) Used to route traffic securely through an external reverse proxy (like Nginx Proxy Manager) when using the production override.

4. Personalizing Docker Compose (Optional)

While optional and generally not recommended for standard setups, you can modify the default ports or volumes directly within the docker-compose.yml file to fit custom infrastructure requirements.

scaling ingest streams

You can adjust the port range for stream ingestion (default: 9999-10010) in your Docker Compose file. Changing this range directly dictates the maximum number of simultaneous, distinct streams your server can handle at the same time.


Local Development Deployment

To quickly spin up the application for development, you can use the provided automation script or run the manual commands below.

Option A: Using the Script

Run the local deployment script from your terminal:

./deploy-local.sh

Option B: Manual Deployment

Execute the following commands to create necessary media directories, assign proper permissions, and build the stack:

mkdir -p ./backend/media/hls
chmod -R 777 ./backend/media
docker compose up --build

Local Endpoints Overview

With the default configuration, the containers will expose the following services locally:

warning

This configuration binds services directly to your host machine and is not recommended for a secure production environment.


Production Deployment Example With Nginx Proxy Manager

For production environments, we recommend running the application stack behind Nginx Proxy Manager (NPM). This ensures traffic is handled securely over standard HTTP/HTTPS ports (80/443) while isolating internal database and container ports.

hardware and bandwidth considerations

Because this project functions as a video streaming platform, ensure your production server is properly provisioned.

High Network Bandwidth: Ingesting live video feeds and serving HLS chunks to multiple concurrent viewers consumes significant inbound and outbound network traffic.

Capable Machine: Each active live stream spins up a dedicated FFmpeg process on the backend to process and package the video feed. Plan your maximum concurrent stream limits around your server specifications.

Step 1: Create the Shared External Network

Before launching your services, create the external Docker network that NPM and your application stack will share:

docker network create nginx-proxy-network

Step 2: Deploy Nginx Proxy Manager

Deploy your Nginx Proxy Manager stack, making sure it is connected to the nginx-proxy-network:

services:
  app:
    image: 'jc21/nginx-proxy-manager:2.15.1'
    restart: unless-stopped
    ports:
      - '80:80'   # Public HTTP Port
      - '443:443' # Public HTTPS Port
      - '81:81'   # Admin Web Port
    environment:
      TZ: "Europe/Paris"
    volumes:
      - ./data:/data
      - ./letsencrypt:/etc/letsencrypt
    networks:
      - nginx-proxy-network

networks:
  nginx-proxy-network:
    external: true

Step 3: Deploy the Application Stack with Production Overrides

Update your .env file with your production domain names and valid URL endpoints, then deploy using the production compose override configuration:

docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d --build

The docker-compose.prod.yml file restricts public ports to loopback/SRT streams and joins the nginx-proxy-network. You can customize it to your needs:

# docker-compose.prod.yml
services:
  backend:
    ports: !override
      - "127.0.0.1:4001:4000"
      - "0.0.0.0:9999-10010:9999-10010"
      - "0.0.0.0:9999-10010:9999-10010/udp"
    networks:
      - nginx-proxy-network

  frontend:
    ports: !override
      - "127.0.0.1:4201:80"
    networks:
      - nginx-proxy-network

  fov-db:
    ports: !override
      - "127.0.0.1:5432:5432"

networks:
  nginx-proxy-network:
    external: true

Step 4: Configure Proxy Hosts in Nginx Proxy Manager

Log into your Nginx Proxy Manager admin panel (at port 81) and map your domains using the internal container names:

  • Frontend Proxy Host:

    • Source Domain: Your public frontend URL (e.g., app.yourdomain.com)
    • Forward Hostname / IP: frontend
    • Forward Port: 80
    • SSL: Request a Let’s Encrypt SSL Certificate and enable Force SSL.
  • Backend API Proxy Host:

    • Source Domain: Your public API URL (e.g., api.yourdomain.com)
    • Forward Hostname / IP: backend
    • Forward Port: 4000
    • SSL: Request a Let’s Encrypt SSL Certificate and enable Force SSL.

production behavior summary

  • Public web traffic enters exclusively through Nginx Proxy Manager on ports 80 and 443.
  • NPM routes requests internally to containers via the shared nginx-proxy-network.
  • The database (fov-db) and local management ports (4001, 4201, 5432) are safely bound exclusively to 127.0.0.1 and are completely shielded from public exposure.
  • Stream ingestion ports (9999-10010) remain publicly accessible for your SRT streaming sources.

FOV Software Documentation

Welcome to the documentation for FOV Software—a specialized fork of OBS Studio created by the Flexible Output View team to introduce robust multi-track streaming functionality.


Table of Contents

  • Building FOV Software Step-by-step instructions for compiling and building the custom OBS Studio fork from source.

  • Architecture Detailed breakdown of the core architectural patterns, module design, and integration layers powering multi-track source isolation.

  • Continuous Integration Overview of the continuous integration workflows, code analysis tools, and automated test suites used for the FOV repository.

  • Doxygen Documentation Comprehensive API reference and low-level source code documentation generated automatically from the codebase.


note

Looking for backend or deployment docs? If you are working on the server-side architecture rather than the OBS fork, please return to the main Developer Documentation.

Building FOV

Prerequisites

Install git on your system.

Then, clone the repository with all submodules:

git clone --recurse-submodules git@github.com:Flexible-Output-View/obs-studio-fov.git

Or if the above does not work, you can clone with HTTPS:

git clone --recurse-submodules https://github.com/Flexible-Output-View/obs-studio-fov.git

Then, navigate to the directory obs-studio-fov.

Linux

Install Dependencies

Run ./requirements.sh.

note

Running requirements.sh will install all dependencies for Debian-based distributions.

If you are building on another distribution, please consult the official OBS documentation for required packages here.

Building

You can run one of the following build scripts depending on your needs:

  • ./build_portable_linux.sh
    • Produces a debug build of FOV.
    • The executable will be located in build/rundir/Debug/bin.
  • ./build_portable_linux_release.sh
    • Produces a release build of FOV.
    • The executable will be located in build/rundir/Release/bin.

macOS

Requires macOS 13.5 or later.

Install Dependencies

Please install the following:

  • CMake 3.30 (minimum: CMake 3.28)
  • Xcode 15.4 or later

Building

You can run one of the following build scripts depending on your needs:

  • ./build_portable_macOS.sh
    • Produces a debug build of FOV.
    • The executable will be located in build_macos/frontend/Debug.
  • ./build_portable_macOS_release.sh
    • Produces a release build of FOV.
    • The executable will be located in build_macos/frontend/Release.

Windows

Requires Windows 10 1909+ (or Windows 11). Windows on ARM is not supported.

Install Dependencies

The automated way

You can run the script .\requirements.bat in PowerShell as an administrator to install the required dependencies. This script uses winget to automatically install everything required.

The manual way

If you don’t want to use the automated script, you need:

Building

You may need to reboot Windows if you just installed all dependencies.

You can run one of the following build scripts depending on your needs:

  • .\build_windows.bat
    • Produces a debug build of FOV.
    • The executable will be located in build_x64\rundir\Debug\bin\64bit.
  • .\build_windows_release.bat
    • Produces a release build of FOV.
    • The executable will be located in build_x64\rundir\Release\bin\64bit.

Build Options

FOV offers the following build options:

OptionLocationDescription
ENABLE_FOV_DEBUG_INGEST./CMakeLists.txt:25Enable a debug route for FOVService. This allows the developer to redirect the output stream to a local URL that can be analyzed locally.
FOV_INTEGRATION_TEST./CMakeLists.txt:26Build the fov-integration-test plugin. This is used to run the automated end-to-end integration test. The plugin will set up a scene with two video sources and start a stream to a locally deployed fov-web instance.

FOV Software Architecture

High-Level Architecture Overview

Traditional OBS Studio collapses all active layers, captures, and audio inputs down into a single flattened canvas and one composite audio master track. The FOV variant alters this behavior by maintaining complete structural isolation of visual and auditory inputs from capture all the way to presentation in the Web GUI.

  • Video Capture Track Isolation: Every distinct active video source inside the current OBS scene is dynamically allocated its own private frame view context and an isolated hardware encoder instance.
  • Audio Source Tracking (1:1 Isolation): Unlike standard OBS which mixes multiple inputs together onto shared master tracks, FOVSystem intercepts audio at the source layer. Each active audio source is automatically hijacked and assigned its own exclusive mixer_id track mask. This prevents audio bleed and locks out human error by disabling custom user mixer modifications in the OBS UI.
  • Unified Container Transport: All parallel encoded video and audio streams are packaged sequentially into a singular MPEG-TS (MPEG Transport Stream) before being pushed over the network.

Technical Pipeline Breakdown

Video Architecture

When a video source transitions to an active state, FOVSystem provisions a dedicated VideoTrack node:

  1. View Context Allocation: An internal obs_view_t is mapped to the source, ensuring raw frame allocation occurs independently of the primary OBS program canvas.
  2. Resolution Alignment: Frame dimensions are processed through an aspect alignment macro: OUT_ALIGN(dimension, 16). This guarantees compatibility with rigorous hardware macroblock requirements.
  3. Muxer Binding: Encoders are appended to an obs_encoder_group_t tracking node and indexed continuously using obs_output_set_video_encoder2.

Audio Architecture

To ensure zero configuration effort and bulletproof stream isolation, audio tracks bypass global mixer matrices entirely:

  1. Automated Mixer Hijacking: When an audio source is registered, FOVSystem overrides its bitmask using obs_source_set_audio_mixers(source, 1 << mixer_id). This forces the source onto a single exclusive pipeline and clears it from all other tracks.
  2. Hardware UI Enforcement: By programmatically assigning and managing these bits at runtime, the platform ignores any changes made in the OBS “Advanced Audio Properties” layout, preserving track isolation.
  3. Index Sequencing: Within the MPEG-TS multiplexer loop, audio track indexing begins exactly where video track array iteration terminates, ensuring flawless track allocation inside the transport stream:

$$\text{Transport Stream Track ID} = \text{Video Track Count} + \text{Source Track Index}$$

Core Class Reference: FOVSystem

The engine relies on FOVSystem to arbitrate scene modifications, monitor input states, and reconstruct the active encoder layout on the fly.

  • void initSystem(obs_output_t *muxerOutput, obs_data_t *vSettings, obs_data_t *aSettings) Configures and initializes the system state. Binds the structural output pipeline to the ffmpeg-mpegts muxer reference and locks in current initialization profiles.

  • void syncSources() The runtime management loop. Executed automatically to track context mutations:

  • Enumerates all global sources utilizing obs_enum_sources.
  • Checks filtering flags (OBS_SOURCE_VIDEO and OBS_SOURCE_AUDIO).
  • Automatically registers active audio sources, binds them programmatically to a unique available mixer_id, and provisions an encoder for that isolated slot.
  • Discards elements that have transitioned to inactive statuses, releases stale encoders, and appends new tracks to the multiplex group.
  • void updateEncoderGroup() Performs critical cleanup of the output container mappings. It detaches all existing audio and video paths from the active muxer reference, registers the modified tracks under a clean unified obs_encoder_group_t, and remaps container tracks dynamically to clear stream PIDs.

Continuous Integration

To ensure the code quality of the FOV software, we use several GitHub Actions workflows and code analysis tools.

This document presents the Continuous Integration (CI) setup used by our repository fork.

Commit Lint

Verifies that every commit message adheres to our formatting guidelines.

  • Triggers: Runs on every push across all branches.
  • Runner: GitHub-hosted runners.
  • Target: Checks compliance with our commit standard.
  • Results:
    • Success: Commit messages follow the required format.
    • Failure: Commit messages violate the standard and the job fails.

CodeChecker (clang-tidy)

Performs a comprehensive project analysis using CodeChecker combined with clang-tidy.

  • Triggers: Pull requests targeting the dev or master branches.
  • Runner: Self-hosted VM.
  • Results: Uploads security and code quality findings directly to the Security tab of the GitHub repository.

Cppcheck

Analyzes C/C++ source code using cppcheck to detect potential bugs, memory issues, and code smells.

  • Triggers:
    • On Push: Analyzes only the files modified in the git diff.
    • On Pull Request: Analyzes all files modified within the pull request.
  • Runner: Self-hosted VM.
  • Results: Inline warnings and error notices are added directly to the affected files in the GitHub pull request view. Full execution logs are uploaded as GitHub artifacts.

End-to-End Integration Test

Executes a full end-to-end integration test with the FOV web stack.

  • Triggers: Runs on pushes to dev and master.
  • Runner: GitHub-hosted runners.
  • Steps:
    • Pulls web-fov and obs-studio-fov.
    • Deploys web-fov locally using Docker.
    • Installs OBS dependencies from requirements.sh.
    • Builds obs-studio-fov in Release mode with the special fov-integration-test plugin.
    • Runs the software using xvfb.
    • Configures a test scene with two video media sources and starts a stream to the locally deployed web-fov instance.
    • Verifies via the web-fov API that the stream is available and contains 2 video and 2 audio tracks.
  • Results: Uploads the generated logs produced by obs-studio-fov and web-fov as GitHub artifacts.

Cross-Platform Builds (Windows, macOS, Linux)

Compiles the project across Linux, Windows, and macOS to verify multi-platform support.

  • Triggers:
    • dev or master branches: Built using the Release configuration.
    • Other branches: Built using the Debug configuration.
  • Runner: Self-hosted VM.
  • Results: Upon successful compilation, build binaries are uploaded as GitHub artifacts.

Flatpak Packaging

Creates a Linux Flatpak package for deployment and application testing.

  • Triggers: Runs on pushes to dev and master.
  • Runner: Self-hosted VM (Windows/Linux) and GitHub-hosted runners (macOS).
  • Results: Uploads the generated Flatpak file as a GitHub artifact.

Research and Development

Welcome to the Research and Development (R&D) section. This section documents our technical investigations, benchmarks, architecture evaluations, and custom implementations carried out throughout the project.

Below is an index of the research topics, benchmarks, and technical reports covered in this section:

Benchmark: Encoding, Transcoding, and Cost Analysis

Date: June 23rd, 2025

Introduction

Transcoding is a video processing method aimed at reducing the bandwidth required by a stream, thereby enabling support for slower network connections or generating adaptive bitrates. The principle consists of reducing the quality (resolution, bitrate) of the source video stream by re-encoding it into a lower quality or with a different codec. However, this process is resource-heavy (computation and memory) and requires high-performance hardware.

As part of this benchmark, we seek to evaluate whether transcoding can be a viable solution to optimize and reduce the bandwidth cost potentially consumed by the project.

Here is the potential architecture of a solution using FFmpeg to transcode the video streams received by the platform before distributing them to users:

Infrastructure Cost Evaluation

The operational costs of the solution will be directly correlated to the number of users and streams managed.

Estimation of minimal costs at OVH:

  • 1 Database: approximately €50/month.
  • 1 Web Hosting Server (with 2 Gbps bandwidth): approximately €60/month.
  • Minimum monthly cost (excluding transcoding): approximately €110/month.

Evaluation of Maximum Bandwidth Capacity

Based on an average video bitrate of 6 Mbps for a 1080p60 stream, the total bandwidth required is given by the relation:

Average bandwidth = Number of streams × ((6 Mbps × average number of streams in a stream) × number of viewers)

With a server offering 2 Gbps, the maximum theoretical number of simultaneous streams is: Maximum number of streams = 2000 Mbps / (6 Mbps/stream) ≈ 333 simultaneous streams

Thus, for a very minimalist deployment of the solution, the project would require approximately €110/month to manage a theoretical maximum of 333 streams.

note

This relationship is not equivalent to 333 different streamers, as our project allows managing multiple streams per streamer.

Transcoding Benchmark

Test Environment

The test consists of evaluating the transcoding of a 1080p60 source stream at 6 Mbps (H.264 Codec) to a 720p25 version.

  • Hardware Used: MSI GF63 Thin 11SC Laptop (CPU: Intel Core i5-11400H - 6 cores/12 threads; GPU: Nvidia GTX 1650 Max-Q).
  • Software: HandBrake (based on FFmpeg) on Windows.

Test Results:

ResourceEncoderMaximum Simultaneous StreamsObservations
GPUNVENC H.2645 streamsThe framerate is maintained above the required minimum.
CPUx2643 streamsThe framerate drops below the required minimum beyond 3 streams.

note

The bottleneck appears to come from the CPU’s computing power.

Although the GPU (with NVENC encoder) allows more streams (limited to 8 on consumer cards), the increase in simultaneous transcoding tasks ends up overloading the CPU, causing the framerate to drop below the minimum required for live playback.

Extrapolation to Project Requirements

To estimate costs at the project scale, we extrapolate the results to high-capacity servers:

  • Server Example: OVH a10-180 Node (120 vCPU, 4 × Nvidia A10).
    • Estimated Capacity: Based on the benchmark, it can be estimated that such a node could handle around 150 simultaneous streams (using the 4 A10 GPUs).

Despite the relatively low capacity (150 streams for 4 GPUs), the envisioned solution would be extremely expensive. A single OVH a10-180 node costs approximately €2,200/month.

Conclusion

At our scale, it does not appear relevant to set up a transcoding infrastructure. The high hardware cost and additional technical complexity do not justify the potential bandwidth gain.

Research: Support and Particularities of Multi-Track Video

Date: October 29th, 2025

Introduction

As part of our project, we want to send all video streams from the sources shared by the streamer to our platform.

We will detail our approach in this document to choose how the streams will be sent from the streaming software to our platform.

Initial Experiments

Sending Separate Video Streams

At the beginning of our project, we built a prototype allowing us to display multiple sources coming from OBS (the streaming software) to our platform.

We implemented this feature using a Node.js server and an Nginx server to receive multiple HLS streams from OBS.

These streams received by the web backend were temporarily stored by Nginx and retransmitted to connected clients.

Experiment on a Local Multi-Track File with FFmpeg and VLC

FFmpeg allows creating (muxing) video files in the MKV format that can contain multiple video tracks (much like having multiple audio tracks), using the command:

ffmpeg -i flux1.mp4 -i flux2.mp4 -map 0:v -map 1:v -c copy output.mkv

It is then possible to play the video and select which video track you want to display.

For example with VLC:

It is also possible to display both tracks simultaneously and synchronously with VLC using the command:

vlc --sout-all --sout '#display' output.mkv

VLC automatically opens a second rendering window when playing the video, the play/pause and scrubbing controls work perfectly and in a synchronized manner:

This last feature is interesting because in our case it will be necessary to render multiple video tracks simultaneously and synchronously. VLC being open-source software, some design elements of the player will likely be useful for our project.

Experimentation and Implementation in OBS

In 2024, the OBS project implemented an “Hybrid MP4” feature to leverage Twitch’s enhanced broadcasting, which aims to let the streamer produce multiple streams of different qualities so that viewers can benefit from multiple stream qualities.

The implementation made by OBS consists of sending an RTMP stream with an MP4 container containing multiple video tracks.

This feature closely aligns with our goal, allowing us to understand how multi-track videos can be created using the internal OBS API.

An experiment allowed us to understand the necessary operations in more detail, consisting of 10 main steps:

  1. Create an output obs_output_t
  2. Create an encoder group obs_encoder_group_t to manage multiple encoders synchronously
  3. Create a view obs_view_t for each video track
  4. Configure the view with options such as framerate, dimensions, and color space
  5. Assign a video source (or the default global video output in the case of enhanced broadcasting) to each view.
  6. Get a video object and add the view to the rendering pipeline with obs_view_add2
  7. For each view, create a dedicated encoder, configure it, and assign the view’s video object as input, resolving potential source resizing issues.
  8. Add the encoder to the output, using the obs_output_set_video_encoder2 function and an index corresponding to the video track into which the data should be muxed.
  9. Add at least one audio encoder
  10. Initialize the encoders and start the output

Thanks to these steps, we can create a small program that will record each video source in a separate track. The results are conclusive, and the resulting video does indeed contain two video tracks, which can be played simultaneously with VLC: On the left, a source that records only the Task Manager application, and on the right, a source that records the entire screen.

A video player such as VLC is required to switch between tracks or launch them simultaneously.

However, this experiment raises potential issues: performance is reduced due to the simultaneous use of multiple encoders. This is not necessarily a major issue if the encoder used is hardware-accelerated (for example with Nvidia NVENC, Intel QuickSync, or AMD AMF) but it remains a problem in the case of CPU encoders.

Furthermore, the number of video players capable of switching or viewing multiple video tracks remains a minority.

Technical Choice and Comparison of Transport Technologies

The choice of the streaming protocol to use (RTMP, HLS, MPEG Dash, WebRTC, SRT, custom, etc.) is detailed in the document Video Transport Protocols

Playback in a Browser

Since browsers do not natively support playing a stream containing multiple tracks simultaneously, it is necessary to implement conversion at the backend level and implement synchronization logic in the web player: Multi track video player

Consulted Resources

Research and Experimentation: Implementing New Features in OBS

Date: November 30th, 2025

Introduction

In this document, we want to detail how we carried out an experiment to understand the internal workings of OBS, its public API, and the possibilities offered by the software.

Documentation

During the first part of this experiment, we attempted to understand the internal workings of OBS and define the main development steps we would need to perform to achieve our goals.

However, the documentation proved to be very brief and focuses mainly on certain technical aspects and specific functions, without really giving a global overview of how the project works and the interactions between the different components.

Based on this observation, we decided to prototype the implementation of the module using the public APIs provided by libOBS to better understand its operation and the relationships between the different components.

To complete our understanding, we also decided to study two third-party plugins for OBS:

  • Multiple RTMP
  • Source Record

These plugins, although not doing exactly what we are trying to achieve, allowed us to understand the functions used to create the objects necessary for video encoding, source enumeration, and streaming using the RTMP protocol.

Experimentation

To put into practice and understand the overall architecture of the project, we conducted an experiment involving the recording of a video file using the libOBS API within a module.

We therefore started by attempting to make a custom implementation of a video output object obs_output_t by creating our own version, as well as our own obs_service_t service.

In OBS, an output is an object that allows configuring the output type (to a file, stream, etc.) to which one or more encoders can be attached.

This output can work in tandem with an obs_service_t which implements the abstractions necessary for communication with third-party services, for example to retrieve stream keys, authenticate a user, and interact with a streaming platform.

Steps

We detail here the main steps for using the public API provided by libOBS. OBS uses a modular architecture that divides the majority of its features into modules (shared libraries) loaded at OBS startup, in order to create a module to record a simple video.

Steps to create an OBS module:

  • Structure
    • Create a folder and add a minimal CMakeLists.txt in the plugins folder; the CMakeLists must include the OBS API libraries: OBS::libobs, OBS::frontend-api, and $<$<PLATFORM_ID:Windows>:OBS::w32-pthreads> for Windows compatibility.
  • Mandatory methods:
    • Two methods must be implemented to register the module with OBS:
    • obs_module_load(void): This is the entry point of the module; the module must allocate its internal structures and data it will need during execution.
    • void obs_module_unload(void): Unloading the module
  • Frontend event callback
    • To continue the experiment, we add a callback when events are generated by the user interface using the function: obs_frontend_add_event_callback(obs_frontend_event_cb callback, void *private_data), with the callback and the pointer to the data allocated by the module as parameters.
  • Creating an output object
    • The output object represents the output to a file or stream. Here we simply record to a file:
    • obs_output_create("mp4_output", "FOV mp4 video", NULL, NULL);
    • Configure the output (using the obs_data_set_string function: path, directory, format, extension)
  • Creating an audio encoder
    • obs_audio_encoder_create("ffmpeg_aac", "FOVAudio", NULL, 0, NULL);
  • Creating a video encoder
    • obs_video_encoder_create("obs_x264", "FOVSource", NULL, NULL);
  • Update the parameters of the audio and video encoders (scaled_size, framerate divisor)
  • Pass the main video source to the video encoder obs_encoder_set_video(video_encoder, obs_get_video());
  • Pass the main audio source to the audio encoder obs_encoder_set_audio(audio_encoder, obs_get_audio());
  • Link the encoders to the output:
    • obs_output_set_audio_encoder(audio_encoder, audio_encoder, 0);
    • obs_output_set_video_encoder(video_encoder, video_encoder);
  • Initialize the encoders linked to the output
    • obs_output_initialize_encoders(fov_app.fov_out, 0);
  • Start the output
    • obs_output_start(fov_app.fov_out);

Consulted Resources

Research: Client-Side Synchronized Multi-Track Player

Date: December 20th, 2025

Introduction

As part of our project, we need to allow viewers to simultaneously view multiple video streams from the streamer (gameplay, webcam, etc.) while maintaining perfect synchronization between these streams.

This document details our research and implementation approach for a client-side multi-track player (web browser) capable of playing multiple HLS streams in a synchronized manner.

Objectives

  • Simultaneous playback of multiple video/audio streams
  • Synchronization between tracks with a maximum deviation of 200ms
  • Dynamic addition and removal of tracks during playback
  • Interface allowing the viewer to customize the layout (drag, resize, z-order)
  • Independent audio mixing per track

Technical Stack Used

ComponentTechnology
Frontend frameworkAngular
HLS Playerhls.js
LanguageTypeScript
StylingSCSS

Benchmark: Web Player Technology Choice

Evaluation CriterionNative HTML5 (<video>)dash.js (MPEG-DASH)hls.js (Apple HLS) 🏆
Protocol UsedHLS (if supported natively)DASHHLS
Buffer Control (MSE)❌ Very limited✅ Excellent✅ Excellent
Maintaining Manual Sync❌ Impossible with precision✅ Possible✅ Possible and documented
Cross-browser Compatibility🟠 Good (especially Safari/iOS)🟡 Depends on Media Source Extensions🟢 Excellent (native fallback on iOS)
Integration Complexity🟢 Very easy🟠 Complex🟡 Moderate
Consistency with FOV Backend❌ Lack of control❌ Requires changing FFmpeg output🟢 Perfect (Backend already in HLS)

Benchmark Conclusion: The Native HTML5 approach was quickly dismissed because it is very limited. Between dash.js and hls.js, our choice fell on hls.js. In addition to integrating perfectly with our FFmpeg pipeline (which generates HLS), its API offers total control over fragments (MSE), network error management, and latency, which is essential to keep our streams below the 200ms drift threshold.

Initial Experiments

Creating Multi-Track Test Files with FFmpeg

For our tests, we first created an MP4 file containing multiple video and audio tracks using FFmpeg:

ffmpeg -i video1.mp4 -i video2.mp4 \
  -map 0:v -map 0:a -map 1:v -map 1:a \
  -c copy multipiste.mp4

Verifying the Created File

ffprobe -hide_banner -i multipiste.mp4

Result

Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'multipiste.mp4':
  Duration: 00:10:11.20, bitrate: 908 kb/s
  Stream #0:0: Video: h264, 640x360, 25.74 fps
  Stream #0:1: Audio: aac, 44100 Hz, stereo
  Stream #0:2: Video: h264, 640x360, 23.98 fps
  Stream #0:3: Audio: aac, 44100 Hz, stereo

Conversion to HLS for Streaming

Converting Each Track into a Separate HLS Stream

#!/bin/bash

INPUT="$1"
OUTPUT_DIR="./hls_out"

# Extraction and conversion of each track
ffmpeg -y -i "$INPUT" -map 0:v:0 -map 0:a:0 -c:v copy -c:a aac \
  -hls_time 2 -hls_list_size 0 -f hls "$OUTPUT_DIR/first.m3u8"

ffmpeg -y -i "$INPUT" -map 0:v:1 -map 0:a:1 -c:v copy -c:a aac \
  -hls_time 2 -hls_list_size 0 -f hls "$OUTPUT_DIR/second.m3u8"

This method generates .m3u8 files and .ts segments for each track, allowing independent playback while preserving original timestamps.

Player Architecture

Data Structure

interface Track {
  index: number;
  name: string;
  videoUrl: string;
  hasAudio: boolean;
}

interface VideoWrapper {
  playerId: string;
  track: Track;
  x: number;
  y: number;
  width: number;
  height: number;
  hls: Hls | null;
  videoElement: HTMLVideoElement | null;
  visible: boolean;
  zIndex: number;
  volume: number;
}

Synchronization Mechanism

The player uses a master track system that serves as a time reference for all other tracks:

private syncAllToMaster() {
  const master = this.videoWrappers[0];
  if (!master?.videoElement) return;

  const masterTime = master.videoElement.currentTime;

  this.videoWrappers.forEach((w, i) => {
    if (i === 0 || !w.videoElement) return;

    const drift = w.videoElement.currentTime - masterTime;
    const absDrift = Math.abs(drift);

    // Record stats
    this.syncStats.set(w.track.name, drift * 1000);

    // Correction if drift > 150ms
    if (absDrift > 0.15) {
      w.videoElement.currentTime = masterTime;
    }
  });
}

Synchronization monitoring is performed every 500ms:

startSyncMonitoring() {
  this.syncInterval = setInterval(() => this.syncAllToMaster(), 500);
}

Performance Tests

Test Conditions

ParameterValue
Number of video streams8
Number of audio streams8
Total simultaneous streams16
Resolution per stream640x360
Test duration10+ minutes
Simulated network conditionsSlow 4G (Chrome DevTools)

Results

MetricResult
Maximum observed drift< 200ms ✅
Average drift~50-80ms
Sync corrections requiredExtremely rare
Lag or stutteringNone
Audio/video desynchronizationNone

Synchronization Monitoring Screenshot

┌─────────────────────────────────────┐
│ Sync Monitor                        │
├─────────────────────────────────────┤
│ first_1        +12ms                │
│ second_2       -45ms                │
│ gameplay_3     +23ms                │
│ webcam_4       -18ms                │
│ handcam_5      +67ms                │
│ screen_6       -34ms                │
│ overlay_7      +8ms                 │
│                                     │
│ Status : Synced (67ms max)          │
└─────────────────────────────────────┘

(Example data)

Testing Under Degraded Network Conditions

We used Chrome DevTools throttling tools to simulate different network conditions:

Network ConditionMax DriftBehavior
No limitation< 50msExcellent
Fast 4G< 100msVery Good
Slow 4G< 200msAcceptable ✅
Offline → Online~500ms then resyncRecovery OK

Implemented Features

Dynamic Track Management

Tracks can be added or removed during playback without interrupting other streams:

addTrack(templateName: string) {
  const track = this.availableTracks.find(t => t.name === templateName);
  if (!track) return;

  // Create wrapper with automatic positioning
  const newWrapper: VideoWrapper = {
    playerId: `player_${track.name}_${Date.now()}`,
    track,
    x: 20,
    y: 20 + (this.videoWrappers.length - 1) * 20,
    width: 300,
    height: 169,
    // ...
  };

  this.videoWrappers.push(newWrapper);
  this.initHlsForWrapper(newWrapper);
}

removeTrack(wrapper: VideoWrapper) {
  if (wrapper.hls) wrapper.hls.destroy();
  this.videoWrappers = this.videoWrappers.filter(w => w !== wrapper);
  
  // Resync if the master was deleted
  if (wasMaster) {
    this.syncStats.clear();
    this.syncAllToMaster();
  }
}

Customization Interface

The viewer can customize the layout in edit mode:

  • Drag & Drop: Move streams within the viewing area

  • Resize: Resize each stream (16:9 ratio preserved)

  • Z-Order: Modify the stacking order of streams

  • Visibility: Hide/show each stream

  • Reset: Return to default layout

    @HostListener(‘window:pointermove’, [‘$event’]) onPointerMove(event: PointerEvent) { if (this.activeDragWrapper) { // Movement constrained within the stage area let newX = Math.max(0, Math.min(newX, maxX)); let newY = Math.max(0, Math.min(newY, maxY)); this.activeDragWrapper.x = newX; this.activeDragWrapper.y = newY; } else if (this.activeResizeWrapper) { // Resizing with 16:9 ratio const newW = Math.max(150, this.initialW + dx); this.activeResizeWrapper.width = newW; this.activeResizeWrapper.height = newW / (16/9); } }

(Later, the 16/9 ratio will be replaced by the original stream ratio)

Audio Mixing

Each track has its own volume control:

setVolume(wrapper: VideoWrapper, event: Event) {
  const volume = parseFloat((event.target as HTMLInputElement).value);
  wrapper.volume = volume;
  if (wrapper.videoElement) {
    wrapper.videoElement.volume = volume;
    wrapper.videoElement.muted = (volume === 0);
  }
}

Encountered Issues and Solutions

Issue 1: Dynamic Creation of DOM Elements

Problem: Video elements created dynamically with document.createElement did not work correctly with the resizing system.

Solution: Using Angular bindings with *ngFor on a track array, and managing positions/dimensions via bound properties:

<div *ngFor="let wrapper of videoWrappers"
     [style.left.px]="wrapper.x"
     [style.top.px]="wrapper.y"
     [style.width.px]="wrapper.width"
     [style.height.px]="wrapper.height">
  <video [id]="'videoElement_' + wrapper.track.index"></video>
</div>

Issue 2: Initial Size of the Main Stream

Problem: Using width: 100% caused position calculation issues during dragging.

Solution: Calculating the size in pixels at creation time:

const stage = document.getElementById('stageArea');
const stageW = stage ? stage.offsetWidth : 800;
const stageH = stage ? stage.offsetHeight : 450;

wrapper.width = isFirst ? stageW : 300;
wrapper.height = isFirst ? stageH : 169;

Issue 3: Synchronization After Master Deletion

Problem: After deleting the master track, other tracks kept their relative drift to the old master.

Solution: Resetting stats and immediate resync when changing master:

if (wasMaster) {
  this.syncStats.clear();
  this.maxDrift = 0;
  this.syncAllToMaster();
}

Conclusion

Our implementation demonstrates that it is possible to simultaneously play 8 video streams + 8 audio streams (16 total streams) in a web browser with synchronization maintained below 200ms, even under degraded network conditions (slow 4G).

Key success factors:

  • hls.js: Robust library for HLS playback in the browser
  • Sync polling: Verification and correction every 500ms
  • Tolerance threshold: Correction only if drift > 150ms (avoids unnecessary corrections)
  • Single master: A single reference track for synchronization

Next Steps

  • Integration with the backend to receive HLS URLs dynamically
  • Saving custom layouts per user
  • Fullscreen mode
  • Reconnection management in case of network interruption

Resources

  • hls.js - JavaScript HLS client
  • Angular Documentation
  • FFmpeg HLS Muxer
  • MDN - HTMLMediaElement

Benchmark: Video Stream Transport Protocols

Date: December 27th, 2025

Introduction

While recording video files remains relatively simple, sending real-time video streams must adhere to much stricter bandwidth and latency constraints than writing to local storage, while supporting a wide range of devices.

Today, there are numerous protocols for sending video streams, each with advantages and disadvantages that we will explore here according to our use case.

Fragmentation vs. Streaming

To fully understand the comparison that follows, we must explain the difference between the concept of fragmentation and transport streaming.

Fragmentation (Segmented HTTP Streaming)

The concept of fragmentation consists of dividing a video file into several segments that are transported separately by the transport protocol. The receiving player must handle receiving individual fragments and recombining them or playing them sequentially.

The most common example is the HLS protocol, in which a video is initially fragmented, and the fragments are then indexed in a video playlist file (.m3u8) to let the client know the URLs of the fragments it needs to download.

This method is very widespread on the web, as it is generally well supported by all browsers.

This method has the advantage of being simple to implement, but it offers much higher latency than streaming transport methods.

Streaming (Packet-based Streaming)

Packet-based streaming methods rely on a simple principle: sending data in real-time to one or more targets.

This technique sends a continuous data stream without fragmentation, using a connection method and a network socket, while keeping an active session/connection. This greatly reduces latency.

Since this technique offers real-time latency, the quality of the stream heavily depends on the network quality between the sender and the receiver.

Sending real-time video streams generally requires the use of a video container suited for transport: for example, .ts (MPEG-TS Transport Stream) for TV and SRT, or sending raw packets through a socket…

This technique is widely used for video broadcasting (for example, live television or video conferencing).

This method is generally flexible regarding the desired network quality of service (reliable with TCP or unreliable with UDP) depending on the chosen protocol.

Application Use Cases

In our project, we need to set up two different types of transmission.

OBS to Backend Stream

This is the stream sent by OBS (the capture software) to our platform.

The stream is a 1-to-1 connection; the protocol must be reliable and capable of transporting multiple synchronized video tracks.

Selection Criteria (sorted by importance)

  1. Support for multiple synchronized streams/tracks
  2. Authentication and security
  3. Reliability / Quality of service
  4. Latency

Backend to Browsers Stream

The video stream received by our platform must be broadcast to all users wishing to watch this live stream.

The stream will be 1-to-many, adapted for broadcasting to web browsers.

Selection Criteria (sorted by importance)

  1. Supported by popular browsers
  2. Reliability
  3. Latency

Comparative Table

RTMPHLSSRTRISTDASHWebRTC
TypeStreamSegmentsStreamStreamSegmentsStream (Peer-To-Peer)
Low-level ProtocolTCPHTTP/TCPUDPUDPHTTP/TCPSRTP/UDP
LatencyLowHighReal-timeReal-timeMediumReal-time
ReliabilityMedium (TCP)High (HTTP, cache, buffering)High (ARQ)Very High (ARQ + Bonding)High (HTTP, cache, buffering)Variable
Browser Support❌✅❌❌✅✅
OBS Support✅✅✅✅❌✅
Multi-video Track Compatible❌Non-native simultaneous playback✅✅Non-native simultaneous playback✅
Multi-video Track Synchronization-WorkaroundNativeNativeWorkaroundNative
Codec RestrictionH.264/AACBrowser supportNoneNoneBrowser supportVP8, VP9, H.264, Opus
Transport Container FormatFLVMPEG-TS or FMP4MPEG-TSMPEG-TSM4S or WEBMRaw RTP packets
Open Protocol❌✅✅✅✅✅
AuthenticationURL / Stream KeyTokens / CookiesPassphrase / StreamIDDTLS / CertificatesTokens / CookiesCertificate exchange
SecurityRTMPSHTTPSAESAESHTTPSMandatory (DTLS/SRTP)

Conclusion

Given the project constraints and the established comparison table, we chose to use the SRT protocol to send a multi-track stream to our backend and then redistribute it to viewers using the HLS protocol.

The choice of HLS is due to browser limitations, as they support very few streaming protocols. WebRTC, although a strong contender, would likely present scalability issues, while DASH is much less widespread.

Consulted Resources

Comparison of SRT / FFmpeg Approaches for Supporting Multiple Video Tracks

Date: January 4th, 2026

We are considering several approaches to building a Node.js backend that supports multiple video tracks from live streams over SRT. Below is a comparison of the options and their capabilities.

Comparison Table

OptionWhat It IsSRT SupportMedia Capability (multiple video/audio tracks)Level of ControlEase of UseTypical Use Case
@eyevinn/srtNode.js bindings for the SRT protocol (transport only)Yes (transport layer only)No (does not demux or decode)Low–MediumMediumWe use this to receive raw SRT packets and pass them to a media processor
FFmpeg CLI (child_process.spawn)Running the FFmpeg binary as an external processYes if FFmpeg is compiled with SRT supportYes (full demux, decode, filters, remux, multiple tracks)MediumEasy–MediumWe use this to handle actual media processing including multiple video and audio tracks
Direct FFmpeg bindings (node-av)Native bindings to FFmpeg’s C API via N-APIYes (via FFmpeg libraries)Yes (full mux/demux, decode, filters)HighHardWe use this when we want in-process access to FFmpeg features
Other FFmpeg binding libs (avcpp / node-ffmpeg)Wrappers around FFmpeg C APIs with Node stream interfacesYes (via FFmpeg libraries)Yes (full mux/demux, decode, filters)HighHardWe use this if we want a stream-like API with direct access to video/audio data
Old libav / legacy bindingsOlder fork or outdated bindingsLimited / outdatedPartialHighHardGenerally not recommended due to lack of modern support

Detailed Explanation

@eyevinn/srt

This option provides Node.js bindings for the SRT transport protocol itself. It handles accepting SRT connections and emitting raw packet data to JavaScript. It does not include media parsing (no demux, decode, or handling of video/audio tracks). To handle multiple video tracks we must combine it with a media processing library such as FFmpeg CLI or a binding that understands codecs and containers.

FFmpeg CLI (child_process.spawn)

This approach runs the external FFmpeg binary as a subprocess from Node.js. When FFmpeg is compiled with SRT support it can listen on an SRT endpoint, demux the incoming container format, and handle all packed streams. FFmpeg can map all video and audio tracks from the input (for example using -map 0:v for all video tracks and -map 0:a for audio), then remux, transcode, or convert to other streaming formats as needed. FFmpeg’s CLI handles the full media stack including codecs, filters, and output formats, making it suitable for supporting multiple tracks out of the box.

Direct FFmpeg Bindings (node-av)

Direct FFmpeg bindings like node-av provide native access to FFmpeg’s C API inside the Node.js process via N-API. This allows in-process control of demuxing, decoding, encoding, and track handling without launching a separate process. It supports all of FFmpeg’s media capabilities (including handling multiple tracks) and offers both low-level and higher-level abstractions. Because it operates inside the same process, we can build custom pipelines and handle frames or packets directly rather than through command-line arguments.

Other FFmpeg Bindings (avcpp / node-ffmpeg)

Other bindings provide a C++ wrapper around FFmpeg’s native interfaces and expose them as Node.js stream APIs. These allow demuxing and processing of multiple tracks and can expose video and audio data as Node streams. They are similar in capability to node-av but differ in the API design (e.g., Readable/Writable interfaces).

Old libav / Legacy Bindings

Older libav projects and outdated bindings are generally not well maintained and lack modern codec/protocol support, including up-to-date SRT and stream handling. Because of this, they are not recommended when building a backend that must support modern streaming workflows.

Notes on Multiple Video Tracks

Supporting multiple video tracks is a function of the media processing layer rather than the transport layer. SRT itself only transports packets and does not interpret how many tracks are inside a container. Tools that demux and process the container format (FFmpeg CLI or direct bindings) are what enable handling multiple video tracks. Using FFmpeg’s stream mapping options you can include all video and audio streams present in the input.

Prototyping: Adding Multi-Track Support to the ffmpeg-ts Output of obs-ffmpeg

Date: January 24th, 2026

Introduction

To leverage the SRT protocol and support multi-track video, we need to use the MPEG-TS transport format.

OBS is natively capable of transmitting a stream containing a single video track over SRT using MPEG-TS. To do this, you can enter an SRT URL such as srt://127.0.0.1:9999?mode=listener in a custom service within the OBS settings.

The stream can then be played back on the URL srt://127.0.0.1:9999?mode=caller with VLC, for example.

Adding Multi-Track Support

For some time now, OBS has updated part of libOBS to support multiple simultaneous encoders and create synchronized groups. This feature was primarily dedicated to Twitch’s “Enhanced broadcasting”, allowing multiple resolutions to be sent to the service.

Several OBS “outputs” (code handling output to a protocol/service) were also updated on this occasion to add support for multiple video tracks (for example, the mp4-output output allowing a multi-track video to be recorded, although this is not exposed in the UI).

However, the output used for SRT (ffmpeg_mpegts_muxer) did not receive this treatment and only allows a single video stream.

We therefore duplicated this output to avoid breaking standard OBS functions, then modified it to add multi-track support.

The modification mainly consisted of converting configuration and context data structures to use arrays, modifying parameters passed to libAV, handling the reception of video packets from multiple encoders, and modifying certain auxiliary functions.

We did not need to modify the encoder objects themselves since this was already done by the OBS team a few years ago, massively simplifying the complexity.

Results

Now that the modifications have been implemented, we can test OBS with 3 inputs that will be passed as output into our SRT stream, resulting in 3 video tracks.

Here, OBS streams 3 “media sources” with different resolutions to 3 tracks of the SRT stream.

To view the properties of the stream produced with ffmpeg, we use the ffprobe command.

➜  ~ ffprobe "srt://127.0.0.1:9999?mode=caller"
Input #0, mpegts, from 'srt://127.0.0.1:9999?mode=caller':
  Duration: N/A, start: 0.045333, bitrate: N/A
  Program 1
    Metadata:
      service_name    : FOV Multi-Stream
      service_provider: FOV Team
  Stream #0:0[0x100]: Video: h264 (High) ([27][0][0][0] / 0x001B), yuv420p(tv, bt709, progressive), 2880x1552 [SAR 1:1 DAR 180:97], 30 fps, 30 tbr, 90k tbn
  Stream #0:1[0x101]: Video: h264 (High) ([27][0][0][0] / 0x001B), yuv420p(tv, bt709, progressive), 1920x1024 [SAR 1:1 DAR 15:8], 30 fps, 30 tbr, 90k tbn
  Stream #0:2[0x102]: Video: h264 (High) ([27][0][0][0] / 0x001B), yuv420p(tv, bt709, progressive), 1024x1072 [SAR 1:1 DAR 64:67], 30 fps, 30 tbr, 90k tbn
  Stream #0:3[0x103]: Audio: aac (LC) ([15][0][0][0] / 0x000F), 48000 Hz, stereo, fltp, 130 kb/s

Using the TSDuck utility under Linux, it is possible to analyze the produced stream in more detail:

➜  ~ tsp -I srt --caller 127.0.0.1:9999 -P analyze -O drop

^C* tsp: user interrupt, terminating...

===============================================================================
|  TRANSPORT STREAM ANALYSIS REPORT                                           |
|=============================================================================|
|  Transport Stream Id: .......... 0x0001 (1)  |  Services: .............. 1  |
|  Bytes: ....................... 127,538,448  |  PID's: Total: .......... 7  |
|  TS packets: ...................... 678,396  |         Clear: .......... 7  |
|     With invalid sync: .................. 0  |         Scrambled: ...... 0  |
|     With transport error: ............... 0  |         With PCR's: ..... 1  |
|     Suspect and ignored: ................ 0  |         Unreferenced: ... 0  |
|-----------------------------------------------------------------------------|
|  Transport stream bitrate, based on ....... 188 bytes/pkt    204 bytes/pkt  |
|  User-specified: ......................... 19,155,697 b/s   20,785,969 b/s  |
|  Estimated based on PCR's: ............... 19,142,257 b/s   20,771,385 b/s  |
|  Selected reference bitrate: ............. 19,142,257 b/s   20,771,385 b/s  |
|-----------------------------------------------------------------------------|
|  Broadcast time: ................................... 53 sec (0 min 53 sec)  |
|-----------------------------------------------------------------------------|
|  Srv Id  Service Name                              Access          Bitrate  |
|  0x0001  FOV Multi-Stream ............................. C   19,002,273 b/s  |
|                                                                             |
|  Note 1: C=Clear, S=Scrambled                                               |
|  Note 2: Unless specified otherwise, bitrates are based on 188 bytes/pkt    |
===============================================================================


===============================================================================
|  SERVICES ANALYSIS REPORT                                                   |
|=============================================================================|
|  Global PID's                                                               |
|  TS packets: 4,961, PID's: 2 (clear: 2, scrambled: 0)                       |
|-----------------------------------------------------------------------------|
|     PID  Usage                                     Access          Bitrate  |
|   Total  Global PID's ................................. C      139,984 b/s  |
|   Subt.  Global PSI/SI PID's (0x00-0x1F) .............. C      139,984 b/s  |
|  0x0000  PAT .......................................... C      136,937 b/s  |
|  0x0011  SDT/BAT ...................................... C        3,047 b/s  |
|=============================================================================|
|  Service: 0x0001 (1), TS: 0x0001 (1), Original Netw: 0xFF01 (65281)         |
|  Service name: FOV Multi-Stream, provider: FOV Team                         |
|  Service type: 0x01 (Digital television service)                            |
|  TS packets: 673,435, PID's: 5 (clear: 5, scrambled: 0)                     |
|  PMT PID: 0x1000 (4096), PCR PID: 0x0100 (256)                              |
|-----------------------------------------------------------------------------|
|     PID  Usage                                     Access          Bitrate  |
|   Total  Digital television service ................... C   19,002,273 b/s  |
|  0x0100  AVC video (2880x1552, high profile, level 5.0  C    6,229,340 b/s  |
|  0x0101  AVC video (1920x1024, high profile, level 4.0  C    6,232,077 b/s  |
|  0x0102  AVC video (1024x1072, high profile, level 3.2  C    6,222,625 b/s  |
|  0x0103  MPEG-2 AAC Audio ............................. C      181,294 b/s  |
|  0x1000  PMT .......................................... C      136,937 b/s  |
|          (C=Clear, S=Scrambled, +=Shared)                                   |
===============================================================================


===============================================================================
|  PIDS ANALYSIS REPORT                                                       |
|=============================================================================|
|  PID: 0x0000 (0)                                                       PAT  |
|-----------------------------------------------------------------------------|
|  Global PID                Transport:                Discontinuities:       |
|  Bitrate: ... 136,937 b/s  Packets: ......... 4,853  Expected: ......... 0  |
|  Access: .......... Clear  Adapt.F.: ............ 0  Unexpect: ......... 0  |
|                            Duplicated: .......... 0  Sections:              |
|                                                      Unit start: ... 4,853  |
|=============================================================================|
|  PID: 0x0011 (17)                                                  SDT/BAT  |
|-----------------------------------------------------------------------------|
|  Global PID                Transport:                Discontinuities:       |
|  Bitrate: ..... 3,047 b/s  Packets: ........... 108  Expected: ......... 0  |
|  Access: .......... Clear  Adapt.F.: ............ 0  Unexpect: ......... 0  |
|                            Duplicated: .......... 0  Sections:              |
|                                                      Unit start: ..... 108  |
|=============================================================================|
|  PID: 0x0100 (256)                                               AVC video  |
|  PES stream id: 0xE0 (Video 0)                                              |
|  2880x1552, high profile, level 5.0, 4:2:0                                  |
|  Service: 0x0001 (1) FOV Multi-Stream                                       |
|-----------------------------------------------------------------------------|
|  Single Service PID        Transport:                Discontinuities:       |
|  Bitrate: . 6,229,340 b/s  Packets: ....... 220,766  Expected: ......... 0  |
|  Access: .......... Clear  Adapt.F.: ........ 2,154  Unexpect: ......... 0  |
|                            Duplicated: .......... 0  PES:                   |
|                            TSrate: . 19,142,257 b/s  Packets: ...... 1,618  |
|                                                      Inv.Start: ........ 0  |
|  Clock values range:                                                        |
|  PCR: ............... 545  PTS: ............. 1,618  DTS: .......... 1,329  |
|  from ................. 0  from ............. 6,000  from .............. 0  |
|  to ....... 1,455,300,000  to ........... 4,857,000  to ........ 4,851,000  |
|  Leaps: ............... 0  Leaps: ............... 0  Leaps: ............ 0  |
|=============================================================================|
|  PID: 0x0101 (257)                                               AVC video  |
|  PES stream id: 0xE0 (Video 0)                                              |
|  1920x1024, high profile, level 4.0, 4:2:0                                  |
|  Service: 0x0001 (1) FOV Multi-Stream                                       |
|-----------------------------------------------------------------------------|
|  Single Service PID        Transport:                Discontinuities:       |
|  Bitrate: . 6,232,077 b/s  Packets: ....... 220,863  Expected: ......... 0  |
|  Access: .......... Clear  Adapt.F.: ........ 1,625  Unexpect: ......... 0  |
|                            Duplicated: .......... 0  PES:                   |
|                                                      Packets: ...... 1,618  |
|                                                      Inv.Start: ........ 0  |
|  Clock values range:                                                        |
|                            PTS: ............. 1,618  DTS: .......... 1,220  |
|                            from ............. 6,000  from .............. 0  |
|                            to ........... 4,854,000  to ........ 4,851,000  |
|                            Leaps: ............... 0  Leaps: ............ 0  |
|=============================================================================|
|  PID: 0x0102 (258)                                               AVC video  |
|  PES stream id: 0xE0 (Video 0)                                              |
|  1024x1072, high profile, level 3.2, 4:2:0                                  |
|  Service: 0x0001 (1) FOV Multi-Stream                                       |
|-----------------------------------------------------------------------------|
|  Single Service PID        Transport:                Discontinuities:       |
|  Bitrate: . 6,222,625 b/s  Packets: ....... 220,528  Expected: ......... 0  |
|  Access: .......... Clear  Adapt.F.: ........ 1,616  Unexpect: ......... 0  |
|                            Duplicated: .......... 0  PES:                   |
|                                                      Packets: ...... 1,617  |
|                                                      Inv.Start: ........ 0  |
|  Clock values range:                                                        |
|                            PTS: ............. 1,617  DTS: .......... 1,224  |
|                            from ............. 6,000  from .............. 0  |
|                            to ........... 4,854,000  to ........ 4,848,000  |
|                            Leaps: ............... 0  Leaps: ............ 0  |
|=============================================================================|
|  PID: 0x0103 (259)                                        MPEG-2 AAC Audio  |
|  PES stream id: 0xC0 (Audio 0)                                              |
|  Service: 0x0001 (1) FOV Multi-Stream                                       |
|-----------------------------------------------------------------------------|
|  Single Service PID        Transport:                Discontinuities:       |
|  Bitrate: ... 181,294 b/s  Packets: ......... 6,425  Expected: ......... 0  |
|  Access: .......... Clear  Adapt.F.: ........ 5,008  Unexpect: ......... 0  |
|                            Duplicated: .......... 0  PES:                   |
|                                                      Packets: ...... 2,524  |
|                                                      Inv.Start: ........ 0  |
|  Clock values range:                                                        |
|                            PTS: ............. 2,524                         |
|                            from ............. 4,080                         |
|                            to ........... 4,848,240                         |
|                            Leaps: ............... 0                         |
|=============================================================================|
|  PID: 0x1000 (4096)                                                    PMT  |
|  Service: 0x0001 (1) FOV Multi-Stream                                       |
|-----------------------------------------------------------------------------|
|  Single Service PID        Transport:                Discontinuities:       |
|  Bitrate: ... 136,937 b/s  Packets: ......... 4,853  Expected: ......... 0  |
|  Access: .......... Clear  Adapt.F.: ............ 0  Unexpect: ......... 0  |
|                            Duplicated: .......... 0  Sections:              |
|                                                      Unit start: ... 4,853  |
===============================================================================


===============================================================================
|  TABLES & SECTIONS ANALYSIS REPORT                                          |
|=============================================================================|
|  PID: 0x0000 (0)                                                       PAT  |
|-----------------------------------------------------------------------------|
|  0x00 (0, PAT), TID ext: 0x0001 (1)                                         |
|      Repetition: ...... 11  ms  Section cnt: .... 4,853                     |
|      Min repet.: ....... 0  ms  Table cnt: ...... 4,853                     |
|      Max repet.: ..... 159  ms  Version: ............ 0                     |
|=============================================================================|
|  PID: 0x0011 (17)                                                  SDT/BAT  |
|-----------------------------------------------------------------------------|
|  0x42 (66, SDT Actual), TID ext: 0x0001 (1)                                 |
|      Repetition: ..... 494  ms  Section cnt: ...... 108                     |
|      Min repet.: ..... 372  ms  Table cnt: ........ 108                     |
|      Max repet.: ..... 618  ms  Version: ............ 0                     |
|=============================================================================|
|  PID: 0x1000 (4096)                                                    PMT  |
|  Service: 0x0001 (1) FOV Multi-Stream                                       |
|-----------------------------------------------------------------------------|
|  0x02 (2, PMT), TID ext: 0x0001 (1)                                         |
|      Repetition: ...... 11  ms  Section cnt: .... 4,853                     |
|      Min repet.: ....... 0  ms  Table cnt: ...... 4,853                     |
|      Max repet.: ..... 159  ms  Version: ............ 0                     |
===============================================================================

Here is the result when we open the SRT stream with VLC; the 3 video tracks are successfully transmitted and can be played back synchronously.

You must also ensure that the source video dimensions are aligned before attempting to encode, as H.264 has certain limitations in this regard.

This stream can then be directly received by our video pipeline backend for conversion and broadcasting to viewers.

Consulted Resources

MediaMTX

Architectural Proposal: Integrating MediaMTX for SRT Multiplexing

Date: February 27th, 2026

Currently, our backend relies on FFmpeg to ingest SRT streams directly. Because FFmpeg’s SRT listener locks the network port, scaling requires dynamically assigning a new port for every incoming stream. Integrating MediaMTX—a high-performance, zero-dependency real-time media server—acts as a dedicated ingest layer, resolving this limitation while offering significant operational advantages for our infrastructure.


I. Strategic Benefits (Pros)

  • Unified Ingest Endpoint (Port Multiplexing): MediaMTX natively supports SRT multiplexing. This allows all our broadcasters to stream to a single, static UDP port (e.g., 9999). MediaMTX routes the incoming traffic internally based on the streamid parameter. This eliminates the need to expose and manage broad port ranges in our Docker configuration or firewall.
  • Native Protocol Translation & Resource Optimization: MediaMTX automatically translates incoming SRT streams into HLS, RTSP, and WebRTC with zero configuration. If our platform does not require custom FFmpeg processing (such as transcoding to multiple resolutions like 720p or 480p), we can offload HLS generation entirely to MediaMTX. This would drastically reduce the CPU overhead on our servers.
  • Enhanced Stream Resilience: Designed specifically for real-time media delivery, MediaMTX handles network jitter, packet loss, and broadcaster reconnections much more gracefully than our raw FFmpeg listener process.
  • Built-in API Management: MediaMTX features an HTTP API that allows us to query active sessions, monitor bandwidth, and terminate streams, reducing the amount of manual process management required in our Node.js backend.

II. Trade-offs and Considerations (Cons)

  • Increased Infrastructure Complexity: Introducing a dedicated media server adds a new component to our stack. Our architecture will evolve from a standalone Node.js application to a multi-service environment (Node.js backend + Database + MediaMTX).
  • Container Orchestration Requirements: A standard docker run command will no longer suffice. We will need to implement a docker-compose.yml file to orchestrate the backend and the MediaMTX containers, ensuring they can communicate over a shared Docker network.
  • Internal Network Overhead: If we choose to retain FFmpeg for custom transcoding, the video data must travel a slightly longer path: Broadcaster -> MediaMTX -> FFmpeg -> HLS Files. While this internal routing only adds a few milliseconds of latency, it is an extra hop in our pipeline.

III. Required Architectural Changes

To implement this solution, the following modifications to our project are necessary:

1. Infrastructure Expansion (Docker Compose)

We will need to transition from our current Dockerfile implementation to a docker-compose.yml setup. This file will define two services:

  • Our existing Node.js backend.
  • The bluenviron/mediamtx image, configured to expose the single SRT ingest port (e.g., 8890) to the public, alongside its API and internal HTTP ports.

2. Backend Refactoring (mediaServer.mjs)

The logic within backend/src/mediaServer.mjs will need to be updated based on how much we want to rely on MediaMTX:

  • Path A (Retain FFmpeg for Transcoding): Our backend will no longer spawn FFmpeg as a listener. Instead, when a user registers a stream, our backend will instruct FFmpeg to run as a caller, pulling the internal RTSP or SRT feed directly from the MediaMTX container (e.g., rtsp://mediamtx:8554/<streamId>) and processing it into HLS segments.
  • Path B (Deprecate FFmpeg - Recommended for simple pass-through): We can remove the child_process spawning logic entirely. The /ffmpeg/register endpoint will simply allocate a streamId, instruct the user to stream to the global MediaMTX port, and return the anticipated HLS playback URL (which MediaMTX will generate automatically).

Performance of the Synchronized Multi-Track Player

Date: March 15th, 2026

Context

This document measures and analyzes the performance of the client-side synchronized HLS multi-track player. Optimizations were performed iteratively, with each modification tested manually and compared against previous results.

Performance Scales

The following tiers define the quality thresholds for each measured metric. The project’s goal is to guarantee an experience of at least “Good” across all indicators under stable network conditions.

Stream Synchronization (Drift)

TierInter-stream DriftDescription
🟢 Excellent< 50msImperceptible to eye and ear
🟢 Very Good50 – 100msUndetectable in normal use
🟡 Good100 – 150msAcceptable, slight potential audio desynchronization
🟠 Poor150 – 200msNoticeable lag between streams
🔴 Very Poor> 200msVisible desynchronization, degraded experience

These tiers are based on psychoacoustic perception limits (Haas effect), where a delay greater than 100ms becomes noticeable to the ear and compromises the coherence of a synchronized multi-source experience.

Streamer → Viewer Delay (Live Latency)

TierDelayDescription
🟢 Excellent< 10sClose to real-time
🟢 Very Good10 – 15sExcellent for multi-stream HLS
🟡 Good15 – 25sAcceptable for non-interactive live streams
🟠 Poor25 – 40sLatency noticeable by the viewer if chat is used
🔴 Very Poor> 40sUnusable for interactive live streaming

Latency thresholds are defined by the technical trade-offs of the HLS protocol, aiming for an optimal balance between live interactivity and the buffer stability required to maintain multi-stream synchronization.

Initial Loading Time (From Click to First Frame)

TierDurationDescription
🟢 Excellent< 5sQuasi-instantaneous
🟢 Very Good5 – 10sFast, comparable to major platforms
🟡 Good10 – 20sAcceptable with a loading indicator
🟠 Poor20 – 30sUser is likely to leave the page
🔴 Very Poor> 30sAlmost certain abandonment

This scale reflects the structural complexity of the project, where the player must negotiate multiple concurrent streams and align their respective segments before enabling playback to guarantee a synchronized startup.

Playback Stability (Stalls / Freezes)

TierStall FrequencyDescription
🟢 Excellent0 stallPerfectly smooth playback
🟡 Good< 1 stall / 5 minRare, barely noticeable
🟠 Poor1 – 3 stalls / 5 minAnnoying for the user
🔴 Very Poor> 3 stalls / 5 minUnusable experience

The near-zero tolerance for this metric aligns with “Broadcast” distribution standards, with the goal of ensuring service continuity despite a network load multiplied by the number of active streams.


Test Environment

ParameterValue
Number of simultaneous streams2 (video + audio each)
Resolution per stream640×368
Framerate30 fps
Encoderx264 (CBR 6000 kbps)
TransportSRT (latency=4s, tlpktdrop=0, rcvbuf=128MB)
SegmentationFFmpeg HLS (2s segments, hls_list_size=15)
Client PlayerAngular + hls.js
Networklocalhost

Optimization History

Phase 1: Initial Config (~8.3s segments, OBS auto keyframes) - December 2025

ParameterValue
OBS Keyframe intervalAuto (~250 frames → ~8.3s segments)
liveSyncDuration6
MIN_BUFFER_FOR_START10s
hls_list_size6

Result: ~50-60s delay 🔴, very slow loading 🔴.

Phase 2: Client Optimizations (~8.3s segments) - February 2026

OptimizationChangeGain
liveSyncDuration6 → 18hls.js loads multiple segments at once
MIN_BUFFER_FOR_START10 → 8Starts playback earlier
MIN_FORWARD_BUFFER12 → 4Accepts a smaller forward buffer
SAFE_POSITION_MARGIN1 → 0.5Less buffer lost on seek
Adaptive pollingFixed 2s → 500ms/1s/2sDetects tracks faster
maxBufferLength60 → 180Stores more segments
hls_list_size6 → 15More segments in playlist

Result: ~25s delay 🟡, ~25s loading 🟠. Client-side limit reached with 8.3s segments.

Phase 3: 2s Keyframes + SRT Fix + Optimizations (Current Configuration) - March 2026

Three simultaneous major changes:

1. OBS Keyframes fixed to 2 seconds

The modified OBS encoder sends keyframes every exactly 2 seconds, allowing FFmpeg to cut HLS segments of 2s instead of 8.3s.

2. Corrected SRT parameters

The multi-stream code had lost SRT parameters during refactoring. Without these parameters, FFmpeg used default SRT values (latency=120ms, tlpktdrop=1), which caused massive packet loss with keyframes synchronized to 2s.

SRT ParameterBefore (Broken)After (Corrected)
latency120ms (default)4000000µs (4s)
tlpktdrop1 (default, drop)0 (never drop)
rcvbufDefault (~8MB)134217728 (128MB)
sndbufDefault (~8MB)134217728 (128MB)
peerlatencyUndefined4000000µs
nakreportUndefined1 (active retransmission)

3. HLS.js parameters optimized for 2s segments

ParameterPhase 2Phase 3Reason
liveSyncDuration1810Smaller segments → less margin needed
MIN_BUFFER_FOR_START8s6s2s segments = buffer fills up faster
MIN_FORWARD_BUFFER4s3s1.5 segments ahead is enough
maxBufferLength180s60sNo longer need a 3-minute buffer
liveBackBufferLength120s30sMemory savings
maxBufferSize400MB200MBConsistent with reductions
Stagger init tracksFixed 200msAdaptive (0-200ms)Depending on number of tracks
Buffer check interval500ms250msDetects buffer readiness faster

Result: ~15s delay 🟢, ~3-5s loading on refresh 🟢.


Current Results (Phase 3) — Evaluated against Scales

1. Delay between Broadcaster (OBS) and Viewer (Website)

ScenarioDelayTier
Initial load (stream just started)~15s🟢 Very Good
Refresh during stream~3-5s🟢 Excellent

Initial Loading Breakdown (~15s)

ComponentDurationOptimizable?
SRT Latency (negotiation + buffer)~4s❌ Network parameter
First FFmpeg segment production~4-6s❌ Depends on keyframes (2s × 2-3 segments)
API Polling + track detection~1s✅ Optimized
Segment loading by hls.js~3-4s✅ Optimized
Synchronized seek + playback start< 1s✅ Optimized

2. Inter-Stream Synchronization

MetricResultTier
Average drift between 2 streams~50-80ms🟢 Very Good
Maximum observed drift< 200ms🟡 Good (worst case)
Hard sync corrections (seek)Extremely rare—
Soft sync corrections (playbackRate)Occasional—
Freeze / stall during playbackNone🟢 Excellent

Quality Commitment: The player guarantees synchronization of at least “Good” (< 150ms) under normal conditions. On average, measurements show a drift between 50 and 80ms, placing the experience at “Very Good”. Desynchronization is undetectable to the user in normal use.

3. Buffer Stability During Playback

MetricResultTier
Forward buffer at startup~9.5s—
Forward buffer in steady state~8-10s (stable)—
Number of bufferStalledError0🟢 Excellent
Number of SRT RCV-DROPPED0🟢 Excellent
Observed stalls / freezes0 in 30+ min test🟢 Excellent

Performance Evolution by Phase

Comparative Summary

MetricPhase 1Phase 2Phase 3Final Tier
Initial load delay~55s 🔴~25s 🟠~15s🟢 Very Good
Refresh delay~30s 🔴~8-10s 🟡~3-5s🟢 Excellent
Average drift~50-80ms 🟢~50-80ms 🟢~50-80ms🟢 Very Good
Stalls per sessionFrequent 🔴0 🟢0🟢 Excellent
Initial loading time~55s 🔴~25s 🟠~15s🟡 Good
Refresh loading time~30s 🔴~8-10s 🟡~3-5s🟢 Excellent

Phase 2 → Phase 3 Gains

MetricPhase 2 (8.3s segments)Phase 3 (2s segments)Gain
Initial load delay~25s 🟠~15s 🟢-10s (40%)
Refresh delay~8-10s 🟡~3-5s 🟢-5s (50%)
Forward buffer at play~15-17s~9.5sSufficient buffer, less time lost
Segment size~8.3s (variable)2.0s (fixed)4× better granularity
Network jitter resistanceLow 🟠Strong 🟢1 lost segment = 2s instead of 8s
SRT RCV-DROPPEDFrequent 🔴0 🟢SRT parameter fix

Why 2s Segments are More Performant

With 8.3s segments (before):

Buffer = [========8.3s========]  → 1 segment
If the next one is delayed by 1s → guaranteed stall
Time to fill 3 segments: ~25s

With 2s segments (now):

Buffer = [=2s=][=2s=][=2s=][=2s=][=2s=] → 5 segments
If one segment is delayed → 4 others absorb the delay
Time to fill 5 segments: ~10s

Steady-state playback mechanism:

During playback, HLS.js continues to download new segments.
The player consumes 2s of buffer, but a new 2s segment arrives.
→ The forward buffer remains stable around ~8-10s continuously.
→ As long as the network delivers segments on time: 0 stalls.

Typical Production Logs

Initial Startup (Stream just started)

[loadTracks] Stream "BotKz" found with 2 tracks
[0] Manifest parsed, 1 levels
[1] Manifest parsed, 1 levels
[0] Ready (buffering...)
[1] Ready (buffering...)
[Buffer] 0: 9.9s [0.0-10.0], 1: 9.9s [0.0-10.0] (need 6s total, 3s forward)
[Buffer] ✅ Ready!
[Buffer]   Common range: 0.0s - 10.0s (9.9s)
[Buffer]   Start position: 0.52s
[Buffer]   Forward buffer: 9.4s
[Sync] Starting synchronized playback at 0.52s
[0] Seeked to 0.52s, forward buffer: 9.4s
[1] Seeked to 0.52s, forward buffer: 9.4s
[Sync] All players seeked
[Sync] All players ready
[Sync] Starting playback NOW
[Sync] 0 forward buffer at play: 9.4s
[Sync] 1 forward buffer at play: 9.4s
[Sync] ✅ Playback started!

Refresh During Playback

[loadTracks] Stream "BotKz" found with 2 tracks
[0] Manifest parsed, 1 levels
[1] Manifest parsed, 1 levels
[0] Ready (buffering...)
[1] Ready (buffering...)
[Buffer] 0: 10.0s [19.1-29.0], 1: 10.0s [19.1-29.0] (need 6s total, 3s forward)
[Buffer] ✅ Ready!
[Buffer]   Common range: 19.1s - 29.0s (10.0s)
[Buffer]   Start position: 19.55s
[Buffer]   Forward buffer: 9.5s
[Sync] Starting synchronized playback at 19.55s
[Sync] 0 forward buffer at play: 9.5s
[Sync] 1 forward buffer at play: 9.5s
[Sync] ✅ Playback started!

FFmpeg Logs (Server-side, regular segments)

[hls] Opening '/media/hls/BotKz/0/seg00000.ts' for writing  speed=7.57x
[hls] Opening '/media/hls/BotKz/0/seg00001.ts' for writing  speed=4.27x
[hls] Opening '/media/hls/BotKz/0/seg00002.ts' for writing  speed=2.09x
[hls] Opening '/media/hls/BotKz/0/seg00003.ts' for writing  speed=1.65x
...
[hls] Opening '/media/hls/BotKz/0/seg00010.ts' for writing  speed=1.01x

FFmpeg stabilizes at speed=1.01x after the first few segments, confirming regular production.


Scalability (Projections)

Number of TracksRecommended Minimum BufferInit StaggerEstimated Loading TimeEstimated Tier
26s200ms~15s (measured)🟢 Very Good
37s100ms~17s (estimated)🟢 Very Good
48s100ms~19s (estimated)🟡 Good
5+8-10s100ms~20-25s (estimated)🟡 Good

The code uses an adaptive stagger depending on the number of tracks to avoid overloading the network with too many simultaneous requests.


Resilience Features

FeatureImplementation
Stream end detectionManifest error counter (threshold = 3 consecutive errors)
Graceful playback endRemaining buffer played fully before stop
Automatic redirection5s countdown → return to home
Network recoveryAutomatic retry on network error (hls.js)
Media recoveryrecoverMediaError() on decoding error

Conclusion

IndicatorResultTier
Broadcaster → viewer delay~15s initial, ~3-5s refresh🟢 Very Good / Excellent
Inter-stream synchronization~50-80ms average, < 200ms max🟢 Very Good
Stability (stalls/freezes)0 stalls, 0 freezes in 30+ min🟢 Excellent
ScalabilityArchitecture ready for 5+ streams🟢 / 🟡 depending on count
Stream end detectionGraceful with buffer drain🟢

Minimum Quality Commitment: On a stable network connection (localhost or LAN), the player guarantees an experience of at least “Good” (🟡) across all indicators. In practice, measurements consistently show results in the “Very Good” to “Excellent” (🟢) zone for the tested configurations (2 streams, 640×368, 30fps).

The main gains come from three combined factors:

  1. OBS keyframes at 2s → regular and small segments → buffer fills up 4× faster
  2. Correctly configured SRT parameters → 0 dropped packets, 0 corruption
  3. HLS.js thresholds adapted to 2s segments → faster startup without sacrificing stability

Deployment Architecture

Date: April 5th, 2026

The infrastructure relies on a clear separation between code management, deployment automation, and containerized service execution on an EPITECH virtual machine. The entire setup is secured and optimized by a proxy and CDN layer.

Lifecycle and CI/CD (GitHub)

The workflow starts on GitHub.

  • Trigger: An action is initiated upon a push to the master branch.
  • GitHub Actions Runner: A runner is installed directly on the Virtual Machine to update the stack (Docker Compose) to rebuild and update the deployment.

Server Infrastructure (Virtual Machine)

The virtual machine hosts the entire application via Docker. The architecture is divided into two distinct Docker Compose groups for isolation:

Entry Point and Proxy (Docker Compose - Nginx Proxy Manager)

  • Nginx Proxy Manager: This container acts as the single entry point for HTTP/HTTPS traffic. It manages the routing of requests coming from Cloudflare to the appropriate internal services (Frontend or Backend) and handles SSL termination.

Application Stack (Docker Compose - Services)

This group contains the core of the platform, segmented into three units:

  • Frontend (Angular & Nginx): The client application developed with Angular is served by a dedicated Nginx instance. It communicates with the proxy for user exposure.
  • Backend (Node.js & FFmpeg):
    • The Node.js server handles business logic and APIs.
    • FFmpeg is integrated to receive the SRT stream from OBS and process the video streams to convert them into HLS.
  • MySQL: MySQL database in a container

Network Flow and Protocols

The architecture supports two types of incoming/outgoing flows:

  • Web Traffic (Frontend / API):
    • The user’s browser (Firefox, etc.) accesses the platform via Cloudflare, which provides DDoS protection and caching.
    • Requests are then forwarded to the Nginx Proxy Manager on the VM.
  • Contribution Flow (SRT Stream):
    • The streaming software (OBS) sends the multi-track video stream directly to the Backend via the SRT protocol.
    • This stream bypasses the standard HTTP proxy to benefit from the low latency and reliability of SRT transport, allowing the FFmpeg module to process data in real time.

Deployment Environment

The GitHub Actions workflow used relies on GitHub deployment environments for the environment variables necessary for deployment.

Architecture Diagram and Configurations

Domains

Summary of Technologies Used

ComponentTechnologyRole
SourceGitHubDeployment via GitHub actions
CDN / SecurityCloudflareProtection and network acceleration
OrchestrationDocker ComposeContainer management
RoutingNginx Proxy ManagerReverse proxy and SSL management
Video ProcessingFFmpegMulti-track stream manipulation
Video TransportSRTProtocol for transmitting video streams to the backend

Metrics: Project Load Testing

Date: June 19th, 2026

As part of our development process, we must define and measure performance indicators regarding the complete solution (both the OBS part and the web application part).

Web Part

One of the key points during a load test is to reproduce real public deployment conditions.

To learn more about the deployment architecture, you can check the deployed architecture document.

note

Unlike public deployment, we do not evaluate Nginx Proxy Manager on our development machine (as it is hosted on a separate VM).

Thus, in order to successfully conduct this load test, we use our self-hosted development virtual machine. The configuration is as follows:

  • Host machine
    • Intel i3 11400H (6c/12t) 2.7 GHz
    • 32 GB RAM DDR4
    • Proxmox 8.2.2 (kernel 6.8.4-2-pve)
    • 500 GB NVMe + 1 TB SATA SSD
    • 500 Mb/s fiber optic
  • Virtual machine
    • 3 vCPUs
    • 6 GB RAM
    • 200 GB (shared SATA SSD)
    • Debian 12

Proxmox view:

IDLE Scenario

The first scenario we developed reflects a complete lack of platform usage. This is the deployed web stack with no clients connected (0 spectators, 0 streams).

This scenario allows us to obtain a baseline reference to compare results with future load tests.

Here are the measurements recorded in terms of CPU usage, memory, disk I/O, and network bandwidth on the virtual machine over a 24-hour period:

Conclusion

  • CPU usage is very low (< 1.3%), mainly dominated by background processes of the virtual machine and Docker.
  • RAM usage is stabilized at 1.5 GB (+ 1 GB of buffers and 3 GB of cache).
  • Network usage is negligible (< 100 bytes/s).
  • Disk I/O usage is also negligible (< 40 ko/s, generated by the VM logs).
  • Storage occupation is 19 GB (this includes the system, dependencies, GitHub Actions for automated deployment, and the complete web stack).

Low Load Scenario

This second scenario represents the case of a single streamer (broadcasting 4 audio and video streams) with a single spectator.

Screenshot of the test configuration.

Here are the measurements recorded in terms of CPU usage, memory, disk I/O, and network bandwidth on the virtual machine over a 15-minute period:

CPU usage climbs to about 4.1% - 4.2% when starting the stream.

Network traffic (Netin and Netout) jumps to about 1.1 - 1.2 MB/s (i.e. ~9-10 Mb/s).

Disk writing intensifies to reach 1.2 MB/s during HLS segment creation.

Process details: FFmpeg and Node.js become the main consumers.

Results Analysis

  • CPU: A clear increase is observed, going from 1.3% to 4.1%. This moderate rise is explained by the SRT ingestion management and HLS segmentation performed by FFmpeg for the 4 isolated streams.
  • Network: Incoming throughput (~1.2 MB/s) matches the stream sent by OBS, configured here at about 8,170 kb/s. Outgoing throughput is equivalent, as the server retransmits these segments to the single spectator.
  • RAM Memory: Usage remains stable around 5.5 GB (92%) in Proxmox, as a large portion is mobilized by system cache, although real process consumption (viewed via htop) is only 1.56 GB.
  • Storage and I/O: Disk writing of 1.2 MB/s is directly correlated with FFmpeg’s activity, which continuously generates and deletes .ts video fragments for the HLS playlist.

Low Load Test Conclusion

The FOV solution behaves stably for a single stream. The bottleneck is not the CPU, but primarily the network bandwidth, which evolves linearly with the number of tracks sent by the streamer (here 4 tracks for ~1.1 MB/s).

The current virtual machine (3 vCPUs) appears capable of supporting about 20 to 25 simultaneous streamers in this configuration before reaching CPU or network saturation.

Prototyping: Media Over QUIC (MOQ) Integration and Architectural Analysis

Date: September 22, 2026

Introduction

This document details the Media Over QUIC (MOQ) Proof of Concept (POC) integration attempt within the FOV (Flexible Output View) OBS Studio fork, explains why its active development was paused, and outlines the underlying architectural considerations.

The MOQ integration was implemented as an independent plugin module located under /plugins/flexible-output-view/moq/ on the dedicated branch poc-media-over-quic, as well as a small patch to web-fov, also on a dedicated branch poc-media-over-quic.


1. Protocol Overview

What is MOQ?

Media Over QUIC (MOQ) is an emerging protocol family (such as MOQT - Media over QUIC Transport) designed specifically for low-latency live streaming. It leverages the QUIC transport layer—combining TCP-like reliability with UDP speed—and utilizes QUIC streams and datagrams (frequently via WebTransport) to deliver adaptive bitrate video streams with mandatory TLS 1.3 encryption.

Technical Characteristics

  • Transport Protocol: QUIC (UDP-based)
  • Wire Format: QUIC streams and datagrams (often via WebTransport)
  • Security: Mandatory TLS 1.3 encryption (inherent to QUIC)
  • Latency Target: Sub-second latency with adaptive bitrate capabilities
  • Platform Support: Cross-platform via IETF standards and C/C++ SDKs

2. Comparison: MOQ vs. Current SRT/HLS Approach

FeatureSRT / HLS (Current)MOQ (POC - Paused)
Transport LayerTCP/UDP (SRT) & TCP (HLS)QUIC (UDP via streams/datagrams)
SecurityOptional TLS / Standard HTTPMandatory TLS 1.3
Viewer Latency~2–5 seconds<1 second (theoretical)
Browser CompatibilityNative (HLS via HTML5 video)Limited (requires WebTransport/WebCodecs support)
Ecosystem MaturityProduction-ready industry standardEmerging / Experimental

Since MOQ operates on a publisher/subscriber model, it can also help reduce bandwidth requirements for users who do not wish to view all video and audio tracks simultaneously.

3. POC Implementation Details & Setup

The core experiment consisted of integrating an existing official MOQ OBS output plugin into our modified architecture to ensure compatibility with our multi-track system.

Because the official MOQ library implementation is written in Rust, we had to add a compilation workflow for it inside our obs-studio-fov repository; we ended up forking the official MOQ repository to implement a fix tailored to our project requirements.

We decided to implement a variant of our main SRT service called fov-service-moq to allow for interoperability during a transition period if we ever migrate to MOQ.

  • Service Registration: The MOQ service provider was registered alongside the existing SRT service (fov_service_srt) under the unique service ID fov_service_moq, linking to the customized fov_moq_output module.
  • Multi-Track Preservation: The POC successfully preserved the multi-track management architecture (FOVSystem), allowing multiple isolated video and audio tracks to be managed.

Running the POC

To run the POC, ensure you have Rust and Cargo installed, then follow these steps:

  1. Clone our forked MOQ repository and run the command just at the root to launch the entire MOQ demo stack.
  2. Clone the web-fov repository and switch to the poc-media-over-quic branch. It contains a debug route that instructs OBS to stream to the relay at http://localhost:4443.
  3. Deploy web-fov locally using docker compose up --build. (Note: We do not use the FOV frontend in this POC since MoQ is not yet integrated into the web stack; instead, we use the official MOQ demo frontend.)
  4. Compile the poc-media-over-quic branch of obs-studio-fov with ENABLE_FOV_DEBUG_INGEST set to ON in the root CMakeLists.txt.
  5. Select the service FOV - Multitrack [MoQ] in the OBS settings.
  6. Set the URL to the locally deployed FOV backend (http://localhost:4000) and the stream key to test.hang (the .hang suffix is mandatory for correct routing).
  7. Start the stream; it will appear at http://localhost:5173/watch.html alongside the MOQ demo stream.

OBS FOV streaming using MOQ

The first video track can be played in the MOQ frontend

The second video track can be played in the MOQ frontend

We can also confirm that multiple tracks are sent in the test.hang stream

4. Architectural Analysis: Why the MOQ Integration Was Paused

Despite successful compilation and a functional prototype operating with the official MOQ-relay and test frontend, active development on the integration was paused due to several critical architectural roadblocks.

Backend Server Architecture Incompatibility

The current FOV video distribution pipeline relies on a proven SRT/HLS workflow:

OBS -> MPEG-TS/SRT -> Backend Ingest -> FFmpeg -> TS Segments -> HLS Distribution -> Frontend

In contrast, a full MOQ pipeline would require:

OBS -> MOQ -> Backend Ingest -> MOQ Relay -> Frontend

While simpler in theory, migrating to MOQ introduces significant infrastructure hurdles. It requires a complete rewrite of our web video player to support simultaneous video and audio tracks via MoQ, alongside major updates to our backend ingestion infrastructure.

Furthermore, browser support for bleeding-edge WebTransport, WebCodecs, and WebAudio APIs varies across platforms and environments.

Cost-Benefit Assessment

  • Benefits: Modern congestion control, native TLS 1.3, potential sub-second latency, and native stream multiplexing.
  • Drawbacks: Demands a total backend rewrite, lacks mature production-grade deployment infrastructure at scale, and requires an engineering investment that outweighs current MVP requirements. The existing SRT + HLS stack already meets all target performance metrics.

5. Future Implications and Next Steps

Current Status of the Experiment

Nothing has been removed from the repository. The working prototype and experimental code remain safely isolated on the poc-media-over-quic branch.

Development was paused because, while the proof of concept successfully validates that multi-track streaming over MOQ is feasible on the OBS side, the sweeping backend and frontend architecture changes required for production are outside the immediate scope of the current project roadmap.

Consulted Resources

What’s next ?