An HLS/IPTV streaming server for creating multi-channel streaming services from your media library.
- Multi-channel HLS streaming
- Jellyfin compatibility a priority; channel guide implemented
- Dynamic playlists with schedule-based content switching
- Media buckets for organizing content into collections
- Schedule blocks for time-based programming (e.g., morning cartoons, prime time movies)
- Progressive playback mode for sequential series progression across days
- IPTV support with M3U playlists and XMLTV EPG generation
- Schedule time tracking for continuous playback positioning
- Manual and automatic media library scanning (automatic scanning disabled by default)
- API key and session-based authentication
- Admin web interface for channel and media management
- Docker deployment support
Channels: The main streaming entities. Each channel has:
- A unique slug (URL identifier)
- Streaming configuration (resolution, bitrate, FPS)
- Media assigned via buckets or direct assignment
- Schedule blocks for time-based programming
- EPG (Electronic Program Guide) generation
Media Buckets: Collections of media files that can be assigned to channels. Buckets support:
- Global buckets (shared across channels)
- Channel-specific buckets
- Media filtering and organization
- Series hierarchy extraction
Schedule Blocks: Time-based programming rules that:
- Define when specific content plays (time ranges, days of week)
- Link buckets to time slots
- Support multiple playback modes (sequential, shuffle, random)
- Enable progressive playback for series (single-series buckets only)
Progressive Playback: Tracks playback position within sequential series:
- Works only with buckets containing a single series
- Continues across days (Day 1: s1e1, s1e2... Day 2: s1e4, s1e5...)
- Persists across EPG regenerations
- Automatically disabled for multi-series buckets
EPG (Electronic Program Guide): Generates XMLTV-compatible program listings:
- Projects virtual timeline onto real-world time
- Uses
schedule_start_timeas the reference point - Updates dynamically based on current playback position
- Supports 48-hour lookahead by default
Database Schema: PostgreSQL stores:
- Channel configurations and state
- Media file metadata and library information
- Bucket definitions and media assignments
- Schedule block configurations
- EPG cache and progression tracking
- User sessions and authentication
- Media Scanning: Media files are scanned and metadata extracted
- Bucket Assignment: Media is organized into buckets
- Channel Configuration: Channels are created and buckets assigned
- Schedule Setup: Schedule blocks define time-based programming
- Stream Generation: FFmpeg creates HLS segments from media files
- EPG Generation: EPG is generated based on schedule and current position
- Playback: Clients request HLS playlists and segments
- Node.js 18+ and npm 9+
- FFmpeg installed and in PATH
- PostgreSQL (required - all features including channels, media management, scheduling, and EPG depend on it)
Before starting, ensure you have:
- Node.js 18+ and npm 9+ installed (
node --version,npm --version) - FFmpeg installed and in PATH (
ffmpeg -version) - PostgreSQL installed and running (
psql --version) - Database created (default:
hls_streaming) or will be created during setup - Media directories exist and are readable
- Port 8080 (or your chosen port) is available
The fastest way to get started:
# Clone the repository
git clone <repository-url>
cd hls-streaming-server-v1.0
# Install dependencies
npm install --no-bin-links
# (Recommended: --no-bin-links avoids symlink issues on Windows/WSL/Docker)
# Run interactive setup (configures everything automatically)
npm run setup
# Build and start
npm run build
npm startThe interactive setup script (npm run setup) will guide you through:
- Media directory configuration
- API key generation
- Streaming quality settings
- Database setup (PostgreSQL)
- Automatic database migrations
-
Clone the repository:
git clone <repository-url> cd hls-streaming-server-v1.0
-
Configure environment:
cp .env.example .env
-
Update
docker-compose.ymlto include PostgreSQL and mount your media directories:Open
docker-compose.ymland add a PostgreSQL service, then update volumes:version: '3.8' services: postgres: image: postgres:15-alpine container_name: hls-postgres environment: POSTGRES_DB: hls_streaming POSTGRES_USER: postgres POSTGRES_PASSWORD: ${DB_PASSWORD:-postgres} volumes: - postgres_data:/var/lib/postgresql/data ports: - "5432:5432" healthcheck: test: ["CMD-SHELL", "pg_isready -U postgres"] interval: 10s timeout: 5s retries: 5 hls-server: build: . container_name: hls-iptv-server depends_on: postgres: condition: service_healthy ports: - "8080:8080" - "8081:8081" volumes: # Mount your media directories here # Replace /path/to/your/media with your actual media paths - /media/movies:/media/movies:ro - /media/shows:/media/shows:ro # - /home/user/Videos:/media/videos:ro # HLS output (persisted) - ./hls_output:/app/hls_output # Temp directory - ./temp:/app/temp environment: # ... (see existing docker-compose.yml for full config) DB_HOST: postgres DB_PORT: 5432 DB_NAME: hls_streaming DB_USER: postgres DB_PASSWORD: ${DB_PASSWORD:-postgres} volumes: postgres_data:
Important Notes:
- Use
:ro(read-only) flag for media volumes to prevent accidental modifications - Use absolute paths on the host system (e.g.,
/media/movies, not~/movies) - On Windows, use Windows-style paths:
C:\Media\Movies:/media/movies:ro - You can mount multiple directories by adding more volume entries
- Use
-
Edit
.envfile to match the mounted paths:# Required: Media directories (comma-separated) # These paths should match the mount points inside the container MEDIA_DIRECTORIES=/media/movies,/media/shows,/media/videos # Required: API key (generate a secure random string) API_KEY=your-secure-random-api-key-here # Optional: Server port PORT=8080 # Database configuration (for Docker, use service name as host) DB_HOST=postgres DB_PORT=5432 DB_NAME=hls_streaming DB_USER=postgres DB_PASSWORD=postgres DB_POOL_MIN=2 DB_POOL_MAX=10 DB_SSL=false
Note: The paths in
MEDIA_DIRECTORIESshould be the container paths (inside/media), not the host paths. -
Run database migrations:
# After containers are running docker-compose exec hls-server npm run migrate
-
Start with Docker Compose:
docker-compose up -d
-
View logs:
# All services docker-compose logs -f # Just the HLS server docker-compose logs -f hls-server # Just PostgreSQL docker-compose logs -f postgres
Example: Mounting Multiple Media Directories
If you have media in different locations:
volumes:
- /mnt/nas/movies:/media/movies:ro
- /mnt/nas/tv-shows:/media/shows:ro
- /home/user/Downloads:/media/downloads:ro
- ./hls_output:/app/hls_output
- ./temp:/app/tempThen in .env:
MEDIA_DIRECTORIES=/media/movies,/media/shows,/media/downloadsThe easiest way to get started is using the interactive setup script:
-
Clone the repository:
git clone <repository-url> cd hls-streaming-server-v1.0
-
Install dependencies:
npm install
Note: If you encounter symlink permission errors (common on Windows or in Docker), use:
npm install --no-bin-links
-
Run interactive setup:
npm run setup
This will guide you through:
- Configuring media directories
- Setting up API keys
- Configuring streaming quality
- Setting up PostgreSQL database (required)
- Running database migrations
-
Build and start:
npm run build npm start
If you prefer manual configuration:
-
Clone and install:
git clone <repository-url> cd hls-streaming-server-v1.0 npm install
Note: If you encounter symlink permission errors (common on Windows, WSL, or in Docker), use:
npm install --no-bin-links
-
Configure environment:
cp .env.example .env # Edit .env with your configuration -
Build the application:
npm run build
-
Run database migrations (required):
npm run migrate
Note:
npm run migrateis cross-platform:- Linux/Mac: Uses
migrate.sh(bash script with psql) - works without building first - Windows: Uses
migrate.ts(TypeScript/Node.js) - requires dependencies installed
You can also use the platform-specific commands directly:
npm run migrate:sh- Force use bash script (Linux/Mac)npm run migrate:ts- Force use TypeScript script (Windows/requires build)
Important: All migrations will be applied automatically. The migration system tracks applied migrations and will skip already-applied ones on subsequent runs.
- Linux/Mac: Uses
-
Start the server:
npm start
Or run in development mode with auto-reload:
npm run dev
The server uses a .env file for configuration. You can either:
-
Use the interactive setup (recommended for first-time setup):
npm run setup
-
Manually edit
.env(copy from.env.example)
Settings Precedence: Settings configured via the Admin UI (stored in the database) take precedence over .env file values. If a setting exists in the database, the .env value is ignored. This allows you to change settings at runtime without restarting the server.
Edit .env file with your configuration:
# Media directories (comma-separated paths, this step is not deprecated, as library creation takes place in the admin UI )
MEDIA_DIRECTORIES=/media/movies,/media/shows,/media/anime
# API key for authentication (generate a secure random string!)
API_KEY=your-secure-api-key-here
# Server port
PORT=8080# Video quality
DEFAULT_VIDEO_BITRATE=1500000 # 1.5 Mbps
DEFAULT_AUDIO_BITRATE=128000 # 128 kbps
DEFAULT_RESOLUTION=1920x1080 # 1080p
DEFAULT_FPS=30
DEFAULT_SEGMENT_DURATION=6 # 6 seconds per segment
# FFmpeg encoding preset (affects quality vs speed)
# Options: ultrafast, superfast, veryfast, faster, fast, medium, slow, slower, veryslow
# Default: 'fast' (good balance of quality and speed)
# Faster presets = lower quality (more blocky), slower = better quality but more CPU
FFMPEG_PRESET=fast
# Concurrent streams
MAX_CONCURRENT_STREAMS=8# NVIDIA GPU
HW_ACCEL=nvenc
# Intel Quick Sync
HW_ACCEL=qsv
# Apple VideoToolbox (macOS)
HW_ACCEL=videotoolbox
# Software encoding (default)
HW_ACCEL=noneAutomatic library scanning is disabled by default. To enable it:
Option 1: Via Admin UI (Recommended)
- Navigate to Settings (⚙️ icon in top-right) → Enable "Automatic Library Scanning"
- This saves the setting to the database and takes precedence over
.env
Option 2: Via .env file
# Enable automatic library scanning
ENABLE_AUTO_SCAN=true
# Set scan interval (in minutes)
AUTO_SCAN_INTERVAL=60Precedence: Settings saved via Admin UI (stored in database) take precedence over .env file values. If a setting exists in the database, the .env value is ignored.
Note: Even with automatic scanning disabled, you can manually scan libraries via:
- Admin UI: Navigate to Libraries tab → Click "Scan" button
- API:
POST /api/libraries/{libraryId}/scan
See .env.example for all available configuration options.
Once running, access your server at:
- Admin Panel: http://localhost:8080/admin
- API: http://localhost:8080/api/channels
- Stream: http://localhost:8080/{channel-slug}/master.m3u8
- EPG: http://localhost:8080/epg.xml
- IPTV M3U: http://localhost:8080/playlist.m3u
Using the API:
curl -X POST http://localhost:8080/api/channels \
-H "X-API-Key: your-api-key" \
-H "Content-Type: application/json" \
-d '{
"name": "Movies Channel",
"slug": "movies",
"resolution": "1920x1080",
"videoBitrate": 2500000,
"audioBitrate": 128000,
"fps": 30,
"segmentDuration": 6
}'curl -X POST http://localhost:8080/api/channels/{channelId}/start \
-H "X-API-Key: your-api-key"VLC Media Player:
Media ? Open Network Stream ? http://localhost:8080/movies/master.m3u8
IPTV Apps:
- Jellyfin: Add as IPTV source
- Kodi: Install IPTV Simple Client addon
- TiviMate (Android): Add playlist URL
- Web Browser: Use hls.js or Video.js
- Many More
Web Browser Example:
<video id="video" controls></video>
<script src="https://cdn.jsdelivr.net/npm/hls.js@latest"></script>
<script>
const video = document.getElementById('video');
const hls = new Hls();
hls.loadSource('http://localhost:8080/movies/master.m3u8');
hls.attachMedia(video);
</script>Jellyfin can use this HLS server as an IPTV source, giving you a live TV experience with electronic program guide (EPG) support.
- Jellyfin server installed and running
- HLS Streaming Server running and accessible from Jellyfin
- At least one channel created and started
- Open Jellyfin Dashboard
- Navigate to Plugins → Catalog
- Find and install "Live TV" plugin (comes pre-installed in most Jellyfin versions)
- Restart Jellyfin if prompted
- Go to Dashboard → Live TV → Tuner Devices
- Click + (Add) button
- Select "M3U Tuner" from the dropdown
- Configure the tuner:
Tuner Settings:
File or URL: http://YOUR-SERVER-IP:8080/playlist.m3u
Example: http://192.168.1.100:8080/playlist.m3u
or http://localhost:8080/playlist.m3u (if on same machine)
User agent: (leave blank or use default)
Simultaneous stream limit: 3 (adjust based on your server capacity)
Advanced Options (optional):
- Auto-loop live streams: Enabled (recommended for continuous channels)
- Enable stream probing: Enabled (helps with codec detection)
- Click Save
- In Dashboard → Live TV → Guide Data Providers
- Click + (Add) to add a new guide provider
- Select "XMLTV" from the dropdown
- Configure the EPG:
XMLTV Settings:
File or URL: http://YOUR-SERVER-IP:8080/epg.xml
Example: http://192.168.1.100:8080/epg.xml
or http://localhost:8080/epg.xml
Refresh guide every: 2 hours (recommended)
Days of guide data: 2 (matches HLS server's 48-hour lookahead)
- Click Save
- Navigate to Live TV in the Jellyfin main menu
- You should see:
- Channels tab showing your HLS channels
- Guide tab showing the program schedule with show titles and times
- Channel logos (if configured in the HLS server)
Channels not appearing:
- Verify the M3U URL is accessible:
curl http://YOUR-SERVER-IP:8080/playlist.m3u - Check Jellyfin logs: Dashboard → Logs → Server
- Ensure channels are started in the HLS admin panel
EPG data not showing:
- Verify the XMLTV URL is accessible:
curl http://YOUR-SERVER-IP:8080/epg.xml - Check that EPG generation is enabled in HLS server:
ENABLE_EPG=truein.env - Refresh guide data manually
- Ensure channel names match between M3U and XMLTV
Playback issues:
- Check network connectivity between Jellyfin and HLS server
- Verify FFmpeg is running for the channel (check HLS server logs)
- Try increasing Simultaneous stream limit in tuner settings
- Enable Direct Play in Jellyfin playback settings
Guide refresh failing:
- Check Jellyfin scheduled tasks for errors
- Verify no firewall blocking EPG URL
- Try reducing Refresh guide every to 4 hours if too frequent
NOTE: Jellyfin caches EPG and stream segments (if not passthru,) which can make it appear as if the server is streaming incorrect content. This generally only happens under rapid testing or frequent playlist updating. For general usage, it probably isn't an issue.
Custom Channel Numbers:
Edit /admin/ to assign specific channel numbers. Jellyfin will respect the tvg-chno attribute in the M3U playlist.
Channel Groups: The HLS server automatically assigns channels to groups based on configuration. These appear as filters in Jellyfin's Live TV interface.
Recording (DVR): Jellyfin can record live streams. Go to Dashboard → Live TV → Recording to configure:
- Recording path
- Post-processing options
- Series recording rules
- Create a library:
curl -X POST http://localhost:8080/api/libraries \
-H "X-API-Key: your-api-key" \
-H "Content-Type: application/json" \
-d '{
"name": "My Movies",
"path": "/media/movies",
"category": "movies",
"enabled": true,
"recursive": true
}'- Scan for media (manual scan - automatic scanning is disabled by default):
curl -X POST http://localhost:8080/api/libraries/{libraryId}/scan \
-H "X-API-Key: your-api-key"Note: Automatic library scanning is disabled by default. You can enable it in Settings (Admin UI) or by setting ENABLE_AUTO_SCAN=true in your .env file.
- Create a bucket:
curl -X POST http://localhost:8080/api/buckets \
-H "X-API-Key: your-api-key" \
-H "Content-Type: application/json" \
-d '{
"name": "Action Movies",
"bucketType": "global",
"description": "High-octane action films"
}'- Add media to bucket:
curl -X POST http://localhost:8080/api/buckets/{bucketId}/media \
-H "X-API-Key: your-api-key" \
-H "Content-Type: application/json" \
-d '{
"mediaIds": ["media-id-1", "media-id-2"]
}'- Assign bucket to channel:
curl -X POST http://localhost:8080/api/channels/{channelId}/buckets \
-H "X-API-Key: your-api-key" \
-H "Content-Type: application/json" \
-d '{
"bucketId": "bucket-id"
}'Create time-based programming:
curl -X POST http://localhost:8080/api/schedules/channels/{channelId}/blocks \
-H "X-API-Key: your-api-key" \
-H "Content-Type: application/json" \
-d '{
"name": "Morning Cartoons",
"startTime": "06:00:00",
"endTime": "12:00:00",
"dayOfWeek": [0,1,2,3,4,5,6],
"bucketId": "cartoons-bucket-id",
"playbackMode": "sequential",
"priority": 1,
"enabled": true
}'Playback Modes:
- Sequential (Progressive): Plays media in order, with progression tracking. Note: Only works with buckets containing a single series. Progression continues across days (Day 1: s1e1, s1e2, s1e3... Day 2: s1e4, s1e5, s1e6...) and persists across EPG regenerations.
- Shuffle: Randomizes order once, then plays sequentially
- Random: Shuffles order each time, untested, and may introduce issues with EPG, which a lot of infrastructure relies on. TODO
Then enable dynamic playlists on the channel:
curl -X PATCH http://localhost:8080/api/channels/{channelId} \
-H "X-API-Key: your-api-key" \
-H "Content-Type: application/json" \
-d '{
"useDynamicPlaylist": true
}'XMLTV Format (for IPTV players):
curl http://localhost:8080/epg.xmlJSON Format (for specific channel):
curl http://localhost:8080/api/epg/channels/{channel-slug}API documentation is available in OpenAPI format:
- OpenAPI Spec:
openapi.yaml - Interactive Docs: http://localhost:8080/api-docs (if enabled)
| Method | Endpoint | Description | Auth Required |
|---|---|---|---|
| GET | /health |
Health check | No |
| GET | /api/channels |
List channels | No |
| POST | /api/channels |
Create channel | Yes |
| POST | /api/channels/:id/start |
Start streaming | Yes |
| POST | /api/channels/:id/stop |
Stop streaming | Yes |
| GET | /epg.xml |
EPG (XMLTV) | No |
| GET | /:slug/master.m3u8 |
Master playlist | No |
| GET | /:slug/stream.m3u8 |
Media playlist | No |
| GET | /playlist.m3u |
IPTV playlist (M3U) | No |
| GET | /api/media/count |
Total media files count | No |
| PUT | /api/channels/:id/schedule-time |
Update schedule start time | Yes |
# Install dependencies
npm install
# If you encounter symlink errors, use: npm install --no-bin-links
# Run in development mode (auto-reload)
npm run dev
# Run tests
npm test
# Build the application
npm run build
# Lint code
npm run lint
# Format code
npm run format-
Check FFmpeg is installed:
ffmpeg -version
-
Verify database is running and migrations are applied:
# Check database connection psql -h localhost -U postgres -d hls_streaming -c "SELECT version FROM schema_migrations ORDER BY version;" # Run migrations if needed npm run migrate
-
Verify media directories exist and are readable
-
Check logs:
# Docker docker-compose logs -f # Local # Check console output or logs directory
-
Supported formats:
.mp4,.mkv,.avi,.mov,.wmv,.flv,.webm,.m4v,.ts,.mpg,.mpeg -
Verify channel has media assigned: Use the admin panel at
/adminto check channel configuration and assigned buckets
- Lower video bitrate:
DEFAULT_VIDEO_BITRATE=1000000 - Lower resolution:
DEFAULT_RESOLUTION=1280x720 - Enable hardware acceleration if available:
HW_ACCEL=nvenc
- Ensure user has read access to media directories
- For Docker, check volume mount permissions
- Check PostgreSQL user permissions if database operations fail
-
Verify PostgreSQL is running:
# Local sudo systemctl status postgresql # Docker docker-compose ps postgres
-
Test connection:
psql -h localhost -U postgres -d hls_streaming -c "SELECT 1;" -
Check database exists:
psql -h localhost -U postgres -l | grep hls_streaming -
Verify credentials in
.env:- Check
DB_HOST,DB_PORT,DB_NAME,DB_USER,DB_PASSWORD - For Docker, use service name as host (e.g.,
DB_HOST=postgres)
- Check
See the Migration Guide section below.
-
Check channel has media assigned:
- Use admin panel at
/admin - Verify buckets are assigned to channel
- Verify buckets contain media files
- Use admin panel at
-
Check FFmpeg logs:
- Look for FFmpeg errors in server logs
- Verify media file paths are correct
- Check file permissions
-
Verify schedule blocks (if using dynamic playlists):
- Check schedule blocks are enabled
- Verify time ranges are correct
- Ensure buckets are assigned to blocks
-
Check EPG is enabled (NEVER disable this):
ENABLE_EPG=true
-
Verify channel has
schedule_start_timeset:- Use admin panel to check/update schedule time
- Or use API:
PUT /api/channels/:id/schedule-time
-
Check channel has media:
- EPG requires media to generate program listings
The system uses database migrations that are applied automatically:
- Initial schema (channels, media_files, libraries, buckets)
- Schedule blocks support
- EPG cache tables
- Progression tracking
- Schedule time tracking
- Additional indexes for performance
- Schema updates and optimizations
- ...
# Using psql
psql -h localhost -U postgres -d hls_streaming -c "SELECT version, applied_at FROM schema_migrations ORDER BY version;"
# Using migration script
npm run migrate
# The script will show which migrations are already appliedAutomatic (Recommended - Cross-Platform):
npm run migrateThis automatically detects your platform:
- Linux/Mac: Uses
migrate.sh(bash script with psql)- Works without building the application first
- Checks which migrations are already applied
- Applies only new migrations
- Shows detailed progress
- Windows: Uses
migrate.ts(TypeScript/Node.js)- Requires dependencies installed (
npm install) - Uses the TypeScript Database class
- Same migration tracking and progress
- Requires dependencies installed (
Platform-Specific Commands:
# Force use bash script (Linux/Mac)
npm run migrate:sh
# Force use TypeScript script (Windows/requires build)
npm run build
npm run migrate:tsDocker:
docker-compose exec hls-server npm run migrateMigration already applied error:
- This is normal - migrations are idempotent
- The system tracks applied migrations and skips them
Connection refused:
- Verify PostgreSQL is running
- Check database credentials in
.env - For Docker, ensure PostgreSQL service is healthy
Permission denied:
- Ensure database user has CREATE/ALTER permissions
- For new databases, user needs to be owner or superuser
Rollback:
- Migrations are designed to be forward-only
- For rollback, restore from database backup
- Always backup before major updates
If automatic migration fails, you can apply migrations manually:
# List all migrations
ls database/migrations/
# Apply specific migration (example)
psql -h localhost -U postgres -d hls_streaming -f database/migrations/001_initial_schema.sqlWarning: Only do this if you understand the migration system. The automatic migration script is safer.
Adjust connection pool settings in .env:
# Minimum connections (always open)
DB_POOL_MIN=2
# Maximum connections (peak capacity)
DB_POOL_MAX=10Recommendations:
- Small deployments (1-3 channels):
DB_POOL_MIN=2,DB_POOL_MAX=5 - Medium deployments (4-10 channels):
DB_POOL_MIN=2,DB_POOL_MAX=10 - Large deployments (10+ channels):
DB_POOL_MIN=5,DB_POOL_MAX=20
Note: Each connection uses ~2-5MB of memory. Don't set DB_POOL_MAX higher than your PostgreSQL max_connections setting.
Hardware Acceleration:
# NVIDIA GPU (recommended if available)
HW_ACCEL=nvenc
# Intel Quick Sync
HW_ACCEL=qsv
# Apple VideoToolbox (macOS)
HW_ACCEL=videotoolbox
# CPU only (default)
HW_ACCEL=noneQuality vs Performance:
# Lower quality = better performance
DEFAULT_VIDEO_BITRATE=1000000 # 1 Mbps (low)
DEFAULT_VIDEO_BITRATE=1500000 # 1.5 Mbps (medium, default)
DEFAULT_VIDEO_BITRATE=3000000 # 3 Mbps (high)
DEFAULT_RESOLUTION=1280x720 # 720p (faster)
DEFAULT_RESOLUTION=1920x1080 # 1080p (default)
DEFAULT_FPS=24 # Lower FPS = less CPU
# FFmpeg preset (most important for quality)
FFMPEG_PRESET=fast # Default: good balance (less blocky than veryfast)
FFMPEG_PRESET=medium # Better quality, more CPU
FFMPEG_PRESET=veryfast # Faster encoding, but can cause blocky video
FFMPEG_PRESET=ultrafast # Fastest, but very blocky (not recommended)# Maximum concurrent FFmpeg processes
MAX_CONCURRENT_STREAMS=8Recommendations:
- CPU encoding:
MAX_CONCURRENT_STREAMS = CPU cores - 1 - Hardware encoding:
MAX_CONCURRENT_STREAMS = 2x GPU capability - Mixed: Start with 4-6, monitor CPU/GPU usage, adjust accordingly
# Segment duration (seconds)
DEFAULT_SEGMENT_DURATION=6
# Shorter segments = more frequent updates but more overhead
# Longer segments = less overhead but slower channel switchingRecommendations:
- Live streaming: 4-6 seconds
- On-demand: 6-10 seconds
- Low bandwidth: 8-10 seconds
For large media libraries (10,000+ files):
-
Add indexes (already included in migrations):
-- These are created automatically, but verify they exist CREATE INDEX IF NOT EXISTS idx_media_files_show_name ON media_files(show_name); CREATE INDEX IF NOT EXISTS idx_media_files_file_exists ON media_files(file_exists);
-
Tune PostgreSQL settings (
postgresql.conf):shared_buffers = 256MB effective_cache_size = 1GB maintenance_work_mem = 128MB checkpoint_completion_target = 0.9 wal_buffers = 16MB default_statistics_target = 100 random_page_cost = 1.1 effective_io_concurrency = 200
-
Regular maintenance:
# Analyze tables (run weekly) psql -h localhost -U postgres -d hls_streaming -c "ANALYZE;" # Vacuum (run monthly or when needed) psql -h localhost -U postgres -d hls_streaming -c "VACUUM ANALYZE;"
Key metrics to watch:
- Database connection pool usage
- FFmpeg CPU/GPU usage
- HLS segment generation rate
- Memory usage (Node.js + PostgreSQL + FFmpeg)
- Disk I/O (media files + HLS output)
Useful commands:
# Database connections
psql -h localhost -U postgres -d hls_streaming -c "SELECT count(*) FROM pg_stat_activity WHERE datname = 'hls_streaming';"
# Database size
psql -h localhost -U postgres -d hls_streaming -c "SELECT pg_size_pretty(pg_database_size('hls_streaming'));"
# FFmpeg processes
ps aux | grep ffmpeg
# Disk usage
du -sh hls_output/This project includes a Model Context Protocol (MCP) server that allows AI assistants to manage the streaming server through natural language commands.
cd mcp-server
npm install
npm run build
cp .env.example .env
# Edit .env with your HLS server URL and API keyConfigure your MCP client (such as Claude Desktop) to use the MCP server. See MCP_SERVER_SETUP.md for detailed instructions.
The MCP server provides tools for:
- Creating and managing channels
- Organizing media into buckets
- Setting up schedule blocks
- Searching and managing media libraries
MIT License - See LICENSE file
This software is provided as-is for development and educational purposes. It is not recommended for production use. Use at your own risk.
For issues, questions, or contributions, please refer to the project repository.

