2017-01-18 17:46:29 +01:00
//
2017-01-26 16:05:41 +01:00
// C o p y r i g h t ( c ) 2 0 1 7 O p e n W h i s p e r S y s t e m s . A l l r i g h t s r e s e r v e d .
2017-01-18 17:46:29 +01:00
//
import Foundation
2017-02-25 02:33:43 +01:00
import AVFoundation
2017-01-18 17:46:29 +01:00
2017-07-12 21:51:07 +02:00
public let CallAudioServiceSessionChanged = Notification . Name ( " CallAudioServiceSessionChanged " )
struct AudioSource : Hashable {
let image : UIImage
let localizedName : String
let portDescription : AVAudioSessionPortDescription ?
let isBuiltInSpeaker : Bool
init ( localizedName : String , image : UIImage , isBuiltInSpeaker : Bool , portDescription : AVAudioSessionPortDescription ? = nil ) {
self . localizedName = localizedName
self . image = image
self . isBuiltInSpeaker = isBuiltInSpeaker
self . portDescription = portDescription
}
init ( portDescription : AVAudioSessionPortDescription ) {
self . init ( localizedName : portDescription . portName ,
image : # imageLiteral ( resourceName : " button_phone_white " ) , // T O D O
isBuiltInSpeaker : false ,
portDescription : portDescription )
}
// S p e a k e r p h o n e i s h a n d l e d s e p a r a t e l y f r o m t h e o t h e r a u d i o r o u t e s a s i t d o e s n ' t a p p e a r a s a n " i n p u t "
static var builtInSpeaker : AudioSource {
return self . init ( localizedName : NSLocalizedString ( " AUDIO_ROUTE_BUILT_IN_SPEAKER " , comment : " action sheet button title to enable built in speaker during a call " ) ,
image : # imageLiteral ( resourceName : " button_phone_white " ) , // T O D O
isBuiltInSpeaker : true )
}
// MARK: H a s h a b l e
static func = = ( lhs : AudioSource , rhs : AudioSource ) -> Bool {
// S i m p l y c o m p a r i n g t h e ` p o r t D e s c r i p t i o n ` v s t h e ` p o r t D e s c r i p t i o n . u i d `
// c a u s e d m u l t i p l e i n s t a n c e s o f t h e b u i l t i n m i c t o t u r n u p i n a s e t .
if lhs . isBuiltInSpeaker && rhs . isBuiltInSpeaker {
return true
}
if lhs . isBuiltInSpeaker || rhs . isBuiltInSpeaker {
return false
}
guard let lhsPortDescription = lhs . portDescription else {
owsFail ( " only the built in speaker should lack a port description " )
return false
}
guard let rhsPortDescription = rhs . portDescription else {
owsFail ( " only the built in speaker should lack a port description " )
return false
}
return lhsPortDescription . uid = = rhsPortDescription . uid
}
var hashValue : Int {
guard let portDescription = self . portDescription else {
assert ( self . isBuiltInSpeaker )
return " Built In Speaker " . hashValue
}
return portDescription . uid . hash
}
}
2017-01-19 15:38:50 +01:00
@objc class CallAudioService : NSObject , CallObserver {
2017-01-18 17:46:29 +01:00
private let TAG = " [CallAudioService] "
private var vibrateTimer : Timer ?
2017-02-25 02:33:43 +01:00
private let audioPlayer = AVAudioPlayer ( )
2017-01-18 18:31:18 +01:00
private let handleRinging : Bool
2017-01-18 17:46:29 +01:00
2017-02-25 02:33:43 +01:00
class Sound {
let TAG = " [Sound] "
static let incomingRing = Sound ( filePath : " r " , fileExtension : " caf " , loop : true )
static let outgoingRing = Sound ( filePath : " outring " , fileExtension : " mp3 " , loop : true )
static let dialing = Sound ( filePath : " sonarping " , fileExtension : " mp3 " , loop : true )
static let busy = Sound ( filePath : " busy " , fileExtension : " mp3 " , loop : false )
static let failure = Sound ( filePath : " failure " , fileExtension : " mp3 " , loop : false )
let filePath : String
let fileExtension : String
let url : URL
let loop : Bool
init ( filePath : String , fileExtension : String , loop : Bool ) {
self . filePath = filePath
self . fileExtension = fileExtension
self . url = Bundle . main . url ( forResource : self . filePath , withExtension : self . fileExtension ) !
self . loop = loop
}
lazy var player : AVAudioPlayer ? = {
let newPlayer : AVAudioPlayer ?
do {
try newPlayer = AVAudioPlayer ( contentsOf : self . url , fileTypeHint : nil )
if self . loop {
newPlayer ? . numberOfLoops = - 1
}
} catch {
2017-07-10 20:52:14 +02:00
owsFail ( " \( self . TAG ) failed to build audio player with error: \( error ) " )
2017-02-25 02:33:43 +01:00
newPlayer = nil
}
return newPlayer
} ( )
2017-01-18 17:46:29 +01:00
}
2017-01-18 18:31:18 +01:00
// MARK: V i b r a t i o n c o n f i g
2017-01-18 17:46:29 +01:00
private let vibrateRepeatDuration = 1.6
// O u r r i n g b u z z i s a p a i r o f v i b r a t i o n s .
// ` p u l s e D u r a t i o n ` i s t h e s m a l l p a u s e b e t w e e n t h e t w o v i b r a t i o n s i n t h e p a i r .
private let pulseDuration = 0.2
2017-01-18 18:31:18 +01:00
// MARK: - I n i t i a l i z e r s
init ( handleRinging : Bool ) {
self . handleRinging = handleRinging
}
2017-01-19 15:38:50 +01:00
// MARK: - C a l l O b s e r v e r
internal func stateDidChange ( call : SignalCall , state : CallState ) {
2017-01-26 16:05:41 +01:00
AssertIsOnMainThread ( )
2017-01-31 18:41:51 +01:00
self . handleState ( call : call )
2017-01-19 15:38:50 +01:00
}
internal func muteDidChange ( call : SignalCall , isMuted : Bool ) {
2017-01-26 16:05:41 +01:00
AssertIsOnMainThread ( )
2017-01-19 15:38:50 +01:00
Logger . verbose ( " \( TAG ) in \( #function ) is no-op " )
}
2017-07-13 21:37:01 +02:00
internal func audioSourceDidChange ( call : SignalCall , audioSource : AudioSource ? ) {
2017-01-26 16:05:41 +01:00
AssertIsOnMainThread ( )
2017-01-27 17:11:33 +01:00
2017-07-03 15:42:30 +02:00
ensureProperAudioSession ( call : call )
2017-07-13 21:37:01 +02:00
// I t ' s i m p o r t e n t t o s e t p r e f e r r e d i n p u t * a f t e r * e n s u r i n g p r o p e r A u d i o S e s s i o n
// b e c a u s e s o m e s o u r c e s a r e o n l y v a l i d f o r c e r t a i n c a t e g o r i e s .
let session = AVAudioSession . sharedInstance ( )
do {
try session . setPreferredInput ( audioSource ? . portDescription )
} catch {
owsFail ( " \( TAG ) setPreferredInput in \( #function ) failed with error: \( error ) " )
}
2017-01-27 17:11:33 +01:00
}
internal func hasLocalVideoDidChange ( call : SignalCall , hasLocalVideo : Bool ) {
AssertIsOnMainThread ( )
2017-07-03 15:42:30 +02:00
ensureProperAudioSession ( call : call )
2017-01-27 17:11:33 +01:00
}
2017-07-03 15:42:30 +02:00
private func ensureProperAudioSession ( call : SignalCall ? ) {
2017-05-03 15:34:13 +02:00
guard let call = call else {
setAudioSession ( category : AVAudioSessionCategoryPlayback ,
mode : AVAudioSessionModeDefault )
return
}
2017-07-03 15:42:30 +02:00
if call . state = = . localRinging {
// S o l o A m b i e n t p l a y s t h r o u g h s p e a k e r , b u t r e s p e c t s s i l e n t s w i t c h
2017-07-07 00:37:25 +02:00
setAudioSession ( category : AVAudioSessionCategorySoloAmbient ,
mode : AVAudioSessionModeDefault )
2017-07-03 15:42:30 +02:00
} else if call . hasLocalVideo {
2017-07-12 21:51:07 +02:00
// D o n ' t a l l o w b l u e t o o t h f o r l o c a l v i d e o i f s p e a k e r p h o n e h a s b e e n e x p l i c i t l y c h o s e n b y t h e u s e r .
let options : AVAudioSessionCategoryOptions = call . isSpeakerphoneEnabled ? [ . defaultToSpeaker ] : [ . defaultToSpeaker , . allowBluetooth ]
2017-01-31 16:43:45 +01:00
setAudioSession ( category : AVAudioSessionCategoryPlayAndRecord ,
mode : AVAudioSessionModeVideoChat ,
2017-07-12 21:51:07 +02:00
options : options )
2017-01-31 16:43:45 +01:00
} else if call . isSpeakerphoneEnabled {
2017-07-12 21:51:07 +02:00
// E n s u r e n o b l u e t o o t h i f u s e r h a s s p e c i f i e d s p e a k e r p h o n e
2017-01-31 16:43:45 +01:00
setAudioSession ( category : AVAudioSessionCategoryPlayAndRecord ,
mode : AVAudioSessionModeVoiceChat ,
2017-07-12 21:51:07 +02:00
options : [ . defaultToSpeaker ] )
2017-01-19 15:38:50 +01:00
} else {
2017-02-03 01:34:58 +01:00
setAudioSession ( category : AVAudioSessionCategoryPlayAndRecord ,
2017-07-07 00:37:25 +02:00
mode : AVAudioSessionModeVoiceChat ,
options : [ . allowBluetooth ] )
2017-01-19 15:38:50 +01:00
}
}
2017-01-18 18:31:18 +01:00
// MARK: - S e r v i c e a c t i o n h a n d l e r s
2017-05-03 15:34:13 +02:00
public func didUpdateVideoTracks ( call : SignalCall ? ) {
Logger . verbose ( " \( TAG ) in \( #function ) " )
2017-07-03 15:42:30 +02:00
self . ensureProperAudioSession ( call : call )
2017-05-03 15:34:13 +02:00
}
2017-01-31 18:41:51 +01:00
public func handleState ( call : SignalCall ) {
2017-01-23 22:17:55 +01:00
assert ( Thread . isMainThread )
2017-01-31 18:41:51 +01:00
Logger . verbose ( " \( TAG ) in \( #function ) new state: \( call . state ) " )
2017-01-19 15:38:50 +01:00
2017-01-31 18:41:51 +01:00
switch call . state {
2017-02-25 02:33:43 +01:00
case . idle : handleIdle ( call : call )
case . dialing : handleDialing ( call : call )
case . answering : handleAnswering ( call : call )
case . remoteRinging : handleRemoteRinging ( call : call )
case . localRinging : handleLocalRinging ( call : call )
case . connected : handleConnected ( call : call )
case . localFailure : handleLocalFailure ( call : call )
case . localHangup : handleLocalHangup ( call : call )
case . remoteHangup : handleRemoteHangup ( call : call )
case . remoteBusy : handleBusy ( call : call )
2017-01-18 17:46:29 +01:00
}
}
2017-02-25 02:33:43 +01:00
private func handleIdle ( call : SignalCall ) {
2017-01-18 17:46:29 +01:00
Logger . debug ( " \( TAG ) \( #function ) " )
}
2017-02-25 02:33:43 +01:00
private func handleDialing ( call : SignalCall ) {
2017-01-18 17:46:29 +01:00
Logger . debug ( " \( TAG ) \( #function ) " )
2017-07-03 15:42:30 +02:00
AssertIsOnMainThread ( )
2017-02-25 02:33:43 +01:00
2017-07-03 15:42:30 +02:00
ensureProperAudioSession ( call : call )
2017-02-25 02:33:43 +01:00
// H A C K : W i t h o u t t h i s a s y n c , d i a l i n g s o u n d o n l y p l a y s o n c e . I d o n ' t r e a l l y u n d e r s t a n d w h y . D o e s t h e a u d i o S e s s i o n
// n e e d s o m e t i m e t o s e t t l e ? I s s o m e t h i g n e l s e i n t e r r u p t i n g o u r s e s s i o n ?
DispatchQueue . main . asyncAfter ( deadline : DispatchTime . now ( ) + 0.2 ) {
2017-07-03 15:42:30 +02:00
self . play ( sound : Sound . dialing )
2017-02-25 02:33:43 +01:00
}
2017-01-18 17:46:29 +01:00
}
2017-02-25 02:33:43 +01:00
private func handleAnswering ( call : SignalCall ) {
2017-01-18 17:46:29 +01:00
Logger . debug ( " \( TAG ) \( #function ) " )
2017-07-03 15:42:30 +02:00
AssertIsOnMainThread ( )
2017-02-25 02:33:43 +01:00
stopPlayingAnySounds ( )
2017-07-03 15:42:30 +02:00
self . ensureProperAudioSession ( call : call )
2017-01-18 17:46:29 +01:00
}
2017-02-25 02:33:43 +01:00
private func handleRemoteRinging ( call : SignalCall ) {
2017-01-18 17:46:29 +01:00
Logger . debug ( " \( TAG ) \( #function ) " )
2017-07-03 15:42:30 +02:00
AssertIsOnMainThread ( )
2017-02-25 02:33:43 +01:00
stopPlayingAnySounds ( )
// F I X M E i f y o u t o g g l e d s p e a k e r p h o n e b e f o r e t h i s p o i n t , t h e o u t g o i n g r i n g d o e s n o t p l a y t h r o u g h s p e a k e r . W h y ?
2017-07-03 15:42:30 +02:00
self . play ( sound : Sound . outgoingRing )
2017-01-18 17:46:29 +01:00
}
2017-02-25 02:33:43 +01:00
private func handleLocalRinging ( call : SignalCall ) {
2017-01-18 18:31:18 +01:00
Logger . debug ( " \( TAG ) in \( #function ) " )
2017-07-03 15:42:30 +02:00
AssertIsOnMainThread ( )
2017-02-25 02:33:43 +01:00
2017-07-03 15:42:30 +02:00
stopPlayingAnySounds ( )
ensureProperAudioSession ( call : call )
2017-02-25 02:33:43 +01:00
startRinging ( call : call )
2017-01-18 17:46:29 +01:00
}
2017-01-31 18:41:51 +01:00
private func handleConnected ( call : SignalCall ) {
2017-01-18 17:46:29 +01:00
Logger . debug ( " \( TAG ) \( #function ) " )
2017-07-03 15:42:30 +02:00
AssertIsOnMainThread ( )
2017-02-25 02:33:43 +01:00
stopPlayingAnySounds ( )
2017-01-18 17:46:29 +01:00
2017-02-25 02:33:43 +01:00
// s t a r t r e c o r d i n g t o t r a n s m i t c a l l a u d i o .
2017-07-03 15:42:30 +02:00
ensureProperAudioSession ( call : call )
2017-01-18 17:46:29 +01:00
}
2017-02-25 02:33:43 +01:00
private func handleLocalFailure ( call : SignalCall ) {
2017-01-18 17:46:29 +01:00
Logger . debug ( " \( TAG ) \( #function ) " )
2017-07-03 15:42:30 +02:00
AssertIsOnMainThread ( )
2017-02-25 02:33:43 +01:00
stopPlayingAnySounds ( )
2017-07-03 15:42:30 +02:00
play ( sound : Sound . failure )
2017-01-18 17:46:29 +01:00
}
2017-02-25 02:33:43 +01:00
private func handleLocalHangup ( call : SignalCall ) {
2017-01-18 17:46:29 +01:00
Logger . debug ( " \( TAG ) \( #function ) " )
2017-07-03 15:42:30 +02:00
AssertIsOnMainThread ( )
2017-02-25 02:33:43 +01:00
handleCallEnded ( call : call )
2017-01-18 17:46:29 +01:00
}
2017-02-25 02:33:43 +01:00
private func handleRemoteHangup ( call : SignalCall ) {
2017-01-18 17:46:29 +01:00
Logger . debug ( " \( TAG ) \( #function ) " )
2017-07-03 15:42:30 +02:00
AssertIsOnMainThread ( )
2017-02-25 02:33:43 +01:00
vibrate ( )
2017-02-27 20:46:19 +01:00
handleCallEnded ( call : call )
2017-01-18 17:46:29 +01:00
}
2017-02-25 02:33:43 +01:00
private func handleBusy ( call : SignalCall ) {
2017-01-18 17:46:29 +01:00
Logger . debug ( " \( TAG ) \( #function ) " )
2017-07-03 15:42:30 +02:00
AssertIsOnMainThread ( )
2017-02-25 02:33:43 +01:00
stopPlayingAnySounds ( )
2017-07-03 15:42:30 +02:00
play ( sound : Sound . busy )
2017-02-25 02:33:43 +01:00
// L e t t h e b u s y s o u n d p l a y f o r 4 s e c o n d s . T h e f u l l f i l e i s l o n g e r t h a n n e c e s s a r y
DispatchQueue . main . asyncAfter ( deadline : DispatchTime . now ( ) + 4.0 ) {
2017-02-27 20:46:19 +01:00
self . handleCallEnded ( call : call )
2017-02-25 02:33:43 +01:00
}
}
private func handleCallEnded ( call : SignalCall ) {
Logger . debug ( " \( TAG ) \( #function ) " )
2017-07-03 15:42:30 +02:00
AssertIsOnMainThread ( )
2017-02-25 02:33:43 +01:00
stopPlayingAnySounds ( )
// S t o p s o l o a u d i o , r e v e r t t o d e f a u l t .
setAudioSession ( category : AVAudioSessionCategoryAmbient )
}
// MARK: P l a y i n g S o u n d s
var currentPlayer : AVAudioPlayer ?
private func stopPlayingAnySounds ( ) {
currentPlayer ? . stop ( )
stopAnyRingingVibration ( )
}
2017-07-03 15:42:30 +02:00
private func play ( sound : Sound ) {
2017-02-25 02:33:43 +01:00
guard let newPlayer = sound . player else {
2017-07-10 20:52:14 +02:00
owsFail ( " \( self . TAG ) unable to build player " )
2017-02-25 02:33:43 +01:00
return
}
Logger . info ( " \( self . TAG ) playing sound: \( sound . filePath ) " )
2017-07-05 18:55:00 +02:00
// I t ' s i m p o r t a n t t o s t o p t h e c u r r e n t p l a y e r * * b e f o r e * * s t a r t i n g t h e n e w p l a y e r . I n t h e c a s e t h a t
// w e ' r e p l a y i n g t h e s a m e s o u n d , s i n c e t h e p l a y e r i s m e m o i z e d o n t h e s o u n d i n s t a n c e , w e ' d o t h e r w i s e
// s t o p t h e s o u n d w e j u s t s t a r t e d .
2017-02-25 02:33:43 +01:00
self . currentPlayer ? . stop ( )
2017-07-03 19:57:54 +02:00
newPlayer . play ( )
2017-02-25 02:33:43 +01:00
self . currentPlayer = newPlayer
2017-01-18 17:46:29 +01:00
}
2017-01-18 18:31:18 +01:00
// MARK: - R i n g i n g
2017-02-25 02:33:43 +01:00
private func startRinging ( call : SignalCall ) {
2017-01-18 18:31:18 +01:00
guard handleRinging else {
Logger . debug ( " \( TAG ) ignoring \( #function ) since CallKit handles it's own ringing state " )
return
}
2017-01-26 16:05:41 +01:00
vibrateTimer = WeakTimer . scheduledTimer ( timeInterval : vibrateRepeatDuration , target : self , userInfo : nil , repeats : true ) { [ weak self ] _ in
2017-01-19 16:57:07 +01:00
self ? . ringVibration ( )
}
vibrateTimer ? . fire ( )
2017-07-03 15:42:30 +02:00
play ( sound : Sound . incomingRing )
2017-01-18 18:31:18 +01:00
}
2017-01-18 17:46:29 +01:00
2017-02-25 02:33:43 +01:00
private func stopAnyRingingVibration ( ) {
2017-01-18 18:31:18 +01:00
guard handleRinging else {
Logger . debug ( " \( TAG ) ignoring \( #function ) since CallKit handles it's own ringing state " )
return
}
Logger . debug ( " \( TAG ) in \( #function ) " )
// S t o p v i b r a t i n g
2017-01-18 17:46:29 +01:00
vibrateTimer ? . invalidate ( )
vibrateTimer = nil
}
// p u b l i c s o i t c a n b e c a l l e d b y t i m e r v i a s e l e c t o r
public func ringVibration ( ) {
// S i n c e a c a l l n o t i f i c a t i o n i s m o r e u r g e n t t h a n a m e s s a g e n o t i f a c t i o n , w e
// v i b r a t e t w i c e , l i k e a p u l s e , t o d i f f e r e n t i a t e f r o m a n o r m a l n o t i f i c a t i o n v i b r a t i o n .
2017-02-25 02:33:43 +01:00
vibrate ( )
2017-01-18 17:46:29 +01:00
DispatchQueue . default . asyncAfter ( deadline : DispatchTime . now ( ) + pulseDuration ) {
2017-02-25 02:33:43 +01:00
self . vibrate ( )
2017-01-18 17:46:29 +01:00
}
}
2017-02-25 02:33:43 +01:00
func vibrate ( ) {
// T O D O i m p l e m e n t H a p t i c A d a p t e r f o r i P h o n e 7 a n d u p
2017-02-27 20:46:19 +01:00
AudioServicesPlaySystemSound ( kSystemSoundID_Vibrate )
2017-02-25 02:33:43 +01:00
}
2017-07-12 21:51:07 +02:00
// M A R K - A u d i o S e s s i o n M G M T
// T O D O m o v e t h i s t o C a l l A u d i o S e s s i o n ?
// N o t e t h i s m e t h o d i s s e n s i t i v e t o t h e c u r r e n t a u d i o s e s s i o n c o n f i g u r a t i o n .
// S p e c i f i c a l l y i f y o u c a l l i t w h i l e s p e a k e r p h o n e i s e n a b l e d y o u w o n ' t s e e
// a n y c o n n e c t e d b l u e t o o t h r o u t e s .
var availableInputs : [ AudioSource ] {
let session = AVAudioSession . sharedInstance ( )
guard let availableInputs = session . availableInputs else {
// I ' m n o t s u r e w h e n t h i s w o u l d h a p p e n .
owsFail ( " No available inputs or inputs not ready " )
return [ AudioSource . builtInSpeaker ]
}
Logger . info ( " \( TAG ) in \( #function ) availableInputs: \( availableInputs ) " )
return [ AudioSource . builtInSpeaker ] + availableInputs . map { portDescription in
return AudioSource ( portDescription : portDescription )
}
}
func currentAudioSource ( call : SignalCall ) -> AudioSource ? {
if call . isSpeakerphoneEnabled {
return AudioSource . builtInSpeaker
} else {
let session = AVAudioSession . sharedInstance ( )
guard let portDescription = session . currentRoute . inputs . first else {
return nil
}
return AudioSource ( portDescription : portDescription )
}
}
public func setPreferredInput ( call : SignalCall , audioSource : AudioSource ? ) {
let session = AVAudioSession . sharedInstance ( )
do {
Logger . debug ( " \( TAG ) in \( #function ) audioSource: \( String ( describing : audioSource ) ) " )
try session . setPreferredInput ( audioSource ? . portDescription )
} catch {
owsFail ( " \( TAG ) failed with error: \( error ) " )
}
self . ensureProperAudioSession ( call : call )
}
2017-01-31 16:43:45 +01:00
private func setAudioSession ( category : String ,
2017-02-03 01:34:58 +01:00
mode : String ? = nil ,
options : AVAudioSessionCategoryOptions = AVAudioSessionCategoryOptions ( rawValue : 0 ) ) {
2017-07-03 15:42:30 +02:00
let session = AVAudioSession . sharedInstance ( )
2017-07-12 21:51:07 +02:00
var audioSessionChanged = false
2017-01-18 17:46:29 +01:00
do {
2017-02-03 01:34:58 +01:00
if #available ( iOS 10.0 , * ) , let mode = mode {
2017-07-07 00:37:25 +02:00
let oldCategory = session . category
let oldMode = session . mode
let oldOptions = session . categoryOptions
2017-07-08 23:43:34 +02:00
guard oldCategory != category || oldMode != mode || oldOptions != options else {
2017-07-03 15:42:30 +02:00
return
}
2017-07-07 00:37:25 +02:00
2017-07-12 21:51:07 +02:00
audioSessionChanged = true
2017-07-07 00:37:25 +02:00
if oldCategory != category {
Logger . debug ( " \( self . TAG ) audio session changed category: \( oldCategory ) -> \( category ) " )
}
if oldMode != mode {
Logger . debug ( " \( self . TAG ) audio session changed mode: \( oldMode ) -> \( mode ) " )
}
if oldOptions != options {
2017-07-08 23:43:34 +02:00
Logger . debug ( " \( self . TAG ) audio session changed options: \( oldOptions ) -> \( options ) " )
2017-07-07 00:37:25 +02:00
}
2017-07-03 15:42:30 +02:00
try session . setCategory ( category , mode : mode , options : options )
2017-07-07 00:37:25 +02:00
2017-01-31 16:43:45 +01:00
} else {
2017-07-07 00:37:25 +02:00
let oldCategory = session . category
let oldOptions = session . categoryOptions
2017-07-08 23:43:34 +02:00
guard session . category != category || session . categoryOptions != options else {
2017-07-03 15:42:30 +02:00
return
}
2017-07-07 00:37:25 +02:00
2017-07-12 21:51:07 +02:00
audioSessionChanged = true
2017-07-07 00:37:25 +02:00
if oldCategory != category {
Logger . debug ( " \( self . TAG ) audio session changed category: \( oldCategory ) -> \( category ) " )
}
if oldOptions != options {
2017-07-08 23:43:34 +02:00
Logger . debug ( " \( self . TAG ) audio session changed options: \( oldOptions ) -> \( options ) " )
2017-07-07 00:37:25 +02:00
}
2017-07-03 15:42:30 +02:00
try session . setCategory ( category , with : options )
2017-07-07 00:37:25 +02:00
2017-01-31 16:43:45 +01:00
}
2017-01-18 17:46:29 +01:00
} catch {
2017-05-05 00:17:18 +02:00
let message = " \( self . TAG ) in \( #function ) failed to set category: \( category ) mode: \( String ( describing : mode ) ) , options: \( options ) with error: \( error ) "
2017-07-10 20:52:14 +02:00
owsFail ( message )
2017-01-18 17:46:29 +01:00
}
2017-07-12 21:51:07 +02:00
if audioSessionChanged {
Logger . info ( " \( TAG ) in \( #function ) " )
NotificationCenter . default . post ( name : CallAudioServiceSessionChanged , object : nil )
}
2017-01-18 17:46:29 +01:00
}
}