Phase 4: Medien-Widget mit dreistufiger Kaskade
Adapter für alles inklusive Browser, AppleScript für Spotify und Musik, Medientasten als letzte Reserve. Die Reihenfolge ist keine Vorliebe, sondern Absicherung: Apple hat die Now-Playing-Schnittstelle in macOS 15.4 schon einmal zugemacht. Fällt der Adapter aus, verliert das Widget seine Anzeige — aber nicht seine Funktion. Deshalb bleiben die Transporttasten auch dann bedienbar, wenn gar keine Metadaten da sind. Der Fortschritt zählt zwischen den Ereignissen selbst hoch. Spike B hatte gezeigt, dass elapsedTime nicht laufend nachgeliefert wird: ein Scrubber, der stur den letzten Wert anzeigt, steht still, während der Titel läuft. Gerechnet wird aus asOf und playbackRate, begrenzt auf die Titellänge — sonst zeigt die Anzeige nach dem Ende weiter steigende Zahlen. Teilmeldungen (diff = true) ergänzen den Stand, statt ihn zu ersetzen. Eine Meldung mit nur der Position würde sonst Titel, Interpret und Cover löschen. In AnyDecodable wird Bool vor Double geprüft. Andernfalls wird "playing": true zu 1 und die Wiedergabe gilt für immer als angehalten. Play/Pause schaltet sofort um, statt auf das nächste Ereignis zu warten. Ein Knopf, der eine halbe Sekunde später reagiert, fühlt sich kaputt an; das echte Ereignis korrigiert den Stand ohnehin. Der Adapter liegt als Quellcode unter Vendor/ (BSD-3, Stand in ONYX-VERSION.txt) und wird beim Bauen kompiliert, statt als fertige Binary eingecheckt zu werden. Er wird bewusst nicht gelinkt: geladen wird er von /usr/bin/perl, im eigenen Prozess greift Apples Entitlement-Prüfung. 124 Tests grün.
This commit is contained in:
+83
@@ -0,0 +1,83 @@
|
||||
// Copyright (c) 2025 Jonas van den Berg
|
||||
// This file is licensed under the BSD 3-Clause License.
|
||||
|
||||
#import "keys.h"
|
||||
|
||||
#import "MediaRemoteAdapter.h"
|
||||
|
||||
NSString *kMRAProcessIdentifier = @"processIdentifier";
|
||||
NSString *kMRABundleIdentifier = @"bundleIdentifier";
|
||||
NSString *kMRAParentApplicationBundleIdentifier =
|
||||
@"parentApplicationBundleIdentifier";
|
||||
NSString *kMRAPlaying = @"playing";
|
||||
|
||||
NSString *kMRADurationMicros = @"durationMicros";
|
||||
NSString *kMRAElapsedTimeMicros = @"elapsedTimeMicros";
|
||||
NSString *kMRATimestampEpochMicros = @"timestampEpochMicros";
|
||||
|
||||
NSString *kMRAElapsedTimeNow = @"elapsedTimeNow";
|
||||
NSString *kMRAElapsedTimeNowMicros = @"elapsedTimeNowMicros";
|
||||
|
||||
NSString *kMRATitle = @"title";
|
||||
NSString *kMRAArtist = @"artist";
|
||||
NSString *kMRAAlbum = @"album";
|
||||
NSString *kMRADuration = @"duration";
|
||||
NSString *kMRAElapsedTime = @"elapsedTime";
|
||||
NSString *kMRATimestamp = @"timestamp";
|
||||
NSString *kMRAArtworkMimeType = @"artworkMimeType";
|
||||
NSString *kMRAArtworkData = @"artworkData";
|
||||
|
||||
NSString *kMRAChapterNumber = @"chapterNumber";
|
||||
NSString *kMRAComposer = @"composer";
|
||||
NSString *kMRAGenre = @"genre";
|
||||
NSString *kMRAIsAdvertisement = @"isAdvertisement";
|
||||
NSString *kMRAIsBanned = @"isBanned";
|
||||
NSString *kMRAIsInWishList = @"isInWishList";
|
||||
NSString *kMRAIsLiked = @"isLiked";
|
||||
NSString *kMRAIsMusicApp = @"isMusicApp";
|
||||
NSString *kMRAPlaybackRate = @"playbackRate";
|
||||
NSString *kMRAProhibitsSkip = @"prohibitsSkip";
|
||||
NSString *kMRAQueueIndex = @"queueIndex";
|
||||
NSString *kMRARadioStationIdentifier = @"radioStationIdentifier";
|
||||
NSString *kMRARepeatMode = @"repeatMode";
|
||||
NSString *kMRAShuffleMode = @"shuffleMode";
|
||||
NSString *kMRAStartTime = @"startTime";
|
||||
NSString *kMRASupportsFastForward15Seconds = @"supportsFastForward15Seconds";
|
||||
NSString *kMRASupportsIsBanned = @"supportsIsBanned";
|
||||
NSString *kMRASupportsIsLiked = @"supportsIsLiked";
|
||||
NSString *kMRASupportsRewind15Seconds = @"supportsRewind15Seconds";
|
||||
NSString *kMRATotalChapterCount = @"totalChapterCount";
|
||||
NSString *kMRATotalDiscCount = @"totalDiscCount";
|
||||
NSString *kMRATotalQueueCount = @"totalQueueCount";
|
||||
NSString *kMRATotalTrackCount = @"totalTrackCount";
|
||||
NSString *kMRATrackNumber = @"trackNumber";
|
||||
NSString *kMRAUniqueIdentifier = @"uniqueIdentifier";
|
||||
NSString *kMRAContentItemIdentifier = @"contentItemIdentifier";
|
||||
NSString *kMRARadioStationHash = @"radioStationHash";
|
||||
NSString *kMRAMediaType = @"mediaType";
|
||||
|
||||
NSArray<NSString *> *mandatoryPayloadKeys(void) {
|
||||
return @[ kMRAProcessIdentifier, kMRATitle, kMRAPlaying ];
|
||||
}
|
||||
|
||||
bool allMandatoryPayloadKeysSet(NSDictionary *data) {
|
||||
NSArray<NSString *> *keys = mandatoryPayloadKeys();
|
||||
for (NSString *key in keys) {
|
||||
if (data[key] == nil || data[key] == [NSNull null]) {
|
||||
return false;
|
||||
}
|
||||
id value = data[key];
|
||||
if ([value isKindOfClass:[NSString class]] &&
|
||||
[(NSString *)value length] == 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
NSArray<NSString *> *identifyingPayloadKeys(void) {
|
||||
return @[
|
||||
kMRAProcessIdentifier, kMRABundleIdentifier,
|
||||
kMRAParentApplicationBundleIdentifier, kMRATitle, kMRAArtist, kMRAAlbum
|
||||
];
|
||||
}
|
||||
Reference in New Issue
Block a user