Skip to content

Pineapple 1.0

Choose a tag to compare

@kiinshuk kiinshuk released this 21 Oct 05:00
· 31 commits to main since this release

First public release of Pineapple — a 1.9 MB Android music player that plays YouTube music with no JavaScript engine, no WebView, and no proof-of-origin token.

Search, tap, sound. The top five stream URLs are resolved while the results are still on screen, so there is no spinner between the tap and the audio.

Install

  1. Download Pineapple-1.0.apk below.
  2. Open it. Android will ask you to allow installation from the app you downloaded with.
  3. Requires Android 8.0 (API 26) or later.
sha256  9d3d587d830318c7fab58bb13b9a54c8a1d0bac4c8c8669beda33ac930c21ebc
size    1.92 MB

What's in it

Playback — starts immediately, keeps going in the background with notification and lock-screen controls, and builds a mix of related tracks behind whatever you chose.

Search — queries YouTube Music rather than YouTube, so results are releases instead of everything the site has ever indexed. Filtering is a whitelist, not a blacklist: a result counts only when YouTube typed it as the track's own audio or its official video. Relevance order is preserved and the first hit is marked BEST — never re-sorted by view count, which would only promote the loudest cover.

Discovery — opens on the current trending chart plus what you played recently.

Library — likes, pinned releases, listening history and your own playlists, persisted across restarts. Long-press any track for its actions sheet.

Interface — dark throughout (#121212 ground, #FF5500 accent), a full-screen player whose background is derived from the artwork, and bottom tabs with a real back-stack: the system back button returns to the page you were on instead of closing the app.

How it plays at all

Audio was blocked for weeks by a bare Response code: 403. Three explanations were plausible and each reproduced something real — IP binding, regional gating, and an apparent 1 MiB cap. All three were wrong.

The gate is the player client that issued the URL. Measured from one IP, same minute, same video, fresh URL per request:

range ANDROID 20.10.38 VISIONOS 1.02
no Range (whole file) 403 200 — read all 4738365 bytes
bytes=0-65535 206 206
bytes=1048576-2097151 403 206
bytes=3000000-4194303 403 206

InnerTube now leads with VISIONOS 1.02 (client id 101) and sends X-Goog-Visitor-Id. Nothing else changed. Proof-of-origin tokens turned out to be required by none of it — the working stream URL contains no pot field at all.

Full story in the README.

Verified

  • 23/23 unit tests passing, pinning YouTube's exact response schemas
  • Playback confirmed on device: state=3 with the position advancing across repeated readings
  • Stream-URL prefetch 5/5
  • Built with R8 + resource shrinking (12.5 MB debug → 1.9 MB release)

Known limitations

  • The queue is not music-only yet — related tracks come from the plain web /next, so a continuous mix can occasionally hand you a vlog between songs.
  • Discovery's chart is a single /browse call; if YouTube renames the explore shelves it falls back to the first carousel rather than erroring.
  • It will break when YouTube changes. See the legal notice below.

Building it yourself

./gradlew clean assembleRelease --console=plain

JDK 17 and ANDROID_HOME required. See CONTRIBUTING.md for setup, the two rules that shape the codebase, and how to diagnose a broken build with the probes in tools/.

Legal

This uses YouTube's private InnerTube API without an API key, which falls outside Google's Terms of Service. It is an independent, sideloaded project and cannot be published to Google Play. It is not affiliated with, endorsed by, or connected to YouTube, Google, or their affiliates. The same technique is used by NewPipe.

Released under the MIT License.