Repository navigation
Releases: kiinshuk/pineapple
Release list
Pineapple v1.5
Pineapple 1.4
Pineapple 1.4
Comes with the full navigation catalogue (artists & albums), an image-pipeline + UI polish wave, offline/reconnect, the media-notification barrier fix, and discovery carousels.
Install
Download the asset below and sideload (Android 8.0+).
size 2.20 MB (2,201,350 bytes)
sha256 6a953b53c8b31de09e846434a4e6612f791872649cb88acc4f420e954f4b4143
What's new since 1.3
Catalogue Phase 1-2.5 (artist + album pages)
- Artist rows in search (Phase 1):
Artist · audiencedoors; tapping opens a dedicated artist page with
banner art, audience line, top songs, and nowAlbums,Videos,Fans might also like,Aboutcarousels. - Album pages (Phase 2): cover art,
artist · year,kind · N songs · M minutesfacts, ordered track list with tap-to-play. - Top-song / track playback uses the same
TrackRoweverywhere (search, artist, album, recents),
so a track looks identical whether you found it in search or an album.
UI/UX wave
- Coil image pipeline replaces the hand-rolled
downloadBitmappump: memory+disk cache,
crossfade from the synthesised gradient tile, no blank flashes on scroll. - Now Playing — poster-bleed background replaced with a clean
PinePlayerTop/Bttomtwo-stop
wash (classic Apple/Spotify cleanliness), 7 dp seek bar with scaling thumb, scale-in artwork entrance. - Motion system
PineMotionshared: easing curves, durations, springs (no per-call ad-hoc magic). - Notification tray player fully functional - Android 12 (API 31) was broken because
MediaSessionServiceonly posts when a provider is attached:- Added runtime
POST_NOTIFICATIONSprompt on Android 13+. - Hard-wired
DefaultMediaNotificationProviderinPlaybackService.onCreate. - added the
media_notification_channel_namestring andRimport.
Notification now shows cover, title, artist and standardplay/pause/next/previouscontrols.
- Added runtime
- Discovery screen now surfaces
New releasesandNew music videosfrom payloads we
already fetched (carousels scroll horizontally), matching the visual rhythm of the rest of the
app. Always-removed-empty-sections (no placeholders).
Network handling wave (PR #114)
- Distinct offline / retrying / stale / failed states at the request boundary.
- Connectivity observer registered in the
MainViewModel, released on clear; reconnect banner without
silent refetch.
Visual wave (PR #112)
- Worked letterbox: artwork fills its square frame in all three states (empty / loading / loaded).
Catalogue integrity
OFFICIAL_MUSIC_TYPES,PLAYER_CLIENTSandEXPLORE_BROWSE_ID(inner constants) were never touched.- Search still only returns official music + the artist/album doors — user-uploads never surface.
Verification
InnerTubeParsingTest: 30 tests, all green (extract id/title/counts, OFFICIAL_MUSIC whitelist,
artist detail (5 tracks, 14 albums, 17 videos, 10 related, bio), album detail (header + 11 tracks),
mixed order assertion).NetworkAccessTest: 10 tests, all green.assembleDebuggreen + APK installed on the M21; primary actions verified.assembleReleasewith R8 + resource shrinking green.
Note
This app uses YouTube's private InnerTube API without an API key, the same technique NewPipe uses. It is sideloaded and cannot be published to Google Play. See Legal.
Pineapple 1.3
Pineapple 1.3
Streams YouTube music with as little ceremony as possible — now with a
real artist/album catalogue on top of search and a much tougher
network story.
Install
Download the asset below and sideload it. Android only, native Kotlin.
Android 8.0+ (minSdk 26).
size 2.19 MB (2,201,350 bytes / 2,201,350)
sha256 c2e812e776083fcbcee7058716adc20f645c47070e44253bf2a05f30ba62ec18
What's new
Catalogue (Phase 1 + Phase 2 + 2.5) — surfaced entirely from the InnerTube
payloads Pineapple already fetched.
- Artist pages: banner header, name, audience line, Top songs, and now
releases (Albums + Singles & EPs), Videos, Live performances, "Fans might
also like" (related artists) and a real About/bio block (from the
description shelf). Tap an album goes to its page, tap a related artist to
theirs. - Album pages: square cover art,
artist · yearline,kind · N songs · M minutesfact line, ordered track list with play, library participation. - Search keeps artist and album doors instead of dropping them at the old
videoId.isEmpty()guard. An artist or album row now opens a real page
instead of being a dead entry.
Offline & slow networks (from PR #114):
- Explicit connectivity checks at the request boundary: distinct
"You're offline" state with the existing retry, while HTTP / rate / API
errors keep their own message. - Network observer registered in the
MainViewModeland released on clear;
reconnect notices shown alongside cached results without silently
re-requesting. - Cancellation + race-free bootstrap (callback installed before the initial
snapshot), trySend under lock.
Visual polish (from PR #112):
- Artwork letterboxing fixed: the player hero now fills its square frame in
all three states (empty / loading / loaded).
Detail fixes:
- Like button now fills while playback is paused.
- Artist avatar now uses the same thumbnail path as tracks.
Verification
- 40/40 unit tests pass (parsing: 30; network: 10)
assembleReleasewith R8 + resource shrinking green- Debug + release launch clean on a Galaxy M21
- Protected InnerTube constants untouched
(PLAYER_CLIENTS,OFFICIAL_MUSIC_TYPES,EXPLORE_BROWSE_ID)
Note
This app uses YouTube's private InnerTube API without an API key, the same
technique NewPipe uses. It is sideloaded and cannot be published to Google
Play. See Legal.
Pineapple 1.2
Pineapple 1.2
Streams YouTube music with as little ceremony as possible.
Install
Download Pineapple-1.2.apk and sideload it. Android only, native Kotlin.
sha256 6995e2f0b252b46bc83966141de0eaedb5dd9c22d6bb8c62b9b64e7dd0dc4e88
size 2,184,966 bytes (2.08 MB)
What's fixed since 1.0
Search in the bottom nav bar was dead. Tapping it from Discovery or
Library did nothing. The tap was being delivered the whole time — the real
cause was a stale closure. goTab compared against a composition-time tab
that the nav bar kept captured from the first frame and never advanced past
SEARCH, so Search was the one tab where next == tab matched and returned
early. Discovery and Library only worked because the stale value happened to
differ from them. Verified over eight consecutive taps after a cold start.
A cancellation was showing up as an error. Every catch in
MainViewModel caught Throwable, which includes CancellationException.
When a newer query superseded an in-flight one, the ordinary cancel was
reported to you as a red banner reading "StandaloneCoroutine was cancelled"
— sitting directly above the results that had loaded successfully. All five
handlers now rethrow it. The play path and the player polling loop had the
same latent flaw.
Icons were invisible. The bundled vector drawables left android:fillColor
off every path. A VectorDrawable path with no fill colour is transparent
black, and Compose's Icon tints with SrcIn, which keeps the source's
alpha — so every icon was rendering perfectly at zero opacity. The build was
green throughout; it took looking at the app to catch.
Changed
- Three-dot menu on every search result, opening the same actions sheet
the Library uses: add to liked, play next, add to queue, add to playlist.
Long-press does the same. - Removed the BEST badge from the top search result.
- Removed the STREAMS INSTANTLY badge from the top right.
- Icons replaced with Google's Material Symbols Rounded (Apache-2.0),
bundled as vector drawables rather than pulled in as a dependency. The
hand-drawn compass, library and heart approximations are gone. - Full-screen player gained real shuffle, repeat and an Up Next queue.
Verification
- 23/23 unit tests pass
- Release build with R8 and resource shrinking intact
- Playback confirmed on device:
state=3with the queue auto-advancing - All nav tabs confirmed reachable on device
Note
This app uses YouTube's private InnerTube API without an API key, the same
technique NewPipe uses. It is sideloaded and cannot be published to Google
Play. See Legal.
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.