# 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 ` 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.