Add audio-only music mode

Persistent per-platform mode that plays only the audio track to save
bandwidth and speed up loads. When enabled, stream selection picks the
best audio-only stream (preferred language, codec, bitrate) and never
fetches the video URL; toggling mid-video reloads at the current
position using the quality-switch path (live vid=no toggling is avoided
due to A/V desync).

- Toggle row in the player quality/settings sheet on iOS, macOS and
  tvOS, next to lock controls
- New audioMode control button type (red when active) for player
  controls and mini player layouts
- Picking a video quality explicitly turns the mode off; audio track
  picks keep it on and now work while audio-only is playing
- Stream URL refresh preserves audio-only playback instead of
  resurrecting video
- Queue preloads and history entries resolved before a toggle are
  discarded so selection re-runs with the current mode
- tvOS player shows the video thumbnail instead of a black screen
  during audio-only playback
- PiP button hidden for audio-only streams
This commit is contained in:
Arkadiusz Fal
2026-07-13 19:06:01 +02:00
parent 66f7602d08
commit 06087220ef
19 changed files with 352 additions and 85 deletions

View File

@@ -39,6 +39,7 @@ enum ControlButtonType: String, Codable, Hashable, Sendable, CaseIterable {
case titleAuthor
case panscan
case autoPlayNext
case audioMode
case seek
case keepOnTop
@@ -116,6 +117,8 @@ enum ControlButtonType: String, Codable, Hashable, Sendable, CaseIterable {
return String(localized: "controls.button.panscan")
case .autoPlayNext:
return String(localized: "controls.button.autoPlayNext")
case .audioMode:
return String(localized: "controls.button.audioMode")
case .seek:
return String(localized: "controls.button.seek")
case .keepOnTop:
@@ -186,6 +189,8 @@ enum ControlButtonType: String, Codable, Hashable, Sendable, CaseIterable {
return "arrow.left.and.right.square"
case .autoPlayNext:
return "play.square.stack.fill"
case .audioMode:
return "music.note"
case .seek:
return "goforward.10" // Default icon, actual icon is determined by settings
case .keepOnTop:
@@ -250,6 +255,7 @@ enum ControlButtonType: String, Codable, Hashable, Sendable, CaseIterable {
.share,
.contextMenu,
.autoPlayNext,
.audioMode,
.mpvDebug
]
#else
@@ -281,6 +287,7 @@ enum ControlButtonType: String, Codable, Hashable, Sendable, CaseIterable {
.panelToggle,
.panscan,
.autoPlayNext,
.audioMode,
.airplay,
.mpvDebug
]
@@ -332,7 +339,8 @@ enum ControlButtonType: String, Codable, Hashable, Sendable, CaseIterable {
.addToPlaylist,
.pictureInPicture,
// Utility
.playbackSpeed
.playbackSpeed,
.audioMode
]
#else
[
@@ -350,7 +358,8 @@ enum ControlButtonType: String, Codable, Hashable, Sendable, CaseIterable {
.airplay,
.pictureInPicture,
// Utility
.playbackSpeed
.playbackSpeed,
.audioMode
]
#endif
}