278 lines
7.2 KiB
Markdown
278 lines
7.2 KiB
Markdown
# 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
|