BitChord v1.2
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.
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 theplayerendpoint, carrying the user agent, origin and signature-timestamp requirement that must travel together;StreamResolverwalksCLIENTS(ANDROID_VR first) with per-network gating, a learned order, and aprobethat fetches a byte before anything is handed to the player or cachedInnertube.ensureVisitorData/fetchVisitorDataβ mints a visitor id offsw.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 botChunkedDataSourceβ bounded-range fetching for player reads, passing already-bounded requests straight through;PlaybackServicesets the media request's headers per URL fromPlayerClient.forStreamUrlCrossfadeControllerrewritten around a ghost player:ARMING/LAPPING/FADING/BAILINGphases, silent seek-sync toSYNC_TOLERANCE_MSwith a learned seek lead, equal-powersin/coscurve,consumeAutoAdvance()soPlaybackServicecan tell the lap's seek from a manual skip, andonSkipRequested()to bail before onePlaybackService.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 diskSpatialAudioProcessorβ mid/side widening plus a delayed, low-passed cross-feed as an ExoPlayerAudioProcessor; one instance per sink, since the delay line can't be sharedDolbyAtmosβ support and on/off detection via the platformSpatializer, 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; retiresAppSettings.spatialAudiothe moment Atmos goes offSkeletons.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 frameUpdateAvailableDialogβ iOS-shaped alert rendered as an overlay so its glass samples the app's ownHazeState; shown once per launch, gated behind Home settlingInnertube.SongPage+browseSongs/moreSongs, andMainViewModel.fillInβ detail pages render their first page and follow continuations in the background, up toMAX_PAGESInnertube.withRetryβ retries transport failures only, never HTTP statuses, never through cancellation- Search pipeline:
searchRequests(DROP_OLDEST), an LRUsearchCache,prefixMatch, andnewestRequestIdso 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 playlistsSong.artworkAt/String?.artworkAtand theROW_ART_PX/CARD_ART_PX/HEADER_ART_PX/NOTIFICATION_ART_PXconstantsBitChordApplicationimplementsSingletonImageLoader.Factoryβ 20% memory cache, 100MB named disk cache, 200ms crossfadeAppSettings.spatialAudio,reduceAnimation,reduceDynamicBlur, with their settings rowsrememberQueueDragState+stableQueueKeysβ per-section drag-to-reorder off the live queue, keyed stably across reorders and duplicate songs
Changed
Innertube.playertakes 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
LoadingStateremoved in favour of per-surface skeletonsPAGE_GUTTER(10dp),ROW_DIVIDER_INSETandSHELF_CARD_WIDTHreplace the per-screen 20/26/32/34dp paddings and 150/166dp card widthsversionCode2 β 3,versionName1.1 β 1.2
Fixed
- Streams that resolved fine and then 403'd every read, from a
playerrequest 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