Kein Video lief jemals an AVPlayerEngine.load() wartete auf asset.load(.duration) und die Spurenliste, BEVOR es zurückkehrte — und erst danach rief das Modell play(). AVFoundation muss für die Dauer aber so viel von der Datei holen, bis es den Index findet, und der sitzt bei MP4 wie MKV oft am Ende. Über eine langsame Leitung sind das Minuten, in denen nur ein Ladekreis zu sehen war, obwohl der Player längst hätte anfangen können. Jetzt kehrt load() zurück, sobald das Item hängt; Dauer und Spuren kommen nach und melden sich über den Ereignisstrom. Dazu AVURLAssetPreferPreciseDurationAndTimingKey: false — sonst durchsucht AVFoundation bei manchen Formaten die ganze Datei. Zweiter Fund an derselben Stelle: VLCMedia(url:) ist nullable. Ging es schief, blieb media still nil, play() tat nichts und es gab keinen Fehler — nur einen Ladekreis ohne Ende. Wird jetzt gemeldet. Kein Querformat im Player requestGeometryUpdate allein dreht nicht: das System dreht sofort zurück, solange die erlaubten Ausrichtungen es nicht hergeben. Die Erlaubnis kommt nur über den App-Delegaten, deshalb OrientationLock als Brücke. Player dreht ins Querformat, beim Verlassen zurück. iPad bleibt frei — dort ist Querformat beim Blättern der Normalfall. Uhrzeit über dem Film Die Statusleiste war nur ausgeblendet, solange die Bedienelemente weg waren. Jetzt immer. Sehr langer Start Zwei Ursachen. Erstens legte der Initialisierer der App die Hintergrund-URLSession an — die spricht über XPC mit nsurlsessiond und verzögerte den allerersten Bildaufbau. Jetzt erst bei Bedarf, und wer keine Downloads hat, braucht sie nie. Zweitens fragte jeder Start erst plex.tv nach der Serverliste und suchte die Verbindung neu. Die zuletzt funktionierende wird jetzt gemerkt (Server-Token im Schlüsselbund) und mit kurzer Zeitgrenze zuerst probiert; die vollständige Suche greift nur noch, wenn sie nicht mehr antwortet — etwa wenn man von zuhause weggeht. Dazu ein Hinweis im Player, wenn die Verbindung über die gedrosselte Plex-Vermittlung läuft. Sonst sucht man den Fehler in der App. 132 Tests. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
71 lines
2.5 KiB
Swift
71 lines
2.5 KiB
Swift
import SwiftUI
|
|
import UIKit
|
|
|
|
/// Bestimmt, in welche Richtungen sich die App drehen darf.
|
|
///
|
|
/// In reinem SwiftUI gibt es dafür keinen Weg: `requestGeometryUpdate` dreht
|
|
/// zwar, aber das System dreht sofort zurück, wenn der Bildschirm es nicht
|
|
/// auch erlaubt. Erlaubt wird es über
|
|
/// `application(_:supportedInterfaceOrientationsFor:)` — und das gibt es nur
|
|
/// am klassischen App-Delegaten. Diese Klasse ist die Brücke dazwischen.
|
|
@MainActor
|
|
final class OrientationLock {
|
|
static let shared = OrientationLock()
|
|
|
|
private(set) var mask: UIInterfaceOrientationMask = .portrait
|
|
|
|
private init() {
|
|
// Auf dem iPad bleibt alles erlaubt: dort ist das Querformat beim
|
|
// Blättern der Normalfall, nicht die Ausnahme.
|
|
if UIDevice.current.userInterfaceIdiom == .pad {
|
|
mask = .all
|
|
}
|
|
}
|
|
|
|
/// Dreht ins Querformat und hält es dort.
|
|
func lockLandscape() {
|
|
guard UIDevice.current.userInterfaceIdiom != .pad else { return }
|
|
apply(.landscape, rotatingTo: .landscape)
|
|
}
|
|
|
|
/// Gibt die Ausrichtung wieder frei und dreht zurück ins Hochformat.
|
|
func releaseLandscape() {
|
|
guard UIDevice.current.userInterfaceIdiom != .pad else { return }
|
|
apply(.portrait, rotatingTo: .portrait)
|
|
}
|
|
|
|
private func apply(
|
|
_ newMask: UIInterfaceOrientationMask,
|
|
rotatingTo target: UIInterfaceOrientationMask
|
|
) {
|
|
mask = newMask
|
|
|
|
guard let scene = UIApplication.shared.connectedScenes
|
|
.compactMap({ $0 as? UIWindowScene })
|
|
.first(where: { $0.activationState == .foregroundActive })
|
|
else { return }
|
|
|
|
// Erst die neue Erlaubnis bekanntgeben, dann drehen. In der anderen
|
|
// Reihenfolge lehnt das System die Drehung ab, weil die alte Maske
|
|
// noch gilt — und das sah aus wie „dreht sich nicht richtig".
|
|
scene.keyWindow?.rootViewController?.setNeedsUpdateOfSupportedInterfaceOrientations()
|
|
scene.requestGeometryUpdate(.iOS(interfaceOrientations: target))
|
|
}
|
|
}
|
|
|
|
/// Dreht beim Erscheinen ins Querformat und beim Verschwinden zurück.
|
|
struct LandscapeWhileVisible: ViewModifier {
|
|
func body(content: Content) -> some View {
|
|
content
|
|
.onAppear { OrientationLock.shared.lockLandscape() }
|
|
.onDisappear { OrientationLock.shared.releaseLandscape() }
|
|
}
|
|
}
|
|
|
|
extension View {
|
|
/// Für den Vollbildplayer: Film ist Querformat.
|
|
func landscapeWhileVisible() -> some View {
|
|
modifier(LandscapeWhileVisible())
|
|
}
|
|
}
|