feat(navidrome): Instant Mix probe, ping identity, and AudioMuse UX

Parse type and serverVersion from Subsonic ping; persist per-server identity and
run a background Instant Mix probe (random songs + getSimilarSongs) for
Navidrome 0.60+. Hide the AudioMuse server toggle when the probe returns no
similar tracks; clear prefs when the server is no longer eligible.

Artist pages fall back to Last.fm similar artists when AudioMuse is enabled but
the server returns none. Instant Mix failures set a per-server issue flag with a
settings warning and toast. Settings description links the official plugin repo
via i18n Trans.
This commit is contained in:
Maxim Isaev
2026-04-10 13:02:46 +03:00
parent 36f3d42dbe
commit 4de577eede
15 changed files with 463 additions and 68 deletions
+53
View File
@@ -0,0 +1,53 @@
/** Fields from Subsonic `ping` / any `subsonic-response` root (Navidrome sets type + serverVersion). */
export type SubsonicServerIdentity = {
type?: string;
serverVersion?: string;
openSubsonic?: boolean;
};
/** Result of `getRandomSongs` + `getSimilarSongs` probe (Instant Mix / agent chain). */
export type InstantMixProbeResult = 'ok' | 'empty' | 'error' | 'skipped';
const NAVIDROME_MIN_FOR_PLUGINS: [number, number, number] = [0, 60, 0];
function parseLeadingSemver(version: string | undefined): [number, number, number] | null {
if (!version) return null;
const m = /^v?(\d+)\.(\d+)\.(\d+)/.exec(String(version).trim());
if (!m) return null;
return [Number(m[1]), Number(m[2]), Number(m[3])];
}
function semverGte(a: [number, number, number], b: [number, number, number]): boolean {
for (let i = 0; i < 3; i++) {
if (a[i] > b[i]) return true;
if (a[i] < b[i]) return false;
}
return true;
}
/**
* Navidrome version from ping supports the plugin system (≥ 0.60). Unknown `type` stays permissive
* until the first successful ping with metadata.
*/
export function isNavidromeAudiomuseSoftwareEligible(identity: SubsonicServerIdentity | undefined): boolean {
if (!identity?.type?.trim()) return true;
const t = identity.type.trim().toLowerCase();
if (t !== 'navidrome') return false;
const parsed = parseLeadingSemver(identity.serverVersion);
if (!parsed) return true;
return semverGte(parsed, NAVIDROME_MIN_FOR_PLUGINS);
}
/**
* Whether to show the per-server AudioMuse (Navidrome plugin) toggle in Settings.
* Uses software eligibility from ping plus an optional Instant Mix probe: if the server returns no
* similar tracks for several random songs, the row stays hidden (typical when no plugin / no agents).
*/
export function showAudiomuseNavidromeServerSetting(
identity: SubsonicServerIdentity | undefined,
instantMixProbe: InstantMixProbeResult | undefined,
): boolean {
if (!isNavidromeAudiomuseSoftwareEligible(identity)) return false;
if (instantMixProbe === 'empty') return false;
return true;
}