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:
Guido Schmit
2026-08-10 21:02:13 +02:00
parent 03dd7b945e
commit 433cdcc3dd
53 changed files with 4888 additions and 2 deletions

View File

@@ -0,0 +1,62 @@
#!/usr/bin/env python3
import subprocess
import urllib.parse
import os
import re
STATIC_BADGES = """
![](https://img.shields.io/github/stars/ungive/mediaremote-adapter?style=flat&label=stars&logo=github&labelColor=444&color=DAAA3F&cacheSeconds=3600)
"""
def get_output(command):
result = subprocess.run(
command,
shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.DEVNULL,
text=True,
)
return result.stdout.strip()
def normalize_whitespace(text):
return re.sub(r'\s+', ' ', text.strip())
def generate_badges():
system_version = get_output(
"system_profiler SPSoftwareDataType | sed -En 's/.*System Version: *//p'"
)
date_output = normalize_whitespace(get_output("date"))
encoded_system_version = urllib.parse.quote(system_version)
encoded_date = urllib.parse.quote(date_output)
badge1 = f"![](https://img.shields.io/static/v1?label=macOS&message={encoded_system_version}&labelColor=444&color=blue)"
badge2 = f"![](https://img.shields.io/static/v1?label=last%20tested&message={encoded_date}&labelColor=444&color)"
return f"{STATIC_BADGES.strip()}\n{badge1}\n{badge2}"
def update_readme(script_dir, new_badges):
readme_path = os.path.join(script_dir, "..", "README.md")
with open(readme_path, "r") as file:
content = file.read()
new_content = re.sub(
r"<!-- BADGES BEGIN -->.*?<!-- BADGES END -->",
f"<!-- BADGES BEGIN -->\n{new_badges}\n<!-- BADGES END -->",
content,
flags=re.DOTALL,
)
with open(readme_path, "w") as file:
file.write(new_content)
def main():
script_dir = os.path.dirname(os.path.abspath(__file__))
badges = generate_badges()
update_readme(script_dir, badges)
if __name__ == "__main__":
main()