Drei Fehler, alle im eigenen Code. Die Sendezeiten stehen beim Programmanbieter im Medien-Block, nicht am Eintrag darüber. Ich habe sie oben gesucht, nicht gefunden und jede Sendung verworfen — übrig blieb „Kein Programm verfügbar" auf jedem Sender, und ohne Sendungen fiel auch die Senderliste auf die Tunerliste zurück. Die Senderspalte im Raster fehlte ganz. Die Zeitachse ist mehrere tausend Punkte breit, und der HStack hat den Nachbarn dafür auf null gedrückt. Jetzt hat die Spalte Vorrang, und die Achse liegt als Auflage über einer durchsichtigen Fläche — eine Auflage zählt bei der Größenrechnung nicht mit. Der Abstand der Achsenbeschriftung gehört in die Spaltenbreite hinein statt daneben, sonst steht die ganze Achse acht Punkte neben dem Programm darunter. Und das Einschalten: die Adresse wurde berechnet und verworfen, dann bekam der Player eine Senderkennung, zu der er einen Film in der Mediathek suchte. Den gibt es nicht. Jetzt reicht die Anfrage die fertige Adresse durch, der Player spielt sie ohne Umweg, und meldet weder Fortschritt noch sucht er Metadaten. Scheitert es doch, steht ab jetzt eine Meldung da statt gar nichts. Geprüft wurde das Raster diesmal mit den Augen: ein Gerüst im Simulator zeichnet es mit erfundenen Sendern. Dafür bekommt es die Bildadresse als Abschluss übergeben, statt sie aus der Sitzung zu lesen. Dazu: Downloads zeigen jetzt an, wenn sie auf WLAN warten. Die Hintergrund-Sitzung wartet in dem Fall von sich aus — kein Fehler, nur Stillstand, und das sah bisher aus wie ein hängender Download. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
93 lines
3.2 KiB
Swift
93 lines
3.2 KiB
Swift
import Foundation
|
|
import Observation
|
|
import PlexKit
|
|
|
|
/// Was abgespielt werden soll.
|
|
struct PlaybackRequest: Identifiable, Hashable {
|
|
let id = UUID()
|
|
let ratingKey: String
|
|
/// Für die Titelzeile, solange die Metadaten noch geladen werden.
|
|
let title: String
|
|
/// Welche Fassung, wenn ein Titel in mehreren vorliegt.
|
|
let mediaIndex: Int
|
|
/// Ab wo — aus dem gemeldeten Fortschritt.
|
|
let startTime: Duration
|
|
/// Eine bereits feststehende Adresse.
|
|
///
|
|
/// Für Live-TV. Ein Sender liegt nicht in der Mediathek, und der übliche
|
|
/// Weg — Kennung nachschlagen, Datei suchen — findet für ihn nichts.
|
|
/// Daran ist das Einschalten gescheitert: die Adresse war längst
|
|
/// berechnet und wurde weggeworfen.
|
|
var streamURL: URL?
|
|
|
|
static func == (lhs: Self, rhs: Self) -> Bool { lhs.id == rhs.id }
|
|
func hash(into hasher: inout Hasher) { hasher.combine(id) }
|
|
}
|
|
|
|
/// Nimmt Abspielwünsche von überall entgegen.
|
|
///
|
|
/// Ohne diese Stelle bräuchte jeder Bildschirm, von dem aus sich etwas starten
|
|
/// lässt — Aufmacher, Reihe, Detailseite, Suche — seinen eigenen
|
|
/// Vollbild-Übergang. Vier Kopien desselben Ablaufs, die auseinanderdriften.
|
|
@MainActor
|
|
@Observable
|
|
final class PlaybackCoordinator {
|
|
var request: PlaybackRequest?
|
|
|
|
func play(_ item: PlexItem, mediaIndex: Int = 0, resuming: Bool = true) {
|
|
request = PlaybackRequest(
|
|
ratingKey: item.ratingKey,
|
|
title: item.playbackTitle,
|
|
mediaIndex: mediaIndex,
|
|
startTime: resuming ? item.resumeTime : .zero
|
|
)
|
|
}
|
|
|
|
func dismiss() {
|
|
request = nil
|
|
}
|
|
}
|
|
|
|
extension PlexItem {
|
|
/// Wie der Titel im Player heißt.
|
|
///
|
|
/// Bei Folgen ist der eigene Titel allein nutzlos — „Die erste Folge"
|
|
/// sagt nichts darüber, welche Serie läuft.
|
|
var playbackTitle: String {
|
|
if kind == .episode, let series = grandparentTitle {
|
|
return "\(series) · \(title)"
|
|
}
|
|
return title
|
|
}
|
|
|
|
/// Ab welcher Stelle fortgesetzt wird.
|
|
var resumeTime: Duration {
|
|
guard let viewOffset, viewOffset > 0 else { return .zero }
|
|
return .milliseconds(viewOffset)
|
|
}
|
|
|
|
/// Die Beschriftung unter einer Kachel.
|
|
///
|
|
/// Sie ordnet ein, was darüber steht: zu einer Folge gehört die Serie, zu
|
|
/// einer Staffel ihre Nummer. Die Reihenfolge ist Absicht — wird der
|
|
/// Platz eng, bleibt der Serienname stehen und die Folgennummer fällt weg.
|
|
var cardSubtitle: String? {
|
|
switch kind {
|
|
case .episode:
|
|
var code: String?
|
|
if let season = parentIndex, let episode = index {
|
|
code = String(localized: "episode.short \(season) \(episode)")
|
|
}
|
|
guard let series = grandparentTitle else { return code }
|
|
return code.map { "\(series) · \($0)" } ?? series
|
|
case .season:
|
|
// Ohne die Serie darüber wäre „Staffel 10" beliebig — deshalb
|
|
// steht die Nummer hier und der Serienname als Titel.
|
|
if let index { return String(localized: "season.short \(index)") }
|
|
return parentTitle == nil ? nil : title
|
|
default:
|
|
return year.map(String.init)
|
|
}
|
|
}
|
|
}
|