straw/docs/sulkta/RECON.md
Kayos ff4dc6f121 Sulkta day-1: straw — KMP/Compose YouTube client fork
Initial Sulkta fork of NewPipe with a new :strawApp module that ships a
clean Compose-based Android APK at applicationId com.sulkta.straw.

What's here:
- :strawApp — thin Android application shell, MaterialTheme + StrawHome
  Composable. Lives alongside legacy :app so we don't break upstream.
- buildSrc — STRAW_APPLICATION_ID/VERSION constants alongside the existing
  NEWPIPE_APPLICATION_ID_OLD/NEW.
- docs/sulkta — RECON.md (NewPipe codebase breakdown) + DECISIONS.md
  (stack + scope decisions).

NewPipe's :shared KMP scaffold is at 892 LOC and renders nothing; this
fork picks up there and races ahead. Day-1 ships a hello APK; day-2 wires
NewPipeExtractor + Media3 player + SponsorBlock + Return YouTube Dislike.

GPL-3.0-or-later per upstream.
2026-05-23 17:37:55 -07:00

5.9 KiB

NewPipe recon — 2026-05-23

Clone: /root/build/newpipe-recon/ (shallow clone of upstream master 94005cf).

TL;DR

NewPipe is mid-migration to Kotlin Multiplatform + Compose Multiplatform, but the new app is at literal day-zero. Of 4 modules, only :app (the legacy Android app) actually works. :shared has 892 LOC of scaffolding but zero screens. :desktopApp is an 18-line stub. :iosApp has 0 lines of Kotlin.

The "fork the legacy app and modernize it" angle is dead — they're modernizing. The actual opening is: pick up the abandoned-looking KMP scaffold and execute the next year of work in a day's worth of vertical-slice ambition. Bake in SponsorBlock + RYD as first-class while we're at it.

Module breakdown

Module LOC Kt files Java files Status
:app (legacy NEWPIPE_APPLICATION_ID_OLD) 82,427 173 295 What users run as v0.28.6
:shared (KMP, NEWPIPE_APPLICATION_ID_NEW) 892 21 0 Hello-world stage, zero screens
:desktopApp 18 1 0 Main.kt stub only
:iosApp 0 0 0 Xcode shell only

:shared current state

shared/src/commonMain/kotlin/net/newpipe/app/ contains:

  • App.kt@Composable fun App(...) that sets up Koin + AppTheme {} with an empty body. The KMP app literally renders nothing.
  • Constants.kt — empty file-level constants.
  • navigation/NavDisplay.kt — wraps androidx-nav3's NavDisplay, but entryProvider { } block is empty. No screens registered.
  • navigation/Screen.kt — sealed interface with zero subclasses.
  • di/KoinApp.kt — bare @KoinApplication object KoinApp with no modules.
  • composable/TopAppBar.kt — basic top bar.
  • theme/ — Color, ServiceTheme, Theme. Solid Material 3 scaffolding.
  • preview/ThemePreviewProvider.kt — Compose preview helper.
  • di/settings/SettingsModule.kt — multiplatform settings DI (separate commonMain interface + androidMain/jvmMain/iosMain impls).

That's it. The entire new app is theme + navigation primitive + Koin stub.

:shared/androidMain

  • ComposeActivity.kt — Android entry that hosts the Compose UI.
  • Constants.kt — Android-specific constants.
  • extensions/Context.kt — extension utilities.

Stack (already chosen by NewPipe, modern as of 2026)

Layer Pick Version Notes
Language Kotlin 2.3.21 Bleeding edge
UI Jetpack Compose + Compose Multiplatform 1.11.1 Material3 1.11.0-alpha07
Build AGP 9.2.1 Latest
Nav androidx-navigation3 1.1.1 Type-safe, KMP-friendly
DI Koin 4.2.1 Annotation-driven via koin-plugin
Async kotlinx.coroutines 1.11.0 Plus kotlinx-coroutines-rx3 bridge to legacy
Serialization kotlinx.serialization 1.11.0 JSON, parcelable replacement
Image loading Coil 3 3.4.0 KMP-native via coil-network-okhttp
HTTP OkHttp 5.3.2 Android, used by extractor
DB Room 2.8.4 Android-only, KMP-incompatible (legacy app)
Player ExoPlayer 2.19.1 Stale. Should be androidx.media3 ≥ 1.4
Crash ACRA 5.13.1 Send-to-self crash reporter
Leak detection LeakCanary 2.14 Debug builds only

Critical dependency: NewPipeExtractor

  • teamnewpipe-newpipe-extractor = "v0.26.2" pulled from JitPack
  • JVM-only. Uses Jsoup + nanojson + Java HTTP.
  • Cannot live in commonMain for KMP. Lives in :app and would need androidMain / jvmMain source sets in :shared.
  • This is why their KMP migration is slow — porting the extractor to KMP is the actual big yak. They haven't started it.

For our fork: same constraint applies. Android-only target for day-1; KMP extractor port is a separate (multi-week) project.

What's actively being deprecated

NewPipe's own codebase has TODOs and migrations in flight:

  • RxJava 3 → coroutines (bridges via kotlinx-coroutines-rx3 show partial)
  • ExoPlayer 2 → Media3 (still on legacy)
  • Fragment + ViewBinding → Compose (the LeakScope findings live in this layer)
  • Material 1 → Material 3 (mid-migration; material = "1.11.0" # TODO: update)
  • KSP2 incompatibility flagged on statesaver
  • Apache commons in app/src/main/java/org/apache/commons/ — vendored, would go away in KMP world

Where to hook SponsorBlock + Return YouTube Dislike

For a from-scratch new app:

  • SponsorBlock: ExoPlayer/Media3 player listener that consumes a list of segments fetched at video resolution time. Skip via player.seekTo() on onPositionDiscontinuity/period-update callbacks. API: SHA-256 prefix lookup at sponsor.ajay.app/api/skipSegments/<prefix>.
  • Return YouTube Dislike: HTTP GET to returnyoutubedislike.com/votes?videoId=<id>. Render in video detail screen next to the like count. No player integration needed.

Both clients can live in :shared/commonMain using Ktor client (KMP HTTP). That makes them iOS/desktop-ready once an extractor port exists.

Existing forks doing the same thing

Surfaced from NewPipe issue thread #13512:

  • PipePipe — fork with SponsorBlock + RYD. Active.
  • Tubular — fork with SponsorBlock + RYD. Active.
  • YouPipe — fork with extra features.
  • SkyTube — separate Android YouTube app, not NewPipe-based.

So this isn't novel territory. We're doing it for the build experience (element-x energy), not to fill a market gap.

Differentiators we could pursue

If we want to be more than "another fork":

  1. Compose-native from day 1 (vs. PipePipe/Tubular which are still on Fragment+XML legacy). Smaller, faster, easier to maintain.
  2. Media3 ExoPlayer (vs. NewPipe's legacy ExoPlayer 2).
  3. Ktor-based clients for SB/RYD in commonMain — KMP-ready for the day someone ports the extractor.
  4. First-class settings sync via our existing Sulkta auth (Authentik OIDC) for cross-device subscriptions/history. None of the existing forks do this.
  5. Sulkta-Coop signing cert — joins the rest of our app family with consistent signature.