Playable nativ auf macOS und Linux
Playable erhält zwei eigenständige Desktop-Apps mit gemeinsamem Swift-Kern. macOS verwendet SwiftUI und AppKit, Linux GTK4 und libadwaita. Beide teilen Fachlogik, API-Vertrag, lokale Datenregeln und Wiedergabezustände, aber keine Oberfläche und keine Annahme über den UI-Ereigniszyklus. Der bestehende Projektplan bleibt bis Epic 09 unverändert; die Linux-Arbeit folgt als Epic 10 bis 13.
Inhalt
- Die Entscheidung
- Der gemeinsame Kern
- Zwei native Apps
- Packages und Ausweichwege
- Die Epics in Reihenfolge
- Prüfpunkte und Fallstricke
- Wo Plan und Fortschritt stehen
Anhang
1. Die Entscheidung
Playable wird nicht als eine Oberfläche gebaut, die sich an zwei Systeme anpasst. Es wird als ein Produkt mit zwei nativen Oberflächen gebaut. Die macOS-App folgt den Konventionen von macOS, die Linux-App den Konventionen von GNOME und libadwaita. Gemeinsam bleibt, was für einen Nutzer auf beiden Systemen dasselbe bedeutet: Podcast, Episode, Favorit, Konto, Warteschlange, Wiedergabezustand, Suche und Synchronisation.
Der gemeinsame Teil ist ein Satz kleiner Swift-Module. Sie kennen weder Fenster noch Schaltflächen, weder AppKit noch GTK. Sie sprechen mit der Playable-Plattform über deren versionierten OpenAPI-Vertrag und erhalten Live-Änderungen über die dafür vorgesehene Echtzeitverbindung. Diese Verträge entstehen bereits in Epic 03 und sind damit die feste Grenze für beide Apps.12
Swift ist auf den vorgesehenen Linux-Systemen offiziell verfügbar.3 Damit liegt die sprachliche Gemeinsamkeit dort, wo sie Wert hat, ohne den nativen Charakter der Apps einzuschränken.
| Teil | Verantwortung | Geteilt |
|---|---|---|
PlayableCore | Domänenmodelle, Anwendungsfälle, Sortierung, Suche, Wiedergabezustand und Fehlerformen | ja |
PlayableAPI | OpenAPI-Client, Transportmodelle, Authentifizierung, Echtzeitereignisse und Abbildung in die Domäne | ja |
PlayableStore | Lokaler Cache, Favoritenzustand, Wiedergabefortschritt und ausstehende Änderungen | ja |
PlayableMac | SwiftUI, AppKit, AVFoundation, Keychain, Now Playing und Apple-Benachrichtigungen | nein |
PlayableLinux | GTK4, libadwaita, GStreamer, libsecret, MPRIS und Flatpak | nein |
2. Der gemeinsame Kern
Der Kern stellt Werte und Fähigkeiten bereit, keine globalen App-Zustände. Ein Podcast oder eine Episode ist ein unveränderlicher Swift-Wert. Ein Repository lädt und verändert Daten. Ein Wiedergabecontroller übersetzt Befehle und Ereignisse in einen nachvollziehbaren Zustand. Die Apps entscheiden selbst, wie sie diesen Zustand beobachten und darstellen.
Die wichtigsten Grenzen sind PodcastRepository, AccountRepository, FavouritesRepository, RealtimeFeed, AudioEngine, CredentialStore, NowPlayingService und NotificationService. Jede Plattform setzt nur die Grenzen um, für die sie eigene Systemdienste besitzt. Transportmodelle bleiben in PlayableAPI, Datenbankzeilen in PlayableStore und Framework-Objekte in der jeweiligen App.
Die API wird aus dem veröffentlichten OpenAPI-Vertrag erzeugt. Der erzeugte Client bleibt hinter PlayableAPI, damit eine Änderung des Generators nicht in die Oberfläche wandert. Auf Linux übernimmt ein AsyncHTTPClient-Transport die Verbindung.45 Die Echtzeitverbindung liefert Ereignisse mit denselben kanonischen Kennungen wie die REST-Antworten und kann nach einem Verbindungsabbruch einen konsistenten Stand wiederherstellen.2
Lokale Daten liegen in SQLite. SQLite.swift ist dafür die erste Wahl, weil es Swift und Linux ausdrücklich unterstützt.6 Geheimnisse gehören nicht in diese Datenbank. Sie liegen auf macOS im Keychain und auf Linux über libsecret im Secret Service.
Die wichtigste technische Grenze
Der gemeinsame Kern ist nicht an MainActor gebunden. macOS führt UI-Änderungen auf dem Swift Main Actor aus. Linux übergibt sie an den GLib Main Context. Ein gemeinsames UI-Beobachtungsobjekt würde diese beiden Modelle vermischen und wird deshalb nicht verwendet.
3. Zwei native Apps
Auf macOS entsteht die Oberfläche mit SwiftUI und gezielten AppKit-Bausteinen. Fenster, Seitenleiste, Toolbar, Menüs, Tastaturkürzel, Einstellungen, Wiederherstellung und Systemmediensteuerung verhalten sich wie Bestandteile einer macOS-App. AVFoundation übernimmt die Wiedergabe, Keychain die Sitzung und MediaPlayer die Systemanzeige.
Auf Linux entsteht die Oberfläche mit GTK4 und libadwaita. Navigation, Toolbar, Listen, Dialoge, adaptive Aufteilung und schmale Fenster folgen der GNOME Human Interface Guideline.7 Die Anwendung verwendet Standard-Widgets, damit Tastaturbedienung und AT-SPI-Semantik bereits in den Grundelementen vorhanden sind.8
swift-adwaita ist die bevorzugte Swift-Bindung. Das Package deckt GTK4 und libadwaita ab und führt eigene Hinweise für GLib-Nebenläufigkeit und GTK-Objektlebenszeiten.91011 Seine Typen bleiben vollständig in PlayableLinux, damit die App bei Bedarf zu einer anderen Bindung wechseln kann.
| Fähigkeit | macOS | Linux |
|---|---|---|
| Oberfläche | SwiftUI und AppKit | GTK4 und libadwaita |
| Audio | AVFoundation | GStreamer |
| Zugangsdaten | Keychain | libsecret und Secret Service |
| Systemmedien | MediaPlayer und Now Playing | MPRIS über GDBus |
| Benachrichtigungen | Apple Push und UserNotifications | Desktop-Benachrichtigungen während der Laufzeit; Hintergrundzustellung als eigener Board-Vorgang |
| Distribution | Mac App Store und signierte Builds | Flatpak und Flathub |
Die Wiedergaberegeln sind gemeinsam, die Decoder nicht. Ein pausierter Live-Stream schließt seine Verbindung. Beim Fortsetzen beginnt er an der aktuellen Live-Position. Eine aufgezeichnete Episode behält ihre Position und kann suchen. Beide Audioadapter melden dieselben Zustände: Auflösen, Puffern, Abspielen, Pausieren, Stoppen und Fehler.
4. Packages und Ausweichwege
Jede externe Abhängigkeit bekommt eine enge Aufgabe und eine definierte Ausweichroute. Ein Package wird nur übernommen, wenn der Feasibility-Lauf seine benötigten Funktionen ohne Fork nachweist.
| Bereich | Bevorzugt | Ausweichweg | Entscheidungskriterium |
|---|---|---|---|
| Linux-Oberfläche | swift-adwaita | Adwaita for Swift, danach SwiftGtk | Native adaptive Fenster, sichere GLib-Übergabe und beherrschbare Objektlebenszeit ohne Fork |
| API | Swift OpenAPI Generator | kleiner typisierter AsyncHTTPClient-Client | Der erzeugte Vertrag bleibt vollständig hinter PlayableAPI |
| Lokale Daten | SQLite.swift | schmaler eigener SQLite-C-Adapter | Migrationen, Transaktionen und Linux-Tests laufen ohne eigene SQLite-Funktionen |
| Linux-Audio | schmaler eigener Adapter um GStreamer playbin | gstreamer-swift, danach libmpv | Streams, Pufferung, Tags, Fehler, Lautstärke und Spektrum funktionieren ohne Package-Fork |
| Linux-Geheimnisse | libsecret-Systemmodul | nur flüchtige Sitzung | Gesperrter, fehlender und abgebrochener Secret Service werden ausdrücklich behandelt |
| Linux-Systemmedien | GDBus und MPRIS | Swift-DBUS nach vollständigem Bus-Name-Management | playerctl findet und steuert die App mit minimalen Sandbox-Rechten |
GStreamer playbin übernimmt Formaterkennung, Decoderwahl und Netzwerkpufferung.12 Der Swift-Adapter bleibt klein und serialisiert alle Pipeline-Aufrufe. gstreamer-swift wird im Feasibility-Lauf geprüft, aber nur übernommen, wenn Bus-Nachrichten, Abbruch, Streamwechsel und Spektraldaten vollständig zugänglich sind.13
libsecret ist die direkte Anbindung an den Secret Service.14 MPRIS stellt Wiedergabestatus, Metadaten, Lautstärke und Befehle bereit.15 Für Live-Inhalte meldet die Linux-App ausdrücklich, dass Suchen nicht möglich ist.
5. Die Epics in Reihenfolge
Die bestehenden Epics 01 bis 07 bauen Plattform, Daten, API, Benachrichtigungen, Website, Dashboard und Dokumentation. Epic 08 und 09 liefern die macOS-App und das vollständige Podcast-Erlebnis. Ihre gemeinsamen Swift-Teile werden von Beginn an frei von Apple-UI-Typen gehalten. Dadurch beginnt Linux nicht mit einer nachträglichen Portierung, sondern mit einem bereits getesteten Kern.
| Epic | Ziel | Ergebnis |
|---|---|---|
| E08 | macOS Live-Erlebnis und portable Desktop-Grenzen | Greenfield-App, OpenAPI-Client, Echtzeitdaten, Live-Wiedergabe, Favoriten und macOS-Integration. PlayableCore, PlayableAPI und PlayableStore enthalten keine Apple-UI-Typen. |
| E09 | Vollständiges On-Demand-Erlebnis | Suche, Mediathek, Episoden, Warteschlange, Downloads, Fortschritt und Synchronisation erweitern dieselben gemeinsamen Module. |
| E10 | Linux-Feasibility und Anwendungsskelett | Swift-Toolchain, libadwaita-Fenster, GLib-Übergabe, API-Aufruf, GStreamer, libsecret, MPRIS und Offline-Flatpak-Build sind im Ziel-Runtime nachgewiesen. |
| E11 | Native Linux-Live-App | Adaptive GTK-Oberfläche, Live-Katalog, Suche, Favoriten, Echtzeitstatus, GStreamer-Wiedergabe und native Desktop-Steuerung. |
| E12 | Native Linux-Podcast-App | Mediathek, Episoden, Wiedergabefortschritt, Warteschlange, Downloads und plattformübergreifend synchronisierte Zustände. |
| E13 | Linux-Distribution und Plattformparität | Flathub-Metadaten, Sandbox-Rechte, Codec-Matrix, GNOME- und KDE-Prüfung, Orca, Installationsprüfung und dokumentierte Plattformunterschiede. |
E10 wird erst als GitHub-Epic angelegt, wenn E08 die gemeinsamen Grenzen tatsächlich liefert. Sein erster Vorgang ist ein Wegwerf-Prototyp, der die riskanten Linux-Anbindungen gemeinsam im Flatpak-Runtime prüft. Die Entscheidung über Wrapper fällt aus diesem Lauf, nicht aus der API-Oberfläche eines Repositories.
6. Prüfpunkte und Fallstricke
Flatpak gehört in den ersten Linux-Lauf. Ein Flathub-Build darf seine SwiftPM-Abhängigkeiten nicht während des Baus aus dem Netz laden. Die Quellen werden deshalb mit dem offiziellen SwiftPM-Generator vorab in das Manifest aufgenommen.1617 GTK, libadwaita und GStreamer kommen dynamisch aus dem GNOME-Runtime. Ein vollständig statisches Swift-Linux-SDK ersetzt diese Bibliotheken nicht.
| Prüfung | Bestanden, wenn |
|---|---|
| GTK und Swift Concurrency | Ein Hintergrundereignis aktualisiert reproduzierbar ein Widget über den GLib Main Context und wird beim Schließen vollständig abgebrochen. |
| Objektlebenszeit | Fenster, Dialoge und Signal-Closures schließen ohne veralteten Callback, Zyklus oder Absturz. |
| Playable-API | Der generierte Client lädt einen kanonischen Live-Stand und stellt einen strukturierten Fehler samt errorId dar. |
| Echtzeit | Eine getrennte Verbindung synchronisiert sich wieder und liefert danach einen konsistenten Stand. |
| Audio | MP3, AAC im vorhandenen Codec-Runtime, Ogg/Opus, Redirects, .m3u und .pls laufen oder scheitern mit eindeutigem Grund. |
| Geheimnisse | Speichern, Lesen, Löschen, gesperrter Keyring, fehlender Dienst und Nutzerabbruch sind sichtbar und sicher behandelt. |
| MPRIS | playerctl findet Playable und steuert Wiedergabe sowie Lautstärke; Live-Inhalte melden CanSeek als falsch. |
| Flatpak | Der Build löst alle Quellen offline auf, installiert sauber und läuft auf x86_64 und aarch64. |
| Fallstrick | Gegenmaßnahme |
|---|---|
| Swift Main Actor und GLib Main Context werden gleichgesetzt | Getrennte Plattform-Presenter und ein ausdrücklicher GLib-Übergang |
| GTK-Signale halten Fenster oder Controller fest | Besitzverhältnisse pro Signal dokumentieren und beim Schließen trennen |
| Ein Codec fehlt nur im ausgelieferten Flatpak | Die Stream-Matrix läuft im exakten Release-Runtime |
| Ein junger Wrapper wird zur unersetzbaren Grundlage | Wrapper-Typen bleiben im Adapter; jeder Bereich besitzt einen benannten Ausweichweg |
| Ein optimistischer Favorit wird bei Wiederholung zurückgedreht | Die Warteschlange speichert den gewünschten Zustand, niemals einen Toggle |
| Der Secret Service ist nicht verfügbar | Flüchtige Sitzung oder klarer Fähigkeitsfehler, niemals Klartextablage |
| Die Linux-App kopiert das macOS-Layout | Gemeinsame Informationsarchitektur, getrennte native Komponenten und Plattformprüfung |
| Hintergrundbenachrichtigungen werden als Desktop-Detail behandelt | Ein eigener Board-Vorgang entscheidet über Dienst, Zustellung und Lebenszyklus |
7. Wo Plan und Fortschritt stehen
Dieses Paper hält die Architektur, die Paketentscheidungen und die Reihenfolge der Epics fest. Es ändert sich, wenn eine Modulgrenze, ein Package, ein Ausweichweg oder die Reihenfolge geändert wird. Das GitHub-Projekt bleibt die einzige Sicht auf den Fortschritt. Ein Epic trägt den Zuschnitt, seine Issues tragen die Arbeit und ihre Checklisten.18
Die Epics E10 bis E13 werden als Board-Vorgänge angelegt, sobald die Linux-Umsetzung freigegeben wird. Bis dahin ist dieses Paper die technische Entscheidung, nicht der Beginn einer Implementierung.
Anhang
Vorgesehene Dateien, Ereigniswege und geprüfte Quellen. Für die Entscheidung und Reihenfolge oben braucht man das hier nicht.
A. Vorgesehene Modulstruktur
Package.swift
Sources/
PlayableCore/
Domain/
Errors/
Ports/
State/
UseCases/
PlayableAPI/
Generated/
Client/
Mapping/
Realtime/
PlayableStore/
Database/
Migrations/
Repositories/
Sync/
PlayableLinux/
Application/
Presentation/
UI/
Platform/
Apps/
macOS/
PlayableMac/
Application/
Presentation/
UI/
Platform/
Packaging/
Flatpak/
PlayableCore kennt nur Swift und Foundation-Werte. PlayableAPI kennt den erzeugten Vertrag. PlayableStore kennt SQLite. Die beiden Apps setzen die Ports zusammen und halten ihre Frameworks in Platform und UI.
B. Daten- und Ereignisfluss
Native UI
-> platform presenter
-> shared use case
-> repository or platform port
-> API, store or audio adapter
-> normalized domain event
-> shared state transition
-> platform presenter
-> native UI event loop
Der letzte Schritt ist absichtlich plattformspezifisch. macOS wechselt auf den Main Actor, Linux auf den GLib Main Context. Wiedergabe und lokale Daten laufen jeweils serialisiert, damit C-Frameworks, Datenbank und Oberfläche nicht gleichzeitig denselben veränderlichen Zustand besitzen.
C. Quellennachweis
- Playable: Define versioned API boundaries and the OpenAPI contract. Issue 28. Quelle↩
- Playable: Publish realtime live-state updates. Issue 34. Quelle↩↩
- Swift.org: Platform Support. Volltext gelesen Quelle↩
- Apple: Swift OpenAPI Generator. Repository und Dokumentation gelesen Quelle↩
- Swift Server Work Group: Swift OpenAPI AsyncHTTPClient Transport. Repository und Dokumentation gelesen Quelle↩
- SQLite.swift: Linux documentation. Volltext gelesen Quelle↩
- GNOME: Human Interface Guidelines. Relevante Abschnitte gelesen Quelle↩
- GNOME: Accessibility guidelines. Volltext gelesen Quelle↩
- Makoni: swift-adwaita. Repository und Dokumentation gelesen Quelle↩
- Makoni: Concurrency on GTK. Volltext gelesen Quelle↩
- Makoni: GTK lifetime and ownership. Volltext gelesen Quelle↩
- GStreamer: playbin documentation. Volltext gelesen Quelle↩
- Wendy Labs: gstreamer-swift. Öffentliche API und Dokumentation gelesen Quelle↩
- GNOME: libsecret documentation. Relevante Abschnitte gelesen Quelle↩
- freedesktop.org: MPRIS Player interface. Volltext gelesen Quelle↩
- Flathub: Requirements for applications. Volltext gelesen Quelle↩
- Flatpak: SwiftPM source generator. Volltext gelesen Quelle↩
- Playable: Product README and Project board. Aktuellen Stand gelesen Quelle↩