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 interactive multi-track stream using FOV.
Prerequisites
Before going live, ensure you have the FOV software installed and running.
Operational Constraints
Because FOV streams sources as independent tracks rather than a single flattened video feed, keep the following operational rules in mind while setting up your workspace:
single-scene & layout lock
FOV operates strictly within a single-scene setup. Scene switching, as well as adding, removing, or reconfiguring sources, is disabled while streaming.
Finalize all scene elements before hitting “Start Streaming”.
automated track isolation & audio mapping
-
Automated Tracking: Track isolation is fully automated by
FOVSystem. Any source currently visible and active in your active scene automatically spawns its own independent stream track. You do not need to manually configure tracks. -
Fixed Audio Mapping: Manual track selections in Advanced Audio Properties are ignored or overwritten. Audio sources map on a strict 1:1 basis depending on the order they were added.
Quickstart Guide
Follow these steps to configure your broadcast:
1. Select the Platform Service
- Launch FOV Software.
- Open Settings from the main dashboard control interface and select the Stream tab in the left sidebar.
- Set the Service dropdown to
FOV - Multitrack.
2. Enter Target Parameters
Populate your connection details:
- Server URL: Enter your target platform API endpoint. Use
https://api.fovapp.livefor the public service, or enter your custom backend API URL if self-hosting (see Developer Documentation). - Stream Key: Insert your authentication token string.
3. Build Your Scene
Add your video and audio sources (game capture, webcam, microphone, desktop audio) to your active scene just as you would in standard OBS.
4. Go Live
- Click Start Streaming in the workspace console interface.
- Open your broadcast URL on https://fovapp.live (or your self-hosted web instance).
Viewers can now watch your stream and interact with individual video layers, such as moving and resizing video tracks dynamically on screen.
Stream Settings
Streaming with FOV Software requires a fundamental shift in how you configure and optimize your encoding settings compared to standard OBS Studio.
Architectural Model: Multi-Track Encoding
Unlike traditional streaming software that flattens your entire scene into a single video feed and audio track, FOV processes and broadcasts each source independently:
-
Independent Encoders: Every active video and audio source in your scene has an encoder attached to it.
-
Per-Track Processing: Each source is ingested, encoded, and sent as a discrete stream track.
-
Bandwidth & CPU Scaling: Broadcasting multiple streams simultaneously multiplies your upload bandwidth requirements and processing load based on the cumulative bitrate and count of active tracks.
supported codecs
- Video: H.264 (
h264), H.265 (h265) - Audio: AAC (
aac), Opus (opus)
Simple Output Mode vs. Advanced Output Mode
Simple Output Mode
In Simple Output Mode, FOV applies uniform bitrate and encoding settings across all audio and video tracks.
bandwidth overhead warning
Using Simple Output Mode can quickly consume far more network bandwidth than required. For example, if your output bitrate is set to 4000 kbps for video and 160 kbps for audio, a scene with 2 video sources and 4 audio sources calculates as:
Because secondary video sources (like webcams or overlay feeds) rarely require the same high bitrate as a primary gameplay feed, uniform settings can strain your network connection unnecessarily.
Advanced Output Mode (Recommended)
To optimize network performance and system resource usage, we strongly recommend switching to Advanced Output Mode. This mode allows you to fine-tune encoding parameters on a per-track basis.
1. Per-Track Video Bitrate & Hardware Encoding
In the Advanced Output settings, you can customize the bitrate and encoder assigned to each video track.
hardware encoding recommendation
Because every video track requires an active encoding pass, using dedicated hardware encoders (NVIDIA NVENC, Intel QSV, or AMD AMF) is strongly recommended.
While software-based encoding (x264) can be used, running multiple concurrent CPU streams places a heavy burden on your processor and will lead to a higher CPU usage.
2. Per-Track Audio Bitrate Adjustment
Advanced settings also enable granular control over individual audio track bitrates, allowing you to allocate higher bitrates to primary audio sources (such as music or main microphones) while dialing down secondary commentary or game audio channels.
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.
Code of Conduct
We Build Together
FOV (Flexible Output View) is built by a small core team and community with one goal: giving viewers ultimate control over their streaming experience.
This Code of Conduct applies to all repositories under the Flexible-Output-View organization (obs-studio-fov, web-fov, documentation-fov), as well as our associated communication channels (GitHub Discussions, issue trackers, and community spaces).
Our Values & Standards
- Respectful collaboration: We value diverse perspectives, open discussion, and mutual respect.
- Constructive feedback: Critique ideas, not people. Be direct, kind, and professional.
- Quality over speed: We ship stable software, not rushed code, prioritizing reliability and care.
- Transparency: Our GitHub Discussions and public channels are the primary places for community input and architectural ideas.
Unacceptable Behavior
To ensure a welcoming, safe, and professional environment, the following behaviors are strictly unacceptable across our repositories and spaces:
- Unwelcome sexual attention, language, or imagery.
- Trolling, insults, derogatory comments, and personal or political attacks.
- Public or private harassment, bullying, or intimidation.
- Publishing others’ private information without explicit permission.
Disagreements & Governance
We resolve technical and project-related disagreements by majority decision within the core team (as outlined in our Contribution Guidelines). Keep discussions constructive, professional, and focused on what’s best for FOV and its users.
Reporting Issues & Enforcement
The core team is responsible for enforcing these standards and holds the authority to remove inappropriate comments, contributions, or ban repeat/severe violators from organization spaces.
- Public Feedback: For general project feedback, ideas, or questions, please use GitHub Discussions.
- Safety Reports: If you encounter harassment, abuse, or safety violations that require confidential intervention, please contact the core team @LucasLoustalot, @RaphxelS or @Slymoz.
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 Frontend - Deployment Guide
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
Security
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
Last Updated: October 5th, 2026
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.
Build & Push Images
Builds and publishes Docker container images for both the backend and frontend to GitHub Packages (GHCR) as packages.
- Triggers: Pushes to
mainanddevbranch. - Runner: GitHub-hosted runners.
- Target: Authenticates with GHCR and builds/pushes
fov-backendandfov-frontendimages tagged with the matching branch/reference name (${{ github.ref_name }}). - Results:
- Success: Container images are successfully built, tagged, and published to GitHub Packages.
- Failure: Build errors or authentication/push failures cause the workflow job to fail.
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.
Deployment Guide
Last Updated: October 5th, 2026
This guide outlines how to deploy the web application stack using your preferred architecture. Choose the method that best fits your environment, whether you prefer building from source, utilizing pre-built container images via GitHub Packages (GHCR), or running natively without Docker.
Choose Your Deployment Approach
- Docker Compose (Recommended): The easiest way to run the entire multi-container stack. Ideal for most homelabbers and standard deployments.
- Standalone Docker CLI: Run individual
docker runcommands within a custom network. Best for users who want granular control over each container without Compose. - Native / Bare Metal: Run the application directly on your host machine’s OS without containerization.
Prerequisites
Before you begin, ensure your host machine is prepared:
- Docker & Docker Compose: Must be installed on your system. Official Installation Guide.
- Git: Required only if you plan to clone the repository and build from source.
- Environment Configuration: See the Environment Variables Reference for required values.
Method 1: Docker Compose Deployment
This method uses Docker Compose to spin up the Database, Backend, and Frontend simultaneously.
Option A: Build from Source (Local Repository)
Use this option if you have cloned the repository and want to build the images locally.
- Set up your environment variables:
cp .env.example .env
Open the .env file and customize the values (passwords, JWT secrets, etc.).
- Create media directories and assign permissions:
mkdir -p ./backend/media/hls
chmod -R 777 ./backend/media
- Build and start the stack:
docker compose up -d --build
tip
You can also use the provided local script: ./deploy-local.sh
Option B: Use Pre-Built Images (GHCR)
Use this option for a faster deployment using stable, pre-built images. Because you are not cloning the repository, you will need to create your own docker-compose.yml file.
- Create your environment file:
Create an
.envfile in your directory and populate it with the required variables (see Reference). - Create media directories and assign permissions:
mkdir -p ./backend/media/hls
chmod -R 777 ./backend/media
- Create the Docker Compose file:
Create a file named
docker-compose.ymland paste the following configuration:
services:
backend:
image: ghcr.io/flexible-output-view/fov-backend:main
restart: unless-stopped
volumes:
- ./backend/media:/var/media
ports:
- "4000:4000" # API
- "9999-10010:9999-10010"
- "9999-10010:9999-10010/udp" # SRT
networks:
- frontend
- backend
depends_on:
fov-db:
condition: service_healthy
environment:
- PORT=4000
- DB_HOST=fov-db
- DB_PORT=5432
- DB_USER=root
- SRT_PORT=9999
- DB_PASSWORD=${DB_PASSWORD}
- DB_NAME=fovwebdb
- FFMPEG_PATH=/usr/local/bin/ffmpeg
- MEDIA_ROOT=/var/media
- SRT_URL=${SRT_URL}
- API_HOSTNAME=${API_URL}
- TWITCH_ID=${TWITCH_ID}
- TWITCH_SECRET=${TWITCH_SECRET}
- JWT_SECRET=${JWT_SECRET}
- JWT_EXPIRES_IN=${JWT_EXPIRES_IN}
frontend:
image: ghcr.io/flexible-output-view/fov-frontend:main
restart: unless-stopped
depends_on:
backend:
condition: service_started
ports:
- "4200:80"
networks:
- frontend
environment:
- API_URL=http://backend:4000
fov-db:
image: postgres:18-bookworm
restart: unless-stopped
volumes:
- fov-db:/var/lib/postgresql
- ./fovwebdb.sql:/docker-entrypoint-initdb.d/init.sql # NOTE: Ensure you provide the initial sql file, or remove this mount if handling the database separately.
networks:
- backend
healthcheck:
test: [ "CMD-SHELL", "pg_isready -U root -d fovwebdb" ]
interval: 10s
timeout: 5s
retries: 10
environment:
POSTGRES_DB: fovwebdb
POSTGRES_USER: root
POSTGRES_PASSWORD: ${DB_PASSWORD}
networks:
frontend:
backend:
internal: true
volumes:
fov-db:
- Download the initial database sql file and save it to
fovwebdb.sqlin the same directory as your docker compose. - Pull images and start the stack:
docker compose pull
docker compose up -d
Local Endpoints Overview
Once the stack is running on default settings, your services are available at:
- Frontend Web App:
http://localhost:4200 - API Backend:
http://localhost:4000 - Stream Ingest (SRT): Ports
9999-10010(TCP/UDP) - Database:
localhost:5432
warning
Default configurations bind services directly to your host machine. This is not recommended for public-facing production environments. See the Production Guide below.
Method 2: Standalone Docker CLI Deployment
If you prefer to manage containers manually without Docker Compose, follow these steps to isolate the stack on a custom bridge network.
1. Create a custom network:
docker network create fov-network
2. Deploy the Database: If using pre-built images, please download the initial database sql file
docker run -d \
--name fov-db \
--network fov-network \
-v fov-db:/var/lib/postgresql \
-v $(pwd)/fovwebdb.sql:/docker-entrypoint-initdb.d/init.sql \
-e POSTGRES_DB=fovwebdb \
-e POSTGRES_USER=root \
-e POSTGRES_PASSWORD=your_secure_db_password_here \
postgres:18-bookworm
3. Deploy the Backend:
If using pre-built images, replace fov-backend with ghcr.io/flexible-output-view/fov-backend:main in the command below.
docker run -d \
--name backend \
--network fov-network \
-p 4000:4000 \
-p 9999-10010:9999-10010 \
-p 9999-10010:9999-10010/udp \
-v $(pwd)/backend/media:/var/media \
-e PORT=4000 \
-e DB_HOST=fov-db \
-e DB_PORT=5432 \
-e DB_USER=root \
-e DB_PASSWORD=your_secure_db_password_here \
-e DB_NAME=fovwebdb \
-e FFMPEG_PATH=/usr/local/bin/ffmpeg \
-e MEDIA_ROOT=/var/media \
-e SRT_PORT=9999 \
-e SRT_URL=localhost \
-e API_HOSTNAME=http://localhost:4000/api \
-e JWT_SECRET=your_jwt_secret_here \
-e JWT_EXPIRES_IN=7d \
fov-backend
4. Deploy the Frontend:
If using pre-built images, replace fov-frontend with ghcr.io/flexible-output-view/fov-frontend:main in the command below.
docker run -d \
--name frontend \
--network fov-network \
-p 4200:80 \
-e API_URL=http://backend:4000 \
fov-frontend
Method 3: Native / Bare Metal Deployment
To run the application directly on your host operating system (without Docker):
- Backend Setup: Refer to the backend documentation for configuring a native Node.js and FFmpeg environment.
- Frontend Setup: Refer to the frontend documentation for running the Angular application natively via Node.js and the Angular CLI.
Production Deployment (Nginx Proxy Manager)
For production environments, the application stack should sit behind a reverse proxy like Nginx Proxy Manager (NPM). This secures traffic over standard HTTP/HTTPS ports (80/443) and safely hides internal database and API ports from the public internet.
hardware & bandwidth considerations
High Network Bandwidth: Ingesting live video feeds and serving HLS chunks consumes massive inbound and outbound data.
Compute Limits: Every active live stream spawns a dedicated FFmpeg process. Provision your server’s CPU accordingly based on your expected concurrent streams.
1. Create a shared external network:
docker network create nginx-proxy-network
2. Deploy Nginx Proxy Manager: Deploy this NPM stack connected to the shared network:
services:
app:
image: 'jc21/nginx-proxy-manager:2.15.1'
restart: unless-stopped
ports:
- '80:80' # Public HTTP
- '443:443' # Public HTTPS
- '81:81' # Admin Panel
environment:
TZ: "Europe/Paris"
volumes:
- ./data:/data
- ./letsencrypt:/etc/letsencrypt
networks:
- nginx-proxy-network
networks:
nginx-proxy-network:
external: true
3. Use the Production Override File (docker-compose.prod.yml):
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:
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
4. Deploy the application with production overrides:
Update your .env file with production domain names, then start the stack:
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d --build
5. Configure Proxy Hosts in NPM:
Log into NPM (port 81) and map your domains to the internal Docker containers:
- Frontend Host: Forward to container
frontendon port80(Enable Force SSL). - Backend API Host: Forward to container
backendon port4000(Enable Force SSL).
(Note: Stream ingestion ports 9999-10010 bypass the proxy and remain publicly accessible for your SRT streaming sources).
Appendix: Architecture & Configuration Reference
Environment Variables Reference
These core variables must be defined in your .env file for the application to run.
| Variable | Description | Default / Example |
|---|---|---|
DB_PASSWORD | Secure root password for PostgreSQL. | your_secure_db_password_here |
SRT_URL | Hostname or IP for SRT video streaming endpoints. | localhost |
API_URL | Publicly accessible URL pointing to the backend API. | http://localhost:4000/api |
JWT_SECRET | Secret key for generating JSON Web Tokens. | your_jwt_secret_here |
JWT_EXPIRES_IN | Token expiration timeframe. | 7d |
TWITCH_ID | (Deprecated soon) Twitch API client ID. | your_twitch_id_here |
TWITCH_SECRET | (Deprecated soon) Twitch API client secret. | your_twitch_secret_here |
Docker Volumes & Storage
| Volume / Mount | Target Container | Purpose |
|---|---|---|
./backend/media:/var/media | Backend | Stores persistent media assets and generated HLS video segments. |
fov-db:/var/lib/postgresql | Database | Named volume ensuring database records persist across restarts. |
./fovwebdb.sql:/docker-entrypoint-initdb.d/init.sql | Database | Seeds the schema and initial data on first-time initialization. |
Docker Networking
| Network Name | Type | Purpose |
|---|---|---|
frontend | Bridge | Connects the frontend to the backend API for web traffic. |
backend | Internal Bridge | Completely isolates the database so it is exclusively reachable by the backend. |
nginx-proxy-network | External Bridge | Used to route traffic securely through an external reverse proxy (Production only). |
Internal Container Variables (Hardcoded in Compose)
For users modifying the architecture, these are the internal routing variables configured within the Docker Compose files.
| Variable | Target | Default Value | Description |
|---|---|---|---|
PORT | Backend | 4000 | Internal port that the Node.js backend listens on. |
DB_HOST | Backend | fov-db | Internal Docker network hostname to reach the database. |
DB_PORT | Backend | 5432 | Port number for database communication. |
DB_USER | Backend | root | Database user profile name. |
DB_NAME | Backend | fovwebdb | Exact target database name. |
SRT_PORT | Backend | 9999 | Base port for stream ingest. Scaling this dictates max streams. |
FFMPEG_PATH | Backend | /usr/local/bin/ffmpeg | Absolute path inside the container where FFmpeg is installed. |
MEDIA_ROOT | Backend | /var/media | Directory mapped for persistent media and HLS storage. |
POSTGRES_DB | Database | fovwebdb | Instructs PostgreSQL to automatically create a database on startup. |
POSTGRES_USER | Database | root | Administrative user account created for the database instance. |
API_URL | Frontend | http://backend:4000 | Internal route via Docker DNS pointing to the backend API. |
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:
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 "<span class="katex"><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathnormal" style="margin-right:0.02778em;">O</span><span class="mord mathnormal" style="margin-right:0.10903em;">U</span><span class="mord mathnormal" style="margin-right:0.13889em;">TP</span><span class="mord mathnormal" style="margin-right:0.10903em;">U</span><span class="mord"><span class="mord mathnormal" style="margin-right:0.13889em;">T</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3283em;"><span style="top:-2.55em;margin-left:-0.1389em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight" style="margin-right:0.02778em;">D</span></span></span></span><span class="vlist-s"></span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mord mathnormal" style="margin-right:0.07847em;">I</span><span class="mord mathnormal" style="margin-right:0.00773em;">R</span><span class="mord">/</span><span class="mord mathnormal" style="margin-right:0.10764em;">f</span><span class="mord mathnormal">i</span><span class="mord mathnormal">rs</span><span class="mord mathnormal">t</span><span class="mord">.</span><span class="mord mathnormal">m</span><span class="mord">3</span><span class="mord mathnormal">u</span><span class="mord">8"</span><span class="mord mathnormal" style="margin-right:0.10764em;">ff</span><span class="mord mathnormal">m</span><span class="mord mathnormal">p</span><span class="mord mathnormal">e</span><span class="mord mathnormal" style="margin-right:0.03588em;">g</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">−</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.7778em;vertical-align:-0.1944em;"></span><span class="mord mathnormal" style="margin-right:0.03588em;">y</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">−</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.6944em;"></span><span class="mord mathnormal">i</span><span class="mord">"</span></span></span></span>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
What’s next ?
Context
FOV is a student project developed as part of our EIP. Active development runs until February 2027. This document explains what happens after that date.
1. What we’re shipping before February 2027
We have four pillars, all planned for delivery before the EIP defense:
| Pillar | Scope | Status target |
|---|---|---|
| person — COMPTES | User accounts, personal space, authentication | Delivered |
| groups — SOCIAL | Follows, interactions between users | Delivered |
| forum — DISCUSSION | Live chat between users | Delivered |
| build — MAINTENANCE | Stability, updates, dependency management | Delivered |
These are the features we commit to. Anything else is out of scope.
2. After February 2027
We will probably not maintain the project actively.
We’re students. After the EIP, we move on to jobs, internships, or other projects. We don’t have a company or funding behind FOV. Any maintenance will be best-effort, driven by personal motivation or by new contributors.
That doesn’t mean the project is dead. We’re leaving it in a state where it can be forked or continued by anyone — including us, if we come back to it.
3. What we leave in the repo
- Full source code (backend, frontend and OBS fork).
- Docker Compose setup that runs the whole stack locally.
README.mdwith setup, architecture overview, and known limitations.SECURITY.mdwith our current security posture and known gaps.- Documentation for the OBS-FOV build (install, start a stream, configure the endpoint).
- Release artifacts for Windows, macOS, and Linux.
- Open GitHub issues covering known bugs, unimplemented features, and security improvements.
good first issuelabels for newcomers.help wantedlabels for issues that need specific expertise (HLS, SRT, multi-track sync).- CI running on pull requests (build + basic tests).
4. If you want to take over
- Fork freely. The license allows it.
- Open a pull request if you want to contribute back. Reviews are best-effort — expect weeks, not days.
- Want to become a maintainer? Open an issue titled
Maintainer applicationwith who you are, what you want to maintain, and how much time you can commit per month.
If we become unreachable and a fork becomes the de-facto upstream, we’d appreciate it if its maintainers state that clearly in their README and keep following the same security process.
5. Archiving
We are not planning to archive. Archiving would make the repo read-only, blocking future contributions.
We’d only consider it after 24 months of complete inactivity, and only cleanly:
- Final commit tagged
final-archive. - A notice at the top of
README.mdpointing to any active fork. - Handover notes (section 6) up to date.
- Dependency snapshot documented.
6. Honest status report for whoever picks it up
What works well
- Multi-stream HLS playback with track synchronization.
- Layout editor (position, size, z-order, visibility, volume) with per-stream persistence.
- OBS-FOV build producing multi-track SRT streams.
- JWT-based authentication.
- Docker Compose setup for local dev.
What is fragile
- Security has known gaps — see
SECURITY.md. - OBS fork tracks an old upstream. Rebuilding against a newer OBS is non-trivial.
Where to start
- Read
README.mdandSECURITY.md. - Run the stack with Docker Compose.
- Pick a
good first issue. - Run
npm auditon both backend and frontend. - Migrate layout persistence to PostgreSQL — small, high-value.
7. TL;DR
- Shipping before February 2027: COMPTES, SOCIAL, DISCUSSION, MAINTENANCE.
- Maintained after? Probably not actively. Best-effort.
- Archived? No, unless 24 months of inactivity.
- Forkable? Yes, under the project license.
- Documented for takeover? Yes — this file,
README.md,SECURITY.md, and open issues.
If you pick it up: welcome, and good luck.
Document Version: 1.0
Last Updated: October 2026
Applies from: February 2027 (after EIP defense)
Acknowledgements to Contributors
Purpose of This File
This file aims to thank and credit external contributors who have participated in the development of Flexible Output View (FOV).
It helps keep a record of external contributions received, the work carried out, their review process, and their integration into the project.
In the context of this contribution, an external contribution is considered valid when it corresponds to a code modification, a documentation correction, a translation, the addition of tests, a properly documented bug report, or an external code review.
This document presents the external contributions received, reviewed, and integrated into the dev branch, as well as the contributors behind them.
External Contributions
Hugo Payet — @SizzleUnrlsd
Project: web-fov
Pull Request: #108 — ci: ADD backend and frontend coverage reporting
Hugo Payet, under the GitHub username @SizzleUnrlsd, contributed to the web-fov project by working on the implementation of backend and frontend test coverage tracking.
This contribution followed issue #61 — Add CI Test Coverage Reporting and README Badge, whose goal was to integrate test coverage generation into the CI workflow and display an indication of this coverage in the README.md.
Pull Request #108 notably included the following commits:
ci(coverage): ADD backend and frontend coverage reportingci(coverage): FIX fork uploads and backend test orderingfix(frontend): FIX stream availability filtering
The Pull Request was then reviewed by the group members. RaphxelS performed a review and approved the changes, after which the Pull Request was merged into the dev branch.
Thank you to Hugo Payet (@SizzleUnrlsd) for this contribution and for his work on improving the project’s quality tracking.
Maël-Mamadou Diagne — @Mogza
Project: obs-studio-fov
Pull Request: #51 — UPDATE: About.Info to introduce FOV as a fork in italian language
Maël-Mamadou Diagne, under the GitHub username @Mogza, contributed to the obs-studio-fov project by adding the Italian translation of the About.Info section.
This contribution followed issue #28 — FOV about page translation, which requested localizing the content of About.Info into the different languages supported by the application.
The contribution added the Italian language file it-IT.ini and adapted the FOV presentation text into Italian.
Pull Request #51 included a modification of the localization file, and the 5 automated checks were validated. It was then reviewed, approved, and merged into the dev branch.
Thank you to Maël-Mamadou Diagne (@Mogza) for this contribution and for helping make FOV accessible to Italian users.
Albéric Flour de Saegher — @albe2
Project: obs-studio-fov
Pull Request: #52 — DOC: adding the spanish translation of About.info
Albéric Flour de Saegher, under the GitHub username @albe2, contributed to the obs-studio-fov project by adding the Spanish translation of the About.Info section.
This contribution followed issue #28 — FOV about page translation, which requested localizing the content of About.Info into the different languages supported by the application.
The contribution added the Spanish language file es-ES.ini and adapted the FOV presentation text into Spanish. The Pull Request consisted of a single commit, DOC: adding the spanish translation of About.info, corresponding to 1 addition and 1 deletion.
Pull Request #52 was then reviewed by Slymoz, who self-requested a review, approved the changes with the comment “lgtm”, and merged commit af71499 into the dev branch. The Pull Request was successfully merged and closed.
Thank you to Albéric Flour de Saegher (@albe2) for this contribution and for helping make FOV accessible to Spanish users.
Acknowledgements
We thank Hugo Payet (@SizzleUnrlsd), Maël-Mamadou Diagne (@Mogza), and Albéric Flour de Saegher (@albe2) for their external contributions to the FOV project.
Their contributions were reviewed, validated, and integrated into the dev branch, and directly contribute to the improvement and evolution of the project.
Their contributions were reviewed, validated, and integrated into the dev branch, and directly contribute to the improvement and evolution of the project.