Add tvOS setting to close video with Menu button

When enabled, the Siri remote Menu button stops playback and clears the
queue instead of only collapsing the player, and the explicit top-bar
close (X) button is hidden.
This commit is contained in:
Arkadiusz Fal
2026-04-18 00:29:59 +02:00
parent 13ade8aad3
commit 8f00fe012f
7 changed files with 68 additions and 9 deletions

View File

@@ -24,6 +24,7 @@ enum SettingsKey: String, CaseIterable {
case preferredAudioLanguage
case preferredSubtitlesLanguage
case resumeAction
case tvOSMenuButtonClosesVideo
// SponsorBlock
case sponsorBlockEnabled

View File

@@ -48,6 +48,20 @@ extension SettingsManager {
}
}
/// 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.
var tvOSMenuButtonClosesVideo: Bool {
get {
if let cached = _tvOSMenuButtonClosesVideo { return cached }
return bool(for: .tvOSMenuButtonClosesVideo, default: false)
}
set {
_tvOSMenuButtonClosesVideo = newValue
set(newValue, for: .tvOSMenuButtonClosesVideo)
}
}
/// Whether DASH streams are enabled (MPV only).
/// Disabled by default as DASH can be unreliable with some Invidious instances.
var dashEnabled: Bool {

View File

@@ -39,6 +39,7 @@ final class SettingsManager {
var _preferredSubtitlesLanguage: String?
var _playerVolume: Float?
var _resumeAction: ResumeAction?
var _tvOSMenuButtonClosesVideo: Bool?
// SponsorBlock
var _sponsorBlockEnabled: Bool?
@@ -410,6 +411,7 @@ final class SettingsManager {
_preferredSubtitlesLanguage = nil
_playerVolume = nil
_resumeAction = nil
_tvOSMenuButtonClosesVideo = nil
_sponsorBlockEnabled = nil
_sponsorBlockCategories = nil
_sponsorBlockAPIURL = nil

View File

@@ -11845,6 +11845,26 @@
}
}
},
"settings.playback.tvOSMenuButtonClosesVideo" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Menu Button Closes Video"
}
}
}
},
"settings.playback.tvOSMenuButtonClosesVideo.footer" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "When enabled, the Siri remote Menu button stops playback and clears the queue. The close (X) button is hidden from the player. When disabled, Menu collapses the player and playback continues in the background."
}
}
}
},
"settings.playback.behavior.header" : {
"localizations" : {
"en" : {

View File

@@ -170,6 +170,9 @@ struct TVPlayerControlsView: View {
// Close button stops playback and dismisses.
// Menu button only hides the player (keeps background playback),
// so an explicit Close is kept here, icon-only in the top bar.
// When `tvOSMenuButtonClosesVideo` is enabled, the Menu button
// takes over this role and the explicit button is hidden.
if appEnvironment?.settingsManager.tvOSMenuButtonClosesVideo != true {
Button {
onClose()
} label: {
@@ -181,6 +184,7 @@ struct TVPlayerControlsView: View {
.accessibilityLabel(Text("player.controls.close"))
}
}
}
private var yatteeServerURL: URL? {
appEnvironment?.instancesManager.yatteeServerInstances.first { $0.isEnabled }?.url

View File

@@ -667,6 +667,9 @@ struct TVPlayerView: View {
} else if controlsVisible {
// Fifth: hide controls
hideControls()
} else if appEnvironment?.settingsManager.tvOSMenuButtonClosesVideo == true {
// Sixth (Menu-closes mode): fully close the video like the xmark button
closeVideo()
} else {
// Sixth: dismiss player (controls already hidden)
dismissPlayer()

View File

@@ -240,7 +240,7 @@ private struct BehaviorSection: View {
@Bindable var settings: SettingsManager
var body: some View {
Section(String(localized: "settings.playback.behavior.header")) {
Section {
PlatformMenuPicker(
String(localized: "settings.playback.resumeAction"),
selection: $settings.resumeAction
@@ -256,6 +256,21 @@ private struct BehaviorSection: View {
isOn: $settings.backgroundPlaybackEnabled
)
#endif
#if os(tvOS)
Toggle(
String(localized: "settings.playback.tvOSMenuButtonClosesVideo"),
isOn: $settings.tvOSMenuButtonClosesVideo
)
#endif
} header: {
Text(String(localized: "settings.playback.behavior.header"))
} footer: {
#if os(tvOS)
Text(String(localized: "settings.playback.tvOSMenuButtonClosesVideo.footer"))
#else
EmptyView()
#endif
}
}
}