Zwei Fehler um dieselbe Bildfläche
**Die Wiedergabe sprang von selbst wieder an.** Auf dem Mac pausiert ein Klick ins Bild — und der Klick, mit dem man ein Fenster nach vorn holt, kommt ebenfalls dort an. Man aktiviert das Fenster und drückt dabei auf Play. Klicks innerhalb einer Viertelsekunde nach dem Aktivieren zählen deshalb nicht: länger braucht niemand bis zum ersten gemeinten Klick, kürzer erwischt den Fall nicht. **Schwarzbild mit Ton nach dem Vergrössern.** Kachel und Vollbild bestehen beim Wechsel kurz gleichzeitig, und beide holten sich dieselbe Bildfläche in ihre Hülle. Eine Ansicht hat aber nur einen Vorgesetzten — wer zuletzt aktualisiert, gewinnt, und das war nicht verlässlich die richtige. Blieb die Fläche bei der Kachel, die gleich darauf verschwand, lief im Vollbild der Ton weiter und das Bild blieb schwarz. Die Hülle allein genügte nicht: sie verhindert, dass das Abräumen der einen Ansicht der anderen etwas wegnimmt, entscheidet aber nicht, wer die Fläche bekommen **soll**. Das sagt jetzt `isActive` — abgeleitet aus `playback.presentation`, also aus derselben Quelle, die auch bestimmt, welche Ansicht überhaupt zu sehen ist. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
348ec96282
commit
279d8e36ae
@@ -84,7 +84,11 @@ struct MiniPlayer: View {
|
|||||||
ZStack {
|
ZStack {
|
||||||
Color.black
|
Color.black
|
||||||
|
|
||||||
VideoSurface(engine: model.engine, surface: model.surface)
|
VideoSurface(
|
||||||
|
engine: model.engine,
|
||||||
|
surface: model.surface,
|
||||||
|
isActive: playback.presentation == .mini
|
||||||
|
)
|
||||||
|
|
||||||
if model.isBuffering {
|
if model.isBuffering {
|
||||||
ProgressView().tint(.white)
|
ProgressView().tint(.white)
|
||||||
|
|||||||
@@ -40,6 +40,8 @@ struct PlayerView: View {
|
|||||||
@State private var hintTask: Task<Void, Never>?
|
@State private var hintTask: Task<Void, Never>?
|
||||||
/// Ob die Tastatur bei diesem Bildschirm ankommt. Nur auf dem Mac gesetzt.
|
/// Ob die Tastatur bei diesem Bildschirm ankommt. Nur auf dem Mac gesetzt.
|
||||||
@FocusState private var keyboardFocus: Bool
|
@FocusState private var keyboardFocus: Bool
|
||||||
|
/// Wann die App zuletzt nach vorn geholt wurde.
|
||||||
|
@State private var becameActive: ContinuousClock.Instant?
|
||||||
/// Ob Zeiger und Tastatur gelten. Einmal beim Start ermittelt.
|
/// Ob Zeiger und Tastatur gelten. Einmal beim Start ermittelt.
|
||||||
private var isMac: Bool { FlixyPlatform.isMac }
|
private var isMac: Bool { FlixyPlatform.isMac }
|
||||||
/// Wann der Zeiger zuletzt die Bedienelemente geholt hat.
|
/// Wann der Zeiger zuletzt die Bedienelemente geholt hat.
|
||||||
@@ -53,7 +55,11 @@ struct PlayerView: View {
|
|||||||
// Bei „nur Ton" bleibt der Schirm dunkel — das ist der halbe
|
// Bei „nur Ton" bleibt der Schirm dunkel — das ist der halbe
|
||||||
// Zweck der Stufe.
|
// Zweck der Stufe.
|
||||||
if model.showsVideo {
|
if model.showsVideo {
|
||||||
VideoSurface(engine: model.engine, surface: model.surface)
|
VideoSurface(
|
||||||
|
engine: model.engine,
|
||||||
|
surface: model.surface,
|
||||||
|
isActive: playback.presentation == .full
|
||||||
|
)
|
||||||
.ignoresSafeArea()
|
.ignoresSafeArea()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -152,6 +158,11 @@ struct PlayerView: View {
|
|||||||
.contentShape(.rect)
|
.contentShape(.rect)
|
||||||
// Für alles außerhalb der Sprungflächen.
|
// Für alles außerhalb der Sprungflächen.
|
||||||
.onTapGesture { primaryTap() }
|
.onTapGesture { primaryTap() }
|
||||||
|
.onReceive(
|
||||||
|
NotificationCenter.default.publisher(for: UIApplication.didBecomeActiveNotification)
|
||||||
|
) { _ in
|
||||||
|
becameActive = .now
|
||||||
|
}
|
||||||
// **Tastatur und Zeiger — nur auf dem Mac.**
|
// **Tastatur und Zeiger — nur auf dem Mac.**
|
||||||
//
|
//
|
||||||
// Dort ist beides selbstverständlich: Leertaste hält an, die Pfeile
|
// Dort ist beides selbstverständlich: Leertaste hält an, die Pfeile
|
||||||
@@ -648,6 +659,22 @@ struct PlayerView: View {
|
|||||||
toggleControls()
|
toggleControls()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// **Der Klick, der das Fenster nach vorn holt, zählt nicht.**
|
||||||
|
//
|
||||||
|
// Auf dem Mac pausiert ein Klick ins Bild. Wer aus einem anderen
|
||||||
|
// Fenster zurückkommt, klickt aber zuerst einmal, um dieses Fenster
|
||||||
|
// zu aktivieren — und genau dieser Klick kam ebenfalls hier an. Die
|
||||||
|
// Wiedergabe sprang dadurch von selbst wieder an, als hätte man auf
|
||||||
|
// Play gedrückt. Hat man ja auch, nur nicht absichtlich.
|
||||||
|
//
|
||||||
|
// Eine Viertelsekunde reicht: länger braucht niemand vom Aktivieren
|
||||||
|
// bis zum ersten gemeinten Klick, und kürzer erwischt den Fall nicht.
|
||||||
|
if let becameActive, ContinuousClock.now - becameActive < .milliseconds(250) {
|
||||||
|
self.becameActive = nil
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
model.togglePlayPause()
|
model.togglePlayPause()
|
||||||
revealControls()
|
revealControls()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,18 +38,32 @@ struct VideoSurface: View {
|
|||||||
/// hier ist es jetzt derselbe Weg.
|
/// hier ist es jetzt derselbe Weg.
|
||||||
let surface: PlayerLayerView
|
let surface: PlayerLayerView
|
||||||
|
|
||||||
|
/// Ob **diese** Ansicht die Bildfläche gerade haben soll.
|
||||||
|
///
|
||||||
|
/// **Der Kern des Schwarzbilds.** Kachel und Vollbild bestehen beim
|
||||||
|
/// Wechsel kurz gleichzeitig, und beide holen sich die Fläche in ihre
|
||||||
|
/// Hülle. Eine Ansicht hat aber nur einen Vorgesetzten — wer zuletzt
|
||||||
|
/// aktualisiert, gewinnt, und das war nicht verlässlich die richtige.
|
||||||
|
/// Blieb die Fläche bei der Kachel, die gleich darauf verschwand, lief
|
||||||
|
/// im Vollbild der Ton weiter und das Bild blieb schwarz.
|
||||||
|
///
|
||||||
|
/// Die Hülle allein genügte dagegen nicht: sie verhindert, dass das
|
||||||
|
/// Abräumen der einen der anderen etwas wegnimmt, entscheidet aber
|
||||||
|
/// nicht, wer sie bekommen **soll**. Das steht hier.
|
||||||
|
var isActive = true
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
#if canImport(FlixyVLC)
|
#if canImport(FlixyVLC)
|
||||||
if let vlcEngine = engine as? VLCPlayerEngine {
|
if let vlcEngine = engine as? VLCPlayerEngine {
|
||||||
SurfaceContainer(view: vlcEngine.videoView)
|
SurfaceContainer(view: vlcEngine.videoView, isActive: isActive)
|
||||||
} else if engine is AVPlayerEngine {
|
} else if engine is AVPlayerEngine {
|
||||||
SurfaceContainer(view: surface)
|
SurfaceContainer(view: surface, isActive: isActive)
|
||||||
} else {
|
} else {
|
||||||
Color.black
|
Color.black
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
if engine is AVPlayerEngine {
|
if engine is AVPlayerEngine {
|
||||||
SurfaceContainer(view: surface)
|
SurfaceContainer(view: surface, isActive: isActive)
|
||||||
} else {
|
} else {
|
||||||
Color.black
|
Color.black
|
||||||
}
|
}
|
||||||
@@ -106,33 +120,38 @@ final class PlayerLayerView: NSView {
|
|||||||
#if canImport(UIKit)
|
#if canImport(UIKit)
|
||||||
private struct SurfaceContainer: UIViewRepresentable {
|
private struct SurfaceContainer: UIViewRepresentable {
|
||||||
let view: PlatformView
|
let view: PlatformView
|
||||||
|
let isActive: Bool
|
||||||
|
|
||||||
func makeUIView(context: Context) -> PlatformView {
|
func makeUIView(context: Context) -> PlatformView {
|
||||||
let host = UIView()
|
let host = UIView()
|
||||||
host.backgroundColor = .black
|
host.backgroundColor = .black
|
||||||
adopt(view, into: host)
|
if isActive { adopt(view, into: host) }
|
||||||
return host
|
return host
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateUIView(_ host: PlatformView, context: Context) {
|
func updateUIView(_ host: PlatformView, context: Context) {
|
||||||
guard view.superview !== host else { return }
|
// Wer nicht dran ist, greift auch nicht zu. Ohne diese Zeile holen
|
||||||
|
// sich beide Ansichten dieselbe Fläche, und die Reihenfolge
|
||||||
|
// entscheidet — mal richtig, mal schwarz.
|
||||||
|
guard isActive, view.superview !== host else { return }
|
||||||
adopt(view, into: host)
|
adopt(view, into: host)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
private struct SurfaceContainer: NSViewRepresentable {
|
private struct SurfaceContainer: NSViewRepresentable {
|
||||||
let view: PlatformView
|
let view: PlatformView
|
||||||
|
let isActive: Bool
|
||||||
|
|
||||||
func makeNSView(context: Context) -> PlatformView {
|
func makeNSView(context: Context) -> PlatformView {
|
||||||
let host = NSView()
|
let host = NSView()
|
||||||
host.wantsLayer = true
|
host.wantsLayer = true
|
||||||
host.layer?.backgroundColor = NSColor.black.cgColor
|
host.layer?.backgroundColor = NSColor.black.cgColor
|
||||||
adopt(view, into: host)
|
if isActive { adopt(view, into: host) }
|
||||||
return host
|
return host
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateNSView(_ host: PlatformView, context: Context) {
|
func updateNSView(_ host: PlatformView, context: Context) {
|
||||||
guard view.superview !== host else { return }
|
guard isActive, view.superview !== host else { return }
|
||||||
adopt(view, into: host)
|
adopt(view, into: host)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user