From 20d932877ed5614d6f4348d64a32217ccd2c01ef Mon Sep 17 00:00:00 2001 From: ronaldheft Date: Tue, 30 Aug 2022 22:33:55 -0400 Subject: [PATCH 1/3] fix: Only mark audio session as active when playing --- ios/App/Shared/player/AudioPlayer.swift | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/ios/App/Shared/player/AudioPlayer.swift b/ios/App/Shared/player/AudioPlayer.swift index bc69f9fb..304181c0 100644 --- a/ios/App/Shared/player/AudioPlayer.swift +++ b/ios/App/Shared/player/AudioPlayer.swift @@ -284,7 +284,8 @@ class AudioPlayer: NSObject { await PlayerProgress.shared.syncFromPlayer(currentTime: currentTime, includesPlayProgress: self.isPlaying(), isStopping: false) } } - + + self.markAudioSessionAs(active: true) self.audioPlayer.play() self.status = 1 self.rate = self.tmpRate @@ -302,6 +303,7 @@ class AudioPlayer: NSObject { guard self.isInitialized() else { return } self.audioPlayer.pause() + self.markAudioSessionAs(active: false) Task { if let currentTime = self.getCurrentTime() { @@ -572,13 +574,20 @@ class AudioPlayer: NSObject { private func initAudioSession() { do { try AVAudioSession.sharedInstance().setCategory(.playback, mode: .spokenAudio) - try AVAudioSession.sharedInstance().setActive(true) } catch { NSLog("Failed to set AVAudioSession category") print(error) } } + private func markAudioSessionAs(active: Bool) { + do { + try AVAudioSession.sharedInstance().setActive(active) + } catch { + NSLog("Failed to set audio session as active=\(active)") + } + } + // MARK: - Now playing private func setupRemoteTransportControls() { DispatchQueue.runOnMainQueue { From 11f22888d541de2ac749dead4f2c03088b08fa73 Mon Sep 17 00:00:00 2001 From: ronaldheft Date: Tue, 30 Aug 2022 22:47:55 -0400 Subject: [PATCH 2/3] feat: Handle resuming iOS audio after pause --- ios/App/Shared/player/AudioPlayer.swift | 29 +++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/ios/App/Shared/player/AudioPlayer.swift b/ios/App/Shared/player/AudioPlayer.swift index 304181c0..f188b268 100644 --- a/ios/App/Shared/player/AudioPlayer.swift +++ b/ios/App/Shared/player/AudioPlayer.swift @@ -71,6 +71,7 @@ class AudioPlayer: NSObject { } // Listen to player events + self.setupInteruptionNotification() self.audioPlayer.addObserver(self, forKeyPath: #keyPath(AVPlayer.rate), options: .new, context: &playerContext) self.audioPlayer.addObserver(self, forKeyPath: #keyPath(AVPlayer.currentItem), options: .new, context: &playerContext) @@ -119,6 +120,7 @@ class AudioPlayer: NSObject { print(error) } + self.removeInteruptionNotification() DispatchQueue.runOnMainQueue { UIApplication.shared.endReceivingRemoteControlEvents() } @@ -146,6 +148,14 @@ class AudioPlayer: NSObject { return 0 } + private func setupInteruptionNotification() { + NotificationCenter.default.addObserver(self, selector: #selector(handleInteruption), name: AVAudioSession.interruptionNotification, object: AVAudioSession.sharedInstance()) + } + + private func removeInteruptionNotification() { + NotificationCenter.default.removeObserver(self, name: AVAudioSession.interruptionNotification, object: AVAudioSession.sharedInstance()) + } + private func setupTimeObserver() { // Time observer should be configured on the main queue DispatchQueue.runOnMainQueue { @@ -588,6 +598,25 @@ class AudioPlayer: NSObject { } } + // MARK: - iOS audio interupt notifications + @objc private func handleInteruption(notification: Notification) { + guard let userInfo = notification.userInfo, + let typeValue = userInfo[AVAudioSessionInterruptionTypeKey] as? UInt, + let type = AVAudioSession.InterruptionType(rawValue: typeValue) else { + return + } + + switch type { + case .ended: + guard let optionsValue = userInfo[AVAudioSessionInterruptionOptionKey] as? UInt else { return } + let options = AVAudioSession.InterruptionOptions(rawValue: optionsValue) + if options.contains(.shouldResume) { + self.play(allowSeekBack: true) + } + default: () + } + } + // MARK: - Now playing private func setupRemoteTransportControls() { DispatchQueue.runOnMainQueue { From 82159ee537ede1a1ad0d0d2809fed7e59b755a63 Mon Sep 17 00:00:00 2001 From: ronaldheft Date: Tue, 30 Aug 2022 22:59:59 -0400 Subject: [PATCH 3/3] feat: Handle route change notifications --- ios/App/Shared/player/AudioPlayer.swift | 44 ++++++++++++++++++++++--- 1 file changed, 39 insertions(+), 5 deletions(-) diff --git a/ios/App/Shared/player/AudioPlayer.swift b/ios/App/Shared/player/AudioPlayer.swift index f188b268..87c9fadf 100644 --- a/ios/App/Shared/player/AudioPlayer.swift +++ b/ios/App/Shared/player/AudioPlayer.swift @@ -71,7 +71,7 @@ class AudioPlayer: NSObject { } // Listen to player events - self.setupInteruptionNotification() + self.setupAudioSessionNotifications() self.audioPlayer.addObserver(self, forKeyPath: #keyPath(AVPlayer.rate), options: .new, context: &playerContext) self.audioPlayer.addObserver(self, forKeyPath: #keyPath(AVPlayer.currentItem), options: .new, context: &playerContext) @@ -120,7 +120,7 @@ class AudioPlayer: NSObject { print(error) } - self.removeInteruptionNotification() + self.removeAudioSessionNotifications() DispatchQueue.runOnMainQueue { UIApplication.shared.endReceivingRemoteControlEvents() } @@ -148,12 +148,14 @@ class AudioPlayer: NSObject { return 0 } - private func setupInteruptionNotification() { + private func setupAudioSessionNotifications() { NotificationCenter.default.addObserver(self, selector: #selector(handleInteruption), name: AVAudioSession.interruptionNotification, object: AVAudioSession.sharedInstance()) + NotificationCenter.default.addObserver(self, selector: #selector(handleRouteChange), name: AVAudioSession.routeChangeNotification, object: AVAudioSession.sharedInstance()) } - private func removeInteruptionNotification() { + private func removeAudioSessionNotifications() { NotificationCenter.default.removeObserver(self, name: AVAudioSession.interruptionNotification, object: AVAudioSession.sharedInstance()) + NotificationCenter.default.removeObserver(self, name: AVAudioSession.routeChangeNotification, object: AVAudioSession.sharedInstance()) } private func setupTimeObserver() { @@ -598,7 +600,7 @@ class AudioPlayer: NSObject { } } - // MARK: - iOS audio interupt notifications + // MARK: - iOS audio session notifications @objc private func handleInteruption(notification: Notification) { guard let userInfo = notification.userInfo, let typeValue = userInfo[AVAudioSessionInterruptionTypeKey] as? UInt, @@ -617,6 +619,38 @@ class AudioPlayer: NSObject { } } + @objc private func handleRouteChange(notification: Notification) { + guard let userInfo = notification.userInfo, + let reasonValue = userInfo[AVAudioSessionRouteChangeReasonKey] as? UInt, + let reason = AVAudioSession.RouteChangeReason(rawValue: reasonValue) else { + return + } + + switch reason { + case .newDeviceAvailable: // New device found. + let session = AVAudioSession.sharedInstance() + let headphonesConnected = hasHeadphones(in: session.currentRoute) + if headphonesConnected { + // We should just let things be, as it's okay to go from speaker to headphones + } + case .oldDeviceUnavailable: // Old device removed. + if let previousRoute = userInfo[AVAudioSessionRouteChangePreviousRouteKey] as? AVAudioSessionRouteDescription { + let headphonesWereConnected = hasHeadphones(in: previousRoute) + if headphonesWereConnected { + // Removing headphones we should pause instead of keeping on playing + self.pause() + } + } + + default: () + } + } + + private func hasHeadphones(in routeDescription: AVAudioSessionRouteDescription) -> Bool { + // Filter the outputs to only those with a port type of headphones. + return !routeDescription.outputs.filter({$0.portType == .headphones}).isEmpty + } + // MARK: - Now playing private func setupRemoteTransportControls() { DispatchQueue.runOnMainQueue {