Keep credentials, HARs and probe dumps out of git; use neutral wording around sessions and stream licenses.
359 lines
11 KiB
Markdown
359 lines
11 KiB
Markdown
# 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)
|
||
- Server-login zonder browser-sessie (provider bot-protectie)
|
||
- 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: "<hls master>",
|
||
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.
|