import Foundation struct Library: Codable, Identifiable, Hashable { let id: String let name: String let mediaType: String? } struct LibraryItem: Codable, Identifiable, Hashable { let id: String let title: String let author: String let durationSeconds: Double let audioFiles: [AudioFile] var mediaType: String = "book" var episodeId: String? = nil var description: String? = nil var isPodcast: Bool { mediaType == "podcast" } var isPodcastContainer: Bool { isPodcast && episodeId == nil } var isPodcastEpisode: Bool { isPodcast && episodeId != nil } static func == (lhs: LibraryItem, rhs: LibraryItem) -> Bool { lhs.id == rhs.id && lhs.episodeId == rhs.episodeId } func hash(into hasher: inout Hasher) { hasher.combine(id) hasher.combine(episodeId) } } struct PodcastEpisode: Identifiable, Hashable, Codable { let id: String let title: String let pubDate: String? let publishedAtMillis: Double? let season: String? let episode: String? let durationSeconds: Double let audioFile: AudioFile var formattedDate: String? { if let ms = publishedAtMillis, ms > 0 { let date = Date(timeIntervalSince1970: ms / 1000.0) let df = DateFormatter() df.dateStyle = .medium df.timeStyle = .none return df.string(from: date) } return pubDate } } struct AudioFile: Codable, Hashable { let ino: String let filename: String let ext: String let durationSeconds: Double let index: Int } struct PlaybackProgress: Codable, Hashable { let itemId: String var episodeId: String? var currentTime: Double var duration: Double var isFinished: Bool var updatedAt: Date var syncKey: String { if let episodeId { return "\(itemId)|\(episodeId)" } return itemId } } enum DownloadState: Equatable { case notDownloaded case downloading(progress: Double) case downloaded case failed(message: String) }