From f296d7ae1a47fb290550c319d7e71b2e75840818 Mon Sep 17 00:00:00 2001 From: Scarriffle Date: Sun, 13 Sep 2026 16:13:10 +0200 Subject: [PATCH] =?UTF-8?q?Mac:=20doch=20Catalyst=20=E2=80=94=20das=20iPad?= =?UTF-8?q?-Aussehen,=20ohne=20es=20nachzubauen?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Deine Frage war die richtige. Eine native Mac-App sah an zwei Dutzend Stellen anders aus, weil AppKit dieselben Dinge anders misst: Schriftgrößen, Spaltenbreiten, Formularstil. Jede davon habe ich heute einzeln nachgezogen — und wäre bei der nächsten Ansicht wieder von vorn angefangen. Mac Catalyst nimmt die iPad-Oberfläche unverändert. Nicht nachgebaut, sondern dieselbe: gleiche Schrift, gleiche Leiste, gleiche Abstände, und zwar auch in allem, was noch kommt. **Ich habe „MKV geht dann nie" gesagt. Das war falsch.** Ohne VLC spielen sie nicht *unverändert* — der Server rechnet sie um, und das kann er längst. Nur war die App darauf nicht vorbereitet: die Entscheidung hätte weiterhin VLC gewählt, den es dort nicht gibt, und das Bild wäre schwarz geblieben. `PlaybackSettings.hasFallbackEngine` fragt jetzt, ob es den zweiten Spieler auf dieser Plattform überhaupt gibt. Wo nicht, übernimmt der Server. Das kostet ihn Rechenzeit und sieht man nicht. Unverändert abspielen kommt später zurück — dafür braucht es einen Decoder mit einem Catalyst-Teil. VLCKits Binärpaket hat keinen: ios-arm64 ios-simulator macos-arm64_x86_64 tvos watchos xros Drei Stellen mussten Catalyst ausdrücklich ausnehmen: dort gilt `os(iOS)`, aber ActivityKit gibt es nicht — die Typen sind da und jeder Aufruf ist als nicht verfügbar markiert. Das ist die Art Falle, die erst der Compiler findet. Die Anpassungen von vorhin bleiben stehen, obwohl Catalyst sie nicht braucht: FlixyDesign baut für macOS mit, und `swift test` läuft dort. Sie kosten nichts und halten den Weg zu einer nativen Mac-App offen, falls VLC ihn eines Tages nötig macht. 263 Tests grün, iOS baut unverändert. Co-Authored-By: Claude Opus 5 --- Flixy/Info-macOS.plist | 21 +++++++- Flixy/Session/PlaybackSettings.swift | 23 ++++++++- .../Engine/DownloadActivityPresenter.swift | 5 +- .../Engine/DownloadCoordinator.swift | 9 +++- .../Model/DownloadActivityAttributes.swift | 5 +- project.yml | 49 +++++++++++++------ 6 files changed, 90 insertions(+), 22 deletions(-) diff --git a/Flixy/Info-macOS.plist b/Flixy/Info-macOS.plist index 988b4c7..6d51a6c 100644 --- a/Flixy/Info-macOS.plist +++ b/Flixy/Info-macOS.plist @@ -31,6 +31,8 @@ CFBundleVersion 1 + ITSAppUsesNonExemptEncryption + LSApplicationCategoryType public.app-category.entertainment NSAppTransportSecurity @@ -38,7 +40,22 @@ NSAllowsLocalNetworking - NSHumanReadableCopyright - + UIBackgroundModes + + audio + + UILaunchScreen + + UIColorName + LaunchBackground + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIUserInterfaceStyle + Dark diff --git a/Flixy/Session/PlaybackSettings.swift b/Flixy/Session/PlaybackSettings.swift index b88b1c9..4e01696 100644 --- a/Flixy/Session/PlaybackSettings.swift +++ b/Flixy/Session/PlaybackSettings.swift @@ -1,4 +1,7 @@ import Foundation +#if canImport(FlixyVLC) +import FlixyVLC +#endif import Observation import FlixyPlayback @@ -53,6 +56,21 @@ final class PlaybackSettings { /// Die Geschwindigkeiten, mit denen eine Wiedergabe beginnen kann. static let rateChoices: [Double] = [0.75, 1.0, 1.25, 1.5, 1.75, 2.0] + /// Ob ein zweiter Spieler zur Verfügung steht. + /// + /// VLC steckt in einem eigenen Paket, das nur für iOS gebaut wird — sein + /// Binärpaket hat keinen Teil für Mac Catalyst. Wo es fehlt, übernimmt der + /// Server: MKV und DTS werden dann umgerechnet statt unverändert + /// übertragen. Das kostet ihn Rechenzeit und sieht man nicht, aber es + /// spielt. + static var hasFallbackEngine: Bool { + #if canImport(FlixyVLC) + true + #else + false + #endif + } + private let defaults: UserDefaults var wifiQuality: Quality { didSet { defaults.set(wifiQuality.rawValue, forKey: Key.wifiQuality) } } @@ -129,7 +147,10 @@ final class PlaybackSettings { return PlaybackPolicy( allowsTranscoding: true, - allowsFallbackEngine: true, + // Nur, wenn es ihn auf dieser Plattform gibt. Ohne VLC wählte die + // Entscheidung sonst einen Spieler, den es nicht gibt — und die + // Wiedergabe bliebe schwarz, statt dass der Server umrechnet. + allowsFallbackEngine: Self.hasFallbackEngine, maximumBitrate: quality.bitrateLimit ) } diff --git a/Packages/FlixyKit/Sources/FlixyDownloads/Engine/DownloadActivityPresenter.swift b/Packages/FlixyKit/Sources/FlixyDownloads/Engine/DownloadActivityPresenter.swift index e4397f0..f1534f7 100644 --- a/Packages/FlixyKit/Sources/FlixyDownloads/Engine/DownloadActivityPresenter.swift +++ b/Packages/FlixyKit/Sources/FlixyDownloads/Engine/DownloadActivityPresenter.swift @@ -1,6 +1,9 @@ import Foundation -#if os(iOS) +// Catalyst ausdrücklich ausgenommen: dort gilt `os(iOS)`, aber ActivityKit +// gibt es nicht — die Typen sind zwar da und jeder Aufruf ist als nicht +// verfügbar markiert. +#if os(iOS) && !targetEnvironment(macCatalyst) // `@preconcurrency`, weil `Activity` und `ActivityContent` keine // Sendable-Zusage tragen. Beide bleiben hier ohnehin auf dem Hauptakteur — // die Klasse ist darauf festgelegt, und ActivityKit erwartet seine Aufrufe diff --git a/Packages/FlixyKit/Sources/FlixyDownloads/Engine/DownloadCoordinator.swift b/Packages/FlixyKit/Sources/FlixyDownloads/Engine/DownloadCoordinator.swift index 824fbf3..6a23a31 100644 --- a/Packages/FlixyKit/Sources/FlixyDownloads/Engine/DownloadCoordinator.swift +++ b/Packages/FlixyKit/Sources/FlixyDownloads/Engine/DownloadCoordinator.swift @@ -149,7 +149,9 @@ public final class DownloadCoordinator { return created } - #if os(iOS) + // Catalyst ausdrücklich ausgenommen: dort gilt `os(iOS)`, aber + // ActivityKit gibt es nicht. + #if os(iOS) && !targetEnvironment(macCatalyst) private let activityPresenter = DownloadActivityPresenter() #endif @@ -564,7 +566,10 @@ public final class DownloadCoordinator { /// gleichzeitigen ist das der, der zuerst fertig wird, und damit der, auf /// den man wartet. private func updateActivity() { - #if os(iOS) + // Catalyst ausdrücklich ausgenommen: dort gilt `os(iOS)`, aber + // ActivityKit gibt es nicht — die Typen sind zwar da, und jeder + // Aufruf ist als nicht verfügbar markiert. + #if os(iOS) && !targetEnvironment(macCatalyst) let running = items.filter { $0.state == .downloading } guard let leading = running.max(by: { ($0.progress ?? 0) < ($1.progress ?? 0) }) else { Task { [activityPresenter] in await activityPresenter.update(with: nil) } diff --git a/Packages/FlixyKit/Sources/FlixyDownloads/Model/DownloadActivityAttributes.swift b/Packages/FlixyKit/Sources/FlixyDownloads/Model/DownloadActivityAttributes.swift index c3fa53c..9281876 100644 --- a/Packages/FlixyKit/Sources/FlixyDownloads/Model/DownloadActivityAttributes.swift +++ b/Packages/FlixyKit/Sources/FlixyDownloads/Model/DownloadActivityAttributes.swift @@ -3,7 +3,10 @@ import Foundation // `os(iOS)` statt `canImport(ActivityKit)`: der Import gelingt auch auf // macOS, die Typen darin sind dort aber als nicht verfügbar markiert. Eine // Prüfung auf den Import allein übersetzt deshalb nicht. -#if os(iOS) +// Catalyst ausdrücklich ausgenommen: dort gilt `os(iOS)`, aber ActivityKit +// gibt es nicht — die Typen sind zwar da und jeder Aufruf ist als nicht +// verfügbar markiert. +#if os(iOS) && !targetEnvironment(macCatalyst) import ActivityKit /// Was im Sperrbildschirm und in der Dynamic Island steht. diff --git a/project.yml b/project.yml index f82ea31..7413425 100644 --- a/project.yml +++ b/project.yml @@ -148,25 +148,27 @@ targets: # Die Live Activity für Downloads. Eine eigene Erweiterung, weil iOS die # Darstellung im Sperrbildschirm in einem getrennten Prozess zeichnet — die # App selbst läuft zu dem Zeitpunkt meist gar nicht. - # Dieselben Quellen, eigenes Ziel. + # Dieselben Quellen, dasselbe Aussehen. # - # Ein gemeinsames Target für beide Plattformen wäre weniger Text, aber die - # Info.plist ist voller iOS-Angaben — Startbildschirm, erlaubte - # Ausrichtungen, Live Activities. Die müssten dann alle bedingt werden, und - # jede spätere Änderung an der iOS-Seite ginge über eine Stelle, die auch - # den Mac betrifft. Zwei Ziele sind ein paar Zeilen mehr und ein Sorge - # weniger. + # Mac Catalyst statt einer eigenen Mac-App: die iPad-Oberfläche läuft damit + # unverändert auf dem Mac — gleiche Schriftgrößen, gleiche Seitenleiste, + # gleiche Abstände. Eine native Mac-App war der erste Versuch; sie sah an + # zwei Dutzend Stellen anders aus, weil AppKit dieselben Dinge anders misst, + # und jede davon hätte einzeln nachgezogen werden müssen. # - # Ohne DownloadWidget (Live Activities gibt es auf dem Mac nicht) und ohne - # FlixyVLC: das Binärpaket ist auf iOS festgelegt. Der Player fällt dort - # allein auf den Systemplayer zurück — was er nicht öffnen kann, sagt er. + # Ohne DownloadWidget (Live Activities gibt es hier nicht) und **ohne + # FlixyVLC**: dessen Binärpaket hat keinen Teil für Catalyst. MKV und DTS + # werden dort deshalb vom Server umgerechnet statt unverändert übertragen — + # siehe `PlaybackSettings.hasFallbackEngine`. FlixyMac: type: application - platform: macOS + platform: iOS sources: - path: Flixy excludes: - "AppIcon.icon" + - path: Flixy/AppIcon.icon + type: file dependencies: - package: FlixyKit product: MediaKit @@ -182,25 +184,42 @@ targets: product: FlixyDownloads settings: base: + SUPPORTS_MACCATALYST: YES + # Nur Mac: dieses Ziel ist nicht dazu da, aufs iPhone zu wandern. + SUPPORTED_PLATFORMS: macosx + # „An das iPad angeglichen" statt „für den Mac optimiert" — genau + # darum geht es. + MACCATALYST_OPTIMIZE_FOR_MAC: NO + DERIVE_MACCATALYST_PRODUCT_BUNDLE_IDENTIFIER: NO PRODUCT_BUNDLE_IDENTIFIER: com.scarriffleservices.flixy PRODUCT_NAME: Flixy - # Selbst signiert, ohne Entwicklerkonto: ein Build zum Ansehen soll - # nicht jedes Mal nach dem Schlüsselbund-Passwort fragen. + ASSETCATALOG_COMPILER_APPICON_NAME: AppIcon + TARGETED_DEVICE_FAMILY: "2" + # Selbst signiert: ein Build zum Ansehen soll nicht jedes Mal nach dem + # Schlüsselbund-Passwort fragen. CODE_SIGN_STYLE: Manual CODE_SIGN_IDENTITY: "-" DEVELOPMENT_TEAM: "" ENABLE_HARDENED_RUNTIME: NO - COMBINE_HIDPI_IMAGES: YES info: path: Flixy/Info-macOS.plist properties: CFBundleDisplayName: Flixy + UILaunchScreen: + UIColorName: LaunchBackground + UIUserInterfaceStyle: Dark + UISupportedInterfaceOrientations: + - UIInterfaceOrientationPortrait + - UIInterfaceOrientationLandscapeLeft + - UIInterfaceOrientationLandscapeRight + UIBackgroundModes: + - audio LSApplicationCategoryType: public.app-category.entertainment - NSHumanReadableCopyright: "" CFBundleURLTypes: - CFBundleURLName: com.scarriffleservices.flixy.auth CFBundleURLSchemes: - com.scarriffleservices.flixy + ITSAppUsesNonExemptEncryption: false NSAppTransportSecurity: NSAllowsLocalNetworking: true