Live-TV: die Sender stehen im Programmraster, nicht in einer Senderliste
Drei geratene Adressen für die Senderliste antworteten auf einem echten Server alle mit 404. Statt ein viertes Mal zu raten, liest die App jetzt zwei Dinge: was /media/providers an Endpunkten angibt, und das Programmraster selbst — die Weboberfläche von Plex zeichnet ihren Guide daraus, und jeder Rastereintrag trägt Sendername, Nummer, Logo und Einschaltweg mit sich. Damit kommen Sender und Programm aus einer Abfrage, und ihre Kennungen passen zueinander. Getrennt geholt taten sie das nicht: das Programm landete in keiner Zeile. Dazu die Ansicht, für die das alles da ist — ein Programmraster mit stehender Senderspalte, mitlaufender Zeitachse und einer Jetzt-Linie. Antippen öffnet die Sendung; läuft sie gerade, schaltet ein Knopf um. Die Geometrie steht in GuideLayout und ist geprüft: in einer View wäre sie unsichtbar falsch. Die Trennlinien gehören in die Zeile statt zwischen die Zeilen — Divider ist auf 3x-Schirmen keinen ganzen Punkt hoch, und der Bruchteil summiert sich, bis die angetippte Zeile zum Nachbarsender gehört. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
9f9eec1b7f
commit
591cba73a7
@@ -16,6 +16,12 @@ struct LiveTVView: View {
|
||||
@State private var channelSource: String?
|
||||
@State private var programs: [String: [PlexProgram]] = [:]
|
||||
@State private var isTuning: String?
|
||||
/// Raster oder Senderliste.
|
||||
///
|
||||
/// Auf dem Handy sind beide Ansichten berechtigt: das Raster zum Stöbern,
|
||||
/// die Liste zum schnellen Umschalten. Die Wahl bleibt erhalten, weil
|
||||
/// niemand sie zweimal treffen will.
|
||||
@AppStorage("liveTV.showsGuide") private var showsGuide = true
|
||||
|
||||
var body: some View {
|
||||
Group {
|
||||
@@ -65,17 +71,38 @@ struct LiveTVView: View {
|
||||
}
|
||||
|
||||
case .available:
|
||||
guide
|
||||
if showsGuide {
|
||||
ProgramGuide(
|
||||
channels: channels,
|
||||
programs: programs,
|
||||
tuning: isTuning,
|
||||
onTune: { channel in Task { await tune(channel) } }
|
||||
)
|
||||
} else {
|
||||
channelList
|
||||
}
|
||||
}
|
||||
}
|
||||
.background(Color.flixyCanvas)
|
||||
.navigationTitle(Text("sidebar.liveTV"))
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbar {
|
||||
if case .available = availability, !channels.isEmpty {
|
||||
ToolbarItem(placement: .topBarTrailing) {
|
||||
Picker("liveTV.mode", selection: $showsGuide) {
|
||||
Image(systemName: "calendar.day.timeline.left").tag(true)
|
||||
Image(systemName: "list.bullet").tag(false)
|
||||
}
|
||||
.pickerStyle(.segmented)
|
||||
.labelsHidden()
|
||||
}
|
||||
}
|
||||
}
|
||||
.task { await load() }
|
||||
.refreshable { await load() }
|
||||
}
|
||||
|
||||
private var guide: some View {
|
||||
private var channelList: some View {
|
||||
ScrollView {
|
||||
LazyVStack(spacing: 0) {
|
||||
ForEach(channels) { channel in
|
||||
@@ -99,26 +126,18 @@ struct LiveTVView: View {
|
||||
|
||||
guard case .available(let dvrs) = availability, let dvr = dvrs.first else { return }
|
||||
|
||||
let lookup = await server.channels(inDVR: dvr)
|
||||
channels = lookup.channels
|
||||
channelSource = lookup.description
|
||||
|
||||
// Der Programmführer kommt getrennt: er kann ausfallen, ohne dass
|
||||
// deshalb das Umschalten unmöglich wäre. Eine Senderliste ohne
|
||||
// Programm ist immer noch nützlich.
|
||||
guard let provider = await server.electronicProgramGuideIdentifier() else { return }
|
||||
let now = Date()
|
||||
let entries = (try? await server.guide(
|
||||
providerIdentifier: provider,
|
||||
from: now.addingTimeInterval(-3_600),
|
||||
to: now.addingTimeInterval(6 * 3_600)
|
||||
)) ?? []
|
||||
|
||||
programs = Dictionary(grouping: entries.compactMap { program in
|
||||
program.channelID.map { (key: $0, program: program) }
|
||||
}, by: \.key).mapValues { pairs in
|
||||
pairs.map(\.program).sorted { $0.startsAt < $1.startsAt }
|
||||
}
|
||||
// Sender und Programm kommen zusammen: die Senderliste des Anbieters
|
||||
// antwortet auf manchen Servern gar nicht, und dann tragen die
|
||||
// Rastereinträge die Sender selbst. Getrennt geholt passten die
|
||||
// Kennungen zudem nicht zueinander, und das Programm landete in
|
||||
// keiner Zeile.
|
||||
// Sechs Stunden: ein ganzer Fernsehabend. Mehr wäre bei einer Liste
|
||||
// mit fünfhundert Sendern eine Antwort im zweistelligen Megabereich,
|
||||
// und über Mobilfunk wartet man darauf.
|
||||
let guide = await server.liveTVGuide(dvr: dvr, hours: 6)
|
||||
channels = guide.channels
|
||||
programs = guide.programs
|
||||
channelSource = guide.source
|
||||
}
|
||||
|
||||
private func tune(_ channel: PlexChannel) async {
|
||||
|
||||
Reference in New Issue
Block a user