Das Regal zeigte weiter das grosse Plakat aus dem Asset-Katalog, obwohl die Datei daneben längst drei Reihen enthielt. Der Grund steht in Apples eigenem Header, zwei Zeilen über der Methode, die wir überschreiben: > If the completion handler is not called in a timely manner or is called > with empty content the system will display the static image instead. Genau das war zu sehen. Und darunter steht, was fehlte: > + (void)topShelfContentDidChange; > Can be called from an application or extension to tell the system that > your Top Shelf content has changed. Die Erweiterung kann sich nicht selbst anstossen — sie läuft nur, wenn sie gefragt wird. Gefragt wird sie, wenn die App es sagt. Die App sagt es jetzt, unmittelbar nachdem sie den Abzug geschrieben hat. Ausgeschlossen, bevor ich hier landete, und zwar nachgemessen statt vermutet: das System kennt die Erweiterung (`pluginkit` listet sie unter `com.apple.tv-top-shelf`), die Hauptklasse ist im Binärcode auffindbar (`_TtC13FlixyTopShelf16TopShelfProvider`), die Gerätefamilie stimmt, und die Datei liegt mit drei Reihen in der App-Gruppe. Nicht nachgewiesen: dass es jetzt zeichnet. Die Erweiterung startet nur, wenn das System sie fragt, und das tut es nur, wenn die App im Menü oben steht und fokussiert ist — beides kann ich im Simulator nicht auslösen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
81 lines
3.3 KiB
Swift
81 lines
3.3 KiB
Swift
import Foundation
|
|
import MediaKit
|
|
import TVServices
|
|
|
|
/// Legt für das Regal über der App hin, was es zeigen soll.
|
|
///
|
|
/// **Beim Laden der Startseite, nicht auf einem Zeitgeber.** Das Regal wird
|
|
/// gezeichnet, wenn jemand im Menü über der App steht — und dann ist die App
|
|
/// nicht gestartet. Was dort steht, ist also immer das, was beim letzten
|
|
/// Öffnen zu sehen war. Ein Zeitgeber änderte daran nichts und hielte nur die
|
|
/// App wach.
|
|
@MainActor
|
|
enum TopShelfWriter {
|
|
/// Die App-Gruppe, die App und Erweiterung teilen.
|
|
static let group = "group.com.scarriffleservices.flixy"
|
|
|
|
/// Die Kante, in der das Regal Plakate zeigt.
|
|
///
|
|
/// **Apples Mass für die doppelte Auflösung**, und für beide Stufen
|
|
/// dasselbe Bild. Ein Apple TV zeichnet das Regal ohnehin doppelt
|
|
/// aufgelöst; zwei Adressen abzulegen hiesse, dem Server zwei Grössen
|
|
/// desselben Plakats abzuverlangen, damit eine davon nie gebraucht wird.
|
|
private static let posterWidth = 666
|
|
private static let posterHeight = 1000
|
|
|
|
static func update(
|
|
rows: [MediaRowGroup],
|
|
backend: (any MediaBackend)?
|
|
) {
|
|
guard let backend else { return }
|
|
|
|
let abschnitte = rows
|
|
.prefix(TopShelfSnapshot.sectionLimit)
|
|
.map { reihe in
|
|
TopShelfSnapshot.Section(
|
|
id: reihe.id,
|
|
title: reihe.title,
|
|
entries: reihe.items
|
|
.prefix(TopShelfSnapshot.entryLimit)
|
|
.map { item in
|
|
TopShelfSnapshot.Entry(
|
|
id: item.id,
|
|
title: item.rowTitle,
|
|
subtitle: item.cardSubtitle,
|
|
// **Das Hochkant-Plakat, nicht `thumb`.** Bei
|
|
// einer Folge ist `thumb` das Szenenbild; im
|
|
// Regal stünde davon ein zufälliger
|
|
// Ausschnitt. Dieselbe Rückfallkette wie auf
|
|
// den Kacheln.
|
|
imageURL: backend.artworkURL(
|
|
path: item.posterArtworkPath,
|
|
width: posterWidth,
|
|
height: posterHeight
|
|
),
|
|
progress: item.watchProgress
|
|
)
|
|
}
|
|
)
|
|
}
|
|
.filter { !$0.entries.isEmpty }
|
|
|
|
TopShelfStore.write(
|
|
TopShelfSnapshot(sections: Array(abschnitte)),
|
|
group: group
|
|
)
|
|
|
|
// **Und dem System sagen, dass es neu fragen soll.**
|
|
//
|
|
// Ohne diese Zeile bleibt die Erweiterung ungefragt, und im Header
|
|
// steht, was dann geschieht: „If the completion handler is not called
|
|
// in a timely manner or is called with empty content the system will
|
|
// display the static image instead." Genau das war zu sehen — das
|
|
// Plakat aus dem Asset-Katalog, gross und unverändert, während die
|
|
// Datei daneben längst drei Reihen enthielt.
|
|
//
|
|
// Die Erweiterung selbst kann das nicht auslösen: sie läuft nur,
|
|
// wenn sie gefragt wird.
|
|
TVTopShelfContentProvider.topShelfContentDidChange()
|
|
}
|
|
}
|