mirror of
https://github.com/yattee/yattee.git
synced 2026-07-21 06:42:01 +00:00
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:
@@ -166,6 +166,7 @@ struct TVPlayerView: View {
|
||||
return appEnvironment?.downloadManager.downloadsDirectory().appendingPathComponent(path)
|
||||
},
|
||||
currentRate: playerState?.rate ?? .x1,
|
||||
isAudioMode: appEnvironment?.settingsManager.audioOnlyModeEnabled ?? false,
|
||||
onStreamSelected: { stream, audioStream in
|
||||
switchToStream(stream, audioStream: audioStream)
|
||||
},
|
||||
@@ -186,6 +187,11 @@ struct TVPlayerView: View {
|
||||
playerState?.rate = rate
|
||||
playerService.currentBackend?.rate = Float(rate.rawValue)
|
||||
},
|
||||
onAudioModeToggled: { enabled in
|
||||
Task {
|
||||
await playerService.setAudioMode(enabled)
|
||||
}
|
||||
},
|
||||
onDismiss: { hideQualitySheet() }
|
||||
)
|
||||
}
|
||||
@@ -606,18 +612,27 @@ struct TVPlayerView: View {
|
||||
|
||||
@ViewBuilder
|
||||
private var videoLayer: some View {
|
||||
if let playerService,
|
||||
let backend = playerService.currentBackend as? MPVBackend,
|
||||
let playerState {
|
||||
MPVRenderViewRepresentable(
|
||||
backend: backend,
|
||||
playerState: playerState
|
||||
)
|
||||
.ignoresSafeArea()
|
||||
.allowsHitTesting(false)
|
||||
} else {
|
||||
// Fallback/loading state - show thumbnail
|
||||
if let video = playerState?.currentVideo,
|
||||
let isAudioOnly = playerState?.currentStream?.isAudioOnly == true
|
||||
let hasBackend = playerService?.currentBackend is MPVBackend
|
||||
|
||||
ZStack {
|
||||
if let playerService,
|
||||
let backend = playerService.currentBackend as? MPVBackend,
|
||||
let playerState {
|
||||
MPVRenderViewRepresentable(
|
||||
backend: backend,
|
||||
playerState: playerState
|
||||
)
|
||||
.ignoresSafeArea()
|
||||
.allowsHitTesting(false)
|
||||
// Keep the render view alive but hidden for audio-only
|
||||
// playback so toggling audio mode doesn't tear down GL state
|
||||
.opacity(isAudioOnly ? 0 : 1)
|
||||
}
|
||||
|
||||
// Thumbnail for audio-only playback and the pre-backend loading state
|
||||
if isAudioOnly || !hasBackend,
|
||||
let video = playerState?.currentVideo,
|
||||
let thumbnailURL = video.bestThumbnail?.url {
|
||||
AsyncImage(url: thumbnailURL) { image in
|
||||
image
|
||||
@@ -735,12 +750,8 @@ struct TVPlayerView: View {
|
||||
}
|
||||
|
||||
private func switchToStream(_ stream: Stream, audioStream: Stream? = nil) {
|
||||
guard let video = playerState?.currentVideo else { return }
|
||||
|
||||
let currentTime = playerState?.currentTime
|
||||
|
||||
Task {
|
||||
await playerService?.play(video: video, stream: stream, audioStream: audioStream, startTime: currentTime)
|
||||
await playerService?.selectStreamManually(stream, audioStream: audioStream)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user