Live-TV: das Programm zeigte die falsche Sendung als laufend

Der Fehler steckte in der Abfrage, und er ist grob:

    beginsAt> = jetzt − 1 Std.
    endsAt<   = jetzt + 6 Std.

Beide Grenzen schließen genau das aus, was man sehen will. Ein
Zweistünder, der um halb vier begann, war um Viertel nach fünf
unsichtbar — und angezeigt wurde stattdessen die nächste Sendung, die
durch den Filter passte. Deshalb wich die Anzeige von Plex' eigener ab.

Jetzt wird nur der **Anfang** eingegrenzt, mit sechs Stunden Vorlauf für
lange Sendungen, und keine Grenze auf das Ende. Zurück kommt, was den
Zeitraum berührt, statt was vollständig hineinpasst.

Gefiltert wird danach nur das Programm, nicht die Senderliste: ein Sender
ohne frische Daten soll „kein Programm" sagen, statt zu verschwinden.

Dazu zwei Wünsche aus dem Feldtest:

Die Kachel lässt sich in der Größe ziehen. Der Griff sitzt in der Ecke,
die zur Bildschirmmitte zeigt — am äußeren Rand läge er halb außerhalb
des Daumens. Gespeichert wird die begrenzte Breite, nicht die gezogene,
sonst bliebe eine einmal zu weit gezogene Kachel für immer zu groß.

Und eine Senderauswahl. Über zwanzig Sender zu zeichnen und ihr Programm
zu holen kostet Zeit, und die meisten sieht man nie an. Eine leere
Auswahl heißt weiterhin „alle" — sonst stünde man beim ersten Öffnen vor
einer leeren Liste.

Die Zeiten im Raster-Abzug liegen jetzt in der Zukunft. Mit den echten
Zeitstempeln von damals wäre er ab dem Tag nach der Aufnahme leer
gewesen, sobald auf Frische gefiltert wird.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Scarriffle
2026-09-12 17:26:14 +02:00
co-authored by Claude Opus 5
parent 26c909a2e2
commit af45bebceb
9 changed files with 437 additions and 19 deletions
@@ -247,11 +247,24 @@ extension PlexServer {
/// Bewusst über einen Zeitraum statt „alles": ein Tag über hundert Sender
/// sind mehrere tausend Sendungen, und mehr als die nächsten Stunden
/// braucht niemand beim Umschalten.
///
/// Zurück kommt, was den Zeitraum **berührt** — nicht, was vollständig
/// hineinpasst. Das ist der Unterschied, an dem das Programm vorher falsch
/// war: gefiltert wurde auf `endsAt <` Fensterende und `beginsAt >`
/// Fensteranfang, und damit fiel ausgerechnet die laufende Sendung heraus.
/// Ein Zweistünder, der um halb vier begann, war um Viertel nach fünf
/// unsichtbar, und angezeigt wurde stattdessen die nächste, die durch den
/// Filter passte — sichtbar falsch, und anders als Plex' eigene Anzeige.
public func guide(
providerIdentifier: String,
from start: Date,
to end: Date
) async throws(PlexError) -> [PlexGuideEntry] {
// Nur der **Anfang** wird eingegrenzt, und mit Vorlauf. Eine Grenze
// auf das Ende schließt jede Sendung aus, die über das Fenster
// hinausläuft — also gerade die längsten.
let lead = start.addingTimeInterval(-Self.longestProgramme)
let container = try await client.send(
PlexRequest(
path: "/\(providerIdentifier)/grid",
@@ -259,8 +272,8 @@ extension PlexServer {
// Ohne Typfilter. `type=1` steht bei Plex für Filme, und
// im Programm laufen auch Folgen, Sport und Nachrichten —
// der Filter hat das Raster leer zurückgegeben.
URLQueryItem(name: "beginsAt>", value: String(Int(start.timeIntervalSince1970))),
URLQueryItem(name: "endsAt<", value: String(Int(end.timeIntervalSince1970))),
URLQueryItem(name: "beginsAt>", value: String(Int(lead.timeIntervalSince1970))),
URLQueryItem(name: "beginsAt<", value: String(Int(end.timeIntervalSince1970))),
],
timeout: .seconds(30)
),
@@ -277,6 +290,12 @@ extension PlexServer {
}
}
/// Wie weit vor dem Fenster gesucht wird.
///
/// Sechs Stunden decken alles ab, was im Fernsehen am Stück läuft — lange
/// Spielfilme, Sportübertragungen, Wahlabende.
static var longestProgramme: TimeInterval { 6 * 3_600 }
/// Senderliste und Programm in einem Zug.
///
/// Der Weg über die Senderliste des Anbieters ist der erste Versuch; wo
@@ -309,7 +328,14 @@ extension PlexServer {
to: now.addingTimeInterval(TimeInterval(hours) * 3_600)
)) ?? []
let programs = Dictionary(grouping: entries.compactMap { entry in
// Der Vorlauf der Abfrage holt auch Abgelaufenes mit — das gehört
// nicht ins Programm. **Nur** die Sendungen werden so gefiltert, nicht
// die Sender: ein Sender ohne frische Programmdaten soll trotzdem in
// der Liste stehen, sonst verschwindet er, statt „kein Programm" zu
// sagen.
let fresh = entries.filter { $0.program.endsAt > now }
let programs = Dictionary(grouping: fresh.compactMap { entry in
entry.program.channelID.map { (key: $0, program: entry.program) }
}, by: \.key).mapValues { pairs in
pairs.map(\.program).sorted { $0.startsAt < $1.startsAt }