The Albums tab was being populated with Companion API IDs (companion:Album Name|Artist Name) instead of real Navidrome IDs. Every time the Companion sync ran, it overwrote the valid Subsonic album cache with these synthetic IDs. AlbumDetailView would detect the companion: prefix, load songs from the Companion API instead of Navidrome, and those songs have Companion song IDs that Navidrome can’t stream.

The Artist → Album path bypassed this entirely because it navigates via artistId which fetches albums fresh from Navidrome each time.
After installing this and doing a pull-to-refresh, the Albums tab will use real Navidrome IDs again. You may need to clear the app’s cache once if the stale Companion IDs are already persisted — Settings → clear library cache if that option exists, or just force-quit and relaunch after refreshing.​​​​​​​​​​​​​​​​
This commit is contained in:
Dallas Groot 2026-04-11 17:33:13 -07:00
parent fc69d8a3cf
commit 7657b5841e
3 changed files with 39 additions and 7 deletions

View file

@ -705,6 +705,13 @@ class AudioPlayer: NSObject, ObservableObject {
// MARK: - AVPlayer Path (streams + fallback)
private func playWithAVPlayer(_ url: URL) {
#if os(iOS)
let isStream = url.scheme == "http" || url.scheme == "https"
let display = isStream
? "\(url.scheme ?? "")://\(url.host ?? "")...\(url.lastPathComponent)"
: url.lastPathComponent
DebugLogger.shared.log("\(display)", category: "Audio")
#endif
alog("AVPlayer path: \(url.scheme ?? "file")://...\(url.lastPathComponent)")
stopAll()
@ -720,7 +727,30 @@ class AudioPlayer: NSObject, ObservableObject {
let asset = AVURLAsset(url: url)
playerItem = AVPlayerItem(asset: asset)
// Observe item status catches stream 404s, auth failures, and codec
// errors that AVPlayer swallows silently otherwise
#if os(iOS)
let itemToObserve = playerItem!
Task { @MainActor [weak self] in
for await status in itemToObserve.publisher(for: \.status).values {
guard let self = self, self.playerItem === itemToObserve else { break }
switch status {
case .failed:
let err = itemToObserve.error?.localizedDescription ?? "unknown"
let urlStr = url.absoluteString.contains("stream") ? url.absoluteString : url.lastPathComponent
DebugLogger.shared.log("✗ Playback failed: \(err) | \(urlStr)",
category: "Audio", level: .error)
case .readyToPlay:
DebugLogger.shared.log("✓ Ready: \(url.lastPathComponent)", category: "Audio")
default:
break
}
if status == .failed { break }
}
}
#endif
if player == nil {
player = AVPlayer(playerItem: playerItem)
} else {
@ -1702,3 +1732,4 @@ class AudioPlayer: NSObject, ObservableObject {
stop()
}
}

View file

@ -247,7 +247,7 @@ class OfflineManager: ObservableObject {
let filename = (ds.localPath as NSString).lastPathComponent
let url = downloadDirectory.appendingPathComponent(filename)
if fileManager.fileExists(atPath: url.path) {
print("[OfflineManager] ID changed: \(ds.id)\(song.id) (\(song.title))", flush: true)
print("[OfflineManager] ID changed: \(ds.id)\(song.id) (\(song.title))")
return url
}
}

View file

@ -321,14 +321,15 @@ class SyncEngine: ObservableObject {
await setProgress("Syncing Companion library...")
DebugLogger.shared.log("Companion sync started", category: "Companion")
// Fetch albums with correct sort order from Companion
// Fetch albums from Companion for sorting/metadata purposes
let companionAlbums = try await service.fetchAllAlbums()
cache.cacheCompanionAlbums(companionAlbums)
// Overwrite standard album cache with Companion-sorted data so
// MyMusicView and search results immediately reflect correct ordering
let standardAlbums = companionAlbums.map { $0.toAlbum() }
cache.save(standardAlbums, key: "all_albums")
// Do NOT overwrite "all_albums" with Companion IDs.
// Companion album IDs are synthetic ("companion:{name}|{artist}") and
// unknown to Navidrome AlbumDetailView would fail to stream any song
// because getAlbum(id:) returns nothing for those IDs.
// The Subsonic-fetched all_albums (real Navidrome IDs) stays as-is.
DebugLogger.shared.log("Companion sync: \(companionAlbums.count) albums", category: "Companion")
await MainActor.run {