frosty/DESIGN.md
copilot d8247843c4
Some checks failed
Actions probe / probe (push) Successful in 0s
Release APK / Build & publish release (push) Failing after 24s
Add freeform tag search and viewer sort for live lists.
Helix cannot filter by tags or sort ascending; use Twitch GQL for Search→Tags→streams and Most/Fewest viewers on Top, category, and tag screens.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-30 23:22:56 +02:00

50 lines
2.6 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.

# Frosty fork — Twitch ad blocking (DESIGN)
Personal Forgejo fork of [tommyxchow/frosty](https://github.com/tommyxchow/frosty) that adds **TwitchAdSolutions (vaft)**-style live ad blocking.
Canonical living plan: session `active-plan.md` for conversation `37b527bf-e540-4753-be1f-39906fe2ba28`. ADR: **ADR-026**.
## Problem
Upstream Frosty plays Twitch HLS (native) or `player.twitch.tv` (WebView). Twitch may stitch ad pods into the media playlist (`stitched` / `EXT-X-DATERANGE`). Upstream only **detects** native ad breaks (to avoid bogus latency recovery); it does **not** block ads. Turbo/sub `hide_ads` via linked web session is separate and not a client blocker.
## Approach (vaft-equivalent)
Browser vaft hooks `Worker`/`fetch` and rewrites m3u8. Frosty’s native player cannot do that from Dart (`loadUrl` + headers only).
| Layer | Behavior |
| --- | --- |
| Primary token | GQL `PlaybackAccessToken` with `playerType=popout` (vaft `ForceAccessTokenPlayerType`) when Settings `blockTwitchAds` is on |
| Ad break | Native `adBreakStream` → probe backup types `embed` → `popout` → `autoplay`; accept first Usher multivariant whose sampled media playlist lacks `stitched`; `loadUrl` that URL |
| Ad end | Restore primary popout Usher URL |
| WebView | Best-effort inject of vendored `assets/js/vaft.user.js` (not true document-start on Android) |
| UI | Settings toggle (default on; enabling forces native player); overlay “Blocking ads” |
## Key code
- `lib/apis/twitch_ad_solutions.dart` — constants + `TwitchAdFreePlayback`
- `lib/apis/twitch_gql_api.dart` — `playerType` / `platform`, `fetchPlaylistText`
- `lib/screens/channel/video/native_video_store.dart` — load + swap
- `lib/screens/channel/video/video_store.dart` — WebView inject
- `lib/screens/settings/stores/settings_store.dart` — `blockTwitchAds`
- `assets/js/vaft.user.js` — upstream TwitchAdSolutions script
## Non-goals (v1)
- Upstream PR
- Full Worker prototype cleaning
- Third-party proxies
- HLS segment stripping inside `better_native_video_player` (unless smoke tests demand it)
## Tags + viewer sort (v5.2.2+)
Helix cannot filter by freeform tags or sort ascending. GQL covers both:
- Search: `SearchFreeformTags` → tap → `TagStreams` (`streams` + `freeformTags`)
- Sort: Top (asc), category (`game.streams`), tag — `VIEWER_COUNT` / `VIEWER_COUNT_ASC` via `StreamSortButton`
Key code: `lib/apis/twitch_gql_api.dart`, `lib/screens/home/search/`, `lib/widgets/stream_sort_button.dart`, `ListStore` / `ListType.tag`.
## Build
Flutter **3.47.0** (pinned in `pubspec.yaml`). Local SDK: `/home/yuna/.local/flutter-3.47.0`.