stremio/docs/f1tv-events-design.md
Jos Vooges | STH 8685870358 Add F1TV Scripts integration with multi-account sync and Events merge.
Tabbed Scripts UI keeps Odido and F1TV separate; F1TV syncs calendar sessions into Events and exposes hub play APIs for upcoming Android TV work.
2026-09-22 03:43:56 +02:00

359 lines
11 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.

# 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: "<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.