Repository navigation
Pineapple 1.0
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
- Download
Pineapple-1.0.apkbelow. - Open it. Android will ask you to allow installation from the app you downloaded with.
- 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=3with 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
/browsecall; 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=plainJDK 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.