Files
MobileMusicAssistant/Mobile Music Assistant/ModelsMAModels.swift
T
2026-03-27 09:21:41 +01:00

300 lines
7.1 KiB
Swift

//
// MAModels.swift
// Mobile Music Assistant
//
// Created by Sven Hanold on 26.03.26.
//
import Foundation
// MARK: - Player Models
struct MAPlayer: Codable, Identifiable, Hashable {
let playerId: String
let name: String
let state: PlayerState
let currentItem: MAQueueItem?
let volume: Int
let powered: Bool
let available: Bool
var id: String { playerId }
enum CodingKeys: String, CodingKey {
case playerId = "player_id"
case name
case state
case currentItem = "current_item"
case volume = "volume_level"
case powered
case available
}
}
enum PlayerState: String, Codable {
case playing
case paused
case idle
case off
}
// MARK: - Queue Models
struct MAQueueItem: Codable, Identifiable, Hashable {
let queueItemId: String
let mediaItem: MAMediaItem?
let name: String
let duration: Int?
let streamDetails: MAStreamDetails?
var id: String { queueItemId }
enum CodingKeys: String, CodingKey {
case queueItemId = "queue_item_id"
case mediaItem = "media_item"
case name
case duration
case streamDetails = "stream_details"
}
}
struct MAStreamDetails: Codable, Hashable {
let providerId: String
let itemId: String
let audioFormat: MAAudioFormat?
enum CodingKeys: String, CodingKey {
case providerId = "provider"
case itemId = "item_id"
case audioFormat = "audio_format"
}
}
struct MAAudioFormat: Codable, Hashable {
let contentType: String
let sampleRate: Int?
let bitDepth: Int?
enum CodingKeys: String, CodingKey {
case contentType = "content_type"
case sampleRate = "sample_rate"
case bitDepth = "bit_depth"
}
}
// MARK: - Media Models
struct MAMediaItem: Codable, Identifiable, Hashable {
let uri: String
let name: String
let mediaType: MediaType
let artists: [MAArtist]?
let album: MAAlbum?
let imageUrl: String?
let duration: Int?
var id: String { uri }
enum CodingKeys: String, CodingKey {
case uri
case name
case mediaType = "media_type"
case artists
case album
case imageUrl = "image"
case duration
}
}
enum MediaType: String, Codable {
case track
case album
case artist
case playlist
case radio
}
struct MAArtist: Codable, Identifiable, Hashable {
let uri: String
let name: String
let imageUrl: String?
let sortName: String?
let musicbrainzId: String?
var id: String { uri }
enum CodingKeys: String, CodingKey {
case uri
case name
case imageUrl = "image"
case sortName = "sort_name"
case musicbrainzId = "musicbrainz_id"
}
}
struct MAAlbum: Codable, Identifiable, Hashable {
let uri: String
let name: String
let artists: [MAArtist]?
let imageUrl: String?
let year: Int?
var id: String { uri }
enum CodingKeys: String, CodingKey {
case uri
case name
case artists
case imageUrl = "image"
case year
}
}
struct MAPlaylist: Codable, Identifiable, Hashable {
let uri: String
let name: String
let owner: String?
let imageUrl: String?
let isEditable: Bool
var id: String { uri }
enum CodingKeys: String, CodingKey {
case uri
case name
case owner
case imageUrl = "image"
case isEditable = "is_editable"
}
}
// MARK: - WebSocket Protocol Models
struct MACommand: Encodable {
let messageId: String
let command: String
let args: [String: AnyCodable]?
enum CodingKeys: String, CodingKey {
case messageId = "message_id"
case command
case args
}
}
struct MAResponse: Decodable {
let messageId: String?
let result: AnyCodable?
let errorCode: String?
let errorMessage: String?
enum CodingKeys: String, CodingKey {
case messageId = "message_id"
case result
case errorCode = "error_code"
case errorMessage = "error"
}
}
struct MAEvent: Decodable {
let event: String
let data: AnyCodable?
}
// MARK: - Auth Models
struct MALoginRequest: Encodable {
let username: String
let password: String
}
struct MALoginResponse: Decodable {
let accessToken: String
enum CodingKeys: String, CodingKey {
case accessToken = "access_token"
}
}
// MARK: - AnyCodable Helper
/// Helper to handle dynamic JSON values
struct AnyCodable: Codable, Hashable {
let value: Any
init(_ value: Any) {
self.value = value
}
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if container.decodeNil() {
value = NSNull()
} else if let bool = try? container.decode(Bool.self) {
value = bool
} else if let int = try? container.decode(Int.self) {
value = int
} else if let double = try? container.decode(Double.self) {
value = double
} else if let string = try? container.decode(String.self) {
value = string
} else if let array = try? container.decode([AnyCodable].self) {
value = array.map { $0.value }
} else if let dict = try? container.decode([String: AnyCodable].self) {
value = dict.mapValues { $0.value }
} else {
throw DecodingError.dataCorruptedError(
in: container,
debugDescription: "Unable to decode value"
)
}
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch value {
case is NSNull:
try container.encodeNil()
case let bool as Bool:
try container.encode(bool)
case let int as Int:
try container.encode(int)
case let double as Double:
try container.encode(double)
case let string as String:
try container.encode(string)
case let array as [Any]:
try container.encode(array.map { AnyCodable($0) })
case let dict as [String: Any]:
try container.encode(dict.mapValues { AnyCodable($0) })
default:
throw EncodingError.invalidValue(
value,
EncodingError.Context(
codingPath: container.codingPath,
debugDescription: "Unable to encode value"
)
)
}
}
static func == (lhs: AnyCodable, rhs: AnyCodable) -> Bool {
// Simple comparison - extend as needed
return String(describing: lhs.value) == String(describing: rhs.value)
}
func hash(into hasher: inout Hasher) {
hasher.combine(String(describing: value))
}
}
extension AnyCodable {
/// Decode the wrapped value to a specific type
func decode<T: Decodable>(as type: T.Type) throws -> T {
let data = try JSONEncoder().encode(self)
return try JSONDecoder().decode(T.self, from: data)
}
}