Files
MatchLiveTv/docs/REPLAY_MODULE.md
eminuxandCursor 05ef56c56d Aggiunge replay YouTube temporaneo, pull registrazioni dai CPX e snapshot ingest in admin.
Così overflow Hetzner e VOD YouTube restano in archivio dopo lo spegnimento del nodo, e la colonna ingest non si svuota.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-28 13:01:41 +02:00

145 lines
5.4 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Modulo Replay — Match Live TV
## Panoramica
Al termine di ogni diretta Premium, MediaMTX registra i segmenti. Sidekiq unisce i segmenti, genera thumbnail e carica su **Garage (S3-compatible)**.
- **Live MatchLiveTV (HLS)**: copia **permanente** (policy `retained`) con retention del piano.
- **Live YouTube**: copia **temporanea** di sicurezza (policy `temporary`, default 48h), poi player YouTube in archivio. **Niente** re-upload automatico del MP4 su YouTube.
## Piani e retention
| Piano | Registrazione | Retention archivio MLTV | Download MP4 | YouTube |
|-------|---------------|-------------------------|--------------|---------|
| Free | No | — | No | No |
| Premium Light | Sì | 30 giorni (`expires_at`) | Sì (solo se file MLTV) | No |
| Premium Full | Sì | 90 giorni (`expires_at`) se HLS; **nessuna scadenza archivio** se YouTube (`expires_at` nil) | Sì solo con file Garage | Live → VOD nativo; verify collega `youtube_video_id` |
## Storage policy (`Recordings::StoragePolicy`)
```text
if youtube_destination? → temporary
elsif can_create_recordings? → retained
else → none
```
| Policy | Prefisso storage | `expires_at` (archivio) | `temp_expires_at` |
|--------|------------------|-------------------------|-------------------|
| `temporary` | `temporary_replays/...` | `nil` | now + `YOUTUBE_TEMP_REPLAY_RETENTION_HOURS` (2472, default 48) |
| `retained` | `teams/...` | piano `recording_days` | `nil` |
| `none` | — | — | — |
## Archivio logico vs storage fisico
Dopo cleanup della copia temp, la partita **resta** in archivio MatchLiveTV. Cambia solo la sorgente:
| `replay_source` | Player | File Garage permanente |
|-----------------|--------|------------------------|
| `youtube` | embed / link YouTube | no (MP4 temp già eliminato) |
| `matchlivetv` | player MP4 proprietario | sì |
| `none` | non playable | — |
`available_in_archive?` = `ready` e (YouTube URL/`youtube_video_id` **oppure** `storage_key`).
Clear/purge temporary: **solo** oggetti S3 video; mai soft-delete; mai azzerare `youtube_video_id` / metadata / score. Thumbnail preferibilmente conservata.
## Flusso YouTube
```text
Stop → StoragePolicy(temporary) → Finalize (expires_at nil, temp_expires_at)
→ Upload temporary_replays/... → ready
→ VerifyYoutubeReplayJob (backoff)
→ ok: youtube_video_id + youtube_verified_at → ClearTemporaryMediaJob
→ fail fino a max attempts: aspetta safety net
→ PurgeTemporaryMediaJob (cron): a temp_expires_at elimina file + log se non verificato
```
## Funzionalità implementate
### 1. Registrazione automatica
Pipeline: `Sessions::Stop``FinalizeSession``UploadJob` → storage → `PostProcessJob`.
MediaMTX registra **solo mentre il telefono pubblica** (RTMP connesso).
### 2. Retention e purge
- Retained: `Recordings::PurgeExpiredJob` / `rails recordings:purge_expired` (soft-delete + S3 + YouTube se collegato)
- Temporary: `rails recordings:purge_temporary` (solo storage)
### 3. Archivio Replay
- Web: `/clubs/:id/replays` (gestione società)
- Pubblico: `/replay` con filtro società/squadra
- Stessa UX listing per YouTube e MLTV (differisce solo il player)
### 4. Riproduzione
- YouTube: embed su `/replay/:id`
- MLTV: MP4 via `/replay/:id/stream` → redirect `/media/...` → Garage
- Contatore visualizzazioni su ogni play (`view_count`)
### 5. Visibilità
Pubblico / privato (`unlisted`) come prima. Sync privacy YouTube se `youtube_video_id` presente.
### 6. Eliminazione anticipata
`Recordings::Delete` manuale — resta distruttiva sul record (distinta dal cleanup temporary automatico).
### 78. KPI, email, thumbnail, download, views — invariati dove applicabili.
## Config ENV
```env
YOUTUBE_TEMP_REPLAY_RETENTION_HOURS=48
YOUTUBE_REPLAY_VERIFY_MAX_ATTEMPTS=12
YOUTUBE_REPLAY_VERIFY_BASE_INTERVAL_SECS=300
YOUTUBE_REPLAY_VERIFY_GRACE_SECS=120
REPLAY_STORAGE_ENDPOINT=http://garage:3900
REPLAY_STORAGE_BUCKET=matchlivetv-replays
...
```
Valori letti da `MatchLiveTv` (`config/initializers/match_live_tv.rb`).
## Storage Garage
Senza endpoint S3: storage locale `/recordings/replays/`.
### Dev / produzione Garage
Vedi script `infra/scripts/setup_garage_*.sh` come in precedenza.
## Cron (produzione)
```bash
cd /opt/matchlivetv/infra
bash scripts/install_production_cron.sh
```
| Orario | Task |
|--------|------|
| 03:00 | `recordings:purge_expired` — replay retained scaduti |
| 03:30 | `recordings:purge_temporary` — copie temp YouTube scadute |
| 08:00 | `recordings:expiry_warnings` — email avviso scadenza (7 gg) |
### Audit duplicati storici
```bash
rails recordings:audit_youtube_duplicates # dry-run di default
```
Nessun cleanup automatico dei vecchi MP4 YouTube già in Garage.
## API
- `GET /teams/:id/recordings` — include `replay_source`, `youtube_watch_url`, `playback_url` (null se youtube-only), `download_enabled` solo con `storage_key`
- `GET /recordings/:id/download` — richiede file Garage
- `POST /recordings/:id/publish_youtube` — solo MatchLiveTV-only con file (non per live già YouTube)
## Colonne DB (migration)
- `storage_policy` (default `retained`)
- `temp_expires_at`, `youtube_verified_at`, `local_media_purged_at`
## Rischi
- VOD YouTube in ritardo oltre retention temp → safety net elimina file; archivio resta se id collegato; altrimenti anomaly log.
- Thumb: se manca storage, poster YouTube da metadata / default.
- Record storici restano `storage_policy=retained`.