Files
MatchLiveTv/docs/LIVE_STREAMING.md
eminuxandCursor 7b3256c8a0 Alleggerisce il relay YouTube e rilascia Android 2.0.8.
ffmpeg fa solo remux copy A/V; le app fissano AAC 48 kHz mono; sync deploy non cancella più garage.prod.toml.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-05 19:25:40 +02:00

6.0 KiB
Raw Permalink Blame History

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 assentealwaysAvailable 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, -c:a copy; lAAC 48 kHz mono è già prodotto dallapp / slate).

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 resumeconnecting, recording on, app resumeStream(); MediaMTX concatena di nuovo camera sulla stessa uscita HLS.
  5. Sitoun solo player HLS per tutta la sessione (mai reload in pausa/ripresa). Copertina brand (brand/CopertinaCanale_6.pnginfra/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 copy ──► 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):

  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/audio → RTMPS (remux) 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

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).