Sulkta day-2: search → detail → player → SponsorBlock + RYD
Phase A: NewPipeExtractor + OkHttp Downloader wired in. Search bar + LazyColumn results. Tap = navigate to detail. Phase B: VideoDetail screen — StreamInfo metadata + Return YouTube Dislike chips + description. Phase C: Media3 ExoPlayer in Compose. Resolves StreamInfo to best playable: DASH MPD → HLS → combined progressive → merged videoOnly+audio. Phase D: SponsorBlock SHA-256 prefix lookup. 250ms position-poll loop inside PlayerScreen — exoPlayer.seekTo(segment.end) when entering a sponsor segment. Toast on skip. Phase E: Verified live on Android 14 emulator. linus tech tips search returns real results with thumbnails; tapped result opens detail; hit Play → video plays through ExoPlayer. Architecture: everything in :strawApp for now (not pushed into :shared yet — KMP refactor is day-3). Pure-state nav (sealed Screen + stack, no nav library). Known polish gaps (day-3): RYD chips render empty on some videos, description has raw HTML (markdown render needed), no Koin DI yet, no persistence. GPL-3.0-or-later per upstream NewPipe.
This commit is contained in:
parent
ff4dc6f121
commit
496ed30bda
16 changed files with 1125 additions and 6 deletions
79
docs/sulkta/DAY2.md
Normal file
79
docs/sulkta/DAY2.md
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
# Straw day-2 — search → detail → player → SponsorBlock + RYD
|
||||
|
||||
Loop pass through phases A → E in one session. Built on the day-1 `:strawApp`
|
||||
module without touching `:shared` or `:app`.
|
||||
|
||||
## What landed
|
||||
|
||||
| Phase | Component | Location |
|
||||
|---|---|---|
|
||||
| A | Search bar + LazyColumn results | `feature/search/Search{ViewModel,Screen}.kt` |
|
||||
| A | NewPipeExtractor + OkHttp Downloader | `extractor/NewPipeDownloader.kt`, `StrawApp.kt` |
|
||||
| B | Video detail screen + RYD chips | `feature/detail/VideoDetail{ViewModel,Screen}.kt` |
|
||||
| B | Return YouTube Dislike client | `net/RydClient.kt` |
|
||||
| C | Media3 ExoPlayer (DASH / HLS / progressive / merge fallback) | `feature/player/PlayerScreen.kt` |
|
||||
| C | Player view-model resolves StreamInfo | `feature/player/PlayerViewModel.kt` |
|
||||
| D | SponsorBlock client (SHA-256 prefix lookup) | `net/SponsorBlockClient.kt` |
|
||||
| D | Auto-skip via position-poll loop | inside `PlayerScreen.kt` |
|
||||
| E | Repo committed, APK rebuilt | this commit |
|
||||
|
||||
## Navigation
|
||||
|
||||
Home → Search → VideoDetail → Player.
|
||||
|
||||
Pure-state nav (sealed `Screen` + `Navigator`, no nav library). Back button
|
||||
unwinds the stack; falling off the root exits the app. Day-3 will switch to
|
||||
`androidx-navigation3` to match upstream's KMP scaffold.
|
||||
|
||||
## Architecture notes
|
||||
|
||||
- **All code lives in `:strawApp` for now** — not pushed into `:shared`. The
|
||||
KMP-skill canonical shape would be domain/data/presentation per feature in
|
||||
`:shared/commonMain`. We'll refactor later; day-2 is about shipping the
|
||||
vertical slice fast.
|
||||
- **No DI yet** — ViewModels are `viewModel()`-constructed with no
|
||||
constructor args. Day-3 will introduce Koin for the OkHttp client,
|
||||
RydClient, SponsorBlockClient.
|
||||
- **No persistence** — search history, watch history, subs all live in
|
||||
memory and die on app close. Day-3 = Room/DataStore.
|
||||
- **No iOS/Desktop** — `:strawApp` is Android-only because NewPipeExtractor
|
||||
is JVM-only. KMP-ification of the extractor is a multi-week project
|
||||
upstream is presumably already eyeing.
|
||||
|
||||
## Player path
|
||||
|
||||
NewPipeExtractor returns a `StreamInfo` with a few possible playback shapes.
|
||||
We try them in this preference order:
|
||||
|
||||
1. **DASH MPD URL** — `info.dashMpdUrl` → `DashMediaSource`. Best quality
|
||||
when YouTube serves it.
|
||||
2. **HLS URL** — `info.hlsUrl` → `HlsMediaSource`. Mostly for live streams.
|
||||
3. **Combined video+audio** — `info.videoStreams.maxByBitrate` →
|
||||
`ProgressiveMediaSource`. Rare on modern YouTube; older clients only.
|
||||
4. **Merged DASH-chunks** — `info.videoOnlyStreams.maxByBitrate` +
|
||||
`info.audioStreams.maxByBitrate` → `MergingMediaSource`. The fallback.
|
||||
5. **Video-only** — last-resort, silent playback.
|
||||
|
||||
## SponsorBlock auto-skip
|
||||
|
||||
Phase D wires `SponsorBlockClient.fetch(videoId, ["sponsor"])` into
|
||||
`PlayerViewModel` and runs a 250ms position-poll loop in `PlayerScreen`
|
||||
that calls `exoPlayer.seekTo(segment.endSec * 1000)` when the playhead
|
||||
enters a segment. A Toast says `skipped <category>` each time.
|
||||
|
||||
Categories default to `sponsor` only — matches siku2 defaults. User-settable
|
||||
categories are day-3.
|
||||
|
||||
## Known limitations / day-3 items
|
||||
|
||||
- ExoPlayer reuse across video changes: each VideoDetail → Player is a fresh
|
||||
resolve. Quick navigation can leave stranded resolves.
|
||||
- No background audio / PiP.
|
||||
- No screen-rotation handling (config-changes are caught so the activity
|
||||
isn't recreated, but the player UI doesn't lock landscape on play).
|
||||
- No watch history / subscriptions.
|
||||
- No channel browse / playlist browse.
|
||||
- No proxy / Tor support.
|
||||
- No "open with Straw" intent filter for YouTube URLs (low-hanging Day-3).
|
||||
- DI via Koin (skill recommends it).
|
||||
- Move logic into `:shared` per KMP best-practices.
|
||||
Loading…
Add table
Add a link
Reference in a new issue