session-ios/Signal/src/call/CallAudioService.swift

196 lines
5.9 KiB
Swift
Raw Normal View History

//
// Copyright © 2017 Open Whisper Systems. All rights reserved.
//
import Foundation
@objc class CallAudioService: NSObject, CallObserver {
private let TAG = "[CallAudioService]"
private var vibrateTimer: Timer?
private let soundPlayer = JSQSystemSoundPlayer.shared()!
private let handleRinging: Bool
enum SoundFilenames: String {
case incomingRing = "r"
}
// MARK: Vibration config
private let vibrateRepeatDuration = 1.6
// Our ring buzz is a pair of vibrations.
// `pulseDuration` is the small pause between the two vibrations in the pair.
private let pulseDuration = 0.2
// MARK: - Initializers
init(handleRinging: Bool) {
self.handleRinging = handleRinging
}
// MARK: - CallObserver
internal func stateDidChange(call: SignalCall, state: CallState) {
DispatchQueue.main.async {
self.handleState(state)
}
}
internal func muteDidChange(call: SignalCall, isMuted: Bool) {
Logger.verbose("\(TAG) in \(#function) is no-op")
}
internal func speakerphoneDidChange(call: SignalCall, isEnabled: Bool) {
if isEnabled {
setAudioSession(category: AVAudioSessionCategoryPlayAndRecord, options: .defaultToSpeaker)
} else {
setAudioSession(category: AVAudioSessionCategoryPlayAndRecord)
}
}
internal func hasVideoDidChange(call: SignalCall, hasVideo: Bool) {
// no-op
}
// MARK: - Service action handlers
public func handleState(_ state: CallState) {
2017-01-23 22:17:55 +01:00
assert(Thread.isMainThread)
Logger.verbose("\(TAG) in \(#function) new state: \(state)")
switch state {
case .idle: handleIdle()
case .dialing: handleDialing()
case .answering: handleAnswering()
case .remoteRinging: handleRemoteRinging()
case .localRinging: handleLocalRinging()
case .connected: handleConnected()
case .localFailure: handleLocalFailure()
case .localHangup: handleLocalHangup()
case .remoteHangup: handleRemoteHangup()
case .remoteBusy: handleBusy()
}
}
private func handleIdle() {
Logger.debug("\(TAG) \(#function)")
}
private func handleDialing() {
Logger.debug("\(TAG) \(#function)")
}
private func handleAnswering() {
Logger.debug("\(TAG) \(#function)")
stopRinging()
}
private func handleRemoteRinging() {
Logger.debug("\(TAG) \(#function)")
}
private func handleLocalRinging() {
Logger.debug("\(TAG) in \(#function)")
startRinging()
}
private func handleConnected() {
Logger.debug("\(TAG) \(#function)")
stopRinging()
// disable start recording to transmit call audio.
setAudioSession(category: AVAudioSessionCategoryPlayAndRecord)
}
private func handleLocalFailure() {
Logger.debug("\(TAG) \(#function)")
stopRinging()
}
private func handleLocalHangup() {
Logger.debug("\(TAG) \(#function)")
stopRinging()
}
private func handleRemoteHangup() {
Logger.debug("\(TAG) \(#function)")
stopRinging()
}
private func handleBusy() {
Logger.debug("\(TAG) \(#function)")
stopRinging()
}
// MARK: - Ringing
private func startRinging() {
guard handleRinging else {
Logger.debug("\(TAG) ignoring \(#function) since CallKit handles it's own ringing state")
return
}
2017-01-19 16:57:07 +01:00
vibrateTimer = WeakTimer.scheduledTimer(timeInterval: vibrateRepeatDuration, target: self, userInfo: nil, repeats: true) {[weak self] timer in
self?.ringVibration()
}
vibrateTimer?.fire()
// Stop other sounds and play ringer through external speaker
setAudioSession(category: AVAudioSessionCategorySoloAmbient)
soundPlayer.playSound(withFilename: SoundFilenames.incomingRing.rawValue, fileExtension: kJSQSystemSoundTypeCAF)
}
private func stopRinging() {
guard handleRinging else {
Logger.debug("\(TAG) ignoring \(#function) since CallKit handles it's own ringing state")
return
}
Logger.debug("\(TAG) in \(#function)")
// Stop vibrating
vibrateTimer?.invalidate()
vibrateTimer = nil
soundPlayer.stopSound(withFilename: SoundFilenames.incomingRing.rawValue)
// Stop solo audio, revert to default.
setAudioSession(category: AVAudioSessionCategoryAmbient)
}
// public so it can be called by timer via selector
public func ringVibration() {
// Since a call notification is more urgent than a message notifaction, we
// vibrate twice, like a pulse, to differentiate from a normal notification vibration.
soundPlayer.playVibrateSound()
DispatchQueue.default.asyncAfter(deadline: DispatchTime.now() + pulseDuration) {
self.soundPlayer.playVibrateSound()
}
}
// MARK: - AVAudioSession Helpers
private func setAudioSession(category: String, options: AVAudioSessionCategoryOptions) {
do {
try AVAudioSession.sharedInstance().setCategory(category, with: options)
Logger.debug("\(self.TAG) set category: \(category) options: \(options)")
} catch {
let message = "\(self.TAG) in \(#function) failed to set category: \(category) with error: \(error)"
assertionFailure(message)
Logger.error(message)
}
}
private func setAudioSession(category: String) {
do {
try AVAudioSession.sharedInstance().setCategory(category)
Logger.debug("\(self.TAG) set category: \(category)")
} catch {
let message = "\(self.TAG) in \(#function) failed to set category: \(category) with error: \(error)"
assertionFailure(message)
Logger.error(message)
}
}
}