# 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 1. **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 2. **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 `.tmp` to final location - Updates database to `transcodingStatus = 'transcoded'` on success - Marks as `'failed'` if transcoding fails - Runs in a loop with configurable delay between batches 3. **Database Schema** - `Video` model includes `transcodingStatus` field - Valid values: `'uploaded'` | `'processing'` | `'transcoded'` | `'failed'` 4. **Video Player** - Uses `HlsPlayer` component 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 ## 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: ```sql 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: ```bash docker build -f Dockerfile.transcoder -t vault-transcoder:latest . ``` ### Running with Docker Compose ```bash 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: ```json { "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`) ```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`) ```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: 1. Ensure FFmpeg is installed on your system 2. Set environment variables: ```bash export DATABASE_URL=postgresql://cms_user:changeme@localhost:5432/cms_db export UPLOADS_DIR=/path/to/uploads ``` 3. Run the transcoder: ```bash cd transcoder npm install npm run dev ``` ## Monitoring & Logs ### CMS Logs ```bash docker logs college-cms ``` ### Transcoder Logs ```bash 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 ```bash # 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**: 1. Verify MP4 files are in `/uploads/videos/` directory 2. Check file permissions: `chmod 644 /uploads/videos/*.mp4` 3. Ensure the path matches the `UPLOADS_DIR` environment variable ### HLS Files Not Generated **Issue**: Videos stay in `'uploaded'` state **Solution**: 1. Check FFmpeg is installed in container: `docker exec college-transcoder ffmpeg -version` 2. Check database connectivity: `docker logs college-transcoder | grep "DATABASE_URL"` 3. Verify disk space: `df -h /uploads` ### Video Player Shows MP4 Instead of HLS **Issue**: HLS not playing even after transcoding **Solution**: 1. Verify `transcodingStatus = 'transcoded'` in database 2. Check HLS files exist: `ls /uploads/hls/{video_id}/` 3. 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_INTERVAL` if CPU usage is high ## Client-Side Configuration The video player is configured to: 1. Try HLS playlist first if available 2. Fall back to MP4 on HLS failure 3. Use adaptive bitrate selection when HLS is available 4. Support all modern browsers (Chrome, Firefox, Safari, Edge) ## Security Considerations 1. **Storage Access**: Only the transcoder container has write access to `/uploads/hls/` 2. **Database**: Use strong PostgreSQL password in production 3. **Network**: Both containers connect through Docker network, no external ports exposed 4. **File Validation**: Consider adding video file validation before transcoding ## Future Enhancements 1. Add thumbnail extraction during transcoding 2. Implement retry logic for failed transcoding jobs 3. Add progress tracking API for long-running transcodes 4. Support multiple audio tracks/subtitles 5. Implement cache invalidation for CDN 6. Add metrics and monitoring dashboard