|  |  | @ -19,7 +19,7 @@ public class AudioActivity: NSObject { | 
			
		
	
		
		
			
				
					
					|  |  |  |     } |  |  |  |     } | 
			
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |     deinit { |  |  |  |     deinit { | 
			
		
	
		
		
			
				
					
					|  |  |  |         OWSAudioSession.shared.ensureAudioSessionActivationState() |  |  |  |         OWSAudioSession.shared.ensureAudioSessionActivationStateAfterDelay() | 
			
				
				
			
		
	
		
		
	
		
		
			
				
					
					|  |  |  |     } |  |  |  |     } | 
			
		
	
		
		
			
				
					
					|  |  |  | } |  |  |  | } | 
			
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  | 
 | 
			
		
	
	
		
		
			
				
					|  |  | @ -38,8 +38,10 @@ public class OWSAudioSession: NSObject { | 
			
		
	
		
		
			
				
					
					|  |  |  |     public func startAmbientAudioActivity(_ audioActivity: AudioActivity) { |  |  |  |     public func startAmbientAudioActivity(_ audioActivity: AudioActivity) { | 
			
		
	
		
		
			
				
					
					|  |  |  |         Logger.debug("\(logTag) in \(#function)") |  |  |  |         Logger.debug("\(logTag) in \(#function)") | 
			
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |         startAudioActivity(audioActivity) |  |  |  |         objc_sync_enter(self) | 
			
				
				
			
		
	
		
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |         defer { objc_sync_exit(self) } | 
			
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |         startAudioActivity(audioActivity) | 
			
		
	
		
		
			
				
					
					|  |  |  |         guard currentActivities.count == 1 else { |  |  |  |         guard currentActivities.count == 1 else { | 
			
		
	
		
		
			
				
					
					|  |  |  |             // We don't want to clobber the audio capabilities configured by (e.g.) media playback or an in-progress call |  |  |  |             // We don't want to clobber the audio capabilities configured by (e.g.) media playback or an in-progress call | 
			
		
	
		
		
			
				
					
					|  |  |  |             Logger.info("\(logTag) in \(#function) not touching audio session since another currentActivity exists.") |  |  |  |             Logger.info("\(logTag) in \(#function) not touching audio session since another currentActivity exists.") | 
			
		
	
	
		
		
			
				
					|  |  | @ -57,6 +59,9 @@ public class OWSAudioSession: NSObject { | 
			
		
	
		
		
			
				
					
					|  |  |  |     public func startPlaybackAudioActivity(_ audioActivity: AudioActivity) { |  |  |  |     public func startPlaybackAudioActivity(_ audioActivity: AudioActivity) { | 
			
		
	
		
		
			
				
					
					|  |  |  |         Logger.debug("\(logTag) in \(#function)") |  |  |  |         Logger.debug("\(logTag) in \(#function)") | 
			
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |         objc_sync_enter(self) | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |         defer { objc_sync_exit(self) } | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |         startAudioActivity(audioActivity) |  |  |  |         startAudioActivity(audioActivity) | 
			
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |         do { |  |  |  |         do { | 
			
		
	
	
		
		
			
				
					|  |  | @ -69,6 +74,9 @@ public class OWSAudioSession: NSObject { | 
			
		
	
		
		
			
				
					
					|  |  |  |     public func startRecordingAudioActivity(_ audioActivity: AudioActivity) -> Bool { |  |  |  |     public func startRecordingAudioActivity(_ audioActivity: AudioActivity) -> Bool { | 
			
		
	
		
		
			
				
					
					|  |  |  |         Logger.debug("\(logTag) in \(#function)") |  |  |  |         Logger.debug("\(logTag) in \(#function)") | 
			
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |         objc_sync_enter(self) | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |         defer { objc_sync_exit(self) } | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |         assert(avAudioSession.recordPermission() == .granted) |  |  |  |         assert(avAudioSession.recordPermission() == .granted) | 
			
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |         startAudioActivity(audioActivity) |  |  |  |         startAudioActivity(audioActivity) | 
			
		
	
	
		
		
			
				
					|  |  | @ -85,17 +93,35 @@ public class OWSAudioSession: NSObject { | 
			
		
	
		
		
			
				
					
					|  |  |  |     public func startAudioActivity(_ audioActivity: AudioActivity) { |  |  |  |     public func startAudioActivity(_ audioActivity: AudioActivity) { | 
			
		
	
		
		
			
				
					
					|  |  |  |         Logger.debug("\(logTag) in \(#function) with \(audioActivity)") |  |  |  |         Logger.debug("\(logTag) in \(#function) with \(audioActivity)") | 
			
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |         objc_sync_enter(self) | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |         defer { objc_sync_exit(self) } | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |         self.currentActivities.append(Weak(value: audioActivity)) |  |  |  |         self.currentActivities.append(Weak(value: audioActivity)) | 
			
		
	
		
		
			
				
					
					|  |  |  |     } |  |  |  |     } | 
			
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |     public func endAudioActivity(_ audioActivity: AudioActivity) { |  |  |  |     public func endAudioActivity(_ audioActivity: AudioActivity) { | 
			
		
	
		
		
			
				
					
					|  |  |  |         Logger.debug("\(logTag) in \(#function) with audioActivity: \(audioActivity)") |  |  |  |         Logger.debug("\(logTag) in \(#function) with audioActivity: \(audioActivity)") | 
			
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |         objc_sync_enter(self) | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |         defer { objc_sync_exit(self) } | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |         currentActivities = currentActivities.filter { return $0.value != audioActivity } |  |  |  |         currentActivities = currentActivities.filter { return $0.value != audioActivity } | 
			
		
	
		
		
			
				
					
					|  |  |  |         ensureAudioSessionActivationState() |  |  |  |         ensureAudioSessionActivationStateAfterDelay() | 
			
				
				
			
		
	
		
		
	
		
		
			
				
					
					|  |  |  |     } |  |  |  |     } | 
			
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |     fileprivate func ensureAudioSessionActivationState() { |  |  |  |     fileprivate func ensureAudioSessionActivationStateAfterDelay() { | 
			
				
				
			
		
	
		
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |         // Without this delay, we sometimes error when deactivating the audio session with: | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |         //     Error Domain=NSOSStatusErrorDomain Code=560030580 “The operation couldn’t be completed. (OSStatus error 560030580.)” | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |         // aka "AVAudioSessionErrorCodeIsBusy" | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |         DispatchQueue.global().asyncAfter(deadline: .now() + 0.5) { | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |             self.ensureAudioSessionActivationState() | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |         } | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |     } | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |     private func ensureAudioSessionActivationState() { | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |         objc_sync_enter(self) | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |         defer { objc_sync_exit(self) } | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |         // Cull any stale activities |  |  |  |         // Cull any stale activities | 
			
		
	
		
		
			
				
					
					|  |  |  |         currentActivities = currentActivities.flatMap { oldActivity in |  |  |  |         currentActivities = currentActivities.flatMap { oldActivity in | 
			
		
	
		
		
			
				
					
					|  |  |  |             guard oldActivity.value != nil else { |  |  |  |             guard oldActivity.value != nil else { | 
			
		
	
	
		
		
			
				
					|  |  | 
 |