Remove redundant center transport controls on tvOS

Siri Remote already handles play/pause and seeking natively, so the
on-screen skip/play/pause cluster was duplicate UI. Initial and
restored focus now targets the progress bar.
This commit is contained in:
Arkadiusz Fal
2026-04-14 17:41:58 +02:00
parent c7942ef555
commit 0d5a733b0b
2 changed files with 2 additions and 97 deletions

View File

@@ -29,8 +29,6 @@ struct TVPlayerControlsView: View {
} }
@State private var playNextTapCount = 0 @State private var playNextTapCount = 0
@State private var seekBackwardTrigger = 0
@State private var seekForwardTrigger = 0
var body: some View { var body: some View {
ZStack { ZStack {
@@ -45,14 +43,6 @@ struct TVPlayerControlsView: View {
Spacer() Spacer()
// Center transport controls - focus section for horizontal nav
transportControls
.focusSection()
// DEBUG: Uncomment to see focus section boundaries
// .border(.blue, width: 2)
Spacer()
// Progress bar - its own focus section // Progress bar - its own focus section
TVPlayerProgressBar( TVPlayerProgressBar(
currentTime: playerState?.currentTime ?? 0, currentTime: playerState?.currentTime ?? 0,
@@ -142,73 +132,6 @@ struct TVPlayerControlsView: View {
} }
} }
// MARK: - Transport Controls
private var transportControls: some View {
HStack(spacing: 80) {
// Skip backward
Button {
seekBackwardTrigger += 1
playerService?.seekBackward(by: 10)
} label: {
Image(systemName: "10.arrow.trianglehead.counterclockwise")
.font(.system(size: 52, weight: .medium))
.symbolEffect(.rotate.byLayer, options: .speed(2).nonRepeating, value: seekBackwardTrigger)
}
.buttonStyle(TVTransportButtonStyle())
.focused($focusedControl, equals: .skipBackward)
.disabled(isTransportDisabled)
// Play/Pause - hide when transport disabled, show spacer to maintain layout
if !isTransportDisabled {
Button {
playerService?.togglePlayPause()
} label: {
Image(systemName: playPauseIcon)
.font(.system(size: 72, weight: .medium))
.contentTransition(.symbolEffect(.replace, options: .speed(2)))
}
.buttonStyle(TVTransportButtonStyle())
.focused($focusedControl, equals: .playPause)
} else {
// Invisible spacer maintains layout stability
Color.clear
.frame(width: 72, height: 72)
.allowsHitTesting(false)
}
// Skip forward
Button {
seekForwardTrigger += 1
playerService?.seekForward(by: 10)
} label: {
Image(systemName: "10.arrow.trianglehead.clockwise")
.font(.system(size: 52, weight: .medium))
.symbolEffect(.rotate.byLayer, options: .speed(2).nonRepeating, value: seekForwardTrigger)
}
.buttonStyle(TVTransportButtonStyle())
.focused($focusedControl, equals: .skipForward)
.disabled(isTransportDisabled)
}
}
/// Whether transport controls should be disabled (during loading/buffering or buffer not ready)
private var isTransportDisabled: Bool {
playerState?.playbackState == .loading ||
playerState?.playbackState == .buffering ||
!(playerState?.isFirstFrameReady ?? false) ||
!(playerState?.isBufferReady ?? false)
}
private var playPauseIcon: String {
switch playerState?.playbackState {
case .playing:
return "pause.fill"
default:
return "play.fill"
}
}
// MARK: - Action Buttons // MARK: - Action Buttons
private var actionButtons: some View { private var actionButtons: some View {
@@ -323,21 +246,6 @@ struct TVPlayerControlsView: View {
// MARK: - Button Styles // MARK: - Button Styles
/// Button style for transport controls (play/pause, skip).
struct TVTransportButtonStyle: ButtonStyle {
@Environment(\.isFocused) private var isFocused
func makeBody(configuration: Configuration) -> some View {
configuration.label
.foregroundStyle(.white)
.opacity(configuration.isPressed ? 0.6 : 1.0)
.scaleEffect(configuration.isPressed ? 0.9 : (isFocused ? 1.15 : 1.0))
.shadow(color: isFocused ? .white.opacity(0.5) : .clear, radius: 20)
.animation(.easeInOut(duration: 0.15), value: isFocused)
.animation(.easeInOut(duration: 0.1), value: configuration.isPressed)
}
}
/// Button style for action buttons (quality, captions, info). /// Button style for action buttons (quality, captions, info).
struct TVActionButtonStyle: ButtonStyle { struct TVActionButtonStyle: ButtonStyle {
@Environment(\.isFocused) private var isFocused @Environment(\.isFocused) private var isFocused

View File

@@ -11,9 +11,6 @@ import SwiftUI
/// Focus targets for tvOS player controls navigation. /// Focus targets for tvOS player controls navigation.
enum TVPlayerFocusTarget: Hashable { enum TVPlayerFocusTarget: Hashable {
case background // For capturing events when controls hidden case background // For capturing events when controls hidden
case skipBackward
case playPause
case skipForward
case progressBar case progressBar
case settingsButton case settingsButton
case infoButton case infoButton
@@ -225,7 +222,7 @@ struct TVPlayerView: View {
} }
.onAppear { .onAppear {
startControlsTimer() startControlsTimer()
focusedControl = .playPause focusedControl = .progressBar
} }
.onDisappear { .onDisappear {
stopControlsTimer() stopControlsTimer()
@@ -354,7 +351,7 @@ struct TVPlayerView: View {
controlsVisible = true controlsVisible = true
} }
if focusedControl == .background || focusedControl == nil { if focusedControl == .background || focusedControl == nil {
focusedControl = .playPause focusedControl = .progressBar
} }
startControlsTimer() startControlsTimer()
} }