Files
Flaro/Packages/FlixyKit/Sources/PlexKit/Endpoints/LibraryQuery.swift
T
GuidoandClaude Opus 5 9a69df4635 Phase 1: Anmeldung, Bibliothek, Detailseiten, Hybrid-Player
Die Datenschicht:
- plex.tv-Anmeldung über das PIN-Verfahren, Token im Schlüsselbund
- Serversuche und Verbindungsauswahl nach Rang (lokal vor direkt vor
  Vermittlung), mit Abgleich der Server-Kennung gegen veraltete
  plex.direct-Adressen
- PlexServer als einzige Stelle, an der die App Endpunkte kennt:
  Mediatheken, Hubs, seitenweises Blättern mit Filtern, Metadaten,
  Staffeln/Folgen, Suche

Die Oberfläche:
- Startseite mit Aufmacher und den Reihen vom Server
- Mediathek als Raster oder Liste, mit Filter- und Sortierblatt
- Detailseiten für Filme, Folgen, Serien und Staffeln inkl. Besetzung,
  Fassungsauswahl und aufklappbaren technischen Angaben
- Suche mit gemerkten Anfragen
- Vollbildplayer mit eigenem Chrome

Der Player:
- AVPlayerEngine und VLCPlayerEngine hinter einem gemeinsamen Protokoll
- VLCKit liegt in einem eigenen Package (FlixyVLC), damit `swift test`
  in FlixyKit nicht das 920-MB-Binärpaket mitzieht

Am echten plex.tv geprüft: der Anmeldecode kommt an. Dabei fiel auf,
dass `strong=true` einen dreißigstelligen Code liefert, der sich bei
plex.tv/link nicht eintippen lässt — jetzt der kurze.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-11 16:46:28 +02:00

86 lines
3.1 KiB
Swift

import Foundation
/// Filter und Sortierung einer Mediatheksansicht.
///
/// Wird zu Abfrageparametern für Plex — die Filterung passiert also auf dem
/// Server. Bei zehntausend Titeln wäre alles andere aussichtslos: die Liste
/// erst vollständig zu laden, um sie dann lokal zu durchsuchen, dauert
/// Minuten und belegt entsprechend Speicher.
public struct LibraryQuery: Sendable, Equatable {
public enum Sort: String, Sendable, Equatable, CaseIterable {
case titleAscending = "titleSort:asc"
case titleDescending = "titleSort:desc"
case recentlyAdded = "addedAt:desc"
case releaseDate = "originallyAvailableAt:desc"
case rating = "rating:desc"
case lastViewed = "lastViewedAt:desc"
}
public var sort: Sort
public var unwatchedOnly: Bool
public var genre: String?
public var year: Int?
public var contentRating: String?
public var resolution: String?
public init(
sort: Sort = .titleAscending,
unwatchedOnly: Bool = false,
genre: String? = nil,
year: Int? = nil,
contentRating: String? = nil,
resolution: String? = nil
) {
self.sort = sort
self.unwatchedOnly = unwatchedOnly
self.genre = genre
self.year = year
self.contentRating = contentRating
self.resolution = resolution
}
/// Ob überhaupt gefiltert wird — steuert, ob die Oberfläche den
/// Filterknopf hervorhebt.
public var isFiltering: Bool {
unwatchedOnly || genre != nil || year != nil || contentRating != nil || resolution != nil
}
func queryItems(libraryKind: PlexLibrary.Kind?) -> [URLQueryItem] {
var items: [URLQueryItem] = [URLQueryItem(name: "sort", value: sort.rawValue)]
// Plex nummeriert die Arten: 1 Film, 2 Serie, 4 Folge. Ohne diese
// Angabe liefert eine Serienmediathek alle Folgen einzeln statt der
// Serien.
switch libraryKind {
case .movie: items.append(URLQueryItem(name: "type", value: "1"))
case .show: items.append(URLQueryItem(name: "type", value: "2"))
default: break
}
if unwatchedOnly { items.append(URLQueryItem(name: "unwatched", value: "1")) }
if let genre { items.append(URLQueryItem(name: "genre", value: genre)) }
if let year { items.append(URLQueryItem(name: "year", value: String(year))) }
if let contentRating { items.append(URLQueryItem(name: "contentRating", value: contentRating)) }
if let resolution { items.append(URLQueryItem(name: "resolution", value: resolution)) }
return items
}
}
/// Ein Ausschnitt aus einer längeren Liste.
public struct PlexPage<Element: Sendable>: Sendable {
public let items: [Element]
public let offset: Int
/// Wie viele Einträge es insgesamt gibt.
public let totalSize: Int
public init(items: [Element], offset: Int, totalSize: Int) {
self.items = items
self.offset = offset
self.totalSize = totalSize
}
public var hasMore: Bool { offset + items.count < totalSize }
public var nextOffset: Int { offset + items.count }
}