From 4374e431a2eefd7f1e444aa82ea9ecc627999ee1 Mon Sep 17 00:00:00 2001 From: Michael Kirk Date: Wed, 18 Jan 2017 11:46:29 -0500 Subject: [PATCH] Respect silent switch in and out of app. // FREEBIE --- Signal.xcodeproj/project.pbxproj | 6 + Signal/src/call/CallAudioService.swift | 154 ++++++++++++++++++ .../view controllers/CallViewController.swift | 111 ------------- 3 files changed, 160 insertions(+), 111 deletions(-) create mode 100644 Signal/src/call/CallAudioService.swift diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj index 323df8580..ba9e295dc 100644 --- a/Signal.xcodeproj/project.pbxproj +++ b/Signal.xcodeproj/project.pbxproj @@ -102,6 +102,8 @@ 45F170AC1E2F0351003FC1F2 /* CallAudioSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45F170AB1E2F0351003FC1F2 /* CallAudioSession.swift */; }; 45F170AD1E2F0351003FC1F2 /* CallAudioSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45F170AB1E2F0351003FC1F2 /* CallAudioSession.swift */; }; 45F170AF1E2F0393003FC1F2 /* CallAudioSessionTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45F170AE1E2F0393003FC1F2 /* CallAudioSessionTest.swift */; }; + 45F170BB1E2FC5D3003FC1F2 /* CallAudioService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45F170BA1E2FC5D3003FC1F2 /* CallAudioService.swift */; }; + 45F170BC1E2FC5D3003FC1F2 /* CallAudioService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45F170BA1E2FC5D3003FC1F2 /* CallAudioService.swift */; }; 45F2B1941D9C9F48000D2C69 /* OWSOutgoingMessageCollectionViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 45F2B1931D9C9F48000D2C69 /* OWSOutgoingMessageCollectionViewCell.m */; }; 45F2B1971D9CA207000D2C69 /* OWSIncomingMessageCollectionViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 45F2B1951D9CA207000D2C69 /* OWSIncomingMessageCollectionViewCell.xib */; }; 45F2B1981D9CA207000D2C69 /* OWSOutgoingMessageCollectionViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 45F2B1961D9CA207000D2C69 /* OWSOutgoingMessageCollectionViewCell.xib */; }; @@ -696,6 +698,7 @@ 45F170AB1E2F0351003FC1F2 /* CallAudioSession.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CallAudioSession.swift; sourceTree = ""; }; 45F170AE1E2F0393003FC1F2 /* CallAudioSessionTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = CallAudioSessionTest.swift; path = test/call/CallAudioSessionTest.swift; sourceTree = ""; }; 45F170B31E2F0A6A003FC1F2 /* RTCAudioSession.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RTCAudioSession.h; sourceTree = ""; }; + 45F170BA1E2FC5D3003FC1F2 /* CallAudioService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CallAudioService.swift; sourceTree = ""; }; 45F2B1921D9C9F48000D2C69 /* OWSOutgoingMessageCollectionViewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSOutgoingMessageCollectionViewCell.h; sourceTree = ""; }; 45F2B1931D9C9F48000D2C69 /* OWSOutgoingMessageCollectionViewCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSOutgoingMessageCollectionViewCell.m; sourceTree = ""; }; 45F2B1951D9CA207000D2C69 /* OWSIncomingMessageCollectionViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = OWSIncomingMessageCollectionViewCell.xib; sourceTree = ""; }; @@ -1535,6 +1538,7 @@ 458DE9D51DEE3FD00071BB03 /* PeerConnectionClient.swift */, 4574A5D51DD6704700C6B692 /* CallService.swift */, 45F170AB1E2F0351003FC1F2 /* CallAudioSession.swift */, + 45F170BA1E2FC5D3003FC1F2 /* CallAudioService.swift */, ); path = call; sourceTree = ""; @@ -3182,6 +3186,7 @@ 453D28B71D32BA5F00D523F0 /* OWSDisplayedMessage.m in Sources */, 76EB05DC18170B33006006FC /* StreamPair.m in Sources */, 76EB064618170B33006006FC /* TimeUtil.m in Sources */, + 45F170BB1E2FC5D3003FC1F2 /* CallAudioService.swift in Sources */, 70BAFD5D190584BE00FA5E0B /* NotificationTracker.m in Sources */, 76EB05A418170B33006006FC /* PacketHandler.m in Sources */, E197B62118BBF12700F073E5 /* AppAudioManager.m in Sources */, @@ -3272,6 +3277,7 @@ B660F70C1C29988E00687D6E /* StretchFactorController.m in Sources */, B660F70D1C29988E00687D6E /* AnonymousAudioCallbackHandler.m in Sources */, 45F170AD1E2F0351003FC1F2 /* CallAudioSession.swift in Sources */, + 45F170BC1E2FC5D3003FC1F2 /* CallAudioService.swift in Sources */, B660F70E1C29988E00687D6E /* RemoteIOAudio.m in Sources */, B660F70F1C29988E00687D6E /* RemoteIOBufferListWrapper.m in Sources */, 456F6E2F1E261D1000FD2210 /* PeerConnectionClientTest.swift in Sources */, diff --git a/Signal/src/call/CallAudioService.swift b/Signal/src/call/CallAudioService.swift new file mode 100644 index 000000000..f29181374 --- /dev/null +++ b/Signal/src/call/CallAudioService.swift @@ -0,0 +1,154 @@ +// +// Copyright © 2017 Open Whisper Systems. All rights reserved. +// + +import Foundation + +@objc class CallAudioService: NSObject { + private let TAG = "[CallAudioService]" + private var vibrateTimer: Timer? + private let audioManager = AppAudioManager.sharedInstance() + private let soundPlayer = JSQSystemSoundPlayer.shared()! + + 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 + + public var isSpeakerphoneEnabled = false { + didSet { + handleUpdatedSpeakerphone() + } + } + + public func handleState(_ state: CallState) { + 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) \(#function)") + + vibrateTimer = Timer.scheduledTimer(timeInterval: vibrateRepeatDuration, target: self, selector: #selector(ringVibration), userInfo: nil, repeats: true) + + // Stop other sounds and play ringer through external speaker + setAudioSession(category: AVAudioSessionCategorySoloAmbient) + soundPlayer.playSound(withFilename: SoundFilenames.incomingRing.rawValue, fileExtension: kJSQSystemSoundTypeCAF) + } + + 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() + } + + private func handleUpdatedSpeakerphone() { + // TODO +// let category = AVAudioSession.sharedInstance().getCategory() +// if isSpeakerphoneEnabled { +// AVAudioSession.sharedInstance().setCategory(category, option: AVAudioSessionCategoryOptionDefaultToSpeaker) +// } else { + // Will this disable speaker? +// AVAudioSession.sharedInstance().setCategory(category) +// } + + audioManager.toggleSpeakerPhone(isEnabled: isSpeakerphoneEnabled) + } + + // MARK: Helpers + + private func stopRinging() { + vibrateTimer?.invalidate() + vibrateTimer = nil + soundPlayer.stopSound(withFilename: SoundFilenames.incomingRing.rawValue) + // Stop playing out of speaker + 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() + } + } + + 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) + } + } +} diff --git a/Signal/src/view controllers/CallViewController.swift b/Signal/src/view controllers/CallViewController.swift index d4f3137f6..18653d144 100644 --- a/Signal/src/view controllers/CallViewController.swift +++ b/Signal/src/view controllers/CallViewController.swift @@ -6,117 +6,6 @@ import Foundation import WebRTC import PromiseKit -// TODO move this somewhere else. -@objc class CallAudioService: NSObject { - private let TAG = "[CallAudioService]" - private var vibrateTimer: Timer? - private let audioManager = AppAudioManager.sharedInstance() - private let soundPlayer = JSQSystemSoundPlayer.shared()! - - 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 - - public var isSpeakerphoneEnabled = false { - didSet { - handleUpdatedSpeakerphone() - } - } - - public func handleState(_ state: CallState) { - 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) \(#function)") - - vibrateTimer = Timer.scheduledTimer(timeInterval: vibrateRepeatDuration, target: self, selector: #selector(ringVibration), userInfo: nil, repeats: true) - soundPlayer.playSound(withFilename: SoundFilenames.incomingRing.rawValue, fileExtension: kJSQSystemSoundTypeCAF) - } - - private func handleConnected() { - Logger.debug("\(TAG) \(#function)") - stopRinging() - } - - 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() - } - - private func handleUpdatedSpeakerphone() { - audioManager.toggleSpeakerPhone(isEnabled: isSpeakerphoneEnabled) - } - - // MARK: Helpers - - private func stopRinging() { - vibrateTimer?.invalidate() - vibrateTimer = nil - - soundPlayer.stopSound(withFilename: SoundFilenames.incomingRing.rawValue) - } - - 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() - } - } -} - // TODO: Add category so that button handlers can be defined where button is created. // TODO: Add logic to button handlers. // TODO: Ensure buttons enabled & disabled as necessary.