Flixy heißt ab hier Halo
Reine Umbenennung, kein Verhalten geändert — bewusst als eigener Commit, damit „umbenannt" und „umgebaut" im Nachhinein auseinanderzuhalten sind. 712 mal `Flixy`, 778 mal `flixy`, 7 mal `FLIXY` in 169 Dateien, dazu 25 Pfade. Ziele: `Halo`, `HaloTV`, `HaloTopShelf`, `HaloUITests`, `HaloKit` mit `HaloDesign`, `HaloDownloads`, `HaloPlayback` und `HaloProbe`. `MediaKit`, `JellyfinKit` und `DownloadWidget` tragen den Namen nicht und bleiben. `docs/superpowers/specs/` ist ausgenommen. Datierte Entwurfsdokumente sind ein Protokoll getroffener Entscheidungen; sie nachträglich umzuschreiben hiesse, die Vergangenheit zu glätten. **Die Bundle-ID wandert mit** — ausdrücklich so gewollt, mit bekanntem Preis: `com.scarriffleservices.halo` ist für iOS eine *andere* App. Die installierte Fassung bleibt daneben stehen, mit Downloads und Anmeldung in ihrem Container; Halo startet leer. Einmal neu anmelden, heruntergeladene Folgen einmal neu laden. Dasselbe gilt für die App-Gruppe, die sich TV-App und Top Shelf teilen. **Eine Folge davon, die beim Entscheiden noch nicht auf dem Tisch lag:** der Aufräumschritt aus dem vorigen Commit kommt an die alten Plex-Token nicht mehr heran. Der Schlüsselbund grenzt nach Team und Bundle-Kennung ab — was unter `…flixy` liegt, ist für `…halo` unerreichbar. Erledigt wird es trotzdem, nur anders: **die alte App löschen.** iOS räumt deren Schlüsselbund-Einträge beim Deinstallieren mit ab. Der Code bleibt stehen, weil er für jeden Stand vor dieser Umbenennung richtig ist und im Jahr einen Startvergleich kostet. Die Projektdatei ist aus `project.yml` neu erzeugt, der alte Ordner gelöscht statt umbenannt — sie steht ohnehin nicht im Repo. Das Verzeichnis des Repos selbst heißt weiter `Flixy`; das umzubenennen ist ein Handgriff ausserhalb des Projekts. 368 Tests grün. iOS und tvOS bauen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
2fcf6821c8
commit
4b9a1e7b67
@@ -0,0 +1,89 @@
|
||||
import AVKit
|
||||
import SwiftUI
|
||||
import HaloPlayback
|
||||
import MediaKit
|
||||
|
||||
/// Hängt Apples Player in den SwiftUI-Baum.
|
||||
///
|
||||
/// **Wem was gehört.** Der `AVPlayerViewController` gehört dem Coordinator,
|
||||
/// nicht der SwiftUI-Struktur — die wird bei jedem Neuzeichnen neu gebaut —
|
||||
/// und nicht dem `PlayerModel`, das frei von UIKit bleiben soll. Das Modell
|
||||
/// gibt nur seinen `AVPlayer` heraus, und das ist AVFoundation.
|
||||
@MainActor
|
||||
struct TVPlayerHost: UIViewControllerRepresentable {
|
||||
let model: PlayerModel
|
||||
/// Eine Momentaufnahme dessen, was den Controller betrifft.
|
||||
///
|
||||
/// **Als Wert, nicht als Verweis.** SwiftUI ruft `updateUIViewController`
|
||||
/// nur, wenn sich am Repräsentanten etwas geändert hat. Stünde hier allein
|
||||
/// das Modell, änderte sich nie etwas: es ist eine Klasse, und ihre
|
||||
/// Kennung bleibt über jeden Spurwechsel hinweg dieselbe. Der Wert wird im
|
||||
/// Rumpf der umgebenden Ansicht gebildet — dort und nur dort entsteht die
|
||||
/// Beobachtungsbeziehung zu `@Observable`.
|
||||
let snapshot: TVPlayerSnapshot
|
||||
/// Der Untertitelstil — er steht im selben Menü wie die Spurwahl.
|
||||
let appearance: SubtitleAppearance
|
||||
let onDismiss: () -> Void
|
||||
|
||||
func makeUIViewController(context: Context) -> AVPlayerViewController {
|
||||
context.coordinator.controller
|
||||
}
|
||||
|
||||
func updateUIViewController(_ controller: AVPlayerViewController, context: Context) {
|
||||
context.coordinator.apply(snapshot, model: model)
|
||||
}
|
||||
|
||||
func makeCoordinator() -> TVPlayerCoordinator {
|
||||
TVPlayerCoordinator(appearance: appearance, onDismiss: onDismiss)
|
||||
}
|
||||
}
|
||||
|
||||
/// Was der Controller von der Wiedergabe wissen muss — und mehr nicht.
|
||||
struct TVPlayerSnapshot: Equatable {
|
||||
/// Wechselt bei jedem Neuaufbau. Daran hängt der Austausch des Players.
|
||||
let playerIdentity: ObjectIdentifier?
|
||||
let audio: [TrackMenu.Entry]
|
||||
let subtitles: [TrackMenu.Entry]
|
||||
/// Läuft gerade ein Neuaufbau? Dann ist das Bild schwarz und das Menü
|
||||
/// gesperrt.
|
||||
let isRebuilding: Bool
|
||||
let title: String
|
||||
let subtitle: String?
|
||||
/// Der Zeitbereich einer Marke — für „Intro überspringen".
|
||||
let markerLabel: String?
|
||||
/// Der eingestellte Untertitelstil.
|
||||
///
|
||||
/// **Gehört in die Momentaufnahme, obwohl der Player ihn nicht braucht.**
|
||||
/// Er steht im selben Menü wie die Spurwahl, und ohne ihn hier merkte
|
||||
/// SwiftUI nicht, dass sich etwas geändert hat — die Haken blieben beim
|
||||
/// alten Wert stehen, während die Untertitel längst anders aussähen.
|
||||
let subtitleStyle: SubtitleStyle
|
||||
|
||||
/// `@MainActor`, weil das Modell es ist: gelesen wird hier zwölfmal aus
|
||||
/// ihm, und zwar im Rumpf einer View — also ohnehin auf dem Hauptakteur.
|
||||
@MainActor
|
||||
init(_ model: PlayerModel, appearance: SubtitleAppearance, offLabel: String) {
|
||||
subtitleStyle = appearance.style
|
||||
playerIdentity = model.systemPlayer.map(ObjectIdentifier.init)
|
||||
let direkt: Bool = if case .directPlay = model.decision?.method { true } else { false }
|
||||
audio = TrackMenu.audio(
|
||||
from: model.audioStreams,
|
||||
selected: model.selectedAudioStream,
|
||||
isDirectPlay: direkt,
|
||||
playerTrackCount: model.audioTracks.count
|
||||
)
|
||||
subtitles = TrackMenu.subtitles(
|
||||
from: model.subtitleStreams,
|
||||
selected: model.selectedSubtitleStream,
|
||||
isDirectPlay: direkt,
|
||||
playerTrackLanguages: model.subtitleTracks.map(\.languageCode),
|
||||
offLabel: offLabel
|
||||
)
|
||||
isRebuilding = model.isChangingQuality
|
||||
title = model.title
|
||||
subtitle = model.subtitle
|
||||
markerLabel = model.activeMarker.map {
|
||||
String(localized: $0.isIntro ? "player.skipIntro" : "player.skipCredits")
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user