straw/docs/sulkta/DAY2.md
Kayos 496ed30bda 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.
2026-05-23 19:22:52 -07:00

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.