Live-TV: die Senderliste wird gesucht, nicht geraten — und Serien richtig beschriftet

Vier geratene Adressen, vier 404. Statt ein fünftes Mal zu raten, klopft
die App jetzt in einem Durchgang und gleichzeitig ein Dutzend Adressen ab:
was /media/providers an Endpunkten angibt, welche Unterverzeichnisse
Programmanbieter und Empfänger in ihrer eigenen Antwort führen, die
bekannten Formen anderer Serverfassungen, die Senderliste des Tuners
selbst — und zuletzt das Programmraster.

Dabei zwei Fehlerquellen mitbehoben, die jede einzeln wie „keine Sender"
ausgesehen hätten: der Sendername steht je nach Tunertyp in title, name,
channelTitle oder callSign, und eine Antwort mit dreißig namenlosen
Einträgen sieht aus wie eine Senderliste, ist aber keine.

Bleibt es leer, steht jetzt jede abgeklopfte Adresse mit ihrer Antwort in
der Ansicht. Das beantwortet „liegt es am Server oder an der App" ohne
eine weitere Runde Rückfragen.

Dazu drei Sachen bei Serien:
- Groß steht, worum es geht — bei einer Folge ihr Titel, bei einer
  Staffel der Name der Serie. „Staffel 10" als Überschrift sagte nicht,
  welche Serie gemeint ist.
- Die Einordnung steht darunter: Serienname bei Folgen, Staffelnummer bei
  Staffeln.
- Von der Folge führt ein Weg zur Staffel und zur Serie. Wer über
  „Weiterschauen" bei einer Folge landete, saß dort bisher fest.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Scarriffle
2026-09-12 12:21:29 +02:00
co-authored by Claude Opus 5
parent 591cba73a7
commit 7e36f37506
11 changed files with 565 additions and 124 deletions
+52 -12
View File
@@ -14,6 +14,8 @@ struct LiveTVView: View {
@State private var channels: [PlexChannel] = []
/// Woher die Senderliste kam — steht in der leeren Ansicht.
@State private var channelSource: String?
/// Jede abgeklopfte Adresse mit ihrer Antwort.
@State private var trace: [ChannelProbe] = []
@State private var programs: [String: [PlexProgram]] = [:]
@State private var isTuning: String?
/// Raster oder Senderliste.
@@ -55,19 +57,12 @@ struct LiveTVView: View {
ContentUnavailableView {
Label("liveTV.noChannels.title", systemImage: "tv")
} description: {
VStack(spacing: FlixySpacing.snug) {
Text("liveTV.noChannels.description")
if let channelSource {
// Ohne diese Angabe ist „keine Sender" eine
// Sackgasse: es sagt nicht, ob der Server nichts
// hergibt oder ob die App am falschen Ort sucht.
Text(channelSource)
.font(.system(.caption2, design: .monospaced))
.foregroundStyle(Color.flixyTextMuted)
}
}
Text("liveTV.noChannels.description")
} actions: {
Button("action.retry") { Task { await load() } }
VStack(spacing: FlixySpacing.regular) {
Button("action.retry") { Task { await load() } }
searchTrace
}
}
case .available:
@@ -102,6 +97,50 @@ struct LiveTVView: View {
.refreshable { await load() }
}
/// Wo überall gesucht wurde, und was dort stand.
///
/// Unbequem, aber nützlich: die Live-TV-Schnittstelle von Plex hat keine
/// Beschreibung, und „keine Sender" allein ist eine Sackgasse. Hier steht
/// jede abgeklopfte Adresse mit ihrer Antwort — das beantwortet die Frage
/// „liegt es am Server oder an der App" ohne eine weitere Runde.
@ViewBuilder
private var searchTrace: some View {
if !trace.isEmpty {
VStack(alignment: .leading, spacing: FlixySpacing.snug) {
if let channelSource {
Text(channelSource)
.font(.flixyCaption)
.foregroundStyle(Color.flixyTextSecondary)
}
ScrollView {
VStack(alignment: .leading, spacing: 3) {
ForEach(trace) { probe in
Text("\(probe.path) → \(probe.result)")
.font(.system(size: 10, design: .monospaced))
.foregroundStyle(Color.flixyTextMuted)
.lineLimit(2)
.textSelection(.enabled)
}
}
.frame(maxWidth: .infinity, alignment: .leading)
}
.frame(maxHeight: 200)
Button {
UIPasteboard.general.string = trace
.map { "\($0.path) → \($0.result)" }
.joined(separator: "\n")
} label: {
Label("diagnostics.copy", systemImage: "doc.on.doc")
.font(.flixyCaption)
}
.buttonStyle(.bordered)
}
.padding(.horizontal, FlixySpacing.screenMargin)
}
}
private var channelList: some View {
ScrollView {
LazyVStack(spacing: 0) {
@@ -138,6 +177,7 @@ struct LiveTVView: View {
channels = guide.channels
programs = guide.programs
channelSource = guide.source
trace = guide.trace
}
private func tune(_ channel: PlexChannel) async {