Skip to content

Release v3.0.0 - #46

Merged
CrossyAtom46 merged 33 commits into
mainfrom
release/v3.0.0
Oct 2, 2026
Merged

CrossyAtom46 merged 33 commits into
mainfrom
release/v3.0.0

Conversation

@CrossyAtom46

Copy link
Copy Markdown
Member

v3.0.0 replaces WebView playback with a native audio engine, moves the local library to a SQLite index, and adds native Spotify and Apple Music downloaders, internet radio and in-app updates.

The complete list of changes is in CHANGELOG.md.

Notes

  • Settings are migrated automatically on first launch. The new format cannot be read by 2.x, so the previous file is kept as mh-settings.json.bak.
  • Spotify playback requires a Widevine device file (.wvd) and, for now, pywidevine. That requirement will go away once Widevine is handled natively in Rust.
  • Apple Music no longer requires gamdl. It is now an optional package, needed only if you choose the gamdl download backend.
  • Internet radio was written entirely with AI to speed up development.
  • CI: parts of the release workflow, including the release notes generator, were also written with AI.

Issues

Fixed in this release: fixes #30, fixes #36, fixes #38, fixes #41, fixes #43, fixes #44.

tauri-build regenerates src/app/gen/ on every build, and .gitignore
already lists it, but these schema files were committed before that rule
existed, so every build left them modified. Nothing reads them from the
repo: capabilities/default.json points at the online $schema URL.
23 source, config and lock files were committed as 100755, including
Cargo.lock, package.json, lib.rs and SettingsPage.tsx. None of them is
meant to be executed. Only the mode changes here; file contents are
untouched, so later commits show content changes alone.
- rust-toolchain.toml pins the stable channel with rustfmt and clippy.
- clippy.toml raises the too-many-arguments threshold to 9.
- .gitattributes stores text files with LF line endings and marks
  images, fonts, PDFs and the Widevine .wvd device file as binary.
- .vscode/extensions.json recommends the Tauri, Rust, ESLint, Prettier
  and Tailwind extensions the project is set up for.
- .gitignore now also skips Flatpak build dirs, macOS ._ files, the
  Microsoft Store icon sizes, local .claude/ state and local debugging
  scripts.

src/core/Cargo.lock is removed: src/core is a workspace member, so
cargo only ever reads the root Cargo.lock. The crate-level lockfile was
a stale leftover that nothing kept up to date.
Unmodified crates.io sources (both checksums match the crates.io index),
trimmed to Cargo.toml, LICENSE and src/. Upstream's tests, benches, docs
and changelogs are not vendored, so the manifests drop the 38 [[test]]
entries in oxideav-aac and the [[test]]/[[bench]] pair in oxideav-core;
cargo refuses a manifest that points at missing files. Nothing else in
either manifest changes.

The root Cargo.toml wires both crates in through [patch.crates-io] and
excludes them from the workspace, so workspace-wide commands don't build
or test them. The patch applies once mediaharbor-core depends on the
crates. The next commit carries the change that vendoring exists for.
Upstream evaluates the IMDCT and the SBR QMF analysis and synthesis
banks as the direct O(N^2) sums from the spec, which decodes HE-AAC at
about 1.25x realtime. Each is now a single FFT: the phase terms fold
into pre- and post-twiddles, leaving a plain DFT that rustfft evaluates.

The output matches the direct sums to floating-point noise, while a long
IMDCT window drops roughly two million cosine evaluations for one
transform. FFT plans are cached per length, since AAC only uses a few.

Adds rustfft 6 as a dependency of the vendored oxideav-aac.
Every service now lives in services/<name>/ and plugs into three shared
traits in services/common: SearchProvider, DownloadProvider and
PlaybackProvider. BackendState dispatches through them instead of
matching on the platform inside lib.rs, which is where most of that
file's removed code came from.

- services/common is the shared layer: the download pipeline
  (orchestrator, downloader, converter, tagger), one subprocess driver
  for gamdl, votify, yt-dlp and OrpheusDL, and search, lyrics,
  share-link and HTTP helpers.
- drm/ replaces crypto/ with a Widevine CDM and a pure-Rust mp4decrypt.
- apis/, streamrip/, meta/ and crypto/ are gone; their code moved into
  the service directories.
- Settings are stored nested and versioned (schema 5), with a migration
  from the flat 2.x file. gamdl and votify INI files are written from a
  whitelist of keys.
- Tauri commands are generated by tauri_delegates! from BackendState
  methods. The files in this commit call tauriAPI directly; the
  window.electron shim stays until its last caller moves over.
- Frontend: typed settings, a download dialog with per-service quality
  options, per-item progress and download summaries, and the settings
  page rebuilt on typed field components.
- Rust dependencies move to reqwest 0.13, axum 0.8, zip 8 and
  thiserror 2, and add what the following commits build on (cpal,
  symphonia, rusqlite, souvlaki, tauri-plugin-updater). Cargo.lock is
  committed whole with them.

Some lines ride along in whole files: App.tsx already wires hooks from
the player, library, auth and updater changes, and the settings schema
carries their keys.

BREAKING CHANGE: native Spotify and Apple Music downloads are AAC-only
(Widevine L3); the PlayPlay and FairPlay-ALAC paths are removed.
mh-settings.json is now stored nested and versioned (schema 5). A 2.x
file is migrated on first launch and kept as mh-settings.json.bak, and
2.x builds do not understand the new layout.
A notification can carry up to two action buttons and a dedupe key; a
new notification with the same key replaces the old one instead of
stacking. Toasts lay the actions out under the message. The player,
credential health and update prompts use this in later commits.
Playback moves out of <audio> elements and the Web Audio crossfade in
the WebView into a Rust engine in core/src/player:

- decoding with symphonia, the oxideav decoders (AAC/HE-AAC, APE,
  Musepack, Speex, TTA, Ogg) and rusty-opus
- resampling, crossfade and replay gain
- output through cpal (PipeWire, PulseAudio, ALSA, WASAPI, CoreAudio)
  with device selection
- a spectrum analyser that feeds the visualizers

OS media controls (MPRIS and Windows SMTC via souvlaki, macOS Now
Playing directly) live in app/src/media_controls.rs. The player store,
Player and NowPlaying drive the engine over IPC; the frontend adds a
seek bar, autoplay continuation, stream prefetch and reports for streams
that cannot be decoded. Panics are appended to the log file, since
playback threads otherwise die silently.

AudioCrossfadeEngine.ts is removed.
Release builds strip symbols, use thin LTO with one codegen unit and
keep overflow checks on. Dev builds optimise the audio decoders, which
are slower than realtime at -O0.
Local music is indexed into library.sqlite by a scanner and file watcher
with a cover cache, instead of being rescanned on every visit;
media/library replaces media/scanner.rs and media/file_watcher.rs. It
adds local playlists with M3U import and export, tag writing, play
history and a library radio.

The library view also shows each streaming service's saved albums,
artists, playlists, follows and home shelves. Saving, following and
playlist edits are written back to the service. Search results open the
same album, artist and playlist views, so the search page's old detail
modal and views are removed. Alt+Left and the mouse back button navigate
back.
A Radio page backed by services/radio:

- search and browse Radio Browser, SomaFM and the Icecast directory
- favourites, recents and custom station lists
- import from a stream URL, a playlist file, an Icecast server or a
  pasted curl command
- a station editor with overrides and custom covers
- now-playing titles from ICY metadata

Stations are stored in library.sqlite. The sidebar gains the Radio entry
and swaps react-icons' Discord glyph for an inline SVG, so react-icons
can be dropped from the dependencies.
Saved credentials for each service are checked at startup, every 15
minutes and after an auth error, and the result shows as a status pill
on that service's settings tab. Qobuz and Tidal can sign in through an
in-app login window, and a Tidal token can be imported as JSON.

credentials.json can override the bundled API keys. apis/credentials.rs
moves to auth/credentials.rs, and auth/netscape.rs reads cookie files.
The dependencies page can install aria2c (yt-dlp's external
downloader), Deno (yt-dlp's JavaScript runtime) and N_m3u8DL-RE (gamdl's
HLS downloader) into a managed bin directory that is put on PATH. Tools
are resolved from that directory, the Python venv or the system; pip
dependencies are matched by exact package name, and an install can be
forced.

Onboarding gains a step for choosing which services to enable. The tour
also gets its Radio step here.
Uses the Tauri updater plugin. The Updates page checks the signed
latest.json, can download in the background and installs with a
restart. Only installs that can replace themselves update in-app
(AppImage, deb, rpm, NSIS, MSI and the macOS app); Flatpak, Snap, AUR
and source builds are pointed at their own channel.

The window.electron shim is removed, since nothing calls it any more.
Removes the MSIX manifest and the Microsoft Store icon sizes. The
release workflow stops building the package in the CI commit.

BREAKING CHANGE: the MSIX package for the Microsoft Store is no longer
built. Windows users should install the NSIS (.exe) or MSI release;
both update in-app (windows-x86_64-nsis/msi in latest.json).
- Tailwind v4's Vite plugin already adds vendor prefixes, so PostCSS and
  autoprefixer go; tailwindcss-animate is replaced by tw-animate-css.
- Packages left over from the Electron app are removed (axios, cheerio,
  chokidar, sharp, ffmpeg-static, librespot, re-unplayplay, ytmusic-api
  and others), along with the postinstall step that patched librespot.
- ESLint 10, Vite 8.2 and Node >= 22. Vite resolves the path aliases
  from tsconfig and carries the Vitest config.
- Local tauri:build scripts pass --no-sign.
- Adds @tanstack/react-virtual and the Radix context-menu and popover
  packages. ui/popover.tsx and ui/tabs.tsx are added but not used yet.
Formatting and lint fixes only, with no behaviour change: Prettier on
files that had no other changes, eslint-disable comments the new ESLint
config no longer needs, and rustfmt plus a redundant closure in
update_checker.rs.
src/style.css and src/pages/placeholder.png were not referenced
anywhere in 2.2. scripts/patch-librespot.js patched the npm librespot
package, which is gone.
The logo SVG gets a solid fill and padding. The app icons are
regenerated from it (about half their old size), along with the Snap
icon, the README banner and the favicon.
The GPG public key of MediaHarbor Release Signing:

    4E7E D764 DFB4 F1B6 59C1  C570 DD7A E5F9 C20A 4047

The release workflow signs SHA256SUMS.txt and the RPM with the private
key, and the README explains how to verify.
ci.yml runs lint, the typecheck and build, Vitest and cargo test on
every push and pull request; rustfmt and clippy are advisory.

tauri-build.yml:
- builds updater artifacts and publishes latest.json with
  per-installer keys
- GPG-signs SHA256SUMS.txt and the RPM, and attests build provenance
- regenerates the Flatpak cargo sources from Cargo.lock
- writes release notes with install and verification steps
- drops the MSIX job
- PipeWire and ALSA runtime libraries for native audio, and clang for
  the pipewire bindgen build.
- N_m3u8DL-RE bundled in the Flatpak and Snap, where the sandbox blocks
  downloading it at runtime.
- ffmpeg and Python for the Snap and AUR packages.
- Flatpak: offline sources regenerated, patch-package steps removed,
  PulseAudio cookie and PipeWire socket access.
- AUR: desktop file capitalised to match the Tauri bundles; .SRCINFO
  pkgdesc synced with PKGBUILD.
- metainfo: new summary, branding colours and screenshot list.
The README gains "Verifying a Release" (checksums, GPG, the RPM
signature and build provenance) and first-launch notes for macOS and
Windows. The bug report template asks for OS, version and install type,
and says where the log file lives on each platform.
package.json, tauri.conf.json, PKGBUILD, .SRCINFO (including its stale
source URL) and the metainfo release entry. Cargo.toml, Cargo.lock and
package-lock.json already carry 3.0.0, because they were committed whole
with their dependency changes.
Tool installs were blocked inside the Flatpak. They go into the app's
data folder again, like in 2.x, so the ffmpeg extension and the bundled
N_m3u8DL-RE are removed from the manifests.
Also updates the release date in the Flatpak metainfo.
Falls back to commit subjects when the version has no entry.
@CrossyAtom46
CrossyAtom46 merged commit 1d56635 into main Oct 2, 2026
1 of 2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment