NavidromeApp/Shared/Models/Models.swift
Dallas Groot d8041c0019 NavidromePlayer: iOS + watchOS Navidrome/Subsonic music player
Features:
- Dual-AVPlayer Smart DJ crossfade with LUFS normalization
- Mitsuha-style FFT visualizer (real-time + offline pre-computed)
- Companion API integration (Smart DJ, tag editing, vis frames)
- Offline-first SyncEngine with delta sync and album detail pre-caching
- Audio pre-fetcher for gapless queue playback
- Optimistic action queue (star/unstar with background retry)
- ShazamKit recognition with MusicKit preview playback
- Radio streaming with HLS/PLS/M3U support and buffer seek
- Watch app with Crown Sequencer and Ultra speaker support
- Batch metadata editing with album_artist fix for split albums
- Cache-first UI pattern across all views
- NWPathMonitor offline detection with reactive song greying
2026-03-28 20:49:47 +00:00

319 lines
6.9 KiB
Swift

import Foundation
// MARK: - Server Configuration
struct ServerConfig: Codable, Identifiable, Hashable {
var id: UUID = UUID()
var name: String
var url: String
var username: String
var password: String // stored in Keychain in production
var isActive: Bool = true
var baseURL: URL? { URL(string: url) }
func hash(into hasher: inout Hasher) {
hasher.combine(id)
}
}
// MARK: - Subsonic API Response Wrappers
struct SubsonicResponse: Codable {
let subsonicResponse: SubsonicResponseBody
enum CodingKeys: String, CodingKey {
case subsonicResponse = "subsonic-response"
}
}
struct SubsonicResponseBody: Codable {
let status: String
let version: String?
let type: String?
let serverVersion: String?
let openSubsonic: Bool?
let error: SubsonicError?
// Various response types
let musicFolders: MusicFoldersContainer?
let indexes: IndexesContainer?
let artists: ArtistsContainer?
let artist: ArtistWithAlbums?
let album: AlbumWithSongs?
let song: Song?
let directory: DirectoryContainer?
let searchResult3: SearchResult3?
let albumList2: AlbumList2Container?
let playlists: PlaylistsContainer?
let playlist: PlaylistWithSongs?
let genres: GenresContainer?
let starred2: Starred2Container?
let nowPlaying: NowPlayingContainer?
let randomSongs: RandomSongsContainer?
let scanStatus: ScanStatus?
let lyrics: LyricsResult?
let internetRadioStations: InternetRadioContainer?
let similarSongs2: SimilarSongsContainer?
}
struct SubsonicError: Codable {
let code: Int
let message: String
}
// MARK: - Music Folders
struct MusicFoldersContainer: Codable {
let musicFolder: [MusicFolder]?
}
struct MusicFolder: Codable, Identifiable {
let id: String
let name: String?
}
// MARK: - Indexes / Artists
struct IndexesContainer: Codable {
let index: [ArtistIndex]?
let lastModified: Int64?
let ignoredArticles: String?
}
struct ArtistsContainer: Codable {
let index: [ArtistIndex]?
let ignoredArticles: String?
}
struct ArtistIndex: Codable, Identifiable {
let name: String
let artist: [Artist]?
var id: String { name }
}
struct Artist: Codable, Identifiable {
let id: String
let name: String
let coverArt: String?
let artistImageUrl: String?
let albumCount: Int?
let starred: String?
let musicBrainzId: String?
let sortName: String?
}
// MARK: - Albums
struct ArtistWithAlbums: Codable, Identifiable {
let id: String
let name: String
let coverArt: String?
let albumCount: Int?
let album: [Album]?
}
struct Album: Codable, Identifiable {
let id: String
let name: String
let artist: String?
let artistId: String?
let coverArt: String?
let songCount: Int?
let duration: Int?
let playCount: Int?
let created: String?
let starred: String?
let year: Int?
let genre: String?
}
struct AlbumWithSongs: Codable, Identifiable {
let id: String
let name: String
let artist: String?
let artistId: String?
let coverArt: String?
let songCount: Int?
let duration: Int?
let playCount: Int?
let created: String?
let starred: String?
let year: Int?
let genre: String?
let song: [Song]?
}
struct AlbumList2Container: Codable {
let album: [Album]?
}
// MARK: - Songs
struct Song: Codable, Identifiable {
let id: String
let parent: String?
let isDir: Bool?
let title: String
let album: String?
let artist: String?
let track: Int?
let year: Int?
let genre: String?
let coverArt: String?
let size: Int64?
let contentType: String?
let suffix: String?
let transcodedContentType: String?
let transcodedSuffix: String?
let duration: Int?
let bitRate: Int?
let path: String?
let playCount: Int?
let discNumber: Int?
let created: String?
let albumId: String?
let artistId: String?
let type: String?
let starred: String?
let bpm: Int?
let musicBrainzId: String?
var durationFormatted: String {
guard let dur = duration else { return "--:--" }
let min = dur / 60
let sec = dur % 60
return String(format: "%d:%02d", min, sec)
}
}
// MARK: - Directory
struct DirectoryContainer: Codable {
let id: String
let name: String?
let parent: String?
let child: [Song]?
}
// MARK: - Search
struct SearchResult3: Codable {
let artist: [Artist]?
let album: [Album]?
let song: [Song]?
}
// MARK: - Playlists
struct PlaylistsContainer: Codable {
let playlist: [Playlist]?
}
struct Playlist: Codable, Identifiable {
let id: String
let name: String
let comment: String?
let songCount: Int?
let duration: Int?
let coverArt: String?
let owner: String?
let `public`: Bool?
let created: String?
let changed: String?
}
struct PlaylistWithSongs: Codable, Identifiable {
let id: String
let name: String
let comment: String?
let songCount: Int?
let duration: Int?
let coverArt: String?
let owner: String?
let `public`: Bool?
let created: String?
let changed: String?
let entry: [Song]?
}
// MARK: - Genres
struct GenresContainer: Codable {
let genre: [Genre]?
}
struct Genre: Codable, Identifiable {
let value: String
let songCount: Int?
let albumCount: Int?
var id: String { value }
enum CodingKeys: String, CodingKey {
case value, songCount, albumCount
}
}
// MARK: - Starred
struct Starred2Container: Codable {
let artist: [Artist]?
let album: [Album]?
let song: [Song]?
}
// MARK: - Now Playing
struct NowPlayingContainer: Codable {
let entry: [NowPlayingEntry]?
}
struct NowPlayingEntry: Codable, Identifiable {
let id: String
let title: String
let artist: String?
let album: String?
let username: String?
let minutesAgo: Int?
let playerId: Int?
let playerName: String?
}
// MARK: - Random Songs
struct RandomSongsContainer: Codable {
let song: [Song]?
}
// MARK: - Scan Status
struct ScanStatus: Codable {
let scanning: Bool
let count: Int?
}
// MARK: - Lyrics
struct LyricsResult: Codable {
let artist: String?
let title: String?
let value: String?
}
// MARK: - Offline Download
struct DownloadedSong: Codable, Identifiable {
let id: String
let serverId: UUID
let song: Song
let localPath: String
let downloadDate: Date
let fileSize: Int64
}
// MARK: - Internet Radio
struct InternetRadioContainer: Codable {
let internetRadioStation: [RadioStation]?
}
struct RadioStation: Codable, Identifiable {
let id: String
let name: String
let streamUrl: String
let homePageUrl: String?
enum CodingKeys: String, CodingKey {
case id, name, streamUrl, homePageUrl
}
}
// MARK: - Similar Songs
struct SimilarSongsContainer: Codable {
let song: [Song]?
}