2017-01-18 17:46:29 +01:00
//
2018-02-03 00:35:32 +01:00
// C o p y r i g h t ( c ) 2 0 1 8 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-11-28 00:17:46 +01:00
import SignalServiceKit
2017-12-01 16:48:18 +01:00
import SignalMessaging
2017-01-18 17:46:29 +01:00
2017-07-12 21:51:07 +02:00
struct AudioSource : Hashable {
let image : UIImage
let localizedName : String
let portDescription : AVAudioSessionPortDescription ?
2017-07-15 00:38:56 +02:00
2017-07-15 23:06:11 +02:00
// 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
2017-07-12 21:51:07 +02:00
let isBuiltInSpeaker : Bool
2017-07-15 23:06:11 +02:00
// 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
2017-07-15 00:38:56 +02:00
let isBuiltInEarPiece : Bool
init ( localizedName : String , image : UIImage , isBuiltInSpeaker : Bool , isBuiltInEarPiece : Bool , portDescription : AVAudioSessionPortDescription ? = nil ) {
2017-07-12 21:51:07 +02:00
self . localizedName = localizedName
self . image = image
self . isBuiltInSpeaker = isBuiltInSpeaker
2017-07-15 00:38:56 +02:00
self . isBuiltInEarPiece = isBuiltInEarPiece
2017-07-12 21:51:07 +02:00
self . portDescription = portDescription
}
init ( portDescription : AVAudioSessionPortDescription ) {
2017-07-14 21:25:55 +02:00
2019-03-30 14:22:31 +01:00
let isBuiltInEarPiece = convertFromAVAudioSessionPort ( portDescription . portType ) = = convertFromAVAudioSessionPort ( AVAudioSession . Port . builtInMic )
2017-07-15 00:38:56 +02:00
2017-07-14 21:25:55 +02:00
// 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 "
2017-07-15 00:38:56 +02:00
let localizedName = isBuiltInEarPiece ? UIDevice . current . localizedModel : portDescription . portName
2017-07-14 21:25:55 +02:00
self . init ( localizedName : localizedName ,
2018-02-22 03:37:32 +01:00
image : # imageLiteral ( resourceName : " button_phone_white " ) , // T O D O
2017-07-12 21:51:07 +02:00
isBuiltInSpeaker : false ,
2017-07-15 00:38:56 +02:00
isBuiltInEarPiece : isBuiltInEarPiece ,
2017-07-12 21:51:07 +02:00
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
2017-07-15 00:38:56 +02:00
isBuiltInSpeaker : true ,
isBuiltInEarPiece : false )
2017-07-12 21:51:07 +02:00
}
// 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 {
2018-08-27 16:27:48 +02:00
owsFailDebug ( " only the built in speaker should lack a port description " )
2017-07-12 21:51:07 +02:00
return false
}
guard let rhsPortDescription = rhs . portDescription else {
2018-08-27 16:27:48 +02:00
owsFailDebug ( " only the built in speaker should lack a port description " )
2017-07-12 21:51:07 +02:00
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
}
}
2018-02-03 00:35:32 +01:00
protocol CallAudioServiceDelegate : class {
func callAudioService ( _ callAudioService : CallAudioService , didUpdateIsSpeakerphoneEnabled isEnabled : Bool )
func callAudioServiceDidChangeAudioSession ( _ callAudioService : CallAudioService )
}
2017-01-19 15:38:50 +01:00
@objc class CallAudioService : NSObject , CallObserver {
2017-01-18 17:46:29 +01:00
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
2018-02-03 00:35:32 +01:00
weak var delegate : CallAudioServiceDelegate ? {
willSet {
assert ( newValue = = nil || delegate = = nil )
}
}
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
2018-02-03 00:35:32 +01:00
var audioSession : OWSAudioSession {
2018-10-23 02:55:39 +02:00
return Environment . shared . audioSession
2018-02-03 00:35:32 +01:00
}
2018-10-23 02:55:39 +02:00
2018-02-03 00:35:32 +01:00
var avAudioSession : AVAudioSession {
return AVAudioSession . sharedInstance ( )
2017-10-28 20:44:29 +02:00
}
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-10-28 20:44:29 +02:00
super . init ( )
2018-02-23 23:08:21 +01:00
// W e c a n n o t a s s e r t s i n g l e t o n h e r e , b e c a u s e t h i s c l a s s g e t s r e b u i l t w h e n t h e u s e r c h a n g e s r e l e v a n t c a l l s e t t i n g s
2017-12-07 16:33:27 +01:00
2017-10-28 20:44:29 +02:00
// C o n f i g u r e a u d i o s e s s i o n s o w e d o n ' t p r o m p t u s e r w i t h R e c o r d p e r m i s s i o n u n t i l c a l l i s c o n n e c t e d .
2018-02-03 00:35:32 +01:00
audioSession . configureRTCAudio ( )
2019-03-30 14:22:31 +01:00
NotificationCenter . default . addObserver ( forName : AVAudioSession . routeChangeNotification , object : avAudioSession , queue : nil ) { _ in
2018-02-03 00:35:32 +01:00
assert ( ! Thread . isMainThread )
self . updateIsSpeakerphoneEnabled ( )
}
2017-01-18 18:31:18 +01:00
}
2018-02-23 20:36:21 +01:00
deinit {
NotificationCenter . default . removeObserver ( self )
}
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 ) {
2018-08-22 19:44:22 +02:00
AssertIsOnMainThread ( )
2018-02-22 03:37:32 +01:00
self . handleState ( call : call )
2017-01-19 15:38:50 +01:00
}
internal func muteDidChange ( call : SignalCall , isMuted : Bool ) {
2018-08-22 19:44:22 +02:00
AssertIsOnMainThread ( )
2017-10-28 20:44:29 +02:00
ensureProperAudioSession ( call : call )
}
internal func holdDidChange ( call : SignalCall , isOnHold : Bool ) {
2018-08-22 19:44:22 +02:00
AssertIsOnMainThread ( )
2017-10-28 20:44:29 +02:00
ensureProperAudioSession ( call : call )
2017-01-19 15:38:50 +01:00
}
2017-07-13 21:37:01 +02:00
internal func audioSourceDidChange ( call : SignalCall , audioSource : AudioSource ? ) {
2018-08-22 19:44:22 +02:00
AssertIsOnMainThread ( )
2017-01-27 17:11:33 +01:00
2017-07-03 15:42:30 +02:00
ensureProperAudioSession ( call : call )
2018-02-03 00:35:32 +01:00
if let audioSource = audioSource , audioSource . isBuiltInSpeaker {
self . isSpeakerphoneEnabled = true
} else {
self . isSpeakerphoneEnabled = false
}
2017-01-27 17:11:33 +01:00
}
internal func hasLocalVideoDidChange ( call : SignalCall , hasLocalVideo : Bool ) {
2018-08-22 19:44:22 +02:00
AssertIsOnMainThread ( )
2017-01-27 17:11:33 +01:00
2017-07-03 15:42:30 +02:00
ensureProperAudioSession ( call : call )
2017-01-27 17:11:33 +01:00
}
2018-02-03 00:35:32 +01:00
// S p e a k e r p h o n e c a n b e m a n i p u l a t e d b y t h e i n - a p p c a l l s c r e e n o r v i a t h e s y s t e m c a l l s c r e e n ( C a l l K i t ) .
// U n l i k e o t h e r C a l l K i t C a l l S c r e e n b u t t o n s , e n a b l i n g d o e s n ' t t r i g g e r a C X A c t i o n , s o i t ' s n o t a s s i m p l e
// t o t r a c k s t a t e c h a n g e s . I n s t e a d w e n e v e r s t o r e t h e s t a t e a n d d i r e c t l y a c c e s s t h e g r o u n d - t r u t h i n t h e
// A V A u d i o S e s s i o n .
private ( set ) var isSpeakerphoneEnabled : Bool = false {
didSet {
self . delegate ? . callAudioService ( self , didUpdateIsSpeakerphoneEnabled : isSpeakerphoneEnabled )
}
}
public func requestSpeakerphone ( isEnabled : Bool ) {
// T h i s i s a l i t t l e t o o s l o w t o e x e c u t e o n t h e m a i n t h r e a d a n d t h e r e s u l t s a r e n o t i m m e d i a t e l y a v a i l a b l e a f t e r e x e c u t i o n
// a n y w a y , s o w e d i s p a t c h a s y n c . I f y o u n e e d t o k n o w t h e n e w v a l u e , y o u ' l l n e e d t o c h e c k i s S p e a k e r p h o n e E n a b l e d a n d t a k e
// a d v a n t a g e o f t h e C a l l A u d i o S e r v i c e D e l e g a t e . c a l l A u d i o S e r v i c e ( _ : d i d U p d a t e I s S p e a k e r p h o n e E n a b l e d : )
DispatchQueue . global ( ) . async {
do {
try self . avAudioSession . overrideOutputAudioPort ( isEnabled ? . speaker : . none )
} catch {
2018-08-27 16:27:48 +02:00
owsFailDebug ( " failed to set \( #function ) = \( isEnabled ) with error: \( error ) " )
2018-02-03 00:35:32 +01:00
}
}
}
private func updateIsSpeakerphoneEnabled ( ) {
let value = avAudioSession . currentRoute . outputs . contains { ( portDescription : AVAudioSessionPortDescription ) -> Bool in
2019-03-30 14:22:31 +01:00
return portDescription . portName = = convertFromAVAudioSessionPort ( AVAudioSession . Port . builtInSpeaker )
2018-02-03 00:35:32 +01:00
}
DispatchQueue . main . async {
self . isSpeakerphoneEnabled = value
}
}
2017-07-03 15:42:30 +02:00
private func ensureProperAudioSession ( call : SignalCall ? ) {
2018-08-22 19:44:22 +02:00
AssertIsOnMainThread ( )
2017-07-14 00:42:35 +02:00
2018-02-17 02:41:41 +01:00
guard let call = call , ! call . isTerminated else {
// R e v e r t t o d e f a u l t a u d i o
2019-03-30 14:22:31 +01:00
setAudioSession ( category : convertFromAVAudioSessionCategory ( AVAudioSession . Category . soloAmbient ) ,
mode : convertFromAVAudioSessionMode ( AVAudioSession . Mode . default ) )
2017-05-03 15:34:13 +02:00
return
}
2017-07-15 23:06:11 +02:00
// 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
2017-07-15 00:38:56 +02:00
// 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 .
2017-07-15 23:06:11 +02:00
// 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
2019-03-30 14:22:31 +01:00
let options : AVAudioSession . CategoryOptions = call . audioSource ? . isBuiltInEarPiece = = true ? [ ] : [ . allowBluetooth ]
2017-07-15 00:38:56 +02:00
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
2019-03-30 14:22:31 +01:00
setAudioSession ( category : convertFromAVAudioSessionCategory ( AVAudioSession . Category . soloAmbient ) ,
mode : convertFromAVAudioSessionMode ( AVAudioSession . Mode . default ) )
2018-02-03 00:35:32 +01:00
} else if call . hasLocalVideo {
2017-10-27 22:34:21 +02:00
// B e c a u s e M o d e V i d e o C h a t a f f e c t s g a i n , w e d o n ' t w a n t t o a p p l y i t u n t i l t h e c a l l i s c o n n e c t e d .
// o t h e r w i s e s o u n d s l i k e r i n g i n g w i l l b e e x t r a l o u d f o r v i d e o v s . s p e a k e r p h o n e
2017-07-14 22:13:30 +02:00
// 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
2019-03-30 14:22:31 +01:00
setAudioSession ( category : convertFromAVAudioSessionCategory ( AVAudioSession . Category . playAndRecord ) ,
mode : convertFromAVAudioSessionMode ( AVAudioSession . Mode . videoChat ) ,
2017-07-15 00:38:56 +02:00
options : options )
2017-01-19 15:38:50 +01:00
} else {
2017-07-14 22:13:30 +02:00
// 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
2019-03-30 14:22:31 +01:00
setAudioSession ( category : convertFromAVAudioSessionCategory ( AVAudioSession . Category . playAndRecord ) ,
mode : convertFromAVAudioSessionMode ( AVAudioSession . Mode . voiceChat ) ,
2017-07-15 00:38:56 +02:00
options : options )
2017-01-19 15:38:50 +01:00
}
2017-07-14 00:42:35 +02:00
do {
2017-07-15 23:06:11 +02:00
// 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
2017-07-14 23:26:29 +02:00
// 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 .
2018-02-03 00:35:32 +01:00
let existingPreferredInput = avAudioSession . preferredInput
2017-07-14 23:26:29 +02:00
if existingPreferredInput != call . audioSource ? . portDescription {
2018-08-23 16:37:34 +02:00
Logger . info ( " changing preferred input: \( String ( describing : existingPreferredInput ) ) -> \( String ( describing : call . audioSource ? . portDescription ) ) " )
2018-02-03 00:35:32 +01:00
try avAudioSession . setPreferredInput ( call . audioSource ? . portDescription )
2017-07-14 23:26:29 +02:00
}
2017-07-14 00:42:35 +02:00
} catch {
2018-08-27 16:27:48 +02:00
owsFailDebug ( " failed setting audio source with error: \( error ) isSpeakerPhoneEnabled: \( call . isSpeakerphoneEnabled ) " )
2017-07-14 00:42:35 +02:00
}
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 ? ) {
2018-08-23 16:37:34 +02:00
Logger . verbose ( " " )
2017-05-03 15:34:13 +02:00
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 )
2018-08-23 16:37:34 +02:00
Logger . verbose ( " new state: \( call . state ) " )
2017-01-19 15:38:50 +01:00
2017-07-15 00:50:02 +02:00
// 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 ( )
2017-07-14 23:26:29 +02:00
self . ensureProperAudioSession ( call : call )
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 )
2018-04-19 15:56:09 +02:00
case . reconnecting : handleReconnecting ( call : call )
2017-02-25 02:33:43 +01:00
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 ) {
2018-08-23 16:37:34 +02:00
Logger . debug ( " " )
2017-01-18 17:46:29 +01:00
}
2017-02-25 02:33:43 +01:00
private func handleDialing ( call : SignalCall ) {
2018-08-22 19:44:22 +02:00
AssertIsOnMainThread ( )
2018-08-23 16:37:34 +02:00
Logger . debug ( " " )
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 ) {
2018-02-22 03:37:32 +01:00
self . play ( sound : OWSSound . callConnecting )
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 ) {
2018-08-22 19:44:22 +02:00
AssertIsOnMainThread ( )
2018-08-23 16:37:34 +02:00
Logger . debug ( " " )
2017-01-18 17:46:29 +01:00
}
2017-02-25 02:33:43 +01:00
private func handleRemoteRinging ( call : SignalCall ) {
2018-08-22 19:44:22 +02:00
AssertIsOnMainThread ( )
2018-08-23 16:37:34 +02:00
Logger . debug ( " " )
2017-07-03 15:42:30 +02:00
2018-02-22 03:37:32 +01:00
self . play ( sound : OWSSound . callOutboundRinging )
2017-01-18 17:46:29 +01:00
}
2017-02-25 02:33:43 +01:00
private func handleLocalRinging ( call : SignalCall ) {
2018-08-22 19:44:22 +02:00
AssertIsOnMainThread ( )
2018-08-23 16:37:34 +02:00
Logger . debug ( " " )
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 ) {
2018-08-22 19:44:22 +02:00
AssertIsOnMainThread ( )
2018-08-23 16:37:34 +02:00
Logger . debug ( " " )
2017-01-18 17:46:29 +01:00
}
2018-04-19 15:56:09 +02:00
private func handleReconnecting ( call : SignalCall ) {
2018-08-22 19:44:22 +02:00
AssertIsOnMainThread ( )
2018-08-23 16:37:34 +02:00
Logger . debug ( " " )
2018-04-19 15:56:09 +02:00
}
2017-02-25 02:33:43 +01:00
private func handleLocalFailure ( call : SignalCall ) {
2018-08-22 19:44:22 +02:00
AssertIsOnMainThread ( )
2018-08-23 16:37:34 +02:00
Logger . debug ( " " )
2017-07-03 15:42:30 +02:00
2018-02-22 03:37:32 +01:00
play ( sound : OWSSound . callFailure )
2018-02-03 00:35:32 +01:00
handleCallEnded ( call : call )
2017-01-18 17:46:29 +01:00
}
2017-02-25 02:33:43 +01:00
private func handleLocalHangup ( call : SignalCall ) {
2018-08-22 19:44:22 +02:00
AssertIsOnMainThread ( )
2018-08-23 16:37:34 +02:00
Logger . debug ( " " )
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 ) {
2018-08-22 19:44:22 +02:00
AssertIsOnMainThread ( )
2018-08-23 16:37:34 +02:00
Logger . debug ( " " )
2017-02-25 02:33:43 +01:00
vibrate ( )
2018-02-22 03:37:32 +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 ) {
2018-08-22 19:44:22 +02:00
AssertIsOnMainThread ( )
2018-08-23 16:37:34 +02:00
Logger . debug ( " " )
2017-07-03 15:42:30 +02:00
2018-02-22 03:37:32 +01:00
play ( sound : OWSSound . callBusy )
2017-07-15 00:50:02 +02:00
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 ) {
2018-08-22 19:44:22 +02:00
AssertIsOnMainThread ( )
2018-08-23 16:37:34 +02:00
Logger . debug ( " " )
2017-07-03 15:42:30 +02:00
2017-02-25 02:33:43 +01:00
// 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 .
2018-02-03 00:35:32 +01:00
isSpeakerphoneEnabled = false
2019-03-30 14:22:31 +01:00
setAudioSession ( category : convertFromAVAudioSessionCategory ( AVAudioSession . Category . soloAmbient ) )
2017-02-25 02:33:43 +01:00
}
// MARK: P l a y i n g S o u n d s
2018-02-23 21:44:46 +01:00
var currentPlayer : OWSAudioPlayer ?
2017-02-25 02:33:43 +01:00
private func stopPlayingAnySounds ( ) {
currentPlayer ? . stop ( )
stopAnyRingingVibration ( )
}
2018-02-22 03:37:32 +01:00
private func play ( sound : OWSSound ) {
2018-10-23 16:40:09 +02:00
guard let newPlayer = OWSSounds . audioPlayer ( for : sound , audioBehavior : . call ) else {
2018-08-27 16:27:48 +02:00
owsFailDebug ( " unable to build player for sound: \( OWSSounds . displayName ( for : sound ) ) " )
2017-02-25 02:33:43 +01:00
return
}
2018-08-23 16:37:34 +02:00
Logger . info ( " playing sound: \( OWSSounds . displayName ( for : sound ) ) " )
2017-02-25 02:33:43 +01:00
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 ( )
2018-10-23 16:40:09 +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 {
2018-08-23 16:37:34 +02:00
Logger . debug ( " ignoring \( #function ) since CallKit handles it's own ringing state " )
2017-01-18 18:31:18 +01:00
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 ( )
2018-02-23 20:36:21 +01:00
play ( sound : . defaultiOSIncomingRingtone )
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 {
2018-08-23 16:37:34 +02:00
Logger . debug ( " ignoring \( #function ) since CallKit handles it's own ringing state " )
2017-01-18 18:31:18 +01:00
return
}
2018-08-23 16:37:34 +02:00
Logger . debug ( " " )
2017-01-18 18:31:18 +01:00
// 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 ( )
2018-10-13 21:21:46 +02:00
DispatchQueue . main . 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
}
2018-08-23 16:37:34 +02:00
// MARK: - A u d i o S e s s i o n M G M T
2017-07-12 21:51:07 +02:00
// 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 ] {
2018-02-03 00:35:32 +01:00
guard let availableInputs = avAudioSession . availableInputs else {
2017-10-05 15:21:22 +02:00
// I ' m n o t s u r e w h y t h i s w o u l d h a p p e n , b u t i t m a y i n d i c a t e a n e r r o r .
2018-08-27 16:27:48 +02:00
owsFailDebug ( " No available inputs or inputs not ready " )
2017-07-12 21:51:07 +02:00
return [ AudioSource . builtInSpeaker ]
}
2018-08-23 16:37:34 +02:00
Logger . info ( " availableInputs: \( availableInputs ) " )
2017-07-12 21:51:07 +02:00
return [ AudioSource . builtInSpeaker ] + availableInputs . map { portDescription in
return AudioSource ( portDescription : portDescription )
}
}
func currentAudioSource ( call : SignalCall ) -> AudioSource ? {
2017-07-13 22:04:39 +02:00
if let audioSource = call . audioSource {
return audioSource
}
2017-07-12 21:51:07 +02:00
2017-07-13 22:04:39 +02:00
// B e f o r e t h e u s e r h a s s p e c i f i e d a n a u d i o s o u r c e o n t h e c a l l , w e r e l y o n t h e e x i s t i n g
// s y s t e m s t a t e t o d e t e r m i n e t h e c u r r e n t a u d i o s o u r c e .
// I f a b l u e t o o t h i s c o n n e c t e d , t h i s w i l l b e b l u e t o o t h , o t h e r w i s e
// t h i s w i l l b e t h e r e c e i v e r .
2018-02-03 00:35:32 +01:00
guard let portDescription = avAudioSession . currentRoute . inputs . first else {
2017-07-13 22:04:39 +02:00
return nil
2017-07-12 21:51:07 +02:00
}
2017-07-13 22:04:39 +02:00
return AudioSource ( portDescription : portDescription )
2017-07-12 21:51:07 +02:00
}
2017-01-31 16:43:45 +01:00
private func setAudioSession ( category : String ,
2017-02-03 01:34:58 +01:00
mode : String ? = nil ,
2019-03-30 14:22:31 +01:00
options : AVAudioSession . CategoryOptions = AVAudioSession . CategoryOptions ( rawValue : 0 ) ) {
2017-07-03 15:42:30 +02:00
2018-08-22 19:44:22 +02:00
AssertIsOnMainThread ( )
2017-09-21 22:03:30 +02:00
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 {
2019-03-30 14:22:31 +01:00
let oldCategory = convertFromAVAudioSessionCategory ( avAudioSession . category )
let oldMode = convertFromAVAudioSessionMode ( avAudioSession . mode )
2018-02-03 00:35:32 +01:00
let oldOptions = avAudioSession . categoryOptions
2017-07-07 00:37:25 +02:00
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 {
2018-08-23 16:37:34 +02:00
Logger . debug ( " audio session changed category: \( oldCategory ) -> \( category ) " )
2017-07-07 00:37:25 +02:00
}
if oldMode != mode {
2018-08-23 16:37:34 +02:00
Logger . debug ( " audio session changed mode: \( oldMode ) -> \( mode ) " )
2017-07-07 00:37:25 +02:00
}
if oldOptions != options {
2018-08-23 16:37:34 +02:00
Logger . debug ( " audio session changed options: \( oldOptions ) -> \( options ) " )
2017-07-07 00:37:25 +02:00
}
2019-03-30 14:22:31 +01:00
try avAudioSession . setCategory ( convertToAVAudioSessionCategory ( category ) , mode : AVAudioSession . Mode ( rawValue : mode ) , options : options )
2017-07-07 00:37:25 +02:00
2017-01-31 16:43:45 +01:00
} else {
2019-03-30 14:22:31 +01:00
let oldCategory = convertFromAVAudioSessionCategory ( avAudioSession . category )
2018-02-03 00:35:32 +01:00
let oldOptions = avAudioSession . categoryOptions
2017-07-08 23:43:34 +02:00
2019-03-30 14:22:31 +01:00
guard convertFromAVAudioSessionCategory ( avAudioSession . category ) != category || avAudioSession . 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 {
2018-08-23 16:37:34 +02:00
Logger . debug ( " audio session changed category: \( oldCategory ) -> \( category ) " )
2017-07-07 00:37:25 +02:00
}
if oldOptions != options {
2018-08-23 16:37:34 +02:00
Logger . debug ( " audio session changed options: \( oldOptions ) -> \( options ) " )
2017-07-07 00:37:25 +02:00
}
2018-02-03 00:35:32 +01:00
try avAudioSession . 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 {
2018-08-23 16:37:34 +02:00
let message = " failed to set category: \( category ) mode: \( String ( describing : mode ) ) , options: \( options ) with error: \( error ) "
2018-08-27 16:27:48 +02:00
owsFailDebug ( message )
2017-01-18 17:46:29 +01:00
}
2017-07-12 21:51:07 +02:00
if audioSessionChanged {
2018-08-23 16:37:34 +02:00
Logger . info ( " " )
2018-02-03 00:35:32 +01:00
self . delegate ? . callAudioServiceDidChangeAudioSession ( self )
2017-07-12 21:51:07 +02:00
}
2017-01-18 17:46:29 +01:00
}
}
2019-03-30 14:22:31 +01:00
// H e l p e r f u n c t i o n i n s e r t e d b y S w i f t 4 . 2 m i g r a t o r .
fileprivate func convertFromAVAudioSessionPort ( _ input : AVAudioSession . Port ) -> String {
return input . rawValue
}
// H e l p e r f u n c t i o n i n s e r t e d b y S w i f t 4 . 2 m i g r a t o r .
fileprivate func convertFromAVAudioSessionCategory ( _ input : AVAudioSession . Category ) -> String {
return input . rawValue
}
// H e l p e r f u n c t i o n i n s e r t e d b y S w i f t 4 . 2 m i g r a t o r .
fileprivate func convertFromAVAudioSessionMode ( _ input : AVAudioSession . Mode ) -> String {
return input . rawValue
}
// H e l p e r f u n c t i o n i n s e r t e d b y S w i f t 4 . 2 m i g r a t o r .
fileprivate func convertToAVAudioSessionCategory ( _ input : String ) -> AVAudioSession . Category {
return AVAudioSession . Category ( rawValue : input )
}