Files
MatchLiveTv/docs/LIVE_STREAMING.md
T
eminuxandCursor a448ad59ee Allinea i doc al flusso live senza OverlayRelay.
Documenta overlay in app, YoutubeRelay copy+AAC in Sidekiq
e HLS sul path camera; aggiorna checklist e troubleshooting.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-29 23:05:50 +02:00

118 lines
5.9 KiB
Markdown
Raw 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.
# Diretta live — architettura MediaMTX
**Ultimo aggiornamento:** 2026-07-29
## Idea
MediaMTX espone **un unico flusso di uscita** per path (`live/match_{uuid}`):
1. **Telefono in onda** → publisher RTMP (camera + **overlay grafico bruciato in app**).
2. **Pausa / rete assente**`alwaysAvailable` con file slate (`/slates/offline.mp4`) **senza interrompere** lHLS verso il sito.
3. **YouTube** (solo se `platform=youtube`) → `Streams::YoutubeRelay` in **Sidekiq**: un processo `ffmpeg` legge RTMP/HLS da MediaMTX e inoltra a YouTube (`-c:v copy`, audio ricodificato in AAC).
Il telefono **non** invia la copertina di pausa: risparmia banda; lo switch slate ↔ camera è lato MediaMTX.
## Componenti
| Pezzo | Ruolo |
|--------|--------|
| App Android / iOS | RTMP 720p AAC mono; overlay tabellone/watermark in GPU sul flusso |
| MediaMTX | Path dinamico, `alwaysAvailable` + slate, HLS |
| `Streams::YoutubeRelay` | Relay continuo verso YouTube (solo Sidekiq con `YOUTUBE_RELAY_WORKER=1`) |
| `Mediamtx::PublisherSync` | Stato Rails da API paths (publisher online) + recording on/off |
| `/hls/...` (edge → MediaMTX in prod; Rails proxy in dev) | Player web |
## Pausa e continuità
1. API `PATCH /sessions/:id/pause` → stato `paused`, registrazione off.
2. App → `pauseStream()` (stop RTMP).
3. MediaMTX → passa alla slate sul **medesimo path** (senza interrompere luscita HLS).
4. **Ripresa** → API `resume``connecting`, recording on, app `resumeStream()`; MediaMTX concatena di nuovo camera sulla stessa uscita HLS.
5. **Sito****un solo** player HLS per tutta la sessione (mai reload in pausa/ripresa). Copertina brand (`brand/CopertinaCanale_6.png``infra/slates/offline.mp4`) muxata da MediaMTX; in pausa solo un messaggio leggero sopra il video.
6. **YouTube** → se attivo, il relay ffmpeg continua sulla stessa uscita MediaMTX (vede slate in pausa).
Non fare `patch` del path MediaMTX in pausa: ricarica il path e interrompe gli HLS reader.
**Recording**: sul path live parte `record: false`; viene acceso via API solo con publisher online e entitlement (`PublisherSync`). I replay finiscono in Garage con job dedicato (vedi `REPLAY_MODULE.md`).
Il player web resta attivo finché `ready|available|online` sul path (non solo quando il telefono è `online`).
## Infrastruttura vs browser
```
Telefono RTMP (camera + overlay app)
MediaMTX path live/match_{uuid}
publisher ON → camera H.264/AAC (già con tabellone se overlay ≠ none)
publisher OFF → alwaysAvailable → /slates/offline.mp4
├──► HLS (segmenti ~1s) ──► edge /hls/ → MediaMTX ──► player web (HLS.js)
└──► (solo platform=youtube)
Streams::YoutubeRelay
ffmpeg: -c:v copy, -c:a aac ──► RTMPS YouTube
```
| Responsabilità | Dove |
|----------------|------|
| Switch copertina ↔ live | **MediaMTX** (stesso path, stesso URL HLS) |
| Tabellone / watermark sullo stream | **App nativa** (GPU → RTMP) |
| Continuità YouTube | **`Streams::YoutubeRelay`** (ffmpeg in Sidekiq) |
| Badge «In onda» / stato pausa | **Rails** (`status.json`, `PublisherSync`) + UI web |
| Uscire dal buffer copertina dopo ripresa | **Browser** (`pendingLiveRecovery`, reload HLS) |
| Fluidità playback | **Browser** (evitare seek continui su `liveSyncPosition`) |
Lo switch slate→camera **non** richiede un nuovo URL lato player: MediaMTX concatena sulla playlist. Il sito può però restare «indietro» nel buffer HLS (ancora segmenti della slate) anche con `publisher_online: true` — da qui i recovery controllati in `show.html.erb`, **senza** chiamare `jumpToLiveEdge()` a ogni frammento bufferizzato.
URL HLS pubblico: `https://www.matchlivetv.it/hls/live/match_{uuid}/index.m3u8` (`StreamSession#effective_hls_path_name` = path camera, **non** più `*_air`).
### Player web — anti-scatti (HLS.js)
Configurazione in `backend/app/views/public/live/show.html.erb`:
- `maxLiveSyncPlaybackRate: 1.08` (evita accelerazioni visibili; prima 1.5 causava micro-scatti).
- `jumpToLiveEdge(force)` con throttle: seek solo se lag > 2s o `force`, e non più di una volta ogni ~8s se lag < 4s.
- **Niente** seek su `FRAG_BUFFERED` né nel poll ogni 1.5s.
- Sync iniziale una volta su `LEVEL_LOADED`; recovery solo se `liveEdgeLagSec() > 4` o `pendingLiveRecovery`.
- Interval 6s solo durante recovery attivo (`tryEscapeCoverBuffer`).
### Grafica nel video (overlay)
**Non** c’è più un relay server `Streams::OverlayRelay` che ricodifica con PNG.
Il tabellone è composito **sul telefono** prima dellRTMP (vedi [`TABELLONI_E_OVERLAY.md`](TABELLONI_E_OVERLAY.md)):
1. `OverlayState` da `effectiveOverlayKind` + `ScoreState`
2. `OverlayRenderer` / canvas → bitmap
3. Filtro GPU sul encoder RTMP
La pagina live può mostrare badge HTML di stato (in onda / pausa / attesa); non sostituisce il tabellone nel video.
### Ruolo di ffmpeg oggi
| Quando | Cosa | Peso CPU |
|--------|------|----------|
| Diretta `platform=youtube` | `YoutubeRelay`: demux + **copy video** + AAC audio → RTMPS | Medio-basso **per diretta**, scala con N processi |
| Diretta solo `matchlivetv` | Nessun ffmpeg in live | Quasi zero |
| Fine diretta | `UploadFromSession`: concat segmenti (`-c copy`) | Picco breve |
| Post-process | `GenerateThumbnail`: un frame → JPEG | Trascurabile |
Non esiste più la ricodifica H.264 server-side delloverlay (era il carico principale).
### Punteggio (persistenza)
Lapp mobile persiste il punteggio con `PATCH /api/v1/sessions/:id/score` (o `score_action`) → `Scoring::SyncState`, poi aggiorna loverlay locale in camera.
`GET /live/:id/status.json` resta per messaggi sotto il player e recovery HLS; `Cache-Control: no-store`.
## Rigenerare la slate
```bash
cd infra
bash scripts/generate_slate.sh
# Copia su server: infra/slates/offline.mp4 montato in /slates nel container mediamtx
```
Formato obbligatorio: **H.264 1280×720 30fps, AAC 48 kHz mono** (allineato allapp).