Skip to content

Releases: kushagrasinghx/BitChord

BitChord v1.3

Choose a tag to compare

@kushagrasinghx kushagrasinghx released this 19 Aug 15:54

BitChord banner

Merges Last.fm/ListenBrainz scrobbling from #3, plus offline downloads, local library, and a round of playback fixes.

Patch notes

  • Offline downloads — save tracks to device with embedded metadata + cover art
  • Local music library scanning (device storage + downloaded tracks)
  • Last.fm + ListenBrainz scrobbling
  • Add-to-playlist picker
  • Dynamic, artwork-driven theming across player/detail screens
  • Draggable bottom nav bar with spring animation
  • Optional back gesture to dismiss lyrics page
  • Fixed several 403 playback errors (updated client versions, shared HTTP stack)
  • Fixed stream-resolve stalls/timeouts
  • Fixed ListenBrainz not logging finished tracks
  • Fixed queue swipe-to-add triggering on accidental drags
  • General UI/bug fixes

Changelog

  • Add LastFM, ListenBrainzManager, ScrobbleManager + scrobbling settings
  • Add Account & Scrobbling settings sub-page
  • Add download package: DownloadService, Downloads, DownloadStore, Downloader
  • Add MediaTagger, Mp4Tagger, WebmTagger for embedded metadata (+ unit tests)
  • Add LocalMediaRepository for local/downloaded track scanning
  • Add PlaylistPickerSheet / PlaylistActionsSheet
  • Add ArtworkPalette, ArtworkBackdrop, ArtworkWash for artwork-driven theming
  • Add shared Http.kt OkHttp client (fixes address-family mismatches causing 403s)
  • Add ANDROID_MUSIC player client, bump other client versions
  • Rework StreamResolver extractor client with its own connection pool + timeout
  • Raise stream-resolve timeout 45s → 120s
  • Pin NewPipeExtractor to v0.26.3
  • Fix probe requests hitting 403 on YouTube Music API
  • Fix ListenBrainz missing ended songs in recent listens
  • Fix queue swipe-to-add threshold
  • New storage permissions + DownloadService manifest entry
  • README/license updates

Install

Download BitChord-v1.3.apk below and sideload it (enable "Install unknown apps" for your browser or file manager). Requires Android 8.0 (API 26) or newer.

Installs straight over v1.2 — same signing key, your session and settings are kept.

BitChord v1.2

Choose a tag to compare

@kushagrasinghx kushagrasinghx released this 15 Aug 15:45

Third release of BitChord. This one is about the two things you actually notice: tracks that start and keep going, and a crossfade that finally overlaps instead of dipping.

Highlights

🎚️ A crossfade you can hear
The old crossfade was one player ramping its own volume, which meant it faded down to silence at the join and climbed back out — a hole where the blend should have been. There are now two decoders: the queue advances to the next track immediately and fades up, while a throwaway tail player carries the outgoing track's last seconds down. Both sides ride an equal-power sin/cos curve, so the transition holds constant loudness the whole way through. Because the queue moves on at the start of the fade, the notification, the artwork and the queue index flip to the song you're about to hear rather than trailing the one that's leaving.

▶️ Tracks that actually play
Stream resolution was the single biggest source of "it loads and then doesn't play". A player request sent without a visitor id gets answered one of two ways — an honest LOGIN_REQUIRED, or a perfectly normal-looking response whose URLs serve one byte and then 403 every real read. BitChord now mints a visitor id deliberately instead of hoping a browse response carries one, walks a list of player client identities rather than betting on a single one, remembers which one last worked, stands down the ones that get refused, and never hands the player a URL — or caches it — until a byte has actually been fetched from it.

⚡ Streaming at line rate
googlevideo paces an open-ended response down to roughly playback speed. Measured on the same track, over the same connection, seconds apart: 15.5 kB/s for an unbounded GET against 5.7 MB/s for the same bytes requested as a bounded range. That is the difference between a player that can build a buffer and one that can only just keep up, and it's why a stall used to never recover. Player reads are now fetched as a run of ranges, the same way read-ahead already worked.

🔊 Spatial audio
Settings → Playback gains a Spatial audio row: a stereo-widening and cross-feed effect running inside the player's own audio pipeline. It's offered only on devices that ship Dolby Atmos and only runs while the system Atmos switch is on — with Atmos off, the row hands you to the panel that owns that switch, and turning Atmos off mid-track takes the effect down with it rather than waiting for the next song.

✋ Drag to reorder the queue
Queue rows can now be dragged into a new order. Your own queue and AutoPlay's section reorder separately — a drag never crosses the line between them — and each swap goes to the player as it happens, so what's on screen is always the real running order.

💀 Skeletons instead of spinners
Home, search, library and album/artist pages now fill with grey placeholders laid out to the same metrics as the real rows, so nothing jumps when the data lands. Album and playlist pages also stop waiting for the whole track list: the first page is shown at once and the rest fills in underneath while you're already reading.

🔍 Search that refines as you type
Results are cached and matched by prefix, so typing "blinding" shows the results from "blind" while the real query is in flight instead of blanking to a spinner on every letter. The pipeline is one long-lived collector with an 80ms debounce, which also puts an end to the "Software caused connection abort" errors that came from every keystroke tearing down a pooled connection someone else was using.

🖼️ Artwork at the size it's drawn
Covers were being fetched at 544px for squares the size of a fingertip — 84kB where 7.8kB would do. Each surface now asks for what it draws: rows small, cards medium, page headers and the player large, the notification generous. Coil also gets a real cache — 100MB on disk, 20% of memory — so covers survive to tomorrow instead of being evicted at the 10MB default floor.

⚙️ Performance settings
Two new switches under Appearance for older or slower devices: Reduce animation freezes the player's mesh gradient instead of letting it drift, and Reduce dynamic blur swaps every frosted surface — status bar, mini player, bottom fade, lyric focus — for solid fills.

🔔 Update alert
The top bar's update icon now comes with a once-per-launch iOS-style alert when a newer build is out. Both read the same gate, so the icon can't announce it a beat before the alert does, and neither shows up over a page of skeletons.

Also in this release

  • Album and single pages now credit their tracks to the artist named in the page header, instead of listing bare titles as "Unknown artist" — and the long-press menu can still open that artist
  • Repeat-all now drops AutoPlay's tracks when switched on, and AutoPlay stops extending the queue while it's on, so repeat-all can actually reach the end it's meant to wrap from
  • The sleep timer's "after this song" is honoured across a crossfade, which arrives looking exactly like a manual skip from the outside
  • Transport failures — a connection reset on mobile data — are retried instead of surfacing as an error; HTTP error statuses still aren't, since repeating the question won't change the answer
  • One page gutter shared by every screen and by the floating bars, so row, card and heading edges line up with the bars stacked below them
  • Settings footer now links to the GitHub repo and the developer
  • A queued song appearing twice no longer collides on the same list key
  • NewPipeExtractor 0.26.3 → 0.26.4

Install

Download BitChord-v1.2.apk below and sideload it (enable "Install unknown apps" for your browser or file manager). Requires Android 8.0 (API 26) or newer.

Installs straight over v1.1 — same signing key, so your session and settings are kept.


Full changelog (technical)

Added

  • PlayerClient — one client identity for the player endpoint, carrying the user agent, origin and signature-timestamp requirement that must travel together; StreamResolver walks CLIENTS (ANDROID_VR first) with per-network gating, a learned order, and a probe that fetches a byte before anything is handed to the player or cached
  • Innertube.ensureVisitorData / fetchVisitorData — mints a visitor id off sw.js_data, found by shape rather than by a path that would rot, with a one-shot refresh when a response accuses the session of being a bot
  • ChunkedDataSource — bounded-range fetching for player reads, passing already-bounded requests straight through; PlaybackService sets the media request's headers per URL from PlayerClient.forStreamUrl
  • CrossfadeController rewritten around a ghost player: ARMING/LAPPING/FADING/BAILING phases, silent seek-sync to SYNC_TOLERANCE_MS with a learned seek lead, equal-power sin/cos curve, consumeAutoAdvance() so PlaybackService can tell the lap's seek from a manual skip, and onSkipRequested() to bail before one
  • PlaybackService.buildGhostPlayer — no audio focus, no noisy handling, no session, same audio session id (so the EQ doesn't change halfway), sharing the main media source factory so the tail plays from disk
  • SpatialAudioProcessor — mid/side widening plus a delayed, low-passed cross-feed as an ExoPlayer AudioProcessor; one instance per sink, since the delay line can't be shared
  • DolbyAtmos — support and on/off detection via the platform Spatializer, the vendor setting and output route changes, settingsIntent() to the OEM panel, and a manifest <queries> block so API 30+ doesn't hide those packages; retires AppSettings.spatialAudio the moment Atmos goes off
  • Skeletons.kt — shimmer placeholders for the feed, library, song lists and detail pages, drawn from inside the draw block so a screenful doesn't recompose per frame
  • UpdateAvailableDialog — iOS-shaped alert rendered as an overlay so its glass samples the app's own HazeState; shown once per launch, gated behind Home settling
  • Innertube.SongPage + browseSongs / moreSongs, and MainViewModel.fillIn — detail pages render their first page and follow continuations in the background, up to MAX_PAGES
  • Innertube.withRetry — retries transport failures only, never HTTP statuses, never through cancellation
  • Search pipeline: searchRequests (DROP_OLDEST), an LRU searchCache, prefixMatch, and newestRequestId so a late answer can't overwrite a newer one
  • InnertubeParser.pageCredit — release-page header credit applied to rows that carry none, with the artist's browse id; releases only, never playlists
  • Song.artworkAt / String?.artworkAt and the ROW_ART_PX / CARD_ART_PX / HEADER_ART_PX / NOTIFICATION_ART_PX constants
  • BitChordApplication implements SingletonImageLoader.Factory — 20% memory cache, 100MB named disk cache, 200ms crossfade
  • AppSettings.spatialAudio, reduceAnimation, reduceDynamicBlur, with their settings rows
  • rememberQueueDragState + stableQueueKeys — per-section drag-to-reorder off the live queue, keyed stably across reorders and duplicate songs

Changed

  • Innertube.player takes a client identity as an argument instead of hardcoding iOS
  • Back buffer 15 min → 30 s: the byte ceiling counts history too, so a track-wide back buffer spent the whole read-ahead budget on audio already heard and never restarted loading
  • After-rebuffer buffer raised again — a stall means the network is genuinely struggling
  • RestartingBackPlayerSessionPlayer, which also tells the crossfade about a skip before carrying it out
  • Thumbnails are taken exactly as offered by the parser; the size is chosen at the point of draw instead of being rewritten to 544px on the way past
  • LoadingState removed in favour of per-surface skeletons
  • PAGE_GUTTER (10dp), ROW_DIVIDER_INSET and SHELF_CARD_WIDTH replace the per-screen 20/26/32/34dp paddin...
Read more

BitChord v1.1

Choose a tag to compare

@kushagrasinghx kushagrasinghx released this 12 Aug 15:55

Second release of BitChord. This one is mostly about playing the right thing, and playing it faster — plus shuffle that actually matches the queue you're looking at.

Highlights

🎵 Songs, not music videos
Home and the charts used to mix catalogue tracks with music-video uploads, so tapping one played the video's audio — wrong title, wrong artwork, and a video entry in your YouTube watch history. BitChord now finds the proper audio release of a track before it ever reaches the queue, and hides video-only shelves and dead-end "N videos" cards entirely.

🔀 Shuffle that matches the queue
Shuffle now rearranges the queue itself instead of playing it in a hidden random order behind your back. What the queue shows is what plays, in that order — and turning shuffle off puts it back exactly as it was.

⏭️ AutoPlay has its own section
Tracks AutoPlay lines up sit under their own heading at the bottom of the queue. Add to queue now drops a song at the end of what you asked for, above the mix, rather than behind it. Turning AutoPlay off takes its tracks back out; turning it on picks up from whatever's playing now.

⚡ Much faster seeking
Scrubbing backwards used to stall for a second or more while the track reloaded and re-decoded from an earlier point. The whole track is now kept buffered behind the playhead, so seeking back is as instant as seeking forward. Playback also starts on far less buffered audio (500ms instead of 2.5s), since the bytes are usually already cached on disk.

♾️ Home keeps loading
The Home feed now pages as you scroll, following YouTube Music's own continuations into mood mixes and more personalised shelves, instead of stopping after one fixed page. Daily/Weekly and Trending charts moved to Explore, where they live in the real app.

💾 Cache limit you control
Settings → Storage gains a Song cache limit slider — 512 MB up to 10 GB — plus a Clear song cache action. More cache means more tracks that replay and seek instantly with no network.

🔔 Update notifications
BitChord now checks GitHub for a newer release on launch and shows an update icon in the top bar when one is out. No more finding out by accident.

Also in this release

  • Artist pages now show the artist's own picture and name, instead of inheriting the cover art and the full "A, B & C" credit from whatever track you opened them from
  • New frosted bottom fade — content now dissolves under the tab bar instead of sliding under a hard-edged panel
  • The Home tab is now Play, with a play icon
  • Swiping the album art in the player shows a fast-forward/rewind hint for which way you're about to skip
  • The lyrics strip tells you it's looking, instead of sitting blank
  • Softer, unclipped blur on out-of-focus lyric lines, and a chevron hinting the strip opens full lyrics
  • Settings no longer hides an album or artist page opened from the player
  • Expanded README disclaimer

Install

Download BitChord-v1.1.apk below and sideload it (enable "Install unknown apps" for your browser or file manager). Requires Android 8.0 (API 26) or newer.

Installs straight over v1.0 — same signing key, so your session and settings are kept.


Full changelog (technical)

Added

  • YtMusicRepository.resolveAudio — swaps video-tagged songs for their catalogue audio release before queueing; wired into play, radio, add-to-queue, play-next and AutoPlay extension
  • QueueShuffle — shuffle as a queue edit, applied with moveMediaItem so the playing track's stream isn't re-resolved
  • Song.fromAutoplay + autoplaySectionStart() / dropAutoplayTracks() — AutoPlay as a real queue section, carried on MediaItem extras and persisted in LastPlayed
  • HomeFeed + moreHome() / parseHomeContinuation — Home pagination via FEmusic_home's own continuation token
  • DynamicLruCacheEvictor — Media3 LRU evictor with a mutable ceiling, so the cache limit can move without reopening SimpleCache
  • AppUpdateChecker — polls releases/latest, compares numerically against BuildConfig.VERSION_NAME
  • BottomFadeBlur — progressive-blur floor under the floating bars
  • dev/prod product flavors — dev ships as com.dev.bitchord with its own label and in-app badge, so both can be installed side by side
  • Settings: song cache limit slider (512 MB – 10 GB) and clear song cache
  • ArtistPage.thumbnailUrl / .name, parsed off the page header
  • QueueShuffleTest and QueueSectionsTest (13 new tests)

Changed

  • FEmusic_charts moved from Home to Explore
  • Back buffer set to 15 min with keyframe retention; bufferForPlaybackMs 2500 → 500, after-rebuffer 5000 → 1000
  • Home tab renamed Play; house icon replaced with a play triangle
  • FloatingBottomBar no longer applies its own blur — it tints over the bottom fade instead
  • Lyric-line blur uses BlurredEdgeTreatment.Unbounded
  • Artist navigation stops passing the source track's artwork through as a placeholder
  • Release builds are now assembleProdRelease
  • versionCode 1 → 2, versionName 1.0 → 1.1; buildConfig enabled

Fixed

  • Music-video uploads reaching the queue, the notification and YouTube watch history in place of the catalogue track
  • Shuffle showing one running order while playing another
  • Add-to-queue appending behind AutoPlay's mix instead of after the user's own tracks
  • Toggling AutoPlay off leaving its tracks in the queue; toggling it on skipping the current track as already-seeded
  • Backward seeks stalling on a full buffer reload and re-decode
  • Video-compilation shelves and cards linking to pages that parse to nothing
  • Settings covering an album/artist page pushed from the player, and surviving a tab switch
  • The three-dot menu missing album/artist rows when opened before those ids resolved

BitChord v1.0

Choose a tag to compare

@kushagrasinghx kushagrasinghx released this 11 Aug 14:24

First release of BitChord — an unofficial YouTube Music client for Android.

Highlights

  • Sign in with your Google account
  • Search, browse and play from YouTube Music
  • Crossfade, playback speed, skip silence
  • Per-network (Wi-Fi / mobile) audio quality
  • Synced lyrics, sleep timer, system equalizer
  • Dynamic mesh-gradient now-playing background
  • Frosted-glass Material 3 UI, light/dark/system theming

Install
Download BitChord-release.apk below and sideload it (enable "Install unknown apps" for your browser/file manager). Requires Android 8.0 (API 26) or newer.

See the README for details on how it works.