Der Klick in den schwarzen Rand trifft jetzt den schwarzen Rand
Er hat weiterhin angehalten statt ein- und auszublenden, und der Grund steckte in der Rechnung: der schwarze Rand wurde aus der Fenstergrösse ausgerechnet, das Bild liegt aber in einer Fläche mit `ignoresSafeArea()`. Auf dem Mac ist der Einzug der Fensterleiste genau der Unterschied zwischen beiden — die Rechnung lag um diesen Betrag daneben. Jetzt wird die Bildfläche gemessen statt angenommen, und beide Zahlen stehen in einem gemeinsamen, benannten Koordinatenraum. Vor allem aber wird zuerst `AVPlayerLayer.videoRect` gefragt: der Player weiss selbst, wo Bild steht. Die eigene Rechnung bleibt für den Moment vor dem ersten Bild und für VLC, das keine solche Ebene hat. Dazu zwei Worte: „Nachschub" heisst jetzt „Nachladen", und „Blatt" heisst wieder „Sheet" — das ist ein Fachbegriff und bleibt englisch. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
f47c6385e3
commit
18958aadbc
@@ -45,8 +45,19 @@ struct PlayerView: View {
|
||||
/// Wie gross die Player-Fläche gerade ist — für die Frage, wo das Bild
|
||||
/// aufhört und das Schwarze anfängt.
|
||||
@State private var playerSize: CGSize = .zero
|
||||
/// Wo die Bildfläche im Player liegt — gemessen, nicht angenommen.
|
||||
///
|
||||
/// **Der Unterschied zur Fenstergrösse.** Die Bildfläche hat
|
||||
/// `ignoresSafeArea()`, der Player selbst nicht. Auf dem Mac liegt oben
|
||||
/// die Fensterleiste als Einzug: die beiden Flächen sind gegeneinander
|
||||
/// verschoben. Wer den schwarzen Rand aus der Fenstergrösse ausrechnet,
|
||||
/// rechnet ihn um genau diesen Einzug falsch — und trifft daneben.
|
||||
@State private var videoFrame: CGRect = .zero
|
||||
/// Ob Zeiger und Tastatur gelten. Einmal beim Start ermittelt.
|
||||
private var isMac: Bool { FlixyPlatform.isMac }
|
||||
|
||||
/// Der gemeinsame Raum für Messen und Tippen.
|
||||
private static let playerSpace = "flixy.player"
|
||||
/// Wann der Zeiger zuletzt die Bedienelemente geholt hat.
|
||||
@State private var lastPointerReveal: Date = .distantPast
|
||||
|
||||
@@ -64,6 +75,9 @@ struct PlayerView: View {
|
||||
isActive: playback.presentation == .full
|
||||
)
|
||||
.ignoresSafeArea()
|
||||
.onGeometryChange(for: CGRect.self) {
|
||||
$0.frame(in: .named(Self.playerSpace))
|
||||
} action: { videoFrame = $0 }
|
||||
}
|
||||
|
||||
// **Unter** den Bedienelementen, nicht darüber. Als Überzug
|
||||
@@ -162,7 +176,11 @@ struct PlayerView: View {
|
||||
// Für alles außerhalb der Sprungflächen. Mit Ort, weil es einen
|
||||
// Unterschied macht, **wo** geklickt wurde — siehe `tap(at:)`.
|
||||
.onGeometryChange(for: CGSize.self) { $0.size } action: { playerSize = $0 }
|
||||
.onTapGesture(coordinateSpace: .local) { tap(at: $0) }
|
||||
// **Ein benannter Raum, kein `.local`.** Gemessen wird die Bildfläche,
|
||||
// getippt wird auf den Player — zwei verschiedene Ansichten. Nur in
|
||||
// einem gemeinsamen Raum sind die beiden Zahlen vergleichbar.
|
||||
.coordinateSpace(.named(Self.playerSpace))
|
||||
.onTapGesture(coordinateSpace: .named(Self.playerSpace)) { tap(at: $0) }
|
||||
.onReceive(
|
||||
NotificationCenter.default.publisher(for: UIApplication.didBecomeActiveNotification)
|
||||
) { _ in
|
||||
@@ -196,7 +214,7 @@ struct PlayerView: View {
|
||||
}
|
||||
// **Die Umgebung wird den Blättern mitgegeben, nicht vererbt.**
|
||||
//
|
||||
// Ein Blatt hängt an einem eigenen Darstellungsbaum. Auf dem iPad kam
|
||||
// Ein Sheet hängt an einem eigenen Darstellungsbaum. Auf dem iPad kam
|
||||
// die Umgebung des Players dort bisher an, auf dem Mac nicht — und
|
||||
// `@Environment(PlaybackSettings.self)` ist kein Wert mit
|
||||
// Ersatzangabe, sondern eine Behauptung: fehlt der Gegenstand, hält
|
||||
@@ -288,7 +306,7 @@ struct PlayerView: View {
|
||||
|
||||
// Getrennte Knöpfe, weil es zwei verschiedene Fragen sind: die
|
||||
// Tonspur stellt man einmal am Anfang um, die Untertitel mitten im
|
||||
// Film. Ein gemeinsames Blatt bedeutete jedes Mal Scrollen an der
|
||||
// Film. Ein gemeinsames Sheet bedeutete jedes Mal Scrollen an der
|
||||
// anderen Liste vorbei.
|
||||
if !model.audioTracks.isEmpty {
|
||||
trackButton(
|
||||
@@ -679,41 +697,58 @@ struct PlayerView: View {
|
||||
|
||||
/// Wo das Bild tatsächlich liegt.
|
||||
///
|
||||
/// Der Player füllt das ganze Fenster, das Bild aber nur den Teil, der
|
||||
/// zum Seitenverhältnis passt — der Rest ist schwarz. Die Maße kommen
|
||||
/// aus `presentationSize`, also von dem, was wirklich ankommt, nicht aus
|
||||
/// den Angaben der Datei.
|
||||
/// **Zuerst die Ebene fragen.** `AVPlayerLayer.videoRect` ist die Fläche,
|
||||
/// auf der wirklich Bild steht — vom Player selbst ausgerechnet, aus dem,
|
||||
/// was gerade ankommt, und einschliesslich dessen, was die Füllart damit
|
||||
/// macht. Jede eigene Rechnung ist daneben eine Nachbildung, die
|
||||
/// irgendwann abweicht.
|
||||
///
|
||||
/// Ohne Maße die ganze Fläche: dann verhält es sich wie bisher, statt
|
||||
/// **Dann die eigene Rechnung.** Solange die Ebene noch nichts zeigt —
|
||||
/// und bei VLC, das keine hat — bleibt das Seitenverhältnis aus den
|
||||
/// Messwerten. Es wird auf `videoFrame` angewandt, nicht auf die
|
||||
/// Fenstergrösse: die beiden unterscheiden sich um den Einzug der
|
||||
/// Fensterleiste, und **das** war der Fehler, wegen dem ein Klick in den
|
||||
/// schwarzen Rand bisher angehalten hat statt einzublenden.
|
||||
///
|
||||
/// Ohne beides die ganze Fläche: dann verhält es sich wie früher, statt
|
||||
/// aus Unkenntnis überall das Falsche zu tun.
|
||||
private var videoRect: CGRect {
|
||||
let ganzes = CGRect(origin: .zero, size: playerSize)
|
||||
let fläche = videoFrame.isEmpty ? ganzes : videoFrame
|
||||
guard fläche.width > 0, fläche.height > 0 else { return ganzes }
|
||||
|
||||
// Die Ebene liegt in der Bildfläche; ihre Angabe ist relativ zu deren
|
||||
// Ecke und muss dorthin verschoben werden.
|
||||
let ausDerEbene = model.surface.videoRect
|
||||
if ausDerEbene.width > 1, ausDerEbene.height > 1 {
|
||||
return ausDerEbene.offsetBy(dx: fläche.minX, dy: fläche.minY)
|
||||
}
|
||||
|
||||
guard let breite = model.statistics?.width,
|
||||
let höhe = model.statistics?.height,
|
||||
breite > 0, höhe > 0,
|
||||
playerSize.width > 0, playerSize.height > 0
|
||||
breite > 0, höhe > 0
|
||||
else { return ganzes }
|
||||
|
||||
let verhältnisBild = CGFloat(breite) / CGFloat(höhe)
|
||||
let verhältnisFläche = playerSize.width / playerSize.height
|
||||
let verhältnisFläche = fläche.width / fläche.height
|
||||
|
||||
if verhältnisBild > verhältnisFläche {
|
||||
// Breiter als das Fenster: Balken oben und unten.
|
||||
let sichtbar = playerSize.width / verhältnisBild
|
||||
// Breiter als die Fläche: Balken oben und unten.
|
||||
let sichtbar = fläche.width / verhältnisBild
|
||||
return CGRect(
|
||||
x: 0,
|
||||
y: (playerSize.height - sichtbar) / 2,
|
||||
width: playerSize.width,
|
||||
x: fläche.minX,
|
||||
y: fläche.minY + (fläche.height - sichtbar) / 2,
|
||||
width: fläche.width,
|
||||
height: sichtbar
|
||||
)
|
||||
}
|
||||
// Höher als das Fenster: Balken links und rechts.
|
||||
let sichtbar = playerSize.height * verhältnisBild
|
||||
// Höher als die Fläche: Balken links und rechts.
|
||||
let sichtbar = fläche.height * verhältnisBild
|
||||
return CGRect(
|
||||
x: (playerSize.width - sichtbar) / 2,
|
||||
y: 0,
|
||||
x: fläche.minX + (fläche.width - sichtbar) / 2,
|
||||
y: fläche.minY,
|
||||
width: sichtbar,
|
||||
height: playerSize.height
|
||||
height: fläche.height
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import FlixyPlayback
|
||||
|
||||
/// Die Qualitätswahl im Player, samt dem, was gerade tatsächlich läuft.
|
||||
///
|
||||
/// Ein Blatt statt eines Dialogs: die Leiter hat je nach Quelle bis zu
|
||||
/// Ein Sheet statt eines Dialogs: die Leiter hat je nach Quelle bis zu
|
||||
/// dreizehn Stufen, und `confirmationDialog` zeigt nur eine Liste von Knöpfen
|
||||
/// ohne Ordnung. Hier stehen sie nach Auflösung gruppiert — und ganz oben,
|
||||
/// was gerade über die Leitung kommt.
|
||||
@@ -15,7 +15,7 @@ struct QualitySheet: View {
|
||||
/// **Als Fragezeichen**, nicht als Behauptung.
|
||||
///
|
||||
/// `@Environment(PlaybackSettings.self)` ohne Optional hält die Anwendung
|
||||
/// an, wenn der Gegenstand nicht im Baum steht — und ein Blatt hängt an
|
||||
/// an, wenn der Gegenstand nicht im Baum steht — und ein Sheet hängt an
|
||||
/// einem eigenen Baum. Genau daran ist der Player eingefroren, und zu
|
||||
/// sehen war nichts davon. Der Player reicht die Einstellungen jetzt
|
||||
/// ausdrücklich herein; das Fragezeichen sorgt dafür, dass ein Vergessen
|
||||
|
||||
@@ -5,7 +5,7 @@ import FlixyPlayback
|
||||
/// Auswahl einer Spur — Ton **oder** Untertitel, nicht beides.
|
||||
///
|
||||
/// Getrennt, weil es zwei verschiedene Fragen sind: die Tonspur wechselt man
|
||||
/// einmal am Anfang, die Untertitel mitten im Film. Beide in einem Blatt
|
||||
/// einmal am Anfang, die Untertitel mitten im Film. Beide in einem Sheet
|
||||
/// bedeutete jedes Mal Scrollen an der anderen Liste vorbei.
|
||||
struct TrackPickerSheet: View {
|
||||
enum Kind: Identifiable {
|
||||
|
||||
@@ -80,6 +80,12 @@ struct VideoSurface: View {
|
||||
final class PlayerLayerView: UIView {
|
||||
override static var layerClass: AnyClass { AVPlayerLayer.self }
|
||||
var playerLayer: AVPlayerLayer { layer as! AVPlayerLayer }
|
||||
|
||||
/// Die Fläche, auf der wirklich Bild steht — ohne die schwarzen Ränder.
|
||||
///
|
||||
/// Vom Player ausgerechnet, aus dem, was gerade ankommt. Vor dem ersten
|
||||
/// Bild ist sie leer; wer sie benutzt, muss das prüfen.
|
||||
var videoRect: CGRect { playerLayer.videoRect }
|
||||
}
|
||||
#else
|
||||
/// Auf dem Mac gibt es kein `layerClass`: eine `NSView` bekommt ihre Ebene
|
||||
@@ -87,6 +93,9 @@ final class PlayerLayerView: UIView {
|
||||
final class PlayerLayerView: NSView {
|
||||
let playerLayer = AVPlayerLayer()
|
||||
|
||||
/// Siehe die UIKit-Fassung oben.
|
||||
var videoRect: CGRect { playerLayer.videoRect }
|
||||
|
||||
override init(frame: NSRect) {
|
||||
super.init(frame: frame)
|
||||
wantsLayer = true
|
||||
|
||||
Reference in New Issue
Block a user