@ -41,7 +41,7 @@ class CallViewController: UIViewController, CallObserver, CallServiceObserver, R
var ongoingCallView : UIView !
var hangUpButton : UIButton !
var speakerPhon eButton: UIButton !
var audioSourc eButton: UIButton !
var audioModeMuteButton : UIButton !
var audioModeVideoButton : UIButton !
var videoModeMuteButton : UIButton !
@ -86,11 +86,45 @@ class CallViewController: UIViewController, CallObserver, CallServiceObserver, R
var settingsNagView : UIView !
var settingsNagDescriptionLabel : UILabel !
// MARK: A u d i o S o u r c e
var hasAlternateAudioSources : Bool {
Logger . info ( " \( TAG ) available audio routes count: \( allAudioSources . count ) " )
// i n t e r n a l m i c a n d s p e a k e r p h o n e w i l l b e t h e f i r s t t w o , a n y m o r e t h a n o n e i n d i c a t e s e . g . a n a t t a c h e d b l u e t o o t h d e v i c e .
// T O D O i s t h i s s u f f i c i e n t ? A r e t h e i r d e v i c e s w / b l u e t o o t h b u t n o e x t e r n a l s p e a k e r ? e . g . i p o d ?
return allAudioSources . count > 2
}
var allAudioSources : Set < AudioSource >
var appropriateAudioSources : Set < AudioSource > {
if call . hasLocalVideo {
let appropriateForVideo = allAudioSources . filter { audioSource in
if audioSource . isBuiltInSpeaker {
return true
} else {
guard let portDescription = audioSource . portDescription else {
owsFail ( " Only built in speaker should be lacking a port description. " )
return false
}
// D o n ' t u s e r e c e i v e r w h e n v i d e o i s e n a b l e d . O n l y b l u e t o o t h o r s p e a k e r
return portDescription . portType != AVAudioSessionPortBuiltInMic
}
}
return Set ( appropriateForVideo )
} else {
return allAudioSources
}
}
// MARK: I n i t i a l i z e r s
required init ? ( coder aDecoder : NSCoder ) {
contactsManager = Environment . getCurrent ( ) . contactsManager
callUIAdapter = Environment . getCurrent ( ) . callUIAdapter
allAudioSources = Set ( callUIAdapter . audioService . availableInputs )
super . init ( coder : aDecoder )
observeNotifications ( )
}
@ -98,6 +132,7 @@ class CallViewController: UIViewController, CallObserver, CallServiceObserver, R
required init ( ) {
contactsManager = Environment . getCurrent ( ) . contactsManager
callUIAdapter = Environment . getCurrent ( ) . callUIAdapter
allAudioSources = Set ( callUIAdapter . audioService . availableInputs )
super . init ( nibName : nil , bundle : nil )
observeNotifications ( )
}
@ -107,6 +142,11 @@ class CallViewController: UIViewController, CallObserver, CallServiceObserver, R
selector : #selector ( didBecomeActive ) ,
name : NSNotification . Name . UIApplicationDidBecomeActive ,
object : nil )
NotificationCenter . default . addObserver ( forName : CallAudioServiceSessionChanged , object : nil , queue : nil ) { _ in
self . didChangeAudioSession ( )
}
}
deinit {
@ -157,7 +197,7 @@ class CallViewController: UIViewController, CallObserver, CallServiceObserver, R
// S u b s c r i b e f o r f u t u r e c a l l u p d a t e s
call . addObserverAndSyncState ( observer : self )
Environment . getCurrent ( ) . callService . addObserverAndSyncState ( observer : self )
Environment . getCurrent ( ) . callService . addObserverAndSyncState ( observer : self )
}
// MARK: - C r e a t e V i e w s
@ -288,8 +328,8 @@ class CallViewController: UIViewController, CallObserver, CallServiceObserver, R
// t e x t M e s s a g e B u t t o n = c r e a t e B u t t o n ( i m a g e N a m e : " m e s s a g e - a c t i v e - w i d e " ,
// a c t i o n : # s e l e c t o r ( d i d P r e s s T e x t M e s s a g e ) )
speakerPhon eButton = createButton ( imageName : " audio-call-speaker-inactive " ,
action : #selector ( didPress Speakerphon e) )
audioSourc eButton = createButton ( imageName : " audio-call-speaker-inactive " ,
action : #selector ( didPress AudioSourc e) )
hangUpButton = createButton ( imageName : " hangup-active-wide " ,
action : #selector ( didPressHangup ) )
audioModeMuteButton = createButton ( imageName : " audio-call-mute-inactive " ,
@ -305,12 +345,53 @@ class CallViewController: UIViewController, CallObserver, CallServiceObserver, R
setButtonSelectedImage ( button : videoModeMuteButton , imageName : " video-mute-selected " )
setButtonSelectedImage ( button : audioModeVideoButton , imageName : " audio-call-video-active " )
setButtonSelectedImage ( button : videoModeVideoButton , imageName : " video-video-selected " )
setButtonSelectedImage ( button : speakerPhoneButton , imageName : " audio-call-speaker-active " )
ongoingCallView = createContainerForCallControls ( controlGroups : [
[ audioModeMuteButton , speakerPhon eButton, audioModeVideoButton ] ,
[ audioModeMuteButton , audioSourc eButton, audioModeVideoButton ] ,
[ videoModeMuteButton , hangUpButton , videoModeVideoButton ]
] )
] )
}
func didChangeAudioSession ( ) {
AssertIsOnMainThread ( )
// W h i c h s o u r c e s a r e a v a i l a b l e d e p e n d s o n t h e s t a t e o f y o u r S e s s i o n .
// W h e n t h e a u d i o s e s s i o n i s n o t y e t i n P l a y A n d R e c o r d n o n e a r e a v a i l a b l e
// T h e n i f w e ' r e i n s p e a k e r p h o n e , b l u e t o o t h i s n ' t a v a i l a b l e .
// S o w e a c r e w a l l p o s s i b l e a u d i o s o u r c e s i n a s e t , a n d t h a t l i s t l i v e s a s l o n g s a s t h e C a l l V i e w C o n t r o l l e r
// T h e d o w n s i d e o f t h i s i s t h a t i f y o u e . g . u n p a i r y o u r b l u e t o o t h m i d c a l l , i t w i l l s t i l l a p p e a r a s a n o p t i o n
// u n t i l y o u r n e x t c a l l .
// FIXME: T h e r e ' s g o t t o b e a b e t t e r w a y , b u t t h i s i s w h e r e I l a n d e d a f t e r a b i t o f w o r k , a n d s e e m s t o w o r k
// p r e t t y w e l l i n p r a c t r i c e .
let availableInputs = callUIAdapter . audioService . availableInputs
self . allAudioSources . formUnion ( availableInputs )
}
func presentAudioSourcePicker ( ) {
AssertIsOnMainThread ( )
let actionSheetController = UIAlertController ( title : nil , message : nil , preferredStyle : . actionSheet )
let dismissAction = UIAlertAction ( title : CommonStrings . dismissButton , style : . cancel , handler : nil )
actionSheetController . addAction ( dismissAction )
let currentAudioSource = callUIAdapter . audioService . currentAudioSource ( call : self . call )
for audioSource in self . appropriateAudioSources {
let routeAudioAction = UIAlertAction ( title : audioSource . localizedName , style : . default ) { _ in
self . callUIAdapter . setAudioSource ( call : self . call , audioSource : audioSource )
}
// H A C K : p r i v a t e A P I t o c r e a t e c h e c k m a r k f o r a c t i v e a u d i o s o u r c e .
routeAudioAction . setValue ( currentAudioSource = = audioSource , forKey : " checked " )
// TODO: p i c k s o m e i c o n s . L e a v i n g o u t f o r M V P
// H A C K : p r i v a t e A P I t o a d d i m a g e t o a c t i o n s h e e t
// r o u t e A u d i o A c t i o n . s e t V a l u e ( a u d i o S o u r c e . i m a g e , f o r K e y : " i m a g e " )
actionSheetController . addAction ( routeAudioAction )
}
self . present ( actionSheetController , animated : true )
}
func setButtonSelectedImage ( button : UIButton , imageName : String ) {
@ -653,7 +734,6 @@ class CallViewController: UIViewController, CallObserver, CallServiceObserver, R
videoModeMuteButton . isSelected = call . isMuted
audioModeVideoButton . isSelected = call . hasLocalVideo
videoModeVideoButton . isSelected = call . hasLocalVideo
speakerPhoneButton . isSelected = call . isSpeakerphoneEnabled
// S h o w I n c o m i n g v s . O n g o i n g c a l l c o n t r o l s
let isRinging = callState = = . localRinging
@ -668,7 +748,8 @@ class CallViewController: UIViewController, CallObserver, CallServiceObserver, R
// R e w o r k c o n t r o l s t a t e i f l o c a l v i d e o i s a v a i l a b l e .
let hasLocalVideo = ! localVideoView . isHidden
for subview in [ speakerPhoneButton , audioModeMuteButton , audioModeVideoButton ] {
for subview in [ audioModeMuteButton , audioModeVideoButton ] {
subview ? . isHidden = hasLocalVideo
}
for subview in [ videoModeMuteButton , videoModeVideoButton ] {
@ -685,6 +766,32 @@ class CallViewController: UIViewController, CallObserver, CallServiceObserver, R
callStatusLabel . isHidden = false
}
// A u d i o S o u r c e H a n d l i n g ( b l u e t o o t h )
if self . hasAlternateAudioSources {
// W i t h b l u e t o o t h , b u t t o n d o e s n o t s t a y s e l e c t e d . P r e s s i n g i t p o p s a n a c t i o n s h e e t
// a n d t h e b u t t o n s h o u l d i m m e d i a t e l y " u n s e l e c t " .
audioSourceButton . isSelected = false
if hasLocalVideo {
audioSourceButton . setImage ( # imageLiteral ( resourceName : " ic_speaker_bluetooth_inactive_video_mode " ) , for : . normal )
audioSourceButton . setImage ( # imageLiteral ( resourceName : " ic_speaker_bluetooth_inactive_video_mode " ) , for : . selected )
} else {
audioSourceButton . setImage ( # imageLiteral ( resourceName : " ic_speaker_bluetooth_inactive_audio_mode " ) , for : . normal )
audioSourceButton . setImage ( # imageLiteral ( resourceName : " ic_speaker_bluetooth_inactive_audio_mode " ) , for : . selected )
}
audioSourceButton . isHidden = false
} else {
// N o b l u e t o o t h a u d i o d e t e c t e d
audioSourceButton . isSelected = call . isSpeakerphoneEnabled
audioSourceButton . setImage ( # imageLiteral ( resourceName : " audio-call-speaker-inactive " ) , for : . normal )
audioSourceButton . setImage ( # imageLiteral ( resourceName : " audio-call-speaker-active " ) , for : . selected )
// I f t h e r e ' s n o b l u e t o o t h , w e a l w a y s u s e s p e a k e r p h o n e , s o n o n e e d f o r
// a b u t t o n , g i v i n g m o r e s c r e e n b a c k f o r t h e v i d e o .
audioSourceButton . isHidden = hasLocalVideo
}
// D i s m i s s H a n d l i n g
switch callState {
case . remoteHangup , . remoteBusy , . localFailure :
@ -742,17 +849,32 @@ class CallViewController: UIViewController, CallObserver, CallServiceObserver, R
}
}
func didPressSpeakerphone ( sender speakerphoneButton : UIButton ) {
func didPressAudioSource ( sender button : UIButton ) {
Logger . info ( " \( TAG ) called \( #function ) " )
if self . hasAlternateAudioSources {
presentAudioSourcePicker ( )
} else {
didPressSpeakerphone ( sender : button )
}
}
func didPressSpeakerphone ( sender button : UIButton ) {
Logger . info ( " \( TAG ) called \( #function ) " )
speakerphoneButton . isSelected = ! speakerphoneButton . isSelected
button. isSelected = ! b utton. isSelected
if let call = self . call {
callUIAdapter . setIsSpeakerphoneEnabled ( call : call , isEnabled : speakerphoneButton . isSelected )
if button . isSelected {
callUIAdapter . setAudioSource ( call : call , audioSource : AudioSource . builtInSpeaker )
} else {
// u s e d e f a u l t a u d i o s o u r c e
callUIAdapter . setAudioSource ( call : call , audioSource : nil )
}
} else {
Logger . warn ( " \( TAG ) pressed mute, but call was unexpectedly nil " )
}
}
func didPressTextMessage ( sender speakerphoneButton : UIButton ) {
func didPressTextMessage ( sender b utton: UIButton ) {
Logger . info ( " \( TAG ) called \( #function ) " )
dismissIfPossible ( shouldDelay : false )
@ -860,7 +982,7 @@ class CallViewController: UIViewController, CallObserver, CallServiceObserver, R
self . updateCallUI ( callState : call . state )
}
internal func speakerphoneDidChange( call : SignalCall , isEnabled : Bool ) {
internal func audioSourceDidChange( call : SignalCall , audioSource : AudioSource ? ) {
AssertIsOnMainThread ( )
self . updateCallUI ( callState : call . state )
}