stremio/apps/ios/VMCTV/Data/Models.swift
Jos Vooges | STH ac951617cb Add Live Events across API/admin/Android TV, mobile ClearKey/DASH fixes, and tvOS app.
Wire FAAB schedule-events into master-api and admin IPTV settings, add Android TV Events UI, fix mobile ClearKey/stream playback (DASH + bump 1.0.23), and scaffold the Apple TV (VMCTV) client with docs/scripts.
2026-09-18 23:34:42 +02:00

347 lines
8.1 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import Foundation
import CoreGraphics
struct DeviceCodeResponse: Decodable {
let code: String
let expiresAt: String?
let pollIntervalSeconds: Int?
let linkUrl: String?
let qrPayload: String?
}
enum PollResult {
case pending
case expired
case alreadyLinked
case linked(refreshToken: String, viewerName: String)
}
struct HomePayload: Decodable {
let continueWatching: [MediaItem]
let spotlight: [MediaItem]?
let shelves: [ShelfRow]
let favorites: [MediaItem]?
}
struct ShelfRow: Decodable, Identifiable {
let id: String
let title: String
let name: String?
let kind: String?
let slug: String?
let items: [MediaItem]
}
struct MediaItem: Codable, Identifiable, Hashable {
let type: String
let id: String
let title: String
let subtitle: String?
let year: Int?
let posterUrl: String?
let backdropUrl: String?
let logoUrl: String?
let mediaFileId: String?
let seriesId: String?
let positionMs: Int64?
let durationMs: Int64?
let progressId: String?
let badge: String?
}
struct PlayResult: Decodable {
let streamUrl: String
let sessionId: String?
let mediaFileId: String?
let defaultSubtitle: DefaultSubtitle?
}
struct DefaultSubtitle: Decodable {
let fileId: Int?
let source: String?
let language: String?
let fileName: String?
let format: String?
let contentBase64: String
let label: String?
}
struct ViewerProfile: Decodable, Identifiable, Hashable {
let id: String
let name: String
let avatarId: String?
let isOwner: Bool?
let isKids: Bool?
let hasPin: Bool?
let showAvatar: Bool?
let hasCustomAvatar: Bool?
let avatarUrl: String?
var showsAvatar: Bool { showAvatar ?? true }
var hasCustomPhoto: Bool { hasCustomAvatar == true }
}
struct ProfilesPayload: Decodable {
let profiles: [ViewerProfile]
let activeProfileId: String?
let canManage: Bool?
}
struct ClientMe: Decodable {
struct Iptv: Decodable { let enabled: Bool? }
let iptv: Iptv?
let profile: ViewerProfile?
let deviceId: String?
var iptvEnabled: Bool { iptv?.enabled == true }
}
struct LinkedDevice: Decodable, Identifiable, Hashable {
let id: String
let name: String
let platform: String?
let lastSeenAt: String?
let createdAt: String?
let current: Bool?
}
struct DevicesPayload: Decodable {
let devices: [LinkedDevice]
}
struct ApproveDeviceResult: Decodable {
let ok: Bool?
let deviceName: String?
let platform: String?
}
struct MediaFileInfo: Decodable, Identifiable, Hashable {
let id: String
let label: String?
let resolution: String?
let videoCodec: String?
let audioCodec: String?
let hdrType: String?
let nodeOnline: Bool?
let nodeName: String?
}
struct MovieDetail: Decodable {
let id: String
let title: String
let overview: String?
let year: Int?
let posterUrl: String?
let backdropUrl: String?
let logoUrl: String?
let runtime: Int?
let genres: [String]?
let rating: String?
let cast: [String]?
let director: [String]?
let mediaFiles: [MediaFileInfo]?
var preferredMediaFileId: String? {
mediaFiles?.first(where: { $0.nodeOnline != false })?.id ?? mediaFiles?.first?.id
}
}
struct SeriesDetail: Decodable {
let id: String
let title: String
let overview: String?
let year: Int?
let posterUrl: String?
let backdropUrl: String?
let logoUrl: String?
let genres: [String]?
let rating: String?
let cast: [String]?
let director: [String]?
let seasons: [Season]?
}
struct Season: Decodable, Identifiable {
var id: Int { seasonNumber }
let seasonNumber: Int
let episodes: [Episode]?
}
struct Episode: Decodable, Identifiable {
let id: String
let title: String?
let episodeNumber: Int?
let seasonNumber: Int?
let overview: String?
let stillUrl: String?
let mediaFileId: String?
let available: Bool?
let runtime: Int?
let completed: Bool?
let positionMs: Int64?
let durationMs: Int64?
var progressFraction: CGFloat {
if completed == true { return 1 }
guard let dur = durationMs, dur > 0, let pos = positionMs else { return 0 }
return CGFloat(pos) / CGFloat(dur)
}
var resumeMs: Int64 {
if completed == true { return 0 }
return max(0, positionMs ?? 0)
}
}
struct NextEpisode: Decodable, Identifiable, Hashable {
let id: String
let seriesId: String
let seasonNumber: Int?
let episodeNumber: Int?
let title: String?
let seriesTitle: String?
let subtitle: String?
let mediaFileId: String
let posterUrl: String?
}
struct SearchPayload: Decodable {
let results: [MediaItem]
}
struct BrowsePayload: Decodable {
let id: String?
let title: String?
let kind: String?
let total: Int?
let offset: Int?
let items: [MediaItem]
}
struct SubtitleHit: Decodable, Identifiable, Hashable {
var id: String { "\(source ?? "x")-\(fileId)" }
let fileId: Int
let language: String?
let release: String?
let fileName: String?
let downloadCount: Int?
let hearingImpaired: Bool?
let source: String?
let uploader: String?
}
struct SubtitleSearchResult: Decodable {
let configured: Bool?
let imdbId: String?
let title: String?
let results: [SubtitleHit]
}
struct FetchedSubtitle: Decodable {
let fileId: Int?
let source: String?
let fileName: String?
let format: String?
let contentBase64: String
}
struct IptvCategory: Decodable, Identifiable, Hashable {
let id: String
let name: String
let channelCount: Int?
}
struct IptvChannel: Decodable, Identifiable, Hashable {
var id: Int { streamId }
let streamId: Int
let name: String
let logoUrl: String?
let number: Int?
let categoryId: String?
let nowTitle: String?
let nextTitle: String?
}
struct IptvPlayInfo: Decodable {
let streamId: Int
let name: String
let logoUrl: String?
let streamUrl: String
let format: String?
let fallbackStreamUrl: String?
let fallbackFormat: String?
}
struct EpgProgram: Decodable, Hashable {
let title: String
let start: String?
let end: String?
private static func parseISO(_ raw: String?) -> Date? {
guard let raw, !raw.isEmpty else { return nil }
if let d = ISO8601DateFormatter.vmcFractional.date(from: raw) { return d }
return ISO8601DateFormatter.vmcBasic.date(from: raw)
}
var progress: CGFloat {
guard let s = Self.parseISO(start), let e = Self.parseISO(end) else { return 0 }
let total = e.timeIntervalSince(s)
guard total > 0 else { return 0 }
return CGFloat(min(1, max(0, Date().timeIntervalSince(s) / total)))
}
var timeRangeLabel: String? {
guard let s = Self.parseISO(start), let e = Self.parseISO(end) else { return nil }
let f = DateFormatter()
f.locale = Locale(identifier: "nl_NL")
f.dateFormat = "HH:mm"
return "\(f.string(from: s)) – \(f.string(from: e))"
}
}
struct IptvEpg: Decodable, Hashable {
let streamId: Int
let channelName: String
let logoUrl: String?
let now: EpgProgram?
let next: EpgProgram?
}
extension ISO8601DateFormatter {
static let vmcFractional: ISO8601DateFormatter = {
let f = ISO8601DateFormatter()
f.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
return f
}()
static let vmcBasic: ISO8601DateFormatter = {
let f = ISO8601DateFormatter()
f.formatOptions = [.withInternetDateTime]
return f
}()
}
struct ShelfPref: Codable, Identifiable, Hashable {
var id: String { shelfId }
let shelfId: String
let name: String
let slug: String?
let kind: String?
var visible: Bool
var sortOrder: Int
}
struct FavoriteToggleResult: Decodable {
let favorited: Bool
let items: [MediaItem]?
}
struct ApiErrorBody: Decodable {
struct ErrorObj: Decodable { let message: String? }
let error: ErrorObj?
}
struct PlayerTrack: Identifiable, Hashable {
let id: Int32
let name: String
}