7.2 KiB
HLS Transcoding Setup Documentation
Overview
This system implements HLS (HTTP Live Streaming) encoding and adaptive bitrate streaming for video content. Videos are stored as MP4s initially and are automatically transcoded to HLS format using FFmpeg.
Architecture
Components
-
CMS Application (Next.js)
- Allows admin users to upload MP4 videos
- Videos are stored in
/uploads/videos/{video_id}.mp4 - Videos are marked with
transcodingStatus = 'uploaded'initially - Video player supports HLS with MP4 fallback
-
Transcoder Service (Node.js + FFmpeg)
- Runs as a batch job, processing all videos with
transcodingStatus = 'uploaded' - Reads MP4 from storage and converts to HLS format
- Creates multiple quality variants (based on source resolution):
- 1080p: 1920x1080 @ 3500 kbps (if source supports)
- 720p: 1280x720 @ 1800 kbps (if source supports)
- 480p: 854x480 @ 900 kbps (if source supports)
- Stores files in
/uploads/hls/{video_id}.tmp/during transcoding - Creates a master playlist (
master.m3u8) combining all variants - Atomically renames folder from
.tmpto final location - Updates database to
transcodingStatus = 'transcoded'on success - Marks as
'failed'if transcoding fails - Runs in a loop with configurable delay between batches
- Runs as a batch job, processing all videos with
-
Database Schema
Videomodel includestranscodingStatusfield- Valid values:
'uploaded'|'processing'|'transcoded'|'failed'
-
Video Player
- Uses
HlsPlayercomponent with HLS.js - Automatically selects source based on availability:
- If
transcodingStatus = 'transcoded': Uses/api/videos/hls/{video_id}/master.m3u8 - Otherwise: Falls back to original MP4 file
- If
- Uses
Directory Structure
/uploads/
├── videos/
│ ├── {video_id_1}.mp4
│ ├── {video_id_2}.mp4
│ └── ...
└── hls/
├── {video_id_1}/
│ ├── master.m3u8 (Master playlist)
│ ├── 1080p.m3u8
│ ├── 1080p_000.ts
│ ├── 1080p_001.ts
│ ├── 720p.m3u8
│ ├── 720p_000.ts
│ ├── 480p.m3u8
│ └── ...
└── {video_id_2}/
└── ...
Database Migration
The migration adds transcodingStatus field to the Video model:
ALTER TABLE "Video" ADD COLUMN "transcodingStatus" TEXT NOT NULL DEFAULT 'uploaded';
CREATE INDEX "Video_transcodingStatus_idx" ON "Video"("transcodingStatus");
Docker Setup
Building the Transcoder
The transcoder is built as a multi-stage Docker image:
docker build -f Dockerfile.transcoder -t vault-transcoder:latest .
Running with Docker Compose
docker-compose up -d
Services started:
- cms (port 3000): Next.js CMS application
- postgres: PostgreSQL database
- transcoder: HLS transcoding service
Environment Variables
Create a .env file in the project root:
# Transcoder (set automatically via docker-compose)
DATABASE_URL=postgresql://cms_user:changeme@postgres:5432/cms_db
UPLOADS_DIR=/uploads
POLL_INTERVAL=5000
CONCURRENT_JOBS=2
# CMS specific (as before)
NEXTAUTH_SECRET=your-secret-key
NEXTAUTH_URL=http://localhost:3000
GOOGLE_CLIENT_ID=...
GOOGLE_CLIENT_SECRET=...
ALLOWED_ADMINS=admin@university.edu
# Paths
UPLOADS_PATH=/mnt/tank/apps/college-platform/uploads
POSTGRES_DATA_PATH=/mnt/tank/apps/college-platform/postgres_data
API Endpoints
Get Video Details
GET /api/videos/{videoId}
Response includes:
{
"video": {
"id": "...",
"title": "...",
"url": "/videos/{video_id}.mp4",
"transcodingStatus": "transcoded",
"videoUrls": {
"hlsUrl": "/api/videos/hls/{video_id}/master.m3u8",
"mp4Url": "/videos/{video_id}.mp4"
}
}
}
File Format: HLS Playlist Examples
Master Playlist (master.m3u8)
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-STREAM-INF:BANDWIDTH=3500000,RESOLUTION=1920x1080
1080p.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=1800000,RESOLUTION=1280x720
720p.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=900000,RESOLUTION=854x480
480p.m3u8
Variant Playlist (1080p.m3u8)
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:6
#EXT-X-PLAYLIST-TYPE:VOD
#EXTINF:6.0,
1080p_000.ts
#EXTINF:6.0,
1080p_001.ts
...
#EXT-X-ENDLIST
Development Mode
For local development without Docker:
- Ensure FFmpeg is installed on your system
- Set environment variables:
export DATABASE_URL=postgresql://cms_user:changeme@localhost:5432/cms_db export UPLOADS_DIR=/path/to/uploads - Run the transcoder:
cd transcoder npm install npm run dev
Monitoring & Logs
CMS Logs
docker logs college-cms
Transcoder Logs
docker logs college-transcoder
Look for lines starting with:
[Transcoding]- Transcoding progress[HLS]- HLS specific operations[Database]- Database updates[Error]- Any errors encountered
Checking Transcoding Status
# Check video transcoding status
psql -U cms_user -d cms_db -c "SELECT id, title, transcodingStatus FROM \"Video\";"
# Monitor real-time transcoding
watch -n 1 'docker exec college-transcoder head -20 /app/transcoder.log'
Troubleshooting
Transcoder Not Finding Videos
Issue: Transcoder logs show "MP4 file not found"
Solution:
- Verify MP4 files are in
/uploads/videos/directory - Check file permissions:
chmod 644 /uploads/videos/*.mp4 - Ensure the path matches the
UPLOADS_DIRenvironment variable
HLS Files Not Generated
Issue: Videos stay in 'uploaded' state
Solution:
- Check FFmpeg is installed in container:
docker exec college-transcoder ffmpeg -version - Check database connectivity:
docker logs college-transcoder | grep "DATABASE_URL" - Verify disk space:
df -h /uploads
Video Player Shows MP4 Instead of HLS
Issue: HLS not playing even after transcoding
Solution:
- Verify
transcodingStatus = 'transcoded'in database - Check HLS files exist:
ls /uploads/hls/{video_id}/ - Verify master playlist is valid:
cat /uploads/hls/{video_id}/master.m3u8
Performance Issues
Recommendation: Adjust CONCURRENT_JOBS based on available system resources:
- 1-2 jobs for systems with <4 CPU cores
- 2-4 jobs for systems with 4-8 CPU cores
- Increase
POLL_INTERVALif CPU usage is high
Client-Side Configuration
The video player is configured to:
- Try HLS playlist first if available
- Fall back to MP4 on HLS failure
- Use adaptive bitrate selection when HLS is available
- Support all modern browsers (Chrome, Firefox, Safari, Edge)
Security Considerations
- Storage Access: Only the transcoder container has write access to
/uploads/hls/ - Database: Use strong PostgreSQL password in production
- Network: Both containers connect through Docker network, no external ports exposed
- File Validation: Consider adding video file validation before transcoding
Future Enhancements
- Add thumbnail extraction during transcoding
- Implement retry logic for failed transcoding jobs
- Add progress tracking API for long-running transcodes
- Support multiple audio tracks/subtitles
- Implement cache invalidation for CDN
- Add metrics and monitoring dashboard