From e8fcee23efbd8d9c89c1336fe9835bd95841fb18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Toni=20F=C3=B6rster?= Date: Sat, 31 Aug 2024 22:42:17 +0200 Subject: [PATCH] make audio ducking and interruption more robust MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Toni Förster fix audio ducking and bluetooth play/pause Signed-off-by: Toni Förster --- Model/Player/Backends/AVPlayerBackend.swift | 6 ++- Model/Player/Backends/MPVBackend.swift | 36 --------------- Model/Player/PlayerModel.swift | 50 ++++++++++++++++++++- 3 files changed, 54 insertions(+), 38 deletions(-) diff --git a/Model/Player/Backends/AVPlayerBackend.swift b/Model/Player/Backends/AVPlayerBackend.swift index 9f8e9445..fd72500b 100644 --- a/Model/Player/Backends/AVPlayerBackend.swift +++ b/Model/Player/Backends/AVPlayerBackend.swift @@ -364,7 +364,11 @@ final class AVPlayerBackend: PlayerBackend { let startPlaying = { #if !os(macOS) - try? AVAudioSession.sharedInstance().setActive(true) + do { + try AVAudioSession.sharedInstance().setActive(true) + } catch { + self.logger.error("Error setting up audio session: \(error)") + } #endif self.setRate(self.model.currentRate) diff --git a/Model/Player/Backends/MPVBackend.swift b/Model/Player/Backends/MPVBackend.swift index daf2c51f..942589ba 100644 --- a/Model/Player/Backends/MPVBackend.swift +++ b/Model/Player/Backends/MPVBackend.swift @@ -248,13 +248,6 @@ final class MPVBackend: PlayerBackend { #if !os(macOS) do { try AVAudioSession.sharedInstance().setActive(true) - - NotificationCenter.default.addObserver( - self, - selector: #selector(self.handleAudioSessionInterruption(_:)), - name: AVAudioSession.interruptionNotification, - object: nil - ) } catch { self.logger.error("Error setting up audio session: \(error)") } @@ -649,33 +642,4 @@ final class MPVBackend: PlayerBackend { logger.info("MPV backend received unhandled property: \(name)") } } - - #if !os(macOS) - @objc func handleAudioSessionInterruption(_ notification: Notification) { - logger.info("Audio session interruption received.") - - guard let info = notification.userInfo, - let typeValue = info[AVAudioSessionInterruptionTypeKey] as? UInt - else { - logger.info("AVAudioSessionInterruptionTypeKey is missing or not a UInt in userInfo.") - return - } - - let type = AVAudioSession.InterruptionType(rawValue: typeValue) - - logger.info("Interruption type received: \(String(describing: type))") - - switch type { - case .began: - pause() - logger.info("Audio session interrupted.") - default: - break - } - } - - deinit { - NotificationCenter.default.removeObserver(self, name: AVAudioSession.interruptionNotification, object: nil) - } - #endif } diff --git a/Model/Player/PlayerModel.swift b/Model/Player/PlayerModel.swift index 610873d0..72c99a9a 100644 --- a/Model/Player/PlayerModel.swift +++ b/Model/Player/PlayerModel.swift @@ -47,7 +47,7 @@ final class PlayerModel: ObservableObject { static var shared = PlayerModel() - let logger = Logger(label: "stream.yattee.app") + let logger = Logger(label: "stream.yattee.player.model") var playerItem: AVPlayerItem? @@ -204,6 +204,14 @@ final class PlayerModel: ObservableObject { #if !os(macOS) mpvBackend.controller = mpvController mpvBackend.client = mpvController.client + + // Register for audio session interruption notifications + NotificationCenter.default.addObserver( + self, + selector: #selector(handleAudioSessionInterruption(_:)), + name: AVAudioSession.interruptionNotification, + object: nil + ) #endif playbackMode = Defaults[.playbackMode] @@ -220,6 +228,10 @@ final class PlayerModel: ObservableObject { currentRate = playerRate } + deinit { + NotificationCenter.default.removeObserver(self, name: AVAudioSession.interruptionNotification, object: nil) + } + func show() { #if os(macOS) if presentingPlayer { @@ -1231,6 +1243,42 @@ final class PlayerModel: ObservableObject { return nil } + #if !os(macOS) + @objc func handleAudioSessionInterruption(_ notification: Notification) { + logger.info("Audio session interruption received.") + logger.info("Notification received: \(notification)") + + guard let info = notification.userInfo, + let typeValue = info[AVAudioSessionInterruptionTypeKey] as? UInt, + let type = AVAudioSession.InterruptionType(rawValue: typeValue) + else { + logger.info("AVAudioSessionInterruptionTypeKey is missing or not a UInt in userInfo.") + return + } + + logger.info("Interruption type received: \(type)") + + switch type { + case .began: + logger.info("Audio session interrupted.") + // We need to call pause() to set all variables correctly, and play() + // directly afterwards, because the .began interrupt is sent after audio + // ducking ended and playback would pause. Audio ducking usually happens + // when using headphones. + pause() + play() + case .ended: + logger.info("Audio session interruption ended.") + // We need to call pause() to set all variables correctly. + // Otherwise, playback does not resume when the interruption ends. + pause() + play() + default: + break + } + } + #endif + #if os(macOS) private func assignKeyPressMonitor() { keyPressMonitor = NSEvent.addLocalMonitorForEvents(matching: .keyDown) { keyEvent -> NSEvent? in