Show macOS player controls immediately while video is loading

The controls overlay was gated on the player backend existing, but the
backend is created only after the video details fetch completes, so the
loading phase showed just the thumbnail and spinner with no controls.

Drop the backend gate (MPV is the only backend type) and resolve the
PiP backend dynamically in the toggle callback. Also make ended/failed
states hide controls directly in shouldShowControls instead of relying
on catching the state transition, and reset the manual visibility
override when a new load starts so controls reappear for the next video.
This commit is contained in:
Arkadiusz Fal
2026-06-26 23:30:30 +02:00
parent 04daf94057
commit 4ccbe153a7
2 changed files with 27 additions and 25 deletions

View File

@@ -796,7 +796,7 @@ extension ExpandedPlayerSheet {
closeVideo()
},
onTogglePiP: {
if let mpvBackend = backend as? MPVBackend {
if let mpvBackend = playerService.currentBackend as? MPVBackend {
mpvBackend.togglePiP()
}
},
@@ -873,9 +873,9 @@ extension ExpandedPlayerSheet {
.offset(y: controlsVerticalOffset)
}
#elseif os(macOS)
if let backend = playerService?.currentBackend,
backend.backendType == .mpv,
let playerState,
// No backend gate: the backend is created only after the video
// details fetch, and controls should be visible while loading.
if let playerState,
let playerService,
playerState.pipState != .active && !playerState.showDebugOverlay {
MacOSPlayerControlsView(
@@ -897,7 +897,7 @@ extension ExpandedPlayerSheet {
closeVideo()
},
onTogglePiP: {
if let mpvBackend = backend as? MPVBackend {
if let mpvBackend = playerService.currentBackend as? MPVBackend {
mpvBackend.togglePiP()
}
},
@@ -1142,9 +1142,9 @@ extension ExpandedPlayerSheet {
.frame(width: controlsWidth, height: controlsHeight)
}
#elseif os(macOS)
if let backend = playerService?.currentBackend,
backend.backendType == .mpv,
let playerState,
// No backend gate: the backend is created only after the video
// details fetch, and controls should be visible while loading.
if let playerState,
let playerService,
playerState.pipState != .active && !playerState.showDebugOverlay {
MacOSPlayerControlsView(
@@ -1166,7 +1166,7 @@ extension ExpandedPlayerSheet {
closeVideo()
},
onTogglePiP: {
if let mpvBackend = backend as? MPVBackend {
if let mpvBackend = playerService.currentBackend as? MPVBackend {
mpvBackend.togglePiP()
}
},
@@ -1447,7 +1447,7 @@ extension ExpandedPlayerSheet {
closeVideo()
},
onTogglePiP: {
if let mpvBackend = backend as? MPVBackend {
if let mpvBackend = playerService.currentBackend as? MPVBackend {
mpvBackend.togglePiP()
}
},
@@ -1551,9 +1551,9 @@ extension ExpandedPlayerSheet {
}
}
#elseif os(macOS)
if let backend = playerService?.currentBackend,
backend.backendType == .mpv,
let playerState,
// No backend gate: the backend is created only after the video
// details fetch, and controls should be visible while loading.
if let playerState,
let playerService,
playerState.pipState != .active && !playerState.showDebugOverlay {
MacOSPlayerControlsView(
@@ -1585,7 +1585,7 @@ extension ExpandedPlayerSheet {
closeVideo()
},
onTogglePiP: {
if let mpvBackend = backend as? MPVBackend {
if let mpvBackend = playerService.currentBackend as? MPVBackend {
mpvBackend.togglePiP()
}
},

View File

@@ -89,6 +89,11 @@ struct MacOSPlayerControlsView: View {
/// Controls visibility - show when hovering, interacting, or paused
private var shouldShowControls: Bool {
// Ended/failed overlays take over the surface; controls stay hidden
// regardless of hover or manual toggles.
if playerState.playbackState == .ended || playerState.isFailed {
return false
}
if let showControls {
return showControls
}
@@ -96,11 +101,9 @@ struct MacOSPlayerControlsView: View {
if isHovering || isInteracting {
return true
}
// Show when paused, loading, or failed
// Show when paused or loading
return playerState.playbackState == .paused ||
playerState.playbackState == .loading ||
playerState.playbackState == .ended ||
playerState.isFailed
playerState.playbackState == .loading
}
/// Yattee Server URL used by `ChannelAvatarView` for avatar fallback.
@@ -295,14 +298,13 @@ struct MacOSPlayerControlsView: View {
if newState == .playing && shouldShowControls {
startHideTimer()
}
// Hide controls when video ends (ended overlay takes over)
if newState == .ended {
showControls = false
cancelHideTimer()
// A new load resets any manual show/hide override (e.g. hidden
// after ended/failed) so controls are visible while loading.
if newState == .loading {
showControls = nil
}
// Hide controls when video fails
if case .failed = newState {
showControls = false
// Ended/failed hide via shouldShowControls; just stop the timer.
if newState == .ended || playerState.isFailed {
cancelHideTimer()
}
}