@ -12,19 +12,34 @@ struct AudioSource: Hashable {
let image : UIImage
let localizedName : String
let portDescription : AVAudioSessionPortDescription ?
// T h e b u i l t - i n l o u d s p e a k e r / a k a s p e a k e r p h o n e
let isBuiltInSpeaker : Bool
init ( localizedName : String , image : UIImage , isBuiltInSpeaker : Bool , portDescription : AVAudioSessionPortDescription ? = nil ) {
// T h e b u i l t - i n q u i e t s p e a k e r , a k a t h e n o r m a l p h o n e h a n d s e t r e c e i v e r e a r p i e c e
let isBuiltInEarPiece : Bool
init ( localizedName : String , image : UIImage , isBuiltInSpeaker : Bool , isBuiltInEarPiece : Bool , portDescription : AVAudioSessionPortDescription ? = nil ) {
self . localizedName = localizedName
self . image = image
self . isBuiltInSpeaker = isBuiltInSpeaker
self . isBuiltInEarPiece = isBuiltInEarPiece
self . portDescription = portDescription
}
init ( portDescription : AVAudioSessionPortDescription ) {
self . init ( localizedName : portDescription . portName ,
let isBuiltInEarPiece = portDescription . portType = = AVAudioSessionPortBuiltInMic
// p o r t D e s c r i p t i o n . p o r t N a m e w o r k s w e l l f o r B T l i n k e d d e v i c e s , b u t i f w e a r e u s i n g
// t h e b u i l t i n m i c , w e h a v e " i P h o n e M i c r o p h o n e " w h i c h i s a l i t t l e a w k w a r d .
// I n t h a t c a s e , i n s t e a d w e p r e f e r j u s t t h e m o d e l n a m e e . g . " i P h o n e " o r " i P a d "
let localizedName = isBuiltInEarPiece ? UIDevice . current . localizedModel : portDescription . portName
self . init ( localizedName : localizedName ,
image : # imageLiteral ( resourceName : " button_phone_white " ) , // T O D O
isBuiltInSpeaker : false ,
isBuiltInEarPiece : isBuiltInEarPiece ,
portDescription : portDescription )
}
@ -32,7 +47,8 @@ struct AudioSource: Hashable {
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 )
isBuiltInSpeaker : true ,
isBuiltInEarPiece : false )
}
// MARK: H a s h a b l e
@ -143,15 +159,6 @@ struct AudioSource: Hashable {
AssertIsOnMainThread ( )
ensureProperAudioSession ( call : call )
// 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 y / o p t i o n c o m b i n a t i o n s .
let session = AVAudioSession . sharedInstance ( )
do {
try session . setPreferredInput ( audioSource ? . portDescription )
} catch {
owsFail ( " \( TAG ) setPreferredInput in \( #function ) failed with error: \( error ) " )
}
}
internal func hasLocalVideoDidChange ( call : SignalCall , hasLocalVideo : Bool ) {
@ -169,32 +176,53 @@ struct AudioSource: Hashable {
return
}
// D i s a l l o w b l u e t o o t h w h i l e ( a n d o n l y w h i l e ) t h e u s e r h a s e x p l i c i t l y c h o s e n t h e b u i l t i n r e c e i v e r .
//
// N O T E : I ' m a c t u a l l y n o t s u r e w h y t h i s i s r e q u i r e d - i t s e e m s l i k e w e s h o u l d j u s t b e a b l e
// t o s e t P r e f e r r e d I n p u t t o c a l l . a u d i o S o u r c e . p o r t D e s c r i p t i o n i n t h i s c a s e ,
// b u t i n p r a c t i c e I ' m s e e i n g t h e c a l l r e v e r t t o t h e b l u e t o o t h h e a d s e t .
// P r e s u m a b l y s o m e t h i n g e l s e ( i n W e b R T C ? ) i s t o u c h i n g o u r s h a r e d A u d i o S e s s i o n . - m j k
let options : AVAudioSessionCategoryOptions = call . audioSource ? . isBuiltInEarPiece = = true ? [ ] : [ . allowBluetooth ]
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
setAudioSession ( category : AVAudioSessionCategorySoloAmbient ,
mode : AVAudioSessionModeDefault )
} else if call . hasLocalVideo {
// 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 ]
// A p p l e D o c s s a y t h a t s e t t i n g m o d e t o A V A u d i o S e s s i o n M o d e V i d e o C h a t h a s t h e
// s i d e e f f e c t o f s e t t i n g o p t i o n s : . a l l o w B l u e t o o t h , w h e n I r e m o v e t h e ( s e e m i n g l y u n n e c e s s a r y )
// o p t i o n , a n d i n s p e c t A V A u d i o S e s s i o n . s h a r e d I n s t a n c e . c a t e g o r y O p t i o n s = = 0 . A n d a v a i l a b l e I n p u t s
// d o e s n o t i n c l u d e m y l i n k e d b l u e t o o t h d e v i c e
setAudioSession ( category : AVAudioSessionCategoryPlayAndRecord ,
mode : AVAudioSessionModeVideoChat ,
options : options )
} else {
// A p p l e D o c s s a y t h a t s e t t i n g m o d e t o A V A u d i o S e s s i o n M o d e V o i c e C h a t h a s t h e
// s i d e e f f e c t o f s e t t i n g o p t i o n s : . a l l o w B l u e t o o t h , w h e n I r e m o v e t h e ( s e e m i n g l y u n n e c e s s a r y )
// o p t i o n , a n d i n s p e c t A V A u d i o S e s s i o n . s h a r e d I n s t a n c e . c a t e g o r y O p t i o n s = = 0 . A n d a v a i l a b l e I n p u t s
// d o e s n o t i n c l u d e m y l i n k e d b l u e t o o t h d e v i c e
setAudioSession ( category : AVAudioSessionCategoryPlayAndRecord ,
mode : AVAudioSessionModeVoiceChat ,
options : [ . allowBluetooth ] )
options : options )
}
let session = AVAudioSession . sharedInstance ( )
do {
// I t ' s i m p o r t a 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 y / o p t i o n c o m b i n a t i o n s .
let existingPreferredInput = session . preferredInput
if existingPreferredInput != call . audioSource ? . portDescription {
Logger . info ( " \( TAG ) changing preferred input: \( String ( describing : existingPreferredInput ) ) -> \( String ( describing : call . audioSource ? . portDescription ) ) " )
try session . setPreferredInput ( call . audioSource ? . portDescription )
}
if call . isSpeakerphoneEnabled {
try session . overrideOutputAudioPort ( . speaker )
} else {
try session . overrideOutputAudioPort ( . none )
}
} catch {
owsFail ( " \( TAG ) failed overrideing output audio. isSpeakerPhoneEnabled: \( call . isSpeakerphoneEnabled ) " )
owsFail ( " \( TAG ) failed setting audio source with error: \( error ) isSpeakerPhoneEnabled: \( call . isSpeakerphoneEnabled ) " )
}
}
@ -211,6 +239,11 @@ struct AudioSource: Hashable {
Logger . verbose ( " \( TAG ) in \( #function ) new state: \( call . state ) " )
// S t o p p l a y i n g s o u n d s w h i l e s w i t c h i n g a u d i o s e s s i o n s o w e d o n ' t
// g e t a n y b l i p s a c r o s s a t e m p o r a r y u n i n t e n d e d r o u t e .
stopPlayingAnySounds ( )
self . ensureProperAudioSession ( call : call )
switch call . state {
case . idle : handleIdle ( call : call )
case . dialing : handleDialing ( call : call )
@ -233,8 +266,6 @@ struct AudioSource: Hashable {
Logger . debug ( " \( TAG ) \( #function ) " )
AssertIsOnMainThread ( )
ensureProperAudioSession ( call : call )
// 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 ) {
@ -245,17 +276,12 @@ struct AudioSource: Hashable {
private func handleAnswering ( call : SignalCall ) {
Logger . debug ( " \( TAG ) \( #function ) " )
AssertIsOnMainThread ( )
stopPlayingAnySounds ( )
self . ensureProperAudioSession ( call : call )
}
private func handleRemoteRinging ( call : SignalCall ) {
Logger . debug ( " \( TAG ) \( #function ) " )
AssertIsOnMainThread ( )
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 ?
self . play ( sound : Sound . outgoingRing )
}
@ -264,27 +290,18 @@ struct AudioSource: Hashable {
Logger . debug ( " \( TAG ) in \( #function ) " )
AssertIsOnMainThread ( )
stopPlayingAnySounds ( )
ensureProperAudioSession ( call : call )
startRinging ( call : call )
}
private func handleConnected ( call : SignalCall ) {
Logger . debug ( " \( TAG ) \( #function ) " )
AssertIsOnMainThread ( )
stopPlayingAnySounds ( )
// 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 .
ensureProperAudioSession ( call : call )
}
private func handleLocalFailure ( call : SignalCall ) {
Logger . debug ( " \( TAG ) \( #function ) " )
AssertIsOnMainThread ( )
stopPlayingAnySounds ( )
play ( sound : Sound . failure )
}
@ -308,9 +325,8 @@ struct AudioSource: Hashable {
Logger . debug ( " \( TAG ) \( #function ) " )
AssertIsOnMainThread ( )
stopPlayingAnySounds ( )
play ( sound : Sound . busy )
// 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 ) {
self . handleCallEnded ( call : call )
@ -321,8 +337,6 @@ struct AudioSource: Hashable {
Logger . debug ( " \( TAG ) \( #function ) " )
AssertIsOnMainThread ( )
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 )
}
@ -431,17 +445,6 @@ struct AudioSource: Hashable {
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 )
}
private func setAudioSession ( category : String ,
mode : String ? = nil ,
options : AVAudioSessionCategoryOptions = AVAudioSessionCategoryOptions ( rawValue : 0 ) ) {