# F1TV → Events & F1 Hub — Technisch design Status: **backend MVP gebouwd** (Scripts + sync + Events-merge + hub-API). Android TV hub volgt. Datum: 2026-09-22 Doel: F1 live-sessies én terugkijken in de app, met een dedicated F1-hub onder Events. --- ## 1. Antwoord op “kan terugkijken ook?” **Ja.** De `spelend.har` bewijst het al: we speelden **2026 Grand Prix van Nederland** als `VIDEO/REPLAY` af via: ``` GET /3.0/R/{locale}/WEB_HLS/ALL/CONTENT/PLAY?contentId={id}&player=player_tm → HLS URL op ott-video-fer-cf.formula1.com → fMP4/CMAF-segmenten (de .mp4’s die je zag) ``` In die capture: **geen Widevine / geen EXT-X-KEY** — clear HLS. Playback voor Events is daardoor eenvoudiger dan Odido (geen CDM voor dit pad). Conclusie: | Content | Discovery | Play | In scope? | |---------|-----------|------|-----------| | Aankomende sessies (`LIVE_EVENT` / `Pre-Live`) | Meeting-PAGE + start/stop | Later via zelfde PLAY | Ja — Events + hub | | Live sessie (`state: Live`) | Zelfde | PLAY (HLS; live mogelijk DASH+DRM) | Ja — MVP live; DRM-pad valideren tijdens raceweekend | | Terugkijken (`REPLAY`) | Meeting-PAGE / VIDEO-detail | Zelfde PLAY (bewezen) | Ja — F1-hub, niet in de algemene Events-agenda | Terugkijken hoort **niet** in de gewone Events-lijst (die filtert `ended` weg en is “nu/straks”). Het hoort wél in de **F1-hub** (agenda + archief). --- ## 2. Context in huidige architectuur ### Wat Events nu is - Bron: externe FAAB JSON (`liveEventsUrl`) - API: `GET /client/events`, `/:id`, `/:id/play` - Play: pre-resolved `streamUrl` + ClearKey keys; alleen `phase === "live"` - Clients (iOS / Android TV): sport-chips → shelves → detail → player - F1 valt nu alleen onder sport-bucket `racing` als FAAB het levert — **geen F1TV-provider** ### Wat Odido is (niet het Events-model) - 24/7 zenders, `LiveChannel`, sync worker, Widevine→ClearKey - Herbruikbaar: `IntegrationSetting`, encrypted secrets, admin Scripts-UI, CDM-helper **als** live DRM nodig blijkt ### Wat F1TV moet worden Een **first-party event-provider** (niet alleen feed-merge): 1. Auth + tokens 2. Periodieke discovery (kalender → meetings → sessies) 3. On-demand play resolve bij `/:id/play` 4. Client: F1-hub UI + bestaande player --- ## 3. Doelen & non-goals ### Doelen (MVP+) 1. Provider `f1tv` in Events-pipeline (admin/viewer toggles) 2. Upcoming + live F1-sessies in Events (bucket `racing`) én in F1-hub 3. F1-hub: agenda weekend + terugkijken (efficient, Events-achtige shelves) 4. Play via server-side `CONTENT/PLAY` → HLS naar clients 5. Multi-account optioneel later (1 Premium account eerst) ### Non-goals (later) - Volledige F1TV VOD-catalogus (documentaires, shows, F2/F3 alles) - Onboard camera picker UI (MVP: default feed / F1 LIVE channel) - Imperva-bypass reverse-engineeren in productie zonder browser-sessie - Live timing / telemetry overlay --- ## 4. F1TV API-model (uit HARs) ### Auth-tokens | Token | Rol | |-------|-----| | `ascendontoken` / subscriptionToken | Login-resultaat; header voor entitlement | | `entitlementtoken` | Catalogus + PLAY; vernieuwd via `USER/ENTITLEMENT` of in PLAY-response | | `sessionid` | WEB-uuid; meenemen op AGL-calls | | Imperva / reese84 | Blokkeert server-side password-login | **Aanbeveling auth-MVP:** admin plakt / vernieuwt tokens (of browser-assisted login later). Password + Imperva is fase 2. ### Discovery ``` PAGE/12343 (seizoen 2026) → BUNDLE/MEETING { MeetingKey, pageUri → PAGE/{meetingId} } PAGE/{meetingId} → VIDEO/LIVE_EVENT | VIDEO/REPLAY → contentId, title, sessionStartDate/EndDate, state (Pre-Live | Live | …), Series ``` ### Play ``` CONTENT/VIDEO/{contentId} → metadata + additionalStreams (channelId, playbackUrl) CONTENT/PLAY?contentId=&channelId=&player=player_tm headers: ascendontoken, entitlementtoken, sessionid → resultObj.tme.feeds[].url (HLS) → streamType SDR_HD_CMAF ``` Multi-cam: ~26 feeds (channelId 1033 = F1 LIVE, plus OBC). MVP speelt default feed (F1 LIVE / eerste feed). --- ## 5. Backend-architectuur ### Nieuwe module `apps/master-api/src/f1tv/` | Bestand | Verantwoordelijkheid | |---------|----------------------| | `settings.ts` | `IntegrationSetting` id=`f1tv`: tokens, locale (`NLD`), device headers, tweaks | | `client.ts` | HTTP: PAGE, VIDEO, PLAY, ENTITLEMENT | | `auth.ts` | Token load/refresh/expiry; health | | `sync.ts` | Kalender → meetings → sessies → interne event-store | | `play.ts` | Resolve HLS (+ optioneel DRM later) | | `map.ts` | F1TV item → Events-shape + hub-shape | ### Data-model (intern) Geen permanente DB-tabel verplicht voor MVP; wel cache + snapshot in `IntegrationSetting` of korte memory/redis-achtige cache zoals schedule-events. **Interne event-record:** ```ts { id: `f1tv:${contentId}`, // stabiel contentId: number, provider: "f1tv", kind: "live_event" | "replay", phase: "upcoming" | "live" | "ended", name, meetingName, meetingKey, series, start, stop, // ISO uit sessionStart/End state, // F1TV raw imageLandscape?, imageBackdrop?, defaultChannelId?: number, // 1033 playable: boolean, // live óf replay (hub); Events-list alleen live } ``` ### Integratie met `schedule-events.ts` Twee lagen: 1. **Merge in `/client/events`** - Alleen `kind=live_event` met `phase` upcoming|live - `sportBucket: "racing"`, `provider: "f1tv"` - `playable` alleen als `phase === "live"` (huidige Events-regel) 2. **Nieuwe hub-API** (apart van FAAB-lijst) ``` GET /api/v1/client/f1/home GET /api/v1/client/f1/meetings GET /api/v1/client/f1/meetings/:meetingKey GET /api/v1/client/f1/content/:contentId GET /api/v1/client/f1/content/:contentId/play ``` Of één compacte home-payload: ```ts { nextMeeting: { ... }, agenda: Session[], // komende 7–14 dagen live: Session[], recentReplays: Session[], // laatste meeting(s) meetings: MeetingSummary[] // seizoen, compact } ``` Play voor hub mag **replay én live**; bestaande Events-play blijft live-only. ### Admin - Scripts-pagina sectie **F1TV** (naast Odido): tokens, locale, sync-now, health, laatste sync-samenvatting - Settings IPTV: provider `f1tv` in disabled-providers / viewer allowlist (zelfde mechanisme) ### Sync-strategie | Job | Interval | Actie | |-----|----------|-------| | Seizoen-kalender | 6–24u | PAGE/12343 → meeting map | | Actieve + aankomende meetings | 5–15 min | PAGE/{id} sessies | | Live window | 1–2 min | state refresh rond start−1h … stop+2h | | Play | on-demand | geen pre-fetch van HLS (TTL ~4u in URL) | HLS-URLs **niet** lang cachen; alleen metadata cachen. Play altijd vers `CONTENT/PLAY`. --- ## 6. DRM / playback-pad ### Bewezen (replay HLS) Client krijgt: ```ts { streamUrl: "", format: "hls", drm: null, // of weglaten } ``` iOS/Android moeten `drm: null` HLS al aankunnen (Events speelt nu ClearKey; check of plain HLS-pad bestaat — zo niet: kleine player-uitbreiding). ### Onzeker (live) Live kan DASH + Widevine zijn. Plan: 1. MVP: aanname “zelfde HLS als replay” 2. Raceweekend: live-HAR valideren 3. Zo DRM: Odido-achtig Widevine→ClearKey **of** client Widevine (TV) — aparte beslissing Geen CDM-werk in MVP tenzij live-HAR het afdwingt. --- ## 7. Client: F1-hub UX ### Entry point In **Events** (list): - Compacte **F1-tile / chip / hero-rail** bovenaan of in de chip-rij - Label: “F1” of “Formule 1” - Sub: “Agenda & terugkijken” - Tap → `F1HubScreen` Niet alleen “racing-shelf”: racing kan MotoGP/etc. blijven; F1-hub is dedicated. ### Navigatie ``` EventsScreen └─ F1HubScreen ├─ MeetingDetail (weekend) │ ├─ Session cards (upcoming / live / replay) │ └─ Play → bestaande PlayerScreen └─ ReplayDetail → Play ``` Android: `f1_hub` / `f1_meeting/{key}` / `f1_play/{contentId}` in `McNav`. iOS: `NavigationStack` path of sheet binnen Events-stack. ### Hub-layout (Events-DNA, F1-skin) Spiegel `EventsLook`, maar F1-palette: - Rood accent (`#E10600` F1-achtig), diep zwart, wit typografie - Geen dashboard-rommel: weinig chips, lange horizontale rails **Eerste viewport (efficient):** 1. Header “F1” + LIVE-pill als iets live is 2. **Nu / Volgende** — 1 grote featured card (live of next session) 3. **Dit weekend** — horizontale rail sessies (FP/Quali/Race) 4. **Terugkijken** — rail laatste races (compacte kaarten) 5. **Kalender** — verticale of horizontale meeting-rail (vlag/circuit-naam) Focus-TV: D-pad FocusRestore zoals Events (filter → first rail). ### Wat wél / niet in algemene Events | | Events-tab | F1-hub | |--|------------|--------| | Live F1-sessie | Ja (`racing`, playable) | Ja (featured) | | Upcoming F1 | Ja (upcoming phase) | Ja (agenda) | | Replay | Nee | Ja | | Multi-cam | Nee (MVP) | Later | Zo blijft Events schoon; hub is de rijke F1-ervaring. --- ## 8. Implementatiefases ### Fase 0 — Design locked (nu) Dit document + akkoord scope hub. ### Fase 1 — Backend skeleton - `IntegrationSetting` f1tv + Scripts UI (tokens handmatig) - Client: PAGE calendar + meeting parse + sync snapshot - Unit/probe script met tokens uit HAR (geen Imperva-login nog) ### Fase 2 — Play path - `play.ts` → CONTENT/PLAY → HLS - Wire `GET .../f1/.../play` én Events `/:id/play` voor `f1tv:*` ids - Client player: plain HLS zonder ClearKey ### Fase 3 — Events merge - Inject upcoming/live in `/client/events` - Provider toggle admin/viewer - Smoke: F1 verschijnt in racing-shelf ### Fase 4 — F1 Hub UI - iOS + Android TV hub screens - Entry tile in Events - Agenda + terugkijken rails - Meeting detail ### Fase 5 — Hardening - Token refresh flow / expiry health - Live-weekend validatie (DRM?) - Optioneel: multi-cam picker - Optioneel: Imperva-assisted login --- ## 9. Risico’s & open punten | Risico | Mitigatie | |--------|-----------| | Imperva blokkeert password-login vanaf server | Tokens handmatig / browser-assisted; geen fake login in MVP | | Live ≠ clear HLS | Live-HAR vóór productie-claim; CDM-fallback klaarzetten | | Token TTL / concurrent streams | Health + rate-limit play; 1 account eerst | | FAAB + F1TV dubbele F1-events | Dedup op naam/tijd of disable FAAB F1 als f1tv aan staat | | HLS auth cookies/headers op CDN | PLAY-URL bevat signed `pa_` path — test of clients zonder extra headers kunnen (HAR suggereert ja) | --- ## 10. Success criteria 1. Upcoming Azerbaijan-sessies zichtbaar in Events (upcoming) en F1-hub 2. Tijdens live: playable in Events + hub 3. Replay uit hub speelt beeld (zelfde pad als `spelend.har`) 4. Viewer zonder `f1tv` in allowlist ziet geen F1TV-items 5. Geen regressie Odido / bestaande Events-providers --- ## 11. Samenvatting - **Terugkijken:** ja, zelfde F1TV PLAY-pad — thuis in de **F1-hub**, niet in de standaard Events-lijst. - **Live/upcoming:** F1TV sync → Events (`racing`) + hub. - **UI:** F1-knoop onder Events → dedicated hub (agenda + replays), Events-layout-principes, F1-skin. - **Auth:** tokens eerst; Imperva-login later. - **DRM:** replay clear HLS; live valideren tijdens raceweekend. Volgende stap na akkoord: **Fase 1** (backend skeleton + token Settings), of eerst alleen hub wireframes als je de UI-richting wilt vastzetten vóór API-werk.