mirror of
https://github.com/yattee/yattee.git
synced 2026-07-19 05:42:04 +00:00
Fix background playback setting behavior across platforms
Hide the toggle on macOS, where apps are never suspended and playback always continues regardless of the setting. Pause playback on iOS when backgrounding with the setting disabled — the audio background mode previously kept mpv playing, making the off state a no-op. Move the continue-playing policy into PlayerService and make the backend's background render pause unconditional (PiP-gated only): it is a GPU optimization that should not depend on the setting. Also ungate the player sheet visibility handlers, which manage foreground mini player rendering unrelated to background playback.
This commit is contained in:
@@ -998,7 +998,7 @@ final class MPVBackend: PlayerBackend {
|
||||
|
||||
// MARK: - Background Playback
|
||||
|
||||
func handleScenePhase(_ phase: ScenePhase, backgroundEnabled: Bool, isPiPActive: Bool) {
|
||||
func handleScenePhase(_ phase: ScenePhase, isPiPActive: Bool) {
|
||||
#if os(iOS)
|
||||
let pipActive = self.isPiPActive || isPiPActive
|
||||
#else
|
||||
@@ -1008,8 +1008,8 @@ final class MPVBackend: PlayerBackend {
|
||||
MPVLogging.logAppLifecycle("handleScenePhase(\(phase))",
|
||||
isPiPActive: pipActive, isRendering: nil)
|
||||
|
||||
guard backgroundEnabled, !pipActive else {
|
||||
MPVLogging.log("handleScenePhase: skipping (bgEnabled:\(backgroundEnabled) pip:\(pipActive))")
|
||||
guard !pipActive else {
|
||||
MPVLogging.log("handleScenePhase: skipping (pip:\(pipActive))")
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -249,11 +249,12 @@ protocol PlayerBackend: AnyObject {
|
||||
// MARK: - Background Playback
|
||||
|
||||
/// Handle scene phase changes for background playback.
|
||||
/// Backends should pause rendering while the app is in the background;
|
||||
/// whether playback itself continues is decided by PlayerService.
|
||||
/// - Parameters:
|
||||
/// - phase: The new scene phase
|
||||
/// - backgroundEnabled: Whether background playback is enabled in settings
|
||||
/// - isPiPActive: Whether Picture-in-Picture is currently active
|
||||
func handleScenePhase(_ phase: ScenePhase, backgroundEnabled: Bool, isPiPActive: Bool)
|
||||
func handleScenePhase(_ phase: ScenePhase, isPiPActive: Bool)
|
||||
}
|
||||
|
||||
// MARK: - Default Implementations
|
||||
@@ -278,7 +279,7 @@ extension PlayerBackend {
|
||||
}
|
||||
|
||||
/// Default implementation does nothing.
|
||||
func handleScenePhase(_ phase: ScenePhase, backgroundEnabled: Bool, isPiPActive: Bool) {
|
||||
func handleScenePhase(_ phase: ScenePhase, isPiPActive: Bool) {
|
||||
// No-op by default
|
||||
}
|
||||
}
|
||||
|
||||
@@ -681,14 +681,23 @@ final class PlayerService {
|
||||
}
|
||||
#endif
|
||||
|
||||
let backgroundEnabled = settingsManager?.backgroundPlaybackEnabled ?? true
|
||||
#if os(iOS)
|
||||
let isPiPActive = (currentBackend as? MPVBackend)?.isPiPActive ?? false
|
||||
#else
|
||||
let isPiPActive = false
|
||||
#endif
|
||||
|
||||
#if os(tvOS)
|
||||
// With background playback disabled, pause when the app leaves the
|
||||
// foreground — the audio background mode would otherwise keep mpv
|
||||
// playing. Not applicable on macOS, where apps are never suspended.
|
||||
#if os(iOS)
|
||||
let backgroundEnabled = settingsManager?.backgroundPlaybackEnabled ?? true
|
||||
if phase == .background, !backgroundEnabled, !isPiPActive, state.playbackState == .playing {
|
||||
LoggingService.shared.debug("PlayerService: pausing playback (phase=\(phase), backgroundPlaybackEnabled=false)", category: .player)
|
||||
pause()
|
||||
}
|
||||
#elseif os(tvOS)
|
||||
let backgroundEnabled = settingsManager?.backgroundPlaybackEnabled ?? true
|
||||
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)
|
||||
@@ -696,7 +705,7 @@ final class PlayerService {
|
||||
}
|
||||
#endif
|
||||
|
||||
currentBackend?.handleScenePhase(phase, backgroundEnabled: backgroundEnabled, isPiPActive: isPiPActive)
|
||||
currentBackend?.handleScenePhase(phase, isPiPActive: isPiPActive)
|
||||
}
|
||||
|
||||
/// Plays the next video in queue, respecting the current queue mode.
|
||||
@@ -2483,12 +2492,11 @@ final class PlayerService {
|
||||
LoggingService.shared.debug("PlayerService: playerSheetDidAppear - backend=\(backendType), playbackState=\(playbackState)", category: .player)
|
||||
|
||||
// Re-enable visual tracks and reattach layer when sheet appears
|
||||
let backgroundEnabled = settingsManager?.backgroundPlaybackEnabled ?? true
|
||||
let isPiPActive = (currentBackend as? MPVBackend)?.isPiPActive ?? false
|
||||
LoggingService.shared.debug("PlayerService: sheetDidAppear checks - backgroundEnabled=\(backgroundEnabled), isPiPActive=\(isPiPActive)", category: .player)
|
||||
LoggingService.shared.debug("PlayerService: sheetDidAppear checks - isPiPActive=\(isPiPActive)", category: .player)
|
||||
|
||||
guard backgroundEnabled && !isPiPActive else {
|
||||
LoggingService.shared.debug("PlayerService: skipping visibility handling (backgroundEnabled=\(backgroundEnabled), isPiPActive=\(isPiPActive))", category: .player)
|
||||
guard !isPiPActive else {
|
||||
LoggingService.shared.debug("PlayerService: skipping visibility handling (isPiPActive=\(isPiPActive))", category: .player)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -2505,12 +2513,11 @@ final class PlayerService {
|
||||
LoggingService.shared.debug("PlayerService: playerSheetDidDisappear - backend=\(backendType), playbackState=\(playbackState)", category: .player)
|
||||
|
||||
// Disable visual tracks and detach layer for background audio playback
|
||||
let backgroundEnabled = settingsManager?.backgroundPlaybackEnabled ?? true
|
||||
let mpvPiPActive = (currentBackend as? MPVBackend)?.isPiPActive ?? false
|
||||
LoggingService.shared.debug("PlayerService: sheetDidDisappear checks - backgroundEnabled=\(backgroundEnabled), mpvPiPActive=\(mpvPiPActive)", category: .player)
|
||||
LoggingService.shared.debug("PlayerService: sheetDidDisappear checks - mpvPiPActive=\(mpvPiPActive)", category: .player)
|
||||
|
||||
guard backgroundEnabled && !mpvPiPActive else {
|
||||
LoggingService.shared.debug("PlayerService: skipping visibility handling on disappear (backgroundEnabled=\(backgroundEnabled), mpvPiP=\(mpvPiPActive))", category: .player)
|
||||
guard !mpvPiPActive else {
|
||||
LoggingService.shared.debug("PlayerService: skipping visibility handling on disappear (mpvPiP=\(mpvPiPActive))", category: .player)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -146,7 +146,7 @@ struct MPVVideoView: View {
|
||||
#if os(iOS) || os(macOS)
|
||||
// Resume the MPV display link when the expanded video view appears.
|
||||
// MiniPlayerView pauses rendering when its video preview hides during expand;
|
||||
// PlayerService.playerSheetDidAppear is gated behind background playback, so it
|
||||
// PlayerService.playerSheetDidAppear skips resuming while PiP is active, so it
|
||||
// can't be relied on to resume. On macOS this also recovers the sheet re-open
|
||||
// black screen when the shared render view reappears without a window re-parent.
|
||||
backend.resumeRendering()
|
||||
|
||||
@@ -270,10 +270,14 @@ private struct BehaviorSection: View {
|
||||
}
|
||||
}
|
||||
|
||||
#if !os(macOS)
|
||||
// macOS never suspends apps, so playback always continues in the
|
||||
// background — the toggle would have no effect there
|
||||
Toggle(
|
||||
String(localized: "settings.playback.backgroundPlayback"),
|
||||
isOn: $settings.backgroundPlaybackEnabled
|
||||
)
|
||||
#endif
|
||||
|
||||
#if os(tvOS)
|
||||
Toggle(
|
||||
|
||||
Reference in New Issue
Block a user