13.14.1
Changelog
Changes in v13.14.0..HEAD:
Highlights
- Android 8–11 Startup Crash Fixed — v13.14.0 could not start at all on Android 8 through 11 (including Android Automotive head units): a
Class.getPackageName()call (API 31) sat in the startup preference flow and killed the process before any UI appeared. The namespace is now derived withClass.getName()(API 1). (Hush) - Download Crash Fixed on Android 8–12 —
Intent.getParcelableExtra(name, Class)(API 33) was called unguarded at the start of every download. Now version-guarded, so downloading no longer throws on older devices. (Hush) - API-Level Regressions Are Now Release-Blocking —
NewApiisfatalandabortOnError = truein both the app and the Waze shim, solintVital(part of every release assemble) fails the build on any above-minSdk call instead of letting it reach a phone. (Hush) - Blocked Artists Enforced in the Playback Queue — blocking an artist now also removes their tracks from the queues the player actually builds (fresh queue, restored queue, radio/infinite, Together), not just the browse screens. (Hush)
- Artwork Out-Of-Memory Fixed — notification, player and widget artwork is decoded at a bounded resolution with a bounds-only first pass, so a large upload no longer allocates tens of megabytes (and no longer allocates a second full-size copy). (Hush)
Crash Fixes (Android 8–11 / Automotive)
Class.getPackageName()(API 31) removed from the startup path —IconUtils/AppIconRepositorynow derive the manifest namespace fromMainActivity::class.java.nameand keep usingcontext.packageNamefor the applicationId, so the dynamic icon alias resolves in debug (.debugsuffix) and on Android 8–11. Bundle/dex verified: nogetPackageName()Ljava/lang/String;remains andMainActivitysurvives R8. (Hush)- Typed
getParcelableExtra(API 33) guarded —ExoDownloadServicereads the Media3 download request with the typed overload on API 33+ and the single-argument overload below it. (Hush) - Receiver flags unified through
ContextCompat— everyregisterReceivercall that passedRECEIVER_EXPORTED/RECEIVER_NOT_EXPORTED(API 33 constants, enforced from API 34) now goes throughContextCompat.registerReceiver, which masks the flags below API 33 and forwards them above it. Covers the app's Waze command receiver, the Bluetooth receiver, the canvas-unlock receiver, and the shim'sWAZE_METADATA_UPDATEreceiver. (Hush)
API-Level Guard (Build / CI)
NewApiis fatal — declared inapp/lint.xmland the newwaze-shim/lint.xml. Verified by injecting the exact defect that shipped (Uri::class.java.packageName):lintVitalGmsMobileUniversalReleasefails withCall requires API level 31, and passes once removed. (Hush)abortOnError = true— with the previousfalse, even a fatal lint issue was only reported, which is how the API-31 call reached users.warningsAsErrorsstays off, so warnings do not fail a build. (Hush)
Playback and Queues
- Blocked artists filtered where the queue is built —
restorePersistentQueue(app restart),startQueue, radio/infinite queue creation and refill, and the Together playback queue all apply the blocked-artist set, read from the database with an empty-set fallback that cannot break queue start. (Hush) - Index-correct filtering —
Queue.Status.filterBlockedArtistsgoes through the samefilterItemspath as explicit/video filtering, so dropping a track before the selected one re-countsmediaItemIndexinstead of starting playback on a different song. Covered byQueueBlockedArtistFilterTest(drop before, drop after, drop selection, drop everything, out-of-range index, empty no-op). (Hush) - Hide music videos unchanged — the existing
filterVideoenforcement is untouched; blocked artists join the same chain rather than replacing it. (Hush)
Performance and Memory
- Bounded artwork decoding — new
BoundedBitmapDecodehelper: bounds-only pass →inSampleSize→ decode straight to software ARGB_8888. The full-resolution bitmap is never allocated, and the extra full-size copy the old code made is gone. Wired intoNotificationArtworkLoader,CoilBitmapLoader(Media3BitmapLoader) andMusicServiceWidgetUpdater's dominant-colour extraction (now sampled at 256 px and recycled). (Hush)
Equalizer
- Active-profile recomposition fix —
AxionEqScreenreadactiveProfileId.valueinside composition (lintStateFlowValueCalledInComposition), so the selected profile did not update the UI. It now collects the flow as state. (Hush)
Build / CI
- TV release build fixed — the Waze shim archive was produced into
app/src/main/assets, which every variant merges, soassembleGmsTvUniversalReleasefailed Gradle's implicit-dependency validation when built alongside the mobile variants. The copy now targets only the mobile flavor's asset directory (the runtime reads the shims from this archive, andWAZE_SUPPORTEDis false on TV). Verified with all three CI release variants in one invocation. (Hush)
SpotiFLAC
- Provider stall watchdog no longer kills healthy downloads — the 25s attempt ceiling was measured from the attempt start, so a provider that was streaming a large lossless file was abandoned mid-transfer and then recorded as stalled and demoted for the full 2-minute cooldown. On device it reported itself as
timed out without progress for 29ms— 29 ms after bytes had arrived. The ceiling is now counted from the last real byte transfer (only a growing byte count counts, so a provider re-emitting its size still cannot look alive), the abort reason is captured when it fires, and the reported silence matches the trigger that fired. (Hush) - Playback cache identity is stable across call sites and queue restores —
MediaMetadata.durationMsreads 0 when an item carries no duration (the norm for a restored queue), and a plain fallback chain never reached the database behind it, so 0 became part of the track's identity. Measured on device for one song: the file was downloaded underAfrica|Toto||0and the next lookup computedAfrica|Toto||272000, missed the cache, and re-downloaded a track already on disk. A singlebestDurationMshelper now takes the first known duration across the queue item, what playback recorded, and the database. (Hush) - Second-chance lookup by media id — when the identity key has no entry, the media-id index still knows which file this queue item resolved to, so a drifted identity serves the existing file instead of re-downloading it. Verified on device:
cache miss key=00d73932…followed bycache hit by mediaId index key=df62ab79…and no download. (Hush) - Quality bucket guard on that lookup — it has no quality component in its key, so entries now record the bucket they were fetched for: a hi-res request is never answered with the lossless copy on disk, while an entry written before the field existed still satisfies the default lossless request. (Hush)
Housekeeping
- Version bumped to 13.14.1 (versionCode 171, app + waze-shim). (Hush)
- SpotiFLAC verified end-to-end on a real device: six provider extensions installed, four gateway sessions self-renewing without a manual check, lossless FLAC files served from deezer, qobuz-web and tidal-web (
codec=flac bits=16 rate=44100), correctSpotiFLAC • <source> • cachedlabels, and cached replay served ahead of the Media3 cache with no re-download. (Hush) - Unit tests pass (328 tests, all green), including new suites for the blocked-artist queue filter, the provider stall policy, and the playback cache identity. The device hashes behind the cache drift are pinned in the cache key test, so the evidence stays attached to the code that has to avoid it. (Hush)
- Lint: 0 errors and 0
NewApifindings inappandwaze-shim; all three CI release variants assemble successfully with the configuration cache enabled. (Hush)
Upstream credits
Hush is built on ArchiveTune and combines features, fixes, and UI from several open-source YouTube Music clients—including Metrolist, Vivi Music, and Echo Music. Those projects are credited below; their licenses and copyright notices are preserved in source.
Thank you to the maintainers and contributors of every project listed above.