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

@@ -87,6 +87,9 @@ struct PlayerControlsActions {
/// Whether auto-play next is enabled
let isAutoPlayNextEnabled: Bool
/// Whether audio-only (music) mode is enabled
var isAudioModeEnabled: Bool = false
/// Yattee Server URL for channel avatar fallback
let yatteeServerURL: URL?
@@ -126,6 +129,9 @@ struct PlayerControlsActions {
/// Toggle auto-play next in queue
var onToggleAutoPlayNext: (() -> Void)?
/// Toggle audio-only (music) mode
var onToggleAudioMode: (() -> Void)?
/// Show settings sheet
var onShowSettings: (() -> Void)?
@@ -304,9 +310,11 @@ struct PlayerControlsActions {
}
#endif
/// Whether PiP button should be enabled
/// Whether PiP button should be enabled.
/// No video track in audio-only playback - `isPiPPossible` may lag behind
/// a video-to-audio reload on a reused backend, so gate explicitly.
var isPiPAvailable: Bool {
playerState.isPiPPossible
playerState.isPiPPossible && playerState.currentStream?.isAudioOnly != true
}
/// Whether play next button should be enabled