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:
- 🎬 Broadcasting: Using a custom version of OBS Studio, the streamer transmits multiple isolated video and audio tracks simultaneously.
- 🌐 Delivery: The FOV Backend ingests these tracks, synchronizes them dynamically, and converts them into optimized browser-ready streams.
- 👀 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:
| Component | Description |
|---|---|
| FOV Software | A modified fork of OBS Studio, the world’s leading streaming software optimized for multi-track isolation. |
| Backend API | The “brain” of the operation that manages users, categories, and the complex media pipelines required for multi-track delivery. |
| Angular Frontend | An 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).
- 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 (
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
--userfor 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)
- Mount the DMG: Double-click the downloaded
.dmgfile to open it. - 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.
- 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)
- Extract: Extract the contents of the ZIP archive anywhere on your system (e.g., your Desktop or a custom tools folder).
- Launch: Open the application using the executable located at:
Release\bin\64bit\obs64.exe
- 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
- Launch the application binary.
- Open Settings from the main dashboard control interface and select the Stream tab on the left margin.
- 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.livefor 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
- Click Start Streaming in the workspace console interface.
- 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.
- Milestone Review: Conduct final performance benchmarks and polish all technical and user-facing documentation.
- Language Localization: Translate the website and FOV Software UI.
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
- Releases
- Contribution & Feature Policy
- Development & Branching Strategy
- Governance & Community Guidelines
Team & Organization
Core Team
FOV is developed and maintained by a 3-person core team:
- Lucas Loustalot (@LucasLoustalot)
- Raphael Scandella (@RaphxelS)
- Samy NASSET (@Slymoz)
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:
obs-studio-fov- Language: C
- License: GPLv2
- Description: A custom fork of
obsproject/obs-studiobuilt specifically to add robust multi-track functionality and source isolation for the FOV ecosystem.
web-fov- Language: JavaScript
- License: MIT
- Description: Repository hosting the Flexible Output View web stack and platform infrastructure.
documentation-fov- License: CC BY-SA 4.0
- Description: Public documentation repository containing user guides, developer docs, and
mdBooksource 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
masterto 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 intodev.main/master(Production Branch): Stable, production-ready code. This branch only receives commits by mergingdevinto it. Direct commits or PRs intomain/masterare 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 forobs-studio-fov). - Automatic Formatting: Contributors must use automatic code formatting tools prior to submission:
- Use
clang-formatfor allobs-studio-fovC code. - Use
eslintorprettierfor the frontend and backend codebase inweb-fov.
- Use
- Comments & Clarity: Write self-documenting code and add clear comments for complex logic or business logic blocks.
- Use Doxygen comments for
obs-studio-fov.
- Use Doxygen comments for
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:
- Fork & Branch: Create a dedicated feature branch off
dev. - Implement & Test: Code your changes following project conventions, apply formatters, and perform local testing.
- Commit: Commit your changes using the standardized commit message format.
- Open PR: Submit a Pull Request targeting the
devbranch, 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
-
FOV Web Documentation Technical specifications, architecture, and deployment procedures for the Angular frontend and Node.js backend infrastructure.
-
FOV Software Documentation Source code guidelines, building instructions, CI workflows, and core architecture for the custom OBS Studio integration.
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
- Installation
- Configuration
- Project Structure
- Running the Server
- API Endpoints
- Testing
- Linting & Code Quality
- Deployment
- Documentation
- 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
-
Clone and navigate to the backend:
cd backend -
Install dependencies:
npm install -
Set up environment variables:
cp .env.example .env # Edit .env with your database and server configuration -
Verify database connection:
node test-db.js
Configuration
Environment Variables
Create a .env file in the backend/ directory. See .env.example for template.
| Variable | Type | Default | Description |
|---|---|---|---|
PORT | int | 4000 | Express server port |
NODE_ENV | string | development | Environment (development/production) |
DB_HOST | string | localhost | MySQL host |
DB_USER | string | admin | MySQL user |
DB_PASSWORD | string | (required) | MySQL password |
DB_NAME | string | fovwebdb | Database name |
MEDIA_ROOT | string | ./media | Path to HLS and media files |
FFMPEG_PATH | string | auto-detected | Path to FFmpeg binary |
CORS_ORIGIN | string | * | 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 IDPOST /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 streamsGET /api/streams/:id- Get stream details & HLS playlistPOST /api/streams- Create new stream
Categories
GET /api/categories- List all categoriesPOST /api/categories- Create new category
Media (HLS)
GET /hls/:stream_id/playlist.m3u8- Master HLS playlistGET /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 testingusers.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:
constpreferred, novar - 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:
- Verify MySQL is running:
mysql -u admin -p - Check
.envcredentials match your MySQL setup - Ensure database exists:
CREATE DATABASE fovwebdb;
Port Already in Use
Error: listen EADDRINUSE :::4000
Solutions:
- Change PORT in
.env:PORT=5000 - 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:
- Review DEVELOPMENT.md for contribution guidelines
- Check existing issues/documentation
- 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?
| Component | Purpose | Alternative | Why Not |
|---|---|---|---|
| SRT Input | Secure, low-latency protocol (OBS, FFmpeg) | RTMP, RTSP | Superior reliability and latency performance |
| HLS Output | HTTP-based delivery | DASH, RTMP | Most compatible (Safari, iOS native, widely supported) |
| FFmpeg | Transcoding/segmentation & format conversion | Node-media-server only | Better 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
/apiprefix - 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 IDPOST /- 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:
-
Application Level (
src/index.js):app.use((err, req, res, next) => { console.error(err); res.status(err.status || 500).json({ error: err.message }); }); -
Route Level (
src/routes/*.js):try { // DB operation } catch (err) { next(err); // Pass to app handler } -
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
| Component | Bottleneck | Solution |
|---|---|---|
| Single Express | CPU-bound work | Cluster module / Load balancer |
| Single DB Pool | Connection limit | Read replicas, sharding |
| Local Media Files | Disk I/O | S3/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
nullreturns 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
| Type | Pattern | Example |
|---|---|---|
| Variable | camelCase | userName, streamCount |
| Constant | UPPER_SNAKE_CASE | DB_LIMIT, HLS_PORT |
| Function | camelCase | getUserById(), validateEmail() |
| Class | PascalCase | UserService, StreamManager |
| File | kebab-case or camelCase | user-routes.js or userRoutes.js |
| Database | snake_case | user_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
-
Run linter:
npm run lint:fix -
Run tests:
npm test -
Check coverage hasn’t dropped:
npm test # View coverage/lcov-report/index.html -
Commit with meaningful message:
git commit -m "feat: add user authentication routes"
Commit Message Format
<type>(<scope>): <subject>
<body>
<footer>
Types:
feat:New featurefix:Bug fixdocs:Documentationstyle:Code style (lint, formatting)test:Testschore: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
| Command | Purpose |
|---|---|
npm run dev | Start dev server with hot-reload |
npm test | Run tests with coverage |
npm run test:watch | Watch mode for tests |
npm run lint | Check code style |
npm run lint:fix | Auto-fix linting issues |
npm start | Production start |
node test-db.js | Test database connection |
Resources
- Express.js Docs
- MySQL 2 Documentation
- Jest Testing Framework
- ESLint Rules
- RESTful API Best Practices
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
- Docker Deployment
- Docker Compose
- Environment Configuration
- Database Setup
- Monitoring & Logging
- Backup & Recovery
- 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
| Component | RTO | RPO | Method |
|---|---|---|---|
| Database | 4 hours | 1 hour | AWS RDS automated backups |
| Media Files | 2 hours | 30 min | S3 versioning + daily sync |
| Application Code | 30 min | 0 min | Git repository |
Recovery Steps:
- Spin up new EC2 instance with latest AMI
- Restore RDS from snapshot
- Restore media from S3 backup
- Deploy latest backend container
- 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/usersreturns 201 with ID - Get User:
GET /api/users/:idreturns 200 - Not Found:
GET /api/users/999returns 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 testshows all green - Linting Passes:
npm run lintshows 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
.envfiles - 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:
.envfile locally (never commit).env.exampleas 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
- Detect: Monitor logs, alerts for suspicious activity
- Contain: Disable affected accounts, revoke tokens
- Investigate: Review logs, identify scope
- Remediate: Patch vulnerability, force password resets
- Communicate: Notify users, regulatory bodies (if required)
- 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
- OWASP Top 10
- Node.js Security Checklist
- Express.js Security Best Practices
- bcrypt Documentation
- JWT Introduction
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 lintpasses with no errors - Auto-fix issues:
npm run lint:fixapplied - Code Review: Manual review of changes
- Comments: Added for complex logic
Testing
- Unit Tests:
npm testpasses 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 testpasses - 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 auditpasses - 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:
- Checkout code
- Setup Node.js v20
- Install dependencies (
npm ci) - Lint code (
npm run lint) - Run tests with coverage (
npm test) - Upload coverage report as artifact
Checking CI Results
GitHub:
- Go to Pull Request
- Scroll to “Checks” section
- Review “Backend Tests & Lint” workflow
- 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
-
Reproduce locally:
npm test -
Check recent changes:
git log -1 --stat git diff HEAD~1 -
Run specific test for debugging:
npm test -- users.test.js npm test -- --verbose -
Check dependencies:
npm audit npm list --depth=0 -
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
-
Run coverage report:
npm test -- --coverage open coverage/lcov-report/index.html -
Identify new uncovered code
-
Write tests for those lines
-
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
| File | Purpose |
|---|---|
.eslintrc.json | ESLint code style rules (4-space indent, single quotes, strict equality, etc.) |
jest.config.js | Jest testing framework configuration with 50% coverage threshold |
package.json | Updated with test/lint scripts and dev dependencies |
.env.example | Environment template for development |
.env.production.example | Production environment template |
Test Files
| File | Coverage | Tests |
|---|---|---|
src/__tests__/db.test.js | Database module | Connection pool, query methods |
src/__tests__/users.test.js | User routes | GET, POST, error handling |
src/__tests__/categories.test.js | Category routes | CRUD operations, validation |
src/__tests__/streams-utils.test.js | Stream utilities | Playlist validation, segment counting |
Total Tests: 18 test cases covering critical modules
Documentation Files
| File | Content |
|---|---|
| readme.md | Complete project guide - Overview, installation, configuration, API endpoints, deployment |
| ARCHITECTURE.md | System design - Technology stack justification, module structure, data flows, scaling strategy |
| DEVELOPMENT.md | Developer guide - Setup, workflow, debugging, code standards, testing guidelines |
| DEPLOYMENT.md | Production deployment - Docker, Docker Compose, cloud platforms, monitoring, backups |
| SECURITY.md | Security policies - Vulnerabilities, best practices, roadmap, compliance considerations |
| CHANGELOG.md | Version history - Changes, roadmap, dependencies, breaking changes |
| QA-CHECKLIST.md | Quality assurance - Pre-commit, pre-deployment, CI/CD, coverage analysis |
| API-TESTING.md | API reference - Endpoints, examples, cURL commands, Postman collection |
CI/CD Files
| File | Purpose |
|---|---|
.github/workflows/backend-tests.yml | GitHub 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
- ✅
constpreferred overvar - ✅ Strict equality (
===) - ✅ No unused variables
- ✅ No
console.logproduction 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
mainbranch
Steps:
- Checkout code
- Setup Node.js v20
- Install dependencies (
npm ci) - Run linter (
npm run lint) - Run tests with coverage (
npm test) - Upload coverage report as artifact
Status Badge (add to README):

📖 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
- Push to feature branch
- GitHub Actions automatically runs:
- Linter ($npm run lint`)
- Tests (
npm test)
- Wait for ✅ all checks pass
- Request review from team
- 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
| Package | Version | Purpose |
|---|---|---|
| express | ^4.22.1 | REST API framework |
| cors | ^2.8.5 | Cross-origin resource sharing |
| morgan | ^1.10.0 | HTTP request logging |
| mysql2 | ^3.6.5 | MySQL database driver |
| dotenv | ^16.0.0 | Environment variables |
| node-media-server | 2.2.0 | SRT ingest and streaming capability |
Development Dependencies
| Package | Version | Purpose |
|---|---|---|
| eslint | ^8.56.0 | Code linting |
| jest | ^29.7.0 | Testing framework |
| supertest | ^6.3.3 | HTTP testing |
| jest-mock-extended | ^3.0.5 | Advanced mocking |
| nodemon | ^3.1.11 | Auto-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)
- JWT Authentication - All endpoints currently public
- Input Sanitization - Prevent SQL injection
- Rate Limiting - Prevent DDoS
- HTTPS - Use in production behind reverse proxy
- Password Hashing - If storing passwords
See SECURITY.md for detailed roadmap.
📞 Support & Resources
Documentation
- readme.md - Start here!
- DEVELOPMENT.md - Developer setup
- ARCHITECTURE.md - System design
- DEPLOYMENT.md - Production deployment
- API-TESTING.md - API examples
- QA-CHECKLIST.md - Testing process
- SECURITY.md - Security practices
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
| Issue | Solution |
|---|---|
| Port 4000 in use | npm run dev and change PORT in .env |
| DB connection failed | Check .env credentials with node test-db.js |
| Tests failing | Run npm test -- --verbose to see details |
| Lint errors | Run 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
| File | Purpose |
|---|---|
src/index.js | App startup |
src/db.js | Database connection |
src/routes/ | API endpoints |
.env | Configuration (don’t commit!) |
.eslintrc.json | Code style rules |
jest.config.js | Test configuration |
package.json | Dependencies & 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
| Doc | Content |
|---|---|
| readme.md | Overview, setup, API docs |
| ARCHITECTURE.md | System design |
| DEVELOPMENT.md | Dev guide |
| DEPLOYMENT.md | Production setup |
| SECURITY.md | Security Guidelines |
| API-TESTING.md | API examples |
| QA-CHECKLIST.md | Testing 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?
- Check this card
- Read DEVELOPMENT.md
- Run
npm runto see all scripts
Test won’t run?
npm test -- --verbose- Check
.envdatabase connection - Run
npm auditfor dependency issues
Deployment help?
- See DEPLOYMENT.md
- Docker: DEPLOYMENT.md#docker-deployment
- Cloud: DEPLOYMENT.md#cloud-platforms
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 /
- User CRUD endpoints:
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
vardeclarations - 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 rulesjest.config.js- Test configuration with coverage thresholds.env.example- Environment template.env.production.example- Production environment templateDockerfile- Container image definition
Package Management
- Updated
package.jsonwith:- 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
- No Authentication: All endpoints are public (security risk for production)
- Basic Input Validation: No comprehensive validation/sanitization
- Local Media Storage: No CDN or object storage integration
- Single Database Connection Pool: Limited across instances
- No Rate Limiting: Vulnerable to DDoS/abuse
- 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
| Package | Version | Purpose |
|---|---|---|
| express | ^4.22.1 | Web framework |
| cors | ^2.8.5 | CORS middleware |
| morgan | ^1.10.0 | HTTP request logging |
| mysql2 | ^3.6.5 | Database driver |
| dotenv | ^16.0.0 | Environment variables |
| node-media-server | 2.2.0 | Media server foundation (SRT ingest capability) |
| srt | ^0.0.3 | SRT protocol support |
Development
| Package | Version | Purpose |
|---|---|---|
| eslint | ^8.56.0 | Code linting |
| jest | ^29.7.0 | Testing framework |
| supertest | ^6.3.3 | HTTP testing |
| nodemon | ^3.1.11 | Auto-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:
- Back up database and media files
- Update
.envwith new variables if needed - Run
npm installto get new dependencies - 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
mainbranch. - 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
mainbranch. - 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
mainbranch. - 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.
| Variable | Description | Default Value |
|---|---|---|
DB_PASSWORD | Secure root password for the PostgreSQL database container. | your_secure_db_password_here |
SRT_URL | Hostname or IP address for SRT video streaming endpoints. | localhost |
API_URL | Full publicly accessible URL pointing to the backend API. | http://localhost:4000/api |
JWT_SECRET | Your JWT secret. | your_jwt_secret_here |
JWT_EXPIRES_IN | The JWT expiration configuration | 7d |
To set up your environment:
- Copy the template file:
cp .env.example .env - Open the newly created
.envfile 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
| Variable | Description | Value / Default |
|---|---|---|
PORT | The internal port that the Node.js backend listens on. | 4000 |
DB_HOST | The internal Docker network hostname used by the backend to reach the database container. | fov-db |
DB_PORT | The port number used for database communication. | 5432 |
DB_USER | The database user profile name. | root |
SRT_PORT | The primary base port allocated for incoming SRT stream ingestion. | 9999 |
DB_NAME | The exact target database name. | fovwebdb |
FFMPEG_PATH | The absolute path inside the container where FFmpeg is installed, essential for processing video feeds. | /usr/local/bin/ffmpeg |
MEDIA_ROOT | The container directory mapped to persistent storage where media assets and HLS segments are saved. | /var/media |
Database Container (fov-db) Variables
| Variable | Description | Value / Default |
|---|---|---|
POSTGRES_DB | Instructs the official PostgreSQL container to automatically create a database upon initial startup. | fovwebdb |
POSTGRES_USER | Defines the administrative user account created for the database instance. | root |
Frontend Build Arguments
| Argument | Description | Source / Context |
|---|---|---|
API_URL | The 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 / Mount | Type | Target Container | Purpose |
|---|---|---|---|
./backend/media:/var/media | Bind Mount | Backend | Stores persistent media assets and HLS segments generated by the backend. |
fov-db:/var/lib/postgresql | Named Volume | Database (fov-db) | Ensures PostgreSQL database records persist safely across container restarts. |
./fovwebdb.sql:/docker-entrypoint-initdb.d/init.sql | Bind Mount | Database (fov-db) | Automatically seeds the database schema and initial data on first-time container initialization. |
Networking
| Network Name | Driver / Type | Purpose |
|---|---|---|
frontend | Bridge | Connects the frontend container to the backend API service to allow user web traffic communication. |
backend | Bridge (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-network | External 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:
- API Backend: http://localhost:4000
- Stream Ingest (SRT): Ports
9999-10010(available on both TCP and UDP) - Frontend Web App: http://localhost:4200
- PostgreSQL Database:
localhost:5432
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.
- Source Domain: Your public frontend URL (e.g.,
-
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.
- Source Domain: Your public API URL (e.g.,
production behavior summary
- Public web traffic enters exclusively through Nginx Proxy Manager on ports
80and443. - 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 to127.0.0.1and 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
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:
- Microsoft VCRedist 2015+ x64
- Install CMake from here or here if cmake.org is down, version 3.28 or later.
- Install Visual Studio 2022 and select Desktop development with C++, then select the following individual components:
- Windows 11 SDK (10.0.22621.0 or later)
- C++ ATL v143 build tools (x86 & x64) (17.13 or later)
- MSVC v143 - VS 2022 C++ x64/x86 build tools (v14.44-17.14 or later)
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:
| Option | Location | Description |
|---|---|---|
ENABLE_FOV_DEBUG_INGEST | ./CMakeLists.txt:25 | Enable 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:26 | Build 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,
FOVSystemintercepts audio at the source layer. Each active audio source is automatically hijacked and assigned its own exclusivemixer_idtrack 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:
- View Context Allocation: An internal
obs_view_tis mapped to the source, ensuring raw frame allocation occurs independently of the primary OBS program canvas. - Resolution Alignment: Frame dimensions are processed through an aspect alignment macro:
OUT_ALIGN(dimension, 16). This guarantees compatibility with rigorous hardware macroblock requirements. - Muxer Binding: Encoders are appended to an
obs_encoder_group_ttracking node and indexed continuously usingobs_output_set_video_encoder2.
Audio Architecture
To ensure zero configuration effort and bulletproof stream isolation, audio tracks bypass global mixer matrices entirely:
- Automated Mixer Hijacking: When an audio source is registered,
FOVSystemoverrides its bitmask usingobs_source_set_audio_mixers(source, 1 << mixer_id). This forces the source onto a single exclusive pipeline and clears it from all other tracks. - 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.
- 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 theffmpeg-mpegtsmuxer 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_VIDEOandOBS_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 unifiedobs_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
devormasterbranches. - 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.
- On Push: Analyzes only the files modified in the
- 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
devandmaster. - Runner: GitHub-hosted runners.
- Steps:
- Pulls
web-fovandobs-studio-fov. - Deploys
web-fovlocally using Docker. - Installs OBS dependencies from
requirements.sh. - Builds
obs-studio-fovin Release mode with the specialfov-integration-testplugin. - Runs the software using
xvfb. - Configures a test scene with two video media sources and starts a stream to the locally deployed
web-fovinstance. - Verifies via the
web-fovAPI that the stream is available and contains 2 video and 2 audio tracks.
- Pulls
- Results: Uploads the generated logs produced by
obs-studio-fovandweb-fovas GitHub artifacts.
Cross-Platform Builds (Windows, macOS, Linux)
Compiles the project across Linux, Windows, and macOS to verify multi-platform support.
- Triggers:
devormasterbranches: 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
devandmaster. - 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:
- Transcoding Cost and Capacity
- Multi-track Video
- OBS Module Development
- Multi-track Video Player Researches
- Video Transport Protocols
- Backend Libraries
- OBS Multi-track SRT Output Implementation
- MediaMTX
- Multi-track Video Player Performances
- Deployed Architecture
- Development and Performance
- Media Over QUIC Experiment
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:
| Resource | Encoder | Maximum Simultaneous Streams | Observations |
|---|---|---|---|
| GPU | NVENC H.264 | 5 streams | The framerate is maintained above the required minimum. |
| CPU | x264 | 3 streams | The 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:
- Create an output
obs_output_t - Create an encoder group
obs_encoder_group_tto manage multiple encoders synchronously - Create a view
obs_view_tfor each video track - Configure the view with options such as framerate, dimensions, and color space
- Assign a video source (or the default global video output in the case of enhanced broadcasting) to each view.
- Get a video object and add the view to the rendering pipeline with
obs_view_add2 - For each view, create a dedicated encoder, configure it, and assign the view’s video object as input, resolving potential source resizing issues.
- Add the encoder to the output, using the
obs_output_set_video_encoder2function and an index corresponding to the video track into which the data should be muxed. - Add at least one audio encoder
- 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
- HLS, RTMP, DASH, WebRTC, and More: A Simple Guide to Streaming Protocols
- HLS, MPEG-DASH, RTMP, and WebRTC: Which Protocol is Right for Your App?
- OBS, Writing an MP4 Muxer for Fun and Profit
- Enhanced Broadcasting with Multiple Encodes
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
pluginsfolder; the CMakeLists must include the OBS API libraries:OBS::libobs,OBS::frontend-api, and$<$<PLATFORM_ID:Windows>:OBS::w32-pthreads>for Windows compatibility.
- Create a folder and add a minimal CMakeLists.txt in the
- 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.
- To continue the experiment, we add a callback when events are generated by the user interface using the function:
- 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_stringfunction: 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
- Original OBS Source Code
- Getting Started With OBS Studio Development
- OBS Multiple RTMP Plugin
- OBS Source Record Plugin
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
| Component | Technology |
|---|---|
| Frontend framework | Angular |
| HLS Player | hls.js |
| Language | TypeScript |
| Styling | SCSS |
Benchmark: Web Player Technology Choice
| Evaluation Criterion | Native HTML5 (<video>) | dash.js (MPEG-DASH) | hls.js (Apple HLS) 🏆 |
|---|---|---|---|
| Protocol Used | HLS (if supported natively) | DASH | HLS |
| 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
| Parameter | Value |
|---|---|
| Number of video streams | 8 |
| Number of audio streams | 8 |
| Total simultaneous streams | 16 |
| Resolution per stream | 640x360 |
| Test duration | 10+ minutes |
| Simulated network conditions | Slow 4G (Chrome DevTools) |
Results
| Metric | Result |
|---|---|
| Maximum observed drift | < 200ms ✅ |
| Average drift | ~50-80ms |
| Sync corrections required | Extremely rare |
| Lag or stuttering | None |
| Audio/video desynchronization | None |
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 Condition | Max Drift | Behavior |
|---|---|---|
| No limitation | < 50ms | Excellent |
| Fast 4G | < 100ms | Very Good |
| Slow 4G | < 200ms | Acceptable ✅ |
| Offline → Online | ~500ms then resync | Recovery 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)
- Support for multiple synchronized streams/tracks
- Authentication and security
- Reliability / Quality of service
- 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)
- Supported by popular browsers
- Reliability
- Latency
Comparative Table
| RTMP | HLS | SRT | RIST | DASH | WebRTC | |
|---|---|---|---|---|---|---|
| Type | Stream | Segments | Stream | Stream | Segments | Stream (Peer-To-Peer) |
| Low-level Protocol | TCP | HTTP/TCP | UDP | UDP | HTTP/TCP | SRTP/UDP |
| Latency | Low | High | Real-time | Real-time | Medium | Real-time |
| Reliability | Medium (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 | - | Workaround | Native | Native | Workaround | Native |
| Codec Restriction | H.264/AAC | Browser support | None | None | Browser support | VP8, VP9, H.264, Opus |
| Transport Container Format | FLV | MPEG-TS or FMP4 | MPEG-TS | MPEG-TS | M4S or WEBM | Raw RTP packets |
| Open Protocol | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ |
| Authentication | URL / Stream Key | Tokens / Cookies | Passphrase / StreamID | DTLS / Certificates | Tokens / Cookies | Certificate exchange |
| Security | RTMPS | HTTPS | AES | AES | HTTPS | Mandatory (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
- https://getstream.io/blog/protocol-comparison/
- https://srtminiserver.com/tpost/g0p2vmk331-low-latency-amp-real-time-streaming-srt
- https://medium.com/@contact_45426/rist-vs-srt-a-comprehensive-comparison-53b20b22464b
- https://medium.com/@n20/hls-rtmp-dash-webrtc-and-more-a-simple-guide-to-streaming-protocols-98cbabcd599f
- https://static.vsf.tv/activity_groups/RIST_poster_for_VidTrans2018Feb25.pdf
- https://www.vmix.com/download/srt_alliance_deployment_guide.pdf
- https://medium.com/@psantana5_/guide-running-rtmp-hls-with-docker-and-ffmpeg-769c3f14462e
- https://ressources.camexia.org/diffusion-dun-flux-video-en-direct-sur-le-web/
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
| Option | What It Is | SRT Support | Media Capability (multiple video/audio tracks) | Level of Control | Ease of Use | Typical Use Case |
|---|---|---|---|---|---|---|
@eyevinn/srt | Node.js bindings for the SRT protocol (transport only) | Yes (transport layer only) | No (does not demux or decode) | Low–Medium | Medium | We 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 process | Yes if FFmpeg is compiled with SRT support | Yes (full demux, decode, filters, remux, multiple tracks) | Medium | Easy–Medium | We 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-API | Yes (via FFmpeg libraries) | Yes (full mux/demux, decode, filters) | High | Hard | We 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 interfaces | Yes (via FFmpeg libraries) | Yes (full mux/demux, decode, filters) | High | Hard | We use this if we want a stream-like API with direct access to video/audio data |
| Old libav / legacy bindings | Older fork or outdated bindings | Limited / outdated | Partial | High | Hard | Generally 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
- TSDuck
- Introduction to MPEG-TS
- MPEG transport stream
- OBS Source code & OBS FFmpeg wrapper
- MPEG-2: Understanding the Transport Stream Structure
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 thestreamidparameter. 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 runcommand will no longer suffice. We will need to implement adocker-compose.ymlfile 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/mediamtximage, 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_processspawning logic entirely. The/ffmpeg/registerendpoint will simply allocate astreamId, 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)
| Tier | Inter-stream Drift | Description |
|---|---|---|
| 🟢 Excellent | < 50ms | Imperceptible to eye and ear |
| 🟢 Very Good | 50 – 100ms | Undetectable in normal use |
| 🟡 Good | 100 – 150ms | Acceptable, slight potential audio desynchronization |
| 🟠 Poor | 150 – 200ms | Noticeable lag between streams |
| 🔴 Very Poor | > 200ms | Visible 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)
| Tier | Delay | Description |
|---|---|---|
| 🟢 Excellent | < 10s | Close to real-time |
| 🟢 Very Good | 10 – 15s | Excellent for multi-stream HLS |
| 🟡 Good | 15 – 25s | Acceptable for non-interactive live streams |
| 🟠 Poor | 25 – 40s | Latency noticeable by the viewer if chat is used |
| 🔴 Very Poor | > 40s | Unusable 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)
| Tier | Duration | Description |
|---|---|---|
| 🟢 Excellent | < 5s | Quasi-instantaneous |
| 🟢 Very Good | 5 – 10s | Fast, comparable to major platforms |
| 🟡 Good | 10 – 20s | Acceptable with a loading indicator |
| 🟠 Poor | 20 – 30s | User is likely to leave the page |
| 🔴 Very Poor | > 30s | Almost 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)
| Tier | Stall Frequency | Description |
|---|---|---|
| 🟢 Excellent | 0 stall | Perfectly smooth playback |
| 🟡 Good | < 1 stall / 5 min | Rare, barely noticeable |
| 🟠 Poor | 1 – 3 stalls / 5 min | Annoying for the user |
| 🔴 Very Poor | > 3 stalls / 5 min | Unusable 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
| Parameter | Value |
|---|---|
| Number of simultaneous streams | 2 (video + audio each) |
| Resolution per stream | 640×368 |
| Framerate | 30 fps |
| Encoder | x264 (CBR 6000 kbps) |
| Transport | SRT (latency=4s, tlpktdrop=0, rcvbuf=128MB) |
| Segmentation | FFmpeg HLS (2s segments, hls_list_size=15) |
| Client Player | Angular + hls.js |
| Network | localhost |
Optimization History
Phase 1: Initial Config (~8.3s segments, OBS auto keyframes) - December 2025
| Parameter | Value |
|---|---|
| OBS Keyframe interval | Auto (~250 frames → ~8.3s segments) |
liveSyncDuration | 6 |
MIN_BUFFER_FOR_START | 10s |
hls_list_size | 6 |
Result: ~50-60s delay 🔴, very slow loading 🔴.
Phase 2: Client Optimizations (~8.3s segments) - February 2026
| Optimization | Change | Gain |
|---|---|---|
liveSyncDuration | 6 → 18 | hls.js loads multiple segments at once |
MIN_BUFFER_FOR_START | 10 → 8 | Starts playback earlier |
MIN_FORWARD_BUFFER | 12 → 4 | Accepts a smaller forward buffer |
SAFE_POSITION_MARGIN | 1 → 0.5 | Less buffer lost on seek |
| Adaptive polling | Fixed 2s → 500ms/1s/2s | Detects tracks faster |
maxBufferLength | 60 → 180 | Stores more segments |
hls_list_size | 6 → 15 | More 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 Parameter | Before (Broken) | After (Corrected) |
|---|---|---|
latency | 120ms (default) | 4000000µs (4s) |
tlpktdrop | 1 (default, drop) | 0 (never drop) |
rcvbuf | Default (~8MB) | 134217728 (128MB) |
sndbuf | Default (~8MB) | 134217728 (128MB) |
peerlatency | Undefined | 4000000µs |
nakreport | Undefined | 1 (active retransmission) |
3. HLS.js parameters optimized for 2s segments
| Parameter | Phase 2 | Phase 3 | Reason |
|---|---|---|---|
liveSyncDuration | 18 | 10 | Smaller segments → less margin needed |
MIN_BUFFER_FOR_START | 8s | 6s | 2s segments = buffer fills up faster |
MIN_FORWARD_BUFFER | 4s | 3s | 1.5 segments ahead is enough |
maxBufferLength | 180s | 60s | No longer need a 3-minute buffer |
liveBackBufferLength | 120s | 30s | Memory savings |
maxBufferSize | 400MB | 200MB | Consistent with reductions |
| Stagger init tracks | Fixed 200ms | Adaptive (0-200ms) | Depending on number of tracks |
| Buffer check interval | 500ms | 250ms | Detects 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)
| Scenario | Delay | Tier |
|---|---|---|
| Initial load (stream just started) | ~15s | 🟢 Very Good |
| Refresh during stream | ~3-5s | 🟢 Excellent |
Initial Loading Breakdown (~15s)
| Component | Duration | Optimizable? |
|---|---|---|
| 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
| Metric | Result | Tier |
|---|---|---|
| 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 playback | None | 🟢 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
| Metric | Result | Tier |
|---|---|---|
| Forward buffer at startup | ~9.5s | — |
| Forward buffer in steady state | ~8-10s (stable) | — |
Number of bufferStalledError | 0 | 🟢 Excellent |
Number of SRT RCV-DROPPED | 0 | 🟢 Excellent |
| Observed stalls / freezes | 0 in 30+ min test | 🟢 Excellent |
Performance Evolution by Phase
Comparative Summary
| Metric | Phase 1 | Phase 2 | Phase 3 | Final 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 session | Frequent 🔴 | 0 🟢 | 0 | 🟢 Excellent |
| Initial loading time | ~55s 🔴 | ~25s 🟠 | ~15s | 🟡 Good |
| Refresh loading time | ~30s 🔴 | ~8-10s 🟡 | ~3-5s | 🟢 Excellent |
Phase 2 → Phase 3 Gains
| Metric | Phase 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.5s | Sufficient buffer, less time lost |
| Segment size | ~8.3s (variable) | 2.0s (fixed) | 4× better granularity |
| Network jitter resistance | Low 🟠 | Strong 🟢 | 1 lost segment = 2s instead of 8s |
SRT RCV-DROPPED | Frequent 🔴 | 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 Tracks | Recommended Minimum Buffer | Init Stagger | Estimated Loading Time | Estimated Tier |
|---|---|---|---|---|
| 2 | 6s | 200ms | ~15s (measured) | 🟢 Very Good |
| 3 | 7s | 100ms | ~17s (estimated) | 🟢 Very Good |
| 4 | 8s | 100ms | ~19s (estimated) | 🟡 Good |
| 5+ | 8-10s | 100ms | ~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
| Feature | Implementation |
|---|---|
| Stream end detection | Manifest error counter (threshold = 3 consecutive errors) |
| Graceful playback end | Remaining buffer played fully before stop |
| Automatic redirection | 5s countdown → return to home |
| Network recovery | Automatic retry on network error (hls.js) |
| Media recovery | recoverMediaError() on decoding error |
Conclusion
| Indicator | Result | Tier |
|---|---|---|
| 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 |
| Scalability | Architecture ready for 5+ streams | 🟢 / 🟡 depending on count |
| Stream end detection | Graceful 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:
- OBS keyframes at 2s → regular and small segments → buffer fills up 4× faster
- Correctly configured SRT parameters → 0 dropped packets, 0 corruption
- 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
pushto themasterbranch. - 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
- The website is deployed on fovapp.live
- The backend API (to configure in OBS) is deployed on api.fovapp.live
- The entry point for SRT streams is at ingest.fovapp.live
Summary of Technologies Used
| Component | Technology | Role |
|---|---|---|
| Source | GitHub | Deployment via GitHub actions |
| CDN / Security | Cloudflare | Protection and network acceleration |
| Orchestration | Docker Compose | Container management |
| Routing | Nginx Proxy Manager | Reverse proxy and SSL management |
| Video Processing | FFmpeg | Multi-track stream manipulation |
| Video Transport | SRT | Protocol 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
.tsvideo 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
| Feature | SRT / HLS (Current) | MOQ (POC - Paused) |
|---|---|---|
| Transport Layer | TCP/UDP (SRT) & TCP (HLS) | QUIC (UDP via streams/datagrams) |
| Security | Optional TLS / Standard HTTP | Mandatory TLS 1.3 |
| Viewer Latency | ~2–5 seconds | <1 second (theoretical) |
| Browser Compatibility | Native (HLS via HTML5 video) | Limited (requires WebTransport/WebCodecs support) |
| Ecosystem Maturity | Production-ready industry standard | Emerging / 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 IDfov_service_moq, linking to the customizedfov_moq_outputmodule. - 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:
- Clone our forked MOQ repository and run the command
justat the root to launch the entire MOQ demo stack. - Clone the web-fov repository and switch to the
poc-media-over-quicbranch. It contains a debug route that instructs OBS to stream to the relay athttp://localhost:4443. - Deploy
web-fovlocally usingdocker 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.) - Compile the
poc-media-over-quicbranch of obs-studio-fov withENABLE_FOV_DEBUG_INGESTset to ON in the rootCMakeLists.txt. - Select the service FOV - Multitrack [MoQ] in the OBS settings.
- Set the URL to the locally deployed FOV backend (
http://localhost:4000) and the stream key totest.hang(the.hangsuffix is mandatory for correct routing). - 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
- Media over QUIC
- MoQ: Refactoring the Internet’s real-time media stack
- IETF Media Over QUIC (moq)
- Replacing WebRTC with Media over QUIC - Luke Curley
- https://github.com/moq-dev/moq