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.
84 lines
3.1 KiB
Objective-C
84 lines
3.1 KiB
Objective-C
// 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
|
|
];
|
|
}
|