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.
79 lines
3.6 KiB
Markdown
79 lines
3.6 KiB
Markdown
# 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.
|