@ -41,7 +41,8 @@ class CallViewController: UIViewController, CallObserver, CallServiceObserver, R
var ongoingCallView : UIView !
var ongoingCallView : UIView !
var hangUpButton : UIButton !
var hangUpButton : UIButton !
var speakerPhoneButton : UIButton !
var audioRouteButton : UIButton !
var soundRouteButton : UIButton !
var audioModeMuteButton : UIButton !
var audioModeMuteButton : UIButton !
var audioModeVideoButton : UIButton !
var audioModeVideoButton : UIButton !
var videoModeMuteButton : UIButton !
var videoModeMuteButton : UIButton !
@ -86,11 +87,70 @@ class CallViewController: UIViewController, CallObserver, CallServiceObserver, R
var settingsNagView : UIView !
var settingsNagView : UIView !
var settingsNagDescriptionLabel : UILabel !
var settingsNagDescriptionLabel : UILabel !
// MARK: A u d i o R o u t i n g
// v a r h a s A l t e r n a t e A u d i o R o u t e s = f a l s e {
// d i d S e t {
// i f o l d V a l u e ! = h a s A l t e r n a t e A u d i o R o u t e s {
// u p d a t e C a l l U I ( c a l l S t a t e : c a l l . s t a t e )
// }
// }
// }
// T O D O u s e " a u d i o S o u r c e " t e r m i n a l o g y r a t h e r t h a n i n p u t / o u t p u t / r o u t e
var hasAlternateAudioRoutes : Bool {
Logger . info ( " \( TAG ) available audio routes count: \( allAvailableAudioRoutes . 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 allAvailableAudioRoutes . count > 2
}
var allAvailableAudioRoutes : Set < AudioSource >
var availableAudioRoutes : Set < AudioSource > {
if call . hasLocalVideo {
let forVideo = allAvailableAudioRoutes . 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
}
return portDescription . portType != AVAudioSessionPortBuiltInMic
}
}
return Set ( forVideo )
} else {
return allAvailableAudioRoutes
}
}
var audioSource : AudioSource ? {
didSet {
if audioSource != oldValue {
if let audioSource = audioSource {
if audioSource . isBuiltInSpeaker {
// T O D O s e e m s l i k e C V C k n o w s t o o m u c h a b o u t A u d i o S o u r c e .
// M a y b e t h e s e c o n d i t i o n a l s b e l o n g i n t h e c a l l U I A d a p t e r ? O r a u d i o S e r v i c e ?
// s e l f . c a l l U I A d a p t e r . a u d i o S e r v i c e . s e t P r e f e r r e d I n p u t ( a u d i o S o u r c e : a u d i o S o u r c e )
self . callUIAdapter . setIsSpeakerphoneEnabled ( call : self . call , isEnabled : true )
return
}
}
self . callUIAdapter . setIsSpeakerphoneEnabled ( call : self . call , isEnabled : false )
self . callUIAdapter . audioService . setPreferredInput ( call : self . call , audioSource : audioSource )
}
}
}
// MARK: I n i t i a l i z e r s
// MARK: I n i t i a l i z e r s
required init ? ( coder aDecoder : NSCoder ) {
required init ? ( coder aDecoder : NSCoder ) {
contactsManager = Environment . getCurrent ( ) . contactsManager
contactsManager = Environment . getCurrent ( ) . contactsManager
callUIAdapter = Environment . getCurrent ( ) . callUIAdapter
callUIAdapter = Environment . getCurrent ( ) . callUIAdapter
allAvailableAudioRoutes = Set ( callUIAdapter . audioService . availableInputs )
super . init ( coder : aDecoder )
super . init ( coder : aDecoder )
observeNotifications ( )
observeNotifications ( )
}
}
@ -98,6 +158,7 @@ class CallViewController: UIViewController, CallObserver, CallServiceObserver, R
required init ( ) {
required init ( ) {
contactsManager = Environment . getCurrent ( ) . contactsManager
contactsManager = Environment . getCurrent ( ) . contactsManager
callUIAdapter = Environment . getCurrent ( ) . callUIAdapter
callUIAdapter = Environment . getCurrent ( ) . callUIAdapter
allAvailableAudioRoutes = Set ( callUIAdapter . audioService . availableInputs )
super . init ( nibName : nil , bundle : nil )
super . init ( nibName : nil , bundle : nil )
observeNotifications ( )
observeNotifications ( )
}
}
@ -107,6 +168,11 @@ class CallViewController: UIViewController, CallObserver, CallServiceObserver, R
selector : #selector ( didBecomeActive ) ,
selector : #selector ( didBecomeActive ) ,
name : NSNotification . Name . UIApplicationDidBecomeActive ,
name : NSNotification . Name . UIApplicationDidBecomeActive ,
object : nil )
object : nil )
NotificationCenter . default . addObserver ( forName : CallAudioServiceSessionChanged , object : nil , queue : nil ) { _ in
self . didChangeAudioSession ( )
}
}
}
deinit {
deinit {
@ -157,7 +223,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
// 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 )
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
// MARK: - C r e a t e V i e w s
@ -288,8 +354,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 " ,
// 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 ) )
// 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 " ,
audioRout eButton = createButton ( imageName : " audio-call-speaker-inactive " ,
action : #selector ( didPress Speakerphon e) )
action : #selector ( didPress AudioRout e) )
hangUpButton = createButton ( imageName : " hangup-active-wide " ,
hangUpButton = createButton ( imageName : " hangup-active-wide " ,
action : #selector ( didPressHangup ) )
action : #selector ( didPressHangup ) )
audioModeMuteButton = createButton ( imageName : " audio-call-mute-inactive " ,
audioModeMuteButton = createButton ( imageName : " audio-call-mute-inactive " ,
@ -305,14 +371,69 @@ class CallViewController: UIViewController, CallObserver, CallServiceObserver, R
setButtonSelectedImage ( button : videoModeMuteButton , imageName : " video-mute-selected " )
setButtonSelectedImage ( button : videoModeMuteButton , imageName : " video-mute-selected " )
setButtonSelectedImage ( button : audioModeVideoButton , imageName : " audio-call-video-active " )
setButtonSelectedImage ( button : audioModeVideoButton , imageName : " audio-call-video-active " )
setButtonSelectedImage ( button : videoModeVideoButton , imageName : " video-video-selected " )
setButtonSelectedImage ( button : videoModeVideoButton , imageName : " video-video-selected " )
setButtonSelectedImage ( button : speakerPhoneButton , imageName : " audio-call-speaker-active " )
// s e t B u t t o n S e l e c t e d I m a g e ( b u t t o n : a u d i o R o u t e B u t t o n , i m a g e N a m e : " a u d i o - c a l l - s p e a k e r - a c t i v e " )
ongoingCallView = createContainerForCallControls ( controlGroups : [
ongoingCallView = createContainerForCallControls ( controlGroups : [
[ audioModeMuteButton , speakerPhon eButton, audioModeVideoButton ] ,
[ audioModeMuteButton , audioRout eButton, audioModeVideoButton ] ,
[ videoModeMuteButton , hangUpButton , videoModeVideoButton ]
[ videoModeMuteButton , hangUpButton , videoModeVideoButton ]
] )
] )
}
}
func didChangeAudioSession ( ) {
AssertIsOnMainThread ( )
// T O D O u n n e c e s s a r y ?
let availableInputs = callUIAdapter . audioService . availableInputs
self . allAvailableAudioRoutes . formUnion ( availableInputs )
}
func presentAudioRoutePicker ( ) {
Logger . info ( " \( TAG ) in \( #function ) " )
AssertIsOnMainThread ( )
let actionSheetController = UIAlertController ( title : nil , message : nil , preferredStyle : . actionSheet )
let dismissAction = UIAlertAction ( title : CommonStrings . dismissActionText , style : . cancel , handler : nil )
actionSheetController . addAction ( dismissAction )
let currentAudioSource = callUIAdapter . audioService . currentAudioSource ( call : self . call )
for audioSource in self . availableAudioRoutes {
// T O D O a d d i m a g e
let routeAudioAction = UIAlertAction ( title : audioSource . localizedName , style : . default ) { _ in
// D i s a b l e a n y s p e a k e r p h o n e
// T O D O w i l l t h i s u p d a t e t h e U I a p p r o p r i a t e l y ?
self . 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 " )
// 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
routeAudioAction . setValue ( audioSource . image , forKey : " image " )
actionSheetController . addAction ( routeAudioAction )
}
// i f l e t b u i l t I n M i c r o p h o n e S o u r c e = s e l f . c a l l U I A d a p t e r . a u d i o S e r v i c e . b u i l t I n M i c r o p h o n e S o u r c e {
// 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 "
// l e t s p e a k e r p h o n e A c t i o n = U I A l e r t A c t i o n ( t i t l e :
// s t y l e : . d e f a u l t ) { _ i n
// s e l f . u p d a t e A u d i o O u t p u t ( a u d i o S o u r c e : b u i l t I n M i c r o p h o n e S o u r c e )
//
// }
// a c t i o n S h e e t C o n t r o l l e r . a d d A c t i o n ( s p e a k e r p h o n e A c t i o n )
// } e l s e {
// o w s F a i l ( " u n a b l e t o f i n d b u i l t i n m i c r o p h o n e s o u r c e " )
// }
self . present ( actionSheetController , animated : true )
}
func updateAudioOutput ( audioSource : AudioSource ) {
Logger . info ( " \( TAG ) in \( #function ) with audioSource: \( audioSource ) " )
// T h i s s e e m s l i k e o v e r r e a c h . a u d i o s e r v i c e a s p r o p e r t y o n C V C ?
}
func setButtonSelectedImage ( button : UIButton , imageName : String ) {
func setButtonSelectedImage ( button : UIButton , imageName : String ) {
let image = UIImage ( named : imageName )
let image = UIImage ( named : imageName )
assert ( image != nil )
assert ( image != nil )
@ -653,7 +774,6 @@ class CallViewController: UIViewController, CallObserver, CallServiceObserver, R
videoModeMuteButton . isSelected = call . isMuted
videoModeMuteButton . isSelected = call . isMuted
audioModeVideoButton . isSelected = call . hasLocalVideo
audioModeVideoButton . isSelected = call . hasLocalVideo
videoModeVideoButton . 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
// 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
let isRinging = callState = = . localRinging
@ -668,7 +788,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 .
// 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
let hasLocalVideo = ! localVideoView . isHidden
for subview in [ speakerPhoneButton , audioModeMuteButton , audioModeVideoButton ] {
for subview in [ audioModeMuteButton , audioModeVideoButton ] {
subview ? . isHidden = hasLocalVideo
subview ? . isHidden = hasLocalVideo
}
}
for subview in [ videoModeMuteButton , videoModeVideoButton ] {
for subview in [ videoModeMuteButton , videoModeVideoButton ] {
@ -685,6 +806,35 @@ class CallViewController: UIViewController, CallObserver, CallServiceObserver, R
callStatusLabel . isHidden = false
callStatusLabel . isHidden = false
}
}
// H a n d l e a u d i o s o u r c e p i c k i n g i n t e r f a c e ( b l u e t o o t h )
if self . hasAlternateAudioRoutes {
// T O D O p r o p e r i m a g e
Logger . info ( " \( TAG ) in \( #function ) setting alternate audio route image " )
// 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 " .
audioRouteButton . isSelected = false
if hasLocalVideo {
audioRouteButton . setImage ( # imageLiteral ( resourceName : " ic_speaker_bluetooth_inactive_video_mode " ) , for : . normal )
audioRouteButton . setImage ( # imageLiteral ( resourceName : " ic_speaker_bluetooth_inactive_video_mode " ) , for : . selected )
} else {
audioRouteButton . setImage ( # imageLiteral ( resourceName : " ic_speaker_bluetooth_inactive_audio_mode " ) , for : . normal )
audioRouteButton . setImage ( # imageLiteral ( resourceName : " ic_speaker_bluetooth_inactive_audio_mode " ) , for : . selected )
}
audioRouteButton . 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
audioRouteButton . isSelected = call . isSpeakerphoneEnabled
audioRouteButton . setImage ( # imageLiteral ( resourceName : " audio-call-speaker-inactive " ) , for : . normal )
audioRouteButton . 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 .
audioRouteButton . isHidden = hasLocalVideo
}
// D i s m i s s H a n d l i n g
// D i s m i s s H a n d l i n g
switch callState {
switch callState {
case . remoteHangup , . remoteBusy , . localFailure :
case . remoteHangup , . remoteBusy , . localFailure :
@ -742,6 +892,16 @@ class CallViewController: UIViewController, CallObserver, CallServiceObserver, R
}
}
}
}
func didPressAudioRoute ( sender button : UIButton ) {
Logger . info ( " \( TAG ) called \( #function ) " )
if self . hasAlternateAudioRoutes {
presentAudioRoutePicker ( )
} else {
didPressSpeakerphone ( sender : button )
}
}
func didPressSpeakerphone ( sender button : UIButton ) {
func didPressSpeakerphone ( sender button : UIButton ) {
Logger . info ( " \( TAG ) called \( #function ) " )
Logger . info ( " \( TAG ) called \( #function ) " )
button . isSelected = ! button . isSelected
button . isSelected = ! button . isSelected