Skip to content

Cut repeat traffic after bot checks, redesign video playlists, shuffle Home - #292

Merged
Ivorisnoob merged 4 commits into
mainfrom
feature/bot-check-traffic-playlist-page-shuffle-home
Sep 18, 2026
Merged

Ivorisnoob merged 4 commits into
mainfrom
feature/bot-check-traffic-playlist-page-shuffle-home

Conversation

@Ivorisnoob

Copy link
Copy Markdown
Owner

Why

A signed-in tester hit "Sign in to confirm you're not a bot" after about an hour of normal use. Signed-in probes (docs/youtube-data.md) showed account cookies cannot fix it: ANDROID_VR, IOS and VISIONOS reject cookie + SAPISIDHASH with HTTP 400 and ignore a bare Cookie header, while MWEB/WEB accept the account but googlevideo refuses their URLs without PO tokens. The refusal is a verdict on the network, so the fix is to stop Koda repeating it.

What changed

Bot-check traffic

  • YouTubeRepository.isBotCheckVerdictActive(): armed when the direct chain is refused after a remint, or refused when NewPipe already threw SignInConfirmNotBotException. Lasts 3 minutes; any successful resolution clears it.
  • While active: no remint-and-retry, music error retries and queue skipping stop, music and Shorts prefetch stand down, downloads stop re-attempting. User-initiated requests still go out.
  • Removed the legacy getVideoStreamUrl fallback, which ran a second full NewPipe extraction after the first failed.

Home reloads

  • Returning to Home no longer refetches account info, every liked-songs and library playlist page, the music home, video feed and Shorts shelf. Loads stand for 10 minutes; pull-to-refresh, sign-in/out and profile switch force. The duplicate launch load (init + HomeScreen effect) now joins the in-flight job.

Video playlist page

  • New VideoPlaylistPage.kt, built like the music playlist page; shared colour helpers moved to PlaylistPageStyle.kt. 16:9 cover hero with first-video fallback, honest counts while paging, Play/Shuffle, Save/Download, connected rows, floating split play button.

Video search: Today

  • VideoSearchDateFilter.TODAY = after:<yesterday>. Probed live: after:<today> leaked days-old results, and the native Today sp padded sparse queries with year-old videos.

Channel sort chips

  • YouTube moved Latest/Popular/Oldest tokens from a sheet onto plain chips, so the sort row had silently vanished. ChannelSortChips reads the new shape; the sheet parser stays as fallback.

Shuffled Home (recommendations off)

  • SubscriptionMix builds pages from each followed channel's recent uploads and its Popular order (2:1 catalogue to recent), dedupes and spreads creators. Pages load 8 channels at a time, 4 fetches concurrently, and stand down during a 429 hold.

Testing

  • compileDebugKotlin, full testDebugUnitTest (485 tests, new: VideoSearchFiltersTest, ChannelSortChipsTest, SubscriptionMixTest).
  • Lint: no new issues from this change beyond untranslated new strings (locales are partial by design).
  • Tested on the emulator by the maintainer.

Docs updated: docs/youtube-data.md, docs/screens.md, docs/channels.md, docs/subscriptions.md, ROADMAP.md.

…e Home

A signed-in tester was refused with "Sign in to confirm you're not a bot".
Signed-in probes showed account cookies cannot help: the stream clients
reject them, and the clients that accept them cannot serve bytes without PO
tokens. What Koda controls is how often it asks again.

- A refusal that survives a fresh visitorData, or that NewPipe shares with the
  direct chain, records a three-minute verdict. Remint-and-retry, music error
  retries and queue skipping, music and Shorts prefetch and download
  re-attempts stand down; user taps still make requests.
- The video and Shorts players no longer run a second NewPipe extraction
  after the first failed.
- Home loads stand for ten minutes instead of refetching the whole account on
  every return, and the duplicate launch load joins the running one.
- The video playlist page shares the music playlist page's construction via
  PlaylistPageStyle.kt.
- Video search gains Today, a one-day after: window (probed live).
- Channel sort chips are read from their new direct-chip shape.
- With recommendations off, Home shuffles each followed channel's recent and
  Popular uploads instead of repeating the Subscriptions feed.

Changelog:
- Koda no longer keeps retrying, reminting or skipping through your queue when YouTube asks to confirm you're not a bot, which reduces the chance of being blocked.
- Returning to Home no longer reloads your whole library and feeds each time.
- Video playlists now have the same cover-led page as music playlists, with Play all, Shuffle, Save and Download.
- Video search has a new Today upload-date filter.
- Channel pages show the Latest, Popular and Oldest sort options again.
- With video recommendations off, Home now shows a shuffled mix of old and new videos from the channels you follow.
Changelog:

- Added a sleep timer to video playback settings, with timed and end-of-video modes.

- Added Listen as music to video playback settings to move a video into the music player.

- The expanded music player now keeps the screen on while playing.

- Motion artwork now pauses with the music instead of dropping to the still cover.
Changelog:

- Reworked video playback settings: quality and speed are now compact rows that expand inline, with autoplay, sleep timer and Listen as music in the same card.
Changelog:

- Added a Music button to the video action bar that moves the video into the music player.

- Channels in the subscriptions manager now open their channel page on tap.

- Android Auto Library tiles now show artwork instead of broken-image placeholders.
val key = AutoArtwork.keyFromUri(uri) ?: throw FileNotFoundException(uri.toString())
val context = context ?: throw FileNotFoundException(uri.toString())
val file = AutoArtwork.fileFor(context, key)
return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY)
require(key in TILE_HUES) { "unknown auto artwork key: $key" }
val dir = File(context.cacheDir, "auto-artwork").also { it.mkdirs() }
val file = File(dir, "$key.png")
if (!file.isFile) {
val dir = File(context.cacheDir, "auto-artwork").also { it.mkdirs() }
val file = File(dir, "$key.png")
if (!file.isFile) {
renderTile(key).compress(Bitmap.CompressFormat.PNG, 100, file.outputStream())
@Ivorisnoob
Ivorisnoob merged commit b26c5ab into main Sep 18, 2026
6 of 7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants