make audio ducking and interruption more robust

Signed-off-by: Toni Förster <toni.foerster@gmail.com>

fix audio ducking and bluetooth play/pause

Signed-off-by: Toni Förster <toni.foerster@gmail.com>
This commit is contained in:
Toni Förster 2024-08-31 22:42:17 +02:00
parent 2d7a101ce0
commit e8fcee23ef
No known key found for this signature in database
GPG Key ID: 292F3E5086C83FC7
3 changed files with 54 additions and 38 deletions

View File

@ -364,7 +364,11 @@ final class AVPlayerBackend: PlayerBackend {
let startPlaying = { let startPlaying = {
#if !os(macOS) #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 #endif
self.setRate(self.model.currentRate) self.setRate(self.model.currentRate)

View File

@ -248,13 +248,6 @@ final class MPVBackend: PlayerBackend {
#if !os(macOS) #if !os(macOS)
do { do {
try AVAudioSession.sharedInstance().setActive(true) try AVAudioSession.sharedInstance().setActive(true)
NotificationCenter.default.addObserver(
self,
selector: #selector(self.handleAudioSessionInterruption(_:)),
name: AVAudioSession.interruptionNotification,
object: nil
)
} catch { } catch {
self.logger.error("Error setting up audio session: \(error)") 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)") 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
} }

View File

@ -47,7 +47,7 @@ final class PlayerModel: ObservableObject {
static var shared = PlayerModel() static var shared = PlayerModel()
let logger = Logger(label: "stream.yattee.app") let logger = Logger(label: "stream.yattee.player.model")
var playerItem: AVPlayerItem? var playerItem: AVPlayerItem?
@ -204,6 +204,14 @@ final class PlayerModel: ObservableObject {
#if !os(macOS) #if !os(macOS)
mpvBackend.controller = mpvController mpvBackend.controller = mpvController
mpvBackend.client = mpvController.client mpvBackend.client = mpvController.client
// Register for audio session interruption notifications
NotificationCenter.default.addObserver(
self,
selector: #selector(handleAudioSessionInterruption(_:)),
name: AVAudioSession.interruptionNotification,
object: nil
)
#endif #endif
playbackMode = Defaults[.playbackMode] playbackMode = Defaults[.playbackMode]
@ -220,6 +228,10 @@ final class PlayerModel: ObservableObject {
currentRate = playerRate currentRate = playerRate
} }
deinit {
NotificationCenter.default.removeObserver(self, name: AVAudioSession.interruptionNotification, object: nil)
}
func show() { func show() {
#if os(macOS) #if os(macOS)
if presentingPlayer { if presentingPlayer {
@ -1231,6 +1243,42 @@ final class PlayerModel: ObservableObject {
return nil 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) #if os(macOS)
private func assignKeyPressMonitor() { private func assignKeyPressMonitor() {
keyPressMonitor = NSEvent.addLocalMonitorForEvents(matching: .keyDown) { keyEvent -> NSEvent? in keyPressMonitor = NSEvent.addLocalMonitorForEvents(matching: .keyDown) { keyEvent -> NSEvent? in