Expose Background Playback toggle on tvOS, default off

Surfaces the existing iOS/macOS Background Playback setting in the tvOS
Playback settings, defaulting to off so audio stops when the user leaves
the app via the TV button. Pauses playback on .background/.inactive when
the toggle is off, regardless of audio route — the user's setting wins
over AirPlay/HomePod handoff. Also auto-shows the tvOS player controls
when returning to foreground so the paused state is immediately visible
and actionable.
This commit is contained in:
Arkadiusz Fal
2026-05-09 14:50:38 +02:00
parent 8a3f76bb1d
commit aabf5313fa
4 changed files with 26 additions and 3 deletions

View File

@@ -40,7 +40,7 @@ extension SettingsManager {
var backgroundPlaybackEnabled: Bool {
get {
if let cached = _backgroundPlaybackEnabled { return cached }
return bool(for: .backgroundPlayback, default: true)
return bool(for: .backgroundPlayback, default: backgroundPlaybackDefault)
}
set {
_backgroundPlaybackEnabled = newValue
@@ -48,6 +48,14 @@ extension SettingsManager {
}
}
private var backgroundPlaybackDefault: Bool {
#if os(tvOS)
return false
#else
return true
#endif
}
/// tvOS only: when enabled, the Siri remote Menu button closes the video
/// (clears queue, stops playback) instead of only collapsing the player.
/// When enabled, the explicit top-bar close button is hidden.

View File

@@ -650,6 +650,15 @@ final class PlayerService {
#else
let isPiPActive = false
#endif
#if os(tvOS)
LoggingService.shared.debug("PlayerService[tvOS-bg]: phase=\(phase) bgEnabled=\(backgroundEnabled) playbackState=\(state.playbackState)", category: .player)
if (phase == .background || phase == .inactive), !backgroundEnabled, state.playbackState == .playing {
LoggingService.shared.debug("PlayerService[tvOS-bg]: pausing playback (phase=\(phase), backgroundPlaybackEnabled=false)", category: .player)
pause()
}
#endif
currentBackend?.handleScenePhase(phase, backgroundEnabled: backgroundEnabled, isPiPActive: isPiPActive)
}

View File

@@ -31,6 +31,7 @@ enum TVPlayerFocusTarget: Hashable {
struct TVPlayerView: View {
@Environment(\.appEnvironment) private var appEnvironment
@Environment(\.dismiss) private var dismiss
@Environment(\.scenePhase) private var scenePhase
// MARK: - State
@@ -397,6 +398,13 @@ struct TVPlayerView: View {
showControls()
}
}
// When app returns to foreground (e.g. after auto-pause from background),
// surface the controls so the user can immediately resume or navigate.
.onChange(of: scenePhase) { oldPhase, newPhase in
if newPhase == .active && oldPhase != .active {
showControls()
}
}
}
// MARK: - Failure Overlays

View File

@@ -271,12 +271,10 @@ private struct BehaviorSection: View {
}
}
#if os(iOS) || os(macOS)
Toggle(
String(localized: "settings.playback.backgroundPlayback"),
isOn: $settings.backgroundPlaybackEnabled
)
#endif
#if os(tvOS)
Toggle(