From e6b6778ba1c1774798f855b7dd9186076a354132 Mon Sep 17 00:00:00 2001 From: Arkadiusz Fal Date: Tue, 18 Nov 2025 16:20:30 +0100 Subject: [PATCH] Fix iOS Now Playing integration for MPV backend The MPV backend now properly displays Now Playing information in iOS Control Center. The fix addresses the issue where the AVAudioSession would become inactive during MPV's playback lifecycle. Key changes: - Added setupAudioSessionForNowPlaying() method to activate AVAudioSession with proper playback category and movie playback mode - Re-activate audio session at critical MPV events: FILE_LOADED, PLAYBACK_RESTART, AUDIO_RECONFIG, and during periodic updates - Initialize audio session immediately after mpv_initialize() in MPVClient The audio session must be re-activated at multiple points during playback, not just at initialization, to ensure iOS recognizes the app as playing media. --- Model/Player/Backends/MPVBackend.swift | 22 +++++++++++++++++++++- Model/Player/Backends/MPVClient.swift | 6 ++++++ Model/Player/PlayerModel.swift | 13 +++++++++++++ 3 files changed, 40 insertions(+), 1 deletion(-) diff --git a/Model/Player/Backends/MPVBackend.swift b/Model/Player/Backends/MPVBackend.swift index 23308963..d8a98ff9 100644 --- a/Model/Player/Backends/MPVBackend.swift +++ b/Model/Player/Backends/MPVBackend.swift @@ -504,7 +504,10 @@ final class MPVBackend: PlayerBackend { updateControls() } - model.updateNowPlayingInfo() + #if !os(macOS) + model.setupAudioSessionForNowPlaying() + model.updateNowPlayingInfo() + #endif handleSegmentsThrottle.execute { model.handleSegments(at: currentTime) @@ -594,6 +597,11 @@ final class MPVBackend: PlayerBackend { onFileLoaded = nil // Reset retry state on successful load resetRetryState() + // Re-activate audio session for Now Playing + #if !os(macOS) + model.setupAudioSessionForNowPlaying() + model.updateNowPlayingInfo() + #endif case MPV_EVENT_PROPERTY_CHANGE: let dataOpaquePtr = OpaquePointer(event.pointee.data) @@ -611,10 +619,22 @@ final class MPVBackend: PlayerBackend { onFileLoaded = nil // Reset retry state on successful playback restart resetRetryState() + // Re-activate audio session for Now Playing + #if !os(macOS) + model.setupAudioSessionForNowPlaying() + model.updateNowPlayingInfo() + #endif case MPV_EVENT_VIDEO_RECONFIG: model.updateAspectRatio() + case MPV_EVENT_AUDIO_RECONFIG: + // Re-activate audio session when audio is reconfigured + #if !os(macOS) + model.setupAudioSessionForNowPlaying() + model.updateNowPlayingInfo() + #endif + case MPV_EVENT_SEEK: isSeeking = true diff --git a/Model/Player/Backends/MPVClient.swift b/Model/Player/Backends/MPVClient.swift index 59267896..bdb2078b 100644 --- a/Model/Player/Backends/MPVClient.swift +++ b/Model/Player/Backends/MPVClient.swift @@ -135,6 +135,12 @@ final class MPVClient: ObservableObject { checkError(mpv_initialize(mpv)) + #if !os(macOS) + // Set up audio session for Now Playing support + backend?.model.setupAudioSessionForNowPlaying() + backend?.model.updateNowPlayingInfo() + #endif + let api = UnsafeMutableRawPointer(mutating: (MPV_RENDER_API_TYPE_OPENGL as NSString).utf8String) var initParams = mpv_opengl_init_params( get_proc_address: getProcAddress, diff --git a/Model/Player/PlayerModel.swift b/Model/Player/PlayerModel.swift index 575ec78a..090e79db 100644 --- a/Model/Player/PlayerModel.swift +++ b/Model/Player/PlayerModel.swift @@ -1149,6 +1149,19 @@ final class PlayerModel: ObservableObject { } } + func setupAudioSessionForNowPlaying() { + #if !os(macOS) + do { + let audioSession = AVAudioSession.sharedInstance() + try audioSession.setCategory(.playback, mode: .moviePlayback, options: []) + try audioSession.setActive(true, options: []) + logger.info("Audio session activated for Now Playing") + } catch { + logger.error("Failed to set up audio session: \(error)") + } + #endif + } + func updateCurrentArtwork() { guard let video = currentVideo, let thumbnailURL = video.thumbnailURL(quality: Constants.isIPhone ? .medium : .maxres)