Skip to content

BitChord v1.2

Choose a tag to compare

@kushagrasinghx kushagrasinghx released this 15 Aug 15:45
· 16 commits to main since this release

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
  • RestartingBackPlayer β†’ SessionPlayer, 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 paddings and 150/166dp card widths
  • versionCode 2 β†’ 3, versionName 1.1 β†’ 1.2

Fixed

  • Streams that resolved fine and then 403'd every read, from a player request with no visitor id
  • Playback throttled to roughly its own consumption rate, so a stall could never recover into a cushion
  • Rebuffering every couple of seconds for the length of a track, from a back buffer that exhausted the load ceiling
  • Crossfade dipping to silence at the join instead of overlapping
  • A crossfade being counted as a manual skip, which stopped "sleep after this song" from firing
  • A user skip, scrub, queue replacement or playback error mid-fade leaving a tail audible over the new track
  • Album and single track rows showing "Unknown artist" where the credit was only in the page header
  • "Software caused connection abort" while typing a search, from each keystroke cancelling a request on a pooled connection
  • Long playlists spending up to ten round trips on a spinner before showing anything
  • Duplicate songs in the queue colliding on one list key, and reorder animations mistaking a moved row for a replaced one
  • Repeat-all never wrapping, because AutoPlay kept extending the queue past its end
  • Spatial audio staying on in preferences after Atmos was switched off in system settings