Skip to content

Stash v0.9.12

Choose a tag to compare

@rawnaldclark rawnaldclark released this 05 May 07:01
· 1301 commits to master since this release

feat: 0.9.12 — lossless-first search-tab preview and download

Search-tab preview and download (artist profile, search results, album
discovery) now route through the v0.9.10 lossless registry first, with
ExoPlayer's SimpleCache acting as a shared byte buffer between the two.
Tap Preview → bytes stream from Qobuz CDN (squid.wtf or kennyy.com.br)
through the cache to the player. Tap Download right after → the cache
is filled to completion (any missing ranges fetched fresh) and the
file is finalised into the library. No duplicate network fetch.

  1. Lossless-first preview + download

A new SearchPreviewMediaSource builds an ExoPlayer MediaSource that
tries the lossless registry first. If a Qobuz match clears the
search-tab confidence threshold (0.65, vs sync's 0.5 — search results
lack ISRC and reliable duration so we want tighter title+artist
agreement), the source plays from a CacheDataSource over the Qobuz
signed URL. On miss, falls through to the existing PreviewUrlExtractor
(InnerTube fast path → yt-dlp slow path) — same fall-through chain as
before, just routed through the same cache so subsequent download
finalises don't re-fetch.

A new SearchDownloadCoordinator replaces the direct DownloadExecutor
call inside TrackActionsDelegate.downloadTrack. It re-resolves the
registry on tap (signed URLs are time-limited; we don't reuse the
prefetcher's cached match), reads through CacheDataSource into a temp
file, hands off to TrackFinalizer for embed/commit/probe, runs
search-specific DB writes (insert/update Track row, link to "Your
Downloads" playlist for orphan-cleanup protection). On lossless miss
or low confidence, falls through to yt-dlp with a typed
SearchDownloadStatus.Downloading(YOUTUBE) event that surfaces a
"Downloading via YouTube (slower)" snackbar.

  1. Stream-while-cache + visible-row prefetch

The preview cache is a 200MB LRU SimpleCache rooted at
/preview_cache/. Cache keys are namespaced (lossless: vs
ytdlp:) so the same trackId can hold both codec variants without
collision. A visible-row LosslessUrlPrefetcher fires a registry.resolve
in the background as rows enter the viewport, with 4 concurrent calls
in flight (matches kennyy.com.br's burst-of-4 budget) and a 5-min TTL
on cached matches (Qobuz signed URLs expire). Stale entries are pruned
every 60s by a loop in StashApplication.onCreate.

  1. Sync refactor

DownloadManager.tryLosslessDownload was refactored to share its
file-side finalisation logic (metadata embed, library file commit,
audio probe) with the new search-tab path via a new TrackFinalizer.
Sync's DB writes stay in DownloadManager — TrackFinalizer is
deliberately stateless w.r.t. the DB so sync continues to preserve its
spotifyUri / isrc / album / explicit fields verbatim. No behavioural
change for sync; same speed, same correctness.

  1. Bug fixed mid-release

The launch-day build had a URL-pollution bug: the preview MediaItem
was being built with ?trackKey=lossless:videoId appended to the
upstream URL. Qobuz signed URLs are HMAC-authenticated against the
exact query string, so the appended param invalidated the signature
and every byte fetch returned 403. ExoPlayer's default 3-retry
exponential backoff turned that into "preview takes forever" in the
user's experience. Fixed by switching to MediaItem.Builder
.setCustomCacheKey() (which propagates to DataSpec.key without
touching the URL) and tuning the LoadErrorHandlingPolicy to fail-fast
(1 retry) so any future transient outage routes to the yt-dlp retry
path within ~10s instead of 30-90s.

Out of scope

No UI changes — same buttons, same screens, same flow. No re-download
on quality-tier change for tracks downloaded via search (forward-only,
matches v0.9.11 semantics). No filter-chip UI or batch "Download
album" button. Preview latency on uncached tracks remains a follow-up
target for v0.9.13.

Bumps versionCode 48 → 50 (49 was the original ship; 50 included the
mid-release URL-pollution fix), versionName 0.9.11 → 0.9.12.

Co-Authored-By: Claude Opus 4.7 (1M context) noreply@anthropic.com


Install

Download the .apk below and open it on your Android device. If you've got a previous Stash release installed, this upgrades in place — your library, sync history, and settings are preserved.

Requires: Android 8.0 (API 26) or newer.

See the README for first-time setup.

Full Changelog: v0.9.11...v0.9.12