615 lines
15 KiB
Markdown
615 lines
15 KiB
Markdown
|
|
# 🎬 Stream Processing Thread - Documentation Complète
|
||
|
|
|
||
|
|
**Date**: 2025-12-05
|
||
|
|
**Service**: `veza-stream-server`
|
||
|
|
**Version**: 1.0.0
|
||
|
|
**Statut**: ✅ **Implémenté**
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 📋 Table des Matières
|
||
|
|
|
||
|
|
1. [Vue d'ensemble](#vue-densemble)
|
||
|
|
2. [Architecture](#architecture)
|
||
|
|
3. [Cycle de Vie](#cycle-de-vie)
|
||
|
|
4. [Modules](#modules)
|
||
|
|
5. [Format des Logs FFmpeg](#format-des-logs-ffmpeg)
|
||
|
|
6. [Mapping Timestamp → Segments](#mapping-timestamp--segments)
|
||
|
|
7. [ABR Support](#abr-support)
|
||
|
|
8. [API Status](#api-status)
|
||
|
|
9. [Troubleshooting](#troubleshooting)
|
||
|
|
10. [Checklist Production](#checklist-production)
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 🎯 Vue d'ensemble
|
||
|
|
|
||
|
|
Le **Stream Processing Thread** est le module central qui gère le traitement temps réel des jobs d'encodage audio via FFmpeg. Il orchestre:
|
||
|
|
|
||
|
|
- ✅ Spawn et monitoring du processus FFmpeg
|
||
|
|
- ✅ Détection en temps réel des segments HLS générés
|
||
|
|
- ✅ Persistance DB incrémentale des segments
|
||
|
|
- ✅ Gestion des erreurs et timeouts
|
||
|
|
- ✅ Callbacks de fin (success, error, timeout)
|
||
|
|
- ✅ Mise à jour des métadonnées HLS
|
||
|
|
|
||
|
|
### Composants Principaux
|
||
|
|
|
||
|
|
```
|
||
|
|
StreamProcessor
|
||
|
|
├─ FFmpegMonitor (parsing stderr)
|
||
|
|
├─ SegmentTracker (suivi segments)
|
||
|
|
└─ ProcessingCallbacks (fin de traitement)
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 🏗️ Architecture
|
||
|
|
|
||
|
|
### Diagramme de la Pipeline
|
||
|
|
|
||
|
|
```
|
||
|
|
┌─────────────────────────────────────────────────────────┐
|
||
|
|
│ EncoderPipeline::start_processing() │
|
||
|
|
└────────────────────┬────────────────────────────────────┘
|
||
|
|
│
|
||
|
|
▼
|
||
|
|
┌─────────────────────────────────────────────────────────┐
|
||
|
|
│ StreamProcessor::new() │
|
||
|
|
│ - job_id, job, db, output_dir │
|
||
|
|
└────────────────────┬────────────────────────────────────┘
|
||
|
|
│
|
||
|
|
▼
|
||
|
|
┌─────────────────────────────────────────────────────────┐
|
||
|
|
│ StreamProcessor::run() │
|
||
|
|
│ │
|
||
|
|
│ 1. spawn_ffmpeg() │
|
||
|
|
│ └─> FfmpegCommandBuilder │
|
||
|
|
│ └─> tokio::process::Command │
|
||
|
|
│ │
|
||
|
|
│ 2. monitor_output() │
|
||
|
|
│ ├─> FFmpegMonitor::monitor_stderr() │
|
||
|
|
│ │ └─> Lire stderr ligne par ligne │
|
||
|
|
│ │ └─> Détecter événements │
|
||
|
|
│ │ ├─ SegmentOpened │
|
||
|
|
│ │ ├─ Progress │
|
||
|
|
│ │ ├─ Error │
|
||
|
|
│ │ └─ MuxingComplete │
|
||
|
|
│ │ │
|
||
|
|
│ └─> SegmentTracker::register() │
|
||
|
|
│ └─> Persist immédiatement en DB │
|
||
|
|
│ │
|
||
|
|
│ 3. finalize() │
|
||
|
|
│ └─> ProcessingCallbacks │
|
||
|
|
│ ├─ on_success() │
|
||
|
|
│ ├─ on_error() │
|
||
|
|
│ └─ on_timeout() │
|
||
|
|
└─────────────────────────────────────────────────────────┘
|
||
|
|
```
|
||
|
|
|
||
|
|
### Flux de Données
|
||
|
|
|
||
|
|
```
|
||
|
|
FFmpeg Process
|
||
|
|
└─> stderr (lignes)
|
||
|
|
└─> FFmpegMonitor
|
||
|
|
└─> FFmpegEvent
|
||
|
|
└─> StreamProcessor
|
||
|
|
└─> SegmentTracker
|
||
|
|
├─> Vec<SegmentInfo> (mémoire)
|
||
|
|
└─> DB (stream_segments)
|
||
|
|
└─> API Status Endpoint
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 🔄 Cycle de Vie
|
||
|
|
|
||
|
|
### 1. Initialisation
|
||
|
|
|
||
|
|
```rust
|
||
|
|
let processor = StreamProcessor::new(
|
||
|
|
job_id,
|
||
|
|
job,
|
||
|
|
db_pool,
|
||
|
|
output_dir,
|
||
|
|
)
|
||
|
|
.with_timeout(Duration::from_secs(600))
|
||
|
|
.with_status_sender(status_tx);
|
||
|
|
```
|
||
|
|
|
||
|
|
### 2. Spawn FFmpeg
|
||
|
|
|
||
|
|
```rust
|
||
|
|
// Créer répertoire de sortie
|
||
|
|
tokio::fs::create_dir_all(&output_dir).await?;
|
||
|
|
|
||
|
|
// Construire commande FFmpeg
|
||
|
|
let command = FfmpegCommandBuilder::new()
|
||
|
|
.input(&job.input_path)
|
||
|
|
.output(&manifest_path)
|
||
|
|
.audio_codec(job.codec)
|
||
|
|
.bitrate(job.bitrate)
|
||
|
|
.container(ContainerFormat::HLS)
|
||
|
|
.hls_time(job.hls_segment_time)
|
||
|
|
.build()?;
|
||
|
|
|
||
|
|
// Spawn processus
|
||
|
|
let child = command.spawn()?;
|
||
|
|
```
|
||
|
|
|
||
|
|
### 3. Monitoring Temps Réel
|
||
|
|
|
||
|
|
```rust
|
||
|
|
// Créer monitor
|
||
|
|
let (monitor, mut event_rx) = FFmpegMonitor::new(output_dir);
|
||
|
|
|
||
|
|
// Démarrer monitoring stderr
|
||
|
|
let monitor_handle = tokio::spawn(async move {
|
||
|
|
monitor.monitor_stderr(stderr).await
|
||
|
|
});
|
||
|
|
|
||
|
|
// Traiter événements
|
||
|
|
while let Some(event) = event_rx.recv().await {
|
||
|
|
match event {
|
||
|
|
FFmpegEvent::SegmentOpened { path, index } => {
|
||
|
|
tracker.register(segment).await?;
|
||
|
|
}
|
||
|
|
FFmpegEvent::Progress { time, speed } => {
|
||
|
|
// Log progression
|
||
|
|
}
|
||
|
|
FFmpegEvent::Error(msg) => {
|
||
|
|
// Log erreur
|
||
|
|
}
|
||
|
|
FFmpegEvent::MuxingComplete { overhead } => {
|
||
|
|
// Fin de muxing
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### 4. Persistance DB
|
||
|
|
|
||
|
|
```rust
|
||
|
|
// Chaque segment est persisté immédiatement
|
||
|
|
sqlx::query!(
|
||
|
|
r#"
|
||
|
|
INSERT INTO stream_segments (track_id, quality, segment_index, path, duration, created_at)
|
||
|
|
VALUES ($1, $2, $3, $4, $5, NOW())
|
||
|
|
ON CONFLICT (track_id, quality, segment_index) DO UPDATE
|
||
|
|
SET path = EXCLUDED.path, duration = EXCLUDED.duration
|
||
|
|
"#,
|
||
|
|
track_id,
|
||
|
|
quality,
|
||
|
|
segment_index,
|
||
|
|
path,
|
||
|
|
duration
|
||
|
|
)
|
||
|
|
.execute(&db)
|
||
|
|
.await?;
|
||
|
|
```
|
||
|
|
|
||
|
|
### 5. Finalisation
|
||
|
|
|
||
|
|
```rust
|
||
|
|
// Succès
|
||
|
|
callbacks.on_success().await?;
|
||
|
|
callbacks.finalize_playlist().await?;
|
||
|
|
|
||
|
|
// Erreur
|
||
|
|
callbacks.on_error(&error_msg).await?;
|
||
|
|
|
||
|
|
// Timeout
|
||
|
|
callbacks.on_timeout().await?;
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 📦 Modules
|
||
|
|
|
||
|
|
### 1. `processor.rs` - StreamProcessor
|
||
|
|
|
||
|
|
**Responsabilités**:
|
||
|
|
- Orchestration du traitement complet
|
||
|
|
- Spawn FFmpeg
|
||
|
|
- Coordination monitor + tracker
|
||
|
|
- Gestion timeout et erreurs
|
||
|
|
|
||
|
|
**API Principale**:
|
||
|
|
```rust
|
||
|
|
pub struct StreamProcessor {
|
||
|
|
job_id: Uuid,
|
||
|
|
job: EncodeJob,
|
||
|
|
db: PgPool,
|
||
|
|
output_dir: PathBuf,
|
||
|
|
job_timeout: Duration,
|
||
|
|
}
|
||
|
|
|
||
|
|
impl StreamProcessor {
|
||
|
|
pub fn new(job_id, job, db, output_dir) -> Self;
|
||
|
|
pub fn with_timeout(self, timeout: Duration) -> Self;
|
||
|
|
pub fn with_status_sender(self, tx: mpsc::Sender<ProcessingStatus>) -> Self;
|
||
|
|
pub async fn run(&mut self) -> Result<()>;
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### 2. `ffmpeg_monitor.rs` - FFmpegMonitor
|
||
|
|
|
||
|
|
**Responsabilités**:
|
||
|
|
- Lecture stderr ligne par ligne
|
||
|
|
- Parsing des événements FFmpeg
|
||
|
|
- Détection segments, erreurs, progression
|
||
|
|
|
||
|
|
**Événements Détectés**:
|
||
|
|
```rust
|
||
|
|
pub enum FFmpegEvent {
|
||
|
|
SegmentOpened { path: PathBuf, index: u32 },
|
||
|
|
Progress { time: Option<Duration>, speed: Option<f32> },
|
||
|
|
Error(String),
|
||
|
|
MuxingComplete { overhead: f32 },
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Regex Utilisées**:
|
||
|
|
- `Opening '([^']+)' for writing` → Segment ouvert
|
||
|
|
- `time=(\d{2}:\d{2}:\d{2}\.\d{2})` → Progression
|
||
|
|
- `(?i)(error|failed|invalid)` → Erreurs
|
||
|
|
- `muxing overhead: ([\d.]+)%` → Fin muxing
|
||
|
|
|
||
|
|
### 3. `segment_tracker.rs` - SegmentTracker
|
||
|
|
|
||
|
|
**Responsabilités**:
|
||
|
|
- Suivi des segments détectés
|
||
|
|
- Persistance DB temps réel
|
||
|
|
- Calcul durée totale
|
||
|
|
|
||
|
|
**API Principale**:
|
||
|
|
```rust
|
||
|
|
pub struct SegmentTracker {
|
||
|
|
segments: Arc<RwLock<Vec<SegmentInfo>>>,
|
||
|
|
job_id: Uuid,
|
||
|
|
track_id: Uuid,
|
||
|
|
quality: String,
|
||
|
|
db: PgPool,
|
||
|
|
}
|
||
|
|
|
||
|
|
impl SegmentTracker {
|
||
|
|
pub async fn register(&self, segment: SegmentInfo) -> Result<()>;
|
||
|
|
pub async fn get_segments(&self) -> Vec<SegmentInfo>;
|
||
|
|
pub async fn get_total_duration(&self) -> f64;
|
||
|
|
pub async fn persist_all(&self) -> Result<()>;
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### 4. `callbacks.rs` - ProcessingCallbacks
|
||
|
|
|
||
|
|
**Responsabilités**:
|
||
|
|
- Callbacks de fin de traitement
|
||
|
|
- Mise à jour DB status
|
||
|
|
- Finalisation playlist HLS
|
||
|
|
|
||
|
|
**API Principale**:
|
||
|
|
```rust
|
||
|
|
pub struct ProcessingCallbacks {
|
||
|
|
job_id: Uuid,
|
||
|
|
track_id: Uuid,
|
||
|
|
db: PgPool,
|
||
|
|
output_dir: PathBuf,
|
||
|
|
}
|
||
|
|
|
||
|
|
impl ProcessingCallbacks {
|
||
|
|
pub async fn on_success(&self) -> Result<()>;
|
||
|
|
pub async fn on_error(&self, error_message: &str) -> Result<()>;
|
||
|
|
pub async fn on_timeout(&self) -> Result<()>;
|
||
|
|
pub async fn finalize_playlist(&self) -> Result<()>;
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 📝 Format des Logs FFmpeg
|
||
|
|
|
||
|
|
### Lignes de Progression
|
||
|
|
|
||
|
|
```
|
||
|
|
frame= 123 fps=0.0 q=-1.0 size= 1024kB time=00:00:05.12 bitrate=1638.4kbits/s speed=10.2x
|
||
|
|
```
|
||
|
|
|
||
|
|
**Parsé par**: `FfmpegProgress::parse()`
|
||
|
|
|
||
|
|
**Champs extraits**:
|
||
|
|
- `time`: `00:00:05.12` → `Duration::from_millis(5120)`
|
||
|
|
- `speed`: `10.2x` → `10.2`
|
||
|
|
- `bitrate`: `1638.4kbits/s` → `"1638.4kbits/s"`
|
||
|
|
|
||
|
|
### Ouverture de Segment
|
||
|
|
|
||
|
|
```
|
||
|
|
Opening 'segment_00000.ts' for writing
|
||
|
|
```
|
||
|
|
|
||
|
|
**Détecté par**: `OPENING_SEGMENT_REGEX`
|
||
|
|
|
||
|
|
**Extraction**:
|
||
|
|
- `path`: `segment_00000.ts`
|
||
|
|
- `index`: `0` (depuis le nom de fichier)
|
||
|
|
|
||
|
|
### Erreurs
|
||
|
|
|
||
|
|
```
|
||
|
|
error: Invalid data found when processing input
|
||
|
|
Error opening input file
|
||
|
|
ERROR: Failed to open output file
|
||
|
|
```
|
||
|
|
|
||
|
|
**Détecté par**: `ERROR_REGEX` (case-insensitive)
|
||
|
|
|
||
|
|
### Fin de Muxing
|
||
|
|
|
||
|
|
```
|
||
|
|
muxing overhead: 1.234567%
|
||
|
|
```
|
||
|
|
|
||
|
|
**Détecté par**: `MUXING_OVERHEAD_REGEX`
|
||
|
|
|
||
|
|
**Extraction**: `overhead`: `1.234567`
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 🗺️ Mapping Timestamp → Segments
|
||
|
|
|
||
|
|
### Détection des Segments
|
||
|
|
|
||
|
|
1. **Depuis stderr**: `Opening 'segment_00000.ts' for writing`
|
||
|
|
- Index extrait depuis le nom de fichier
|
||
|
|
- Durée par défaut: `4.0s` (sera mise à jour depuis le manifest)
|
||
|
|
|
||
|
|
2. **Depuis le répertoire**: Scan périodique des fichiers `.ts`
|
||
|
|
- Utile pour détecter les segments déjà générés
|
||
|
|
- Méthode: `FFmpegMonitor::detect_existing_segments()`
|
||
|
|
|
||
|
|
3. **Depuis le manifest**: Parsing du `.m3u8` (après complétion)
|
||
|
|
- Durée exacte depuis `#EXTINF:4.000,`
|
||
|
|
- Ordre des segments
|
||
|
|
|
||
|
|
### Structure SegmentInfo
|
||
|
|
|
||
|
|
```rust
|
||
|
|
pub struct SegmentInfo {
|
||
|
|
pub index: u32, // 0, 1, 2, ...
|
||
|
|
pub path: PathBuf, // /data/streams/track_id/quality/segment_00000.ts
|
||
|
|
pub duration: f64, // 4.0 (secondes)
|
||
|
|
pub timestamp: SystemTime, // Timestamp de création
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### Persistance DB
|
||
|
|
|
||
|
|
```sql
|
||
|
|
INSERT INTO stream_segments (
|
||
|
|
track_id, quality, segment_index, path, duration, created_at
|
||
|
|
)
|
||
|
|
VALUES ($1, $2, $3, $4, $5, NOW())
|
||
|
|
ON CONFLICT (track_id, quality, segment_index) DO UPDATE
|
||
|
|
SET path = EXCLUDED.path, duration = EXCLUDED.duration
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 🎚️ ABR Support
|
||
|
|
|
||
|
|
### Structure Multi-Qualité
|
||
|
|
|
||
|
|
```
|
||
|
|
/data/streams/<track_id>/
|
||
|
|
low/
|
||
|
|
index.m3u8
|
||
|
|
segment_00000.ts
|
||
|
|
segment_00001.ts
|
||
|
|
...
|
||
|
|
medium/
|
||
|
|
index.m3u8
|
||
|
|
segment_00000.ts
|
||
|
|
segment_00001.ts
|
||
|
|
...
|
||
|
|
high/
|
||
|
|
index.m3u8
|
||
|
|
segment_00000.ts
|
||
|
|
segment_00001.ts
|
||
|
|
...
|
||
|
|
hi_res/
|
||
|
|
index.m3u8
|
||
|
|
segment_00000.ts
|
||
|
|
segment_00001.ts
|
||
|
|
...
|
||
|
|
```
|
||
|
|
|
||
|
|
### Master Playlist (Futur)
|
||
|
|
|
||
|
|
Pour supporter ABR automatique, créer un `master.m3u8`:
|
||
|
|
|
||
|
|
```m3u8
|
||
|
|
#EXTM3U
|
||
|
|
#EXT-X-VERSION:3
|
||
|
|
#EXT-X-STREAM-INF:BANDWIDTH=64000,CODECS="mp4a.40.2"
|
||
|
|
low/playlist.m3u8
|
||
|
|
#EXT-X-STREAM-INF:BANDWIDTH=128000,CODECS="mp4a.40.2"
|
||
|
|
medium/playlist.m3u8
|
||
|
|
#EXT-X-STREAM-INF:BANDWIDTH=192000,CODECS="mp4a.40.2"
|
||
|
|
high/playlist.m3u8
|
||
|
|
#EXT-X-STREAM-INF:BANDWIDTH=320000,CODECS="mp4a.40.2"
|
||
|
|
hi_res/playlist.m3u8
|
||
|
|
```
|
||
|
|
|
||
|
|
**Note**: Le master playlist n'est pas encore généré automatiquement. C'est une feature future.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 🌐 API Status
|
||
|
|
|
||
|
|
### Endpoint
|
||
|
|
|
||
|
|
```
|
||
|
|
GET /api/streams/jobs/{id}/status
|
||
|
|
```
|
||
|
|
|
||
|
|
### Response
|
||
|
|
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"id": "550e8400-e29b-41d4-a716-446655440000",
|
||
|
|
"track_id": "123e4567-e89b-12d3-a456-426614174000",
|
||
|
|
"status": "encoding",
|
||
|
|
"segments": [
|
||
|
|
{
|
||
|
|
"index": 0,
|
||
|
|
"path": "/data/streams/track_id/medium/segment_00000.ts",
|
||
|
|
"duration": 4.0,
|
||
|
|
"created_at": "2025-12-05T10:30:00Z"
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"index": 1,
|
||
|
|
"path": "/data/streams/track_id/medium/segment_00001.ts",
|
||
|
|
"duration": 4.0,
|
||
|
|
"created_at": "2025-12-05T10:30:04Z"
|
||
|
|
}
|
||
|
|
],
|
||
|
|
"current_duration": 8.0,
|
||
|
|
"progress": 0.5,
|
||
|
|
"created_at": "2025-12-05T10:29:55Z",
|
||
|
|
"started_at": null,
|
||
|
|
"completed_at": null,
|
||
|
|
"error": null
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### Statuts Possibles
|
||
|
|
|
||
|
|
- `pending`: Job en attente
|
||
|
|
- `encoding`: Job en cours de traitement
|
||
|
|
- `done`: Job terminé avec succès
|
||
|
|
- `error`: Job échoué
|
||
|
|
- `timeout`: Job expiré
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 🔧 Troubleshooting
|
||
|
|
|
||
|
|
### Problème: Segments non détectés
|
||
|
|
|
||
|
|
**Symptômes**:
|
||
|
|
- Aucun segment dans la DB
|
||
|
|
- `segments_count = 0` dans l'API status
|
||
|
|
|
||
|
|
**Solutions**:
|
||
|
|
1. Vérifier que FFmpeg génère bien les segments:
|
||
|
|
```bash
|
||
|
|
ls -la /data/streams/<track_id>/<quality>/
|
||
|
|
```
|
||
|
|
|
||
|
|
2. Vérifier les logs stderr:
|
||
|
|
```bash
|
||
|
|
# Les logs doivent contenir "Opening 'segment_*.ts' for writing"
|
||
|
|
```
|
||
|
|
|
||
|
|
3. Vérifier les permissions du répertoire:
|
||
|
|
```bash
|
||
|
|
chmod -R 755 /data/streams
|
||
|
|
```
|
||
|
|
|
||
|
|
### Problème: Timeout fréquents
|
||
|
|
|
||
|
|
**Symptômes**:
|
||
|
|
- Jobs qui expirent avant la fin
|
||
|
|
- `status = "timeout"`
|
||
|
|
|
||
|
|
**Solutions**:
|
||
|
|
1. Augmenter le timeout:
|
||
|
|
```rust
|
||
|
|
processor.with_timeout(Duration::from_secs(1200)); // 20 minutes
|
||
|
|
```
|
||
|
|
|
||
|
|
2. Vérifier la charge CPU:
|
||
|
|
```bash
|
||
|
|
top -p $(pgrep -f ffmpeg)
|
||
|
|
```
|
||
|
|
|
||
|
|
3. Réduire le nombre de workers simultanés
|
||
|
|
|
||
|
|
### Problème: Erreurs FFmpeg non détectées
|
||
|
|
|
||
|
|
**Symptômes**:
|
||
|
|
- Job en `encoding` indéfiniment
|
||
|
|
- Pas d'erreur dans les logs
|
||
|
|
|
||
|
|
**Solutions**:
|
||
|
|
1. Vérifier les logs stderr:
|
||
|
|
```rust
|
||
|
|
// Les erreurs sont loggées avec tracing::warn!
|
||
|
|
```
|
||
|
|
|
||
|
|
2. Vérifier le code de sortie FFmpeg:
|
||
|
|
```rust
|
||
|
|
// child.wait() retourne le status
|
||
|
|
```
|
||
|
|
|
||
|
|
### Problème: Segments dupliqués en DB
|
||
|
|
|
||
|
|
**Symptômes**:
|
||
|
|
- Plusieurs entrées pour le même `segment_index`
|
||
|
|
|
||
|
|
**Solutions**:
|
||
|
|
1. Utiliser `ON CONFLICT DO UPDATE` (déjà implémenté)
|
||
|
|
2. Vérifier les contraintes UNIQUE:
|
||
|
|
```sql
|
||
|
|
CONSTRAINT stream_segments_unique UNIQUE (track_id, quality, segment_index)
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## ✅ Checklist Production
|
||
|
|
|
||
|
|
### Avant Déploiement
|
||
|
|
|
||
|
|
- [ ] Vérifier que FFmpeg est installé et accessible
|
||
|
|
- [ ] Vérifier les permissions `/data/streams`
|
||
|
|
- [ ] Configurer le timeout approprié (défaut: 10 minutes)
|
||
|
|
- [ ] Vérifier la connexion DB
|
||
|
|
- [ ] Tester avec un fichier audio de test
|
||
|
|
|
||
|
|
### Monitoring
|
||
|
|
|
||
|
|
- [ ] Logs structurés activés (`tracing`)
|
||
|
|
- [ ] Métriques Prometheus (si disponible)
|
||
|
|
- [ ] Alertes sur timeouts fréquents
|
||
|
|
- [ ] Alertes sur erreurs FFmpeg
|
||
|
|
|
||
|
|
### Performance
|
||
|
|
|
||
|
|
- [ ] Limiter le nombre de workers simultanés
|
||
|
|
- [ ] Monitorer l'utilisation CPU
|
||
|
|
- [ ] Monitorer l'espace disque (`/data/streams`)
|
||
|
|
- [ ] Optimiser les requêtes DB (index)
|
||
|
|
|
||
|
|
### Sécurité
|
||
|
|
|
||
|
|
- [ ] Validation des chemins d'entrée/sortie
|
||
|
|
- [ ] Isolation des processus FFmpeg
|
||
|
|
- [ ] Nettoyage des fichiers temporaires
|
||
|
|
- [ ] Rate limiting sur l'API
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 📚 Références
|
||
|
|
|
||
|
|
- [HLS Specification (Apple)](https://developer.apple.com/streaming/)
|
||
|
|
- [FFmpeg Documentation](https://ffmpeg.org/documentation.html)
|
||
|
|
- [Documentation STREAM_ENCODING_PIPELINE.md](./STREAM_ENCODING_PIPELINE.md)
|
||
|
|
- [Audit AUDIT_STREAM_PROCESSING.md](./AUDIT_STREAM_PROCESSING.md)
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
**Auteur**: Veza Stream Server Team
|
||
|
|
**Dernière mise à jour**: 2025-12-05
|
||
|
|
|