Die Medientasten bedienen jetzt Flixy

Die App hat sich beim System nie als „läuft gerade" gemeldet — das
MediaPlayer-Framework kam im ganzen Projekt nicht vor. macOS schickt die
Medientasten aber genau dorthin: an das Programm, das sich gemeldet hat.
Meldet sich keines, gewinnt irgendein anderes, und man pausiert etwas,
das man gar nicht sieht.

`NowPlaying` meldet Titel, Untertitel, Länge, Stelle und Tempo und nimmt
entgegen: Abspielen, Anhalten, Umschalten, Vor- und Zurückspulen mit den
**eingestellten** Weiten, und das Ziehen am Balken. Damit tun
Medientasten, Kontrollzentrum, Sperrbildschirm und Kopfhörerknöpfe
dasselbe wie die Knöpfe im Player — es ist eine Sache, nicht vier.

Eine Anmeldung für die ganze App, nicht eine je Player: das System kennt
ohnehin nur einen laufenden Titel. Abgemeldet wird nur vom Player, der
auch angemeldet ist — sonst räumte einer, der spät zu Ende geht, dem
längst gestarteten nächsten die Tasten weg.

Die Rückrufe springen auf den Hauptakteur, statt ihn anzunehmen. Dass sie
dort ankommen, ist nirgends zugesagt, und dieselbe Annahme hat die App im
VLC-Engine schon einmal zum Absturz gebracht.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Scarriffle
2026-09-15 21:55:49 +02:00
co-authored by Claude Opus 5
parent fc812f5a25
commit 5d09836686
2 changed files with 173 additions and 0 deletions
+19
View File
@@ -56,6 +56,15 @@ final class PlayerModel {
private(set) var isOffline = false
/// Ob ein laufender Sender abgespielt wird.
private(set) var isLive = false
/// Die Spulweiten, wie sie eingestellt sind.
///
/// Nach außen sichtbar, weil die Systembedienung — Kontrollzentrum,
/// Sperrbildschirm, Medientasten — dieselben Zahlen anbieten soll wie die
/// Knöpfe im Player. Zwei Weiten, die auseinanderlaufen, sind schlimmer
/// als eine, die nicht jedem gefällt.
var rewindSeconds: Int { settings.rewindSeconds }
var forwardSeconds: Int { settings.forwardSeconds }
/// Die im Player gewählte Stufe. `automatic` folgt den Einstellungen.
private(set) var quality: PlayerQuality = .automatic
/// Ob gerade auf eine andere Stufe umgestellt wird.
@@ -202,6 +211,10 @@ final class PlayerModel {
func start() async {
diagnostics.begin(title: request.title, connection: connectionDescription)
// Beim System anmelden, bevor das erste Bild kommt: ab hier gehören
// die Medientasten diesem Titel und nicht mehr dem, was vorher lief.
NowPlaying.shared.takeOver(self)
// Die lokale Datei hat immer Vorrang. Sie ist schneller, kostet kein
// Datenvolumen und funktioniert auch dann, wenn der Server gerade
// nicht erreichbar ist — der eigentliche Sinn des Herunterladens.
@@ -583,6 +596,7 @@ final class PlayerModel {
isPlaying = false
isBuffering = false
bufferWatchdog?.cancel()
NowPlaying.shared.release(for: self)
}
/// Meldet dem Server, dass Schluss ist. Darf dauern.
@@ -767,6 +781,7 @@ final class PlayerModel {
isPlaying = state == .playing
isBuffering = state == .buffering || state == .loading
watchBuffering(isBuffering)
NowPlaying.shared.update(from: self)
// Jeder Zustandswechsel mit Zeitstempel. Erst daran ist
// abzulesen, ob die Engine hängt oder die Leitung.
diagnostics.note("Zustand: \(state)")
@@ -783,6 +798,9 @@ final class PlayerModel {
}
case .timeChanged(let time):
currentTime = time
// Gedrosselt in `NowPlaying`: zwischen zwei Meldungen
// zählt das System selbst weiter, es kennt das Tempo.
NowPlaying.shared.update(from: self)
// Angekommen: ab hier zeigt das Bild wieder die Stelle,
// an der man war.
if let target = resumeTarget, time + .seconds(2) >= target {
@@ -791,6 +809,7 @@ final class PlayerModel {
updateActiveMarker()
case .durationResolved(let value):
duration = value
NowPlaying.shared.update(from: self)
case .reachedEnd:
isPlaying = false
hasReachedEnd = true