mirror of
https://github.com/oxen-io/session-ios.git
synced 2023-12-13 21:30:14 +01:00
Merge branch 'mkirk/webrtc/fix-non-callkit-ringer' into feature/webrtc
This commit is contained in:
commit
cabd85c854
|
@ -102,6 +102,10 @@
|
|||
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 */; };
|
||||
45F170D61E315310003FC1F2 /* Weak.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45F170D51E315310003FC1F2 /* Weak.swift */; };
|
||||
45F170CC1E310E22003FC1F2 /* WeakTimer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45F170CB1E310E22003FC1F2 /* WeakTimer.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 +700,9 @@
|
|||
45F170AB1E2F0351003FC1F2 /* CallAudioSession.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CallAudioSession.swift; sourceTree = "<group>"; };
|
||||
45F170AE1E2F0393003FC1F2 /* CallAudioSessionTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = CallAudioSessionTest.swift; path = test/call/CallAudioSessionTest.swift; sourceTree = "<group>"; };
|
||||
45F170B31E2F0A6A003FC1F2 /* RTCAudioSession.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RTCAudioSession.h; sourceTree = "<group>"; };
|
||||
45F170BA1E2FC5D3003FC1F2 /* CallAudioService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CallAudioService.swift; sourceTree = "<group>"; };
|
||||
45F170D51E315310003FC1F2 /* Weak.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Weak.swift; sourceTree = "<group>"; };
|
||||
45F170CB1E310E22003FC1F2 /* WeakTimer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WeakTimer.swift; sourceTree = "<group>"; };
|
||||
45F2B1921D9C9F48000D2C69 /* OWSOutgoingMessageCollectionViewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSOutgoingMessageCollectionViewCell.h; sourceTree = "<group>"; };
|
||||
45F2B1931D9C9F48000D2C69 /* OWSOutgoingMessageCollectionViewCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSOutgoingMessageCollectionViewCell.m; sourceTree = "<group>"; };
|
||||
45F2B1951D9CA207000D2C69 /* OWSIncomingMessageCollectionViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = OWSIncomingMessageCollectionViewCell.xib; sourceTree = "<group>"; };
|
||||
|
@ -1535,6 +1542,7 @@
|
|||
458DE9D51DEE3FD00071BB03 /* PeerConnectionClient.swift */,
|
||||
4574A5D51DD6704700C6B692 /* CallService.swift */,
|
||||
45F170AB1E2F0351003FC1F2 /* CallAudioSession.swift */,
|
||||
45F170BA1E2FC5D3003FC1F2 /* CallAudioService.swift */,
|
||||
);
|
||||
path = call;
|
||||
sourceTree = "<group>";
|
||||
|
@ -1944,6 +1952,8 @@
|
|||
45CD81F01DC03A22004C9430 /* OWSLogger.h */,
|
||||
45CD81F11DC03A22004C9430 /* OWSLogger.m */,
|
||||
450DF2041E0D74AC003D14BE /* Platform.swift */,
|
||||
45F170D51E315310003FC1F2 /* Weak.swift */,
|
||||
45F170CB1E310E22003FC1F2 /* WeakTimer.swift */,
|
||||
);
|
||||
path = util;
|
||||
sourceTree = "<group>";
|
||||
|
@ -3131,6 +3141,7 @@
|
|||
453D28BA1D332DB100D523F0 /* OWSMessagesBubblesSizeCalculator.m in Sources */,
|
||||
45F170AC1E2F0351003FC1F2 /* CallAudioSession.swift in Sources */,
|
||||
B68EF9BB1C0B1EBD009C3DCD /* FLAnimatedImageView.m in Sources */,
|
||||
45F170CC1E310E22003FC1F2 /* WeakTimer.swift in Sources */,
|
||||
A5E9D4BB1A65FAD800E4481C /* TSVideoAttachmentAdapter.m in Sources */,
|
||||
E197B61118BBEC1A00F073E5 /* AudioProcessor.m in Sources */,
|
||||
FCAC964019FEF99A0046DFC5 /* InboxTableViewCell.m in Sources */,
|
||||
|
@ -3182,6 +3193,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 */,
|
||||
|
@ -3215,6 +3227,7 @@
|
|||
BFB074C719A5611000F2947C /* FutureUtil.m in Sources */,
|
||||
45E1F3A51DEF20A100852CF1 /* NoSignalContactsView.swift in Sources */,
|
||||
FCD274E21A5AFD8000202277 /* PrivacySettingsTableViewController.m in Sources */,
|
||||
45F170D61E315310003FC1F2 /* Weak.swift in Sources */,
|
||||
76EB057218170B33006006FC /* RecentCall.m in Sources */,
|
||||
B97CBFA818860EA3008E0DE9 /* CountryCodeViewController.m in Sources */,
|
||||
B6B1013C196D213F007E3930 /* SignalKeyingStorage.m in Sources */,
|
||||
|
@ -3272,6 +3285,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 */,
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
#import "UIFont+OWS.h"
|
||||
#import "UIUtil.h"
|
||||
#import "UIView+OWS.h"
|
||||
#import <JSQSystemSoundPlayer.h>
|
||||
#import <SignalServiceKit/Contact.h>
|
||||
#import <SignalServiceKit/Cryptography.h>
|
||||
#import <SignalServiceKit/NSData+Base64.h>
|
||||
|
|
195
Signal/src/call/CallAudioService.swift
Normal file
195
Signal/src/call/CallAudioService.swift
Normal file
|
@ -0,0 +1,195 @@
|
|||
//
|
||||
// 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) {
|
||||
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
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -924,6 +924,7 @@ fileprivate let timeoutSeconds = 60
|
|||
peerConnectionClient?.terminate()
|
||||
|
||||
peerConnectionClient = nil
|
||||
call?.removeAllObservers()
|
||||
call = nil
|
||||
thread = nil
|
||||
incomingCallPromise = nil
|
||||
|
|
|
@ -14,6 +14,9 @@ class NonCallKitCallUIAdaptee: CallUIAdaptee {
|
|||
let notificationsAdapter: CallNotificationsAdapter
|
||||
let callService: CallService
|
||||
|
||||
// Starting/Stopping incoming call ringing is our apps responsibility for the non CallKit interface.
|
||||
let hasManualRinger = true
|
||||
|
||||
required init(callService: CallService, notificationsAdapter: CallNotificationsAdapter) {
|
||||
self.callService = callService
|
||||
self.notificationsAdapter = notificationsAdapter
|
||||
|
|
|
@ -17,10 +17,11 @@ enum CallState: String {
|
|||
case remoteBusy // terminal
|
||||
}
|
||||
|
||||
protocol CallDelegate: class {
|
||||
protocol CallObserver: class {
|
||||
func stateDidChange(call: SignalCall, state: CallState)
|
||||
func hasVideoDidChange(call: SignalCall, hasVideo: Bool)
|
||||
func muteDidChange(call: SignalCall, isMuted: Bool)
|
||||
func speakerphoneDidChange(call: SignalCall, isEnabled: Bool)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -30,7 +31,7 @@ protocol CallDelegate: class {
|
|||
|
||||
let TAG = "[SignalCall]"
|
||||
|
||||
weak var delegate: CallDelegate?
|
||||
var observers = [Weak<CallObserver>]()
|
||||
let remotePhoneNumber: String
|
||||
|
||||
// Signal Service identifier for this Call. Used to coordinate the call across remote clients.
|
||||
|
@ -38,12 +39,16 @@ protocol CallDelegate: class {
|
|||
|
||||
// Distinguishes between calls locally, e.g. in CallKit
|
||||
let localId: UUID
|
||||
|
||||
var hasVideo = false {
|
||||
didSet {
|
||||
Logger.debug("\(TAG) hasVideo changed: \(oldValue) -> \(hasVideo)")
|
||||
delegate?.hasVideoDidChange(call: self, hasVideo: hasVideo)
|
||||
for observer in observers {
|
||||
observer.value?.hasVideoDidChange(call: self, hasVideo: hasVideo)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var state: CallState {
|
||||
didSet {
|
||||
Logger.debug("\(TAG) state changed: \(oldValue) -> \(state)")
|
||||
|
@ -56,20 +61,35 @@ protocol CallDelegate: class {
|
|||
} else {
|
||||
connectedDate = nil
|
||||
}
|
||||
|
||||
delegate?.stateDidChange(call: self, state: state)
|
||||
for observer in observers {
|
||||
observer.value?.stateDidChange(call: self, state: state)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var isMuted = false {
|
||||
didSet {
|
||||
Logger.debug("\(TAG) muted changed: \(oldValue) -> \(isMuted)")
|
||||
delegate?.muteDidChange(call: self, isMuted: isMuted)
|
||||
for observer in observers {
|
||||
observer.value?.muteDidChange(call: self, isMuted: isMuted)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var isSpeakerphoneEnabled = false {
|
||||
didSet {
|
||||
Logger.debug("\(TAG) isSpeakerphoneEnabled changed: \(oldValue) -> \(isSpeakerphoneEnabled)")
|
||||
for observer in observers {
|
||||
observer.value?.speakerphoneDidChange(call: self, isEnabled: isSpeakerphoneEnabled)
|
||||
}
|
||||
}
|
||||
}
|
||||
var connectedDate: NSDate?
|
||||
|
||||
var error: CallError?
|
||||
|
||||
// MARK: Initializers and Factory Methods
|
||||
|
||||
init(localId: UUID, signalingId: UInt64, state: CallState, remotePhoneNumber: String) {
|
||||
self.localId = localId
|
||||
self.signalingId = signalingId
|
||||
|
@ -85,7 +105,27 @@ protocol CallDelegate: class {
|
|||
return SignalCall(localId: localId, signalingId: signalingId, state: .answering, remotePhoneNumber: remotePhoneNumber)
|
||||
}
|
||||
|
||||
// -
|
||||
|
||||
func addObserverAndSyncState(observer: CallObserver) {
|
||||
observers.append(Weak(value: observer))
|
||||
|
||||
// Synchronize observer with current call state
|
||||
observer.stateDidChange(call: self, state: self.state)
|
||||
}
|
||||
|
||||
func removeObserver(_ observer: CallObserver) {
|
||||
while let index = observers.index(where: { $0.value === observer }) {
|
||||
observers.remove(at: index)
|
||||
}
|
||||
}
|
||||
|
||||
func removeAllObservers() {
|
||||
observers = []
|
||||
}
|
||||
|
||||
// MARK: Equatable
|
||||
|
||||
static func == (lhs: SignalCall, rhs: SignalCall) -> Bool {
|
||||
return lhs.localId == rhs.localId
|
||||
}
|
||||
|
|
|
@ -23,7 +23,10 @@ final class CallKitCallUIAdaptee: NSObject, CallUIAdaptee, CXProviderDelegate {
|
|||
internal let notificationsAdapter: CallNotificationsAdapter
|
||||
private let provider: CXProvider
|
||||
|
||||
/// The app's provider configuration, representing its CallKit capabilities
|
||||
// CallKit handles incoming ringer stop/start for us. Yay!
|
||||
let hasManualRinger = false
|
||||
|
||||
// The app's provider configuration, representing its CallKit capabilities
|
||||
static var providerConfiguration: CXProviderConfiguration {
|
||||
let localizedName = NSLocalizedString("APPLICATION_NAME", comment: "Name of application")
|
||||
let providerConfiguration = CXProviderConfiguration(localizedName: localizedName)
|
||||
|
|
|
@ -8,6 +8,7 @@ import CallKit
|
|||
|
||||
protocol CallUIAdaptee {
|
||||
var notificationsAdapter: CallNotificationsAdapter { get }
|
||||
var hasManualRinger: Bool { get }
|
||||
|
||||
func startOutgoingCall(_ call: SignalCall)
|
||||
func reportIncomingCall(_ call: SignalCall, callerName: String)
|
||||
|
@ -41,6 +42,7 @@ class CallUIAdapter {
|
|||
let TAG = "[CallUIAdapter]"
|
||||
private let adaptee: CallUIAdaptee
|
||||
private let contactsManager: OWSContactsManager
|
||||
private let audioService: CallAudioService
|
||||
|
||||
required init(callService: CallService, contactsManager: OWSContactsManager, notificationsAdapter: CallNotificationsAdapter) {
|
||||
self.contactsManager = contactsManager
|
||||
|
@ -57,9 +59,13 @@ class CallUIAdapter {
|
|||
Logger.info("\(TAG) choosing non-callkit adaptee for older iOS")
|
||||
adaptee = NonCallKitCallUIAdaptee(callService: callService, notificationsAdapter: notificationsAdapter)
|
||||
}
|
||||
|
||||
audioService = CallAudioService(handleRinging: adaptee.hasManualRinger)
|
||||
}
|
||||
|
||||
internal func reportIncomingCall(_ call: SignalCall, thread: TSContactThread) {
|
||||
call.addObserverAndSyncState(observer: audioService)
|
||||
|
||||
let callerName = self.contactsManager.displayName(forPhoneIdentifier: call.remotePhoneNumber)
|
||||
adaptee.reportIncomingCall(call, callerName: callerName)
|
||||
}
|
||||
|
@ -71,6 +77,8 @@ class CallUIAdapter {
|
|||
|
||||
internal func startOutgoingCall(handle: String) -> SignalCall {
|
||||
let call = SignalCall.outgoingCall(localId: UUID(), remotePhoneNumber: handle)
|
||||
call.addObserverAndSyncState(observer: audioService)
|
||||
|
||||
adaptee.startOutgoingCall(call)
|
||||
return call
|
||||
}
|
||||
|
@ -96,10 +104,23 @@ class CallUIAdapter {
|
|||
}
|
||||
|
||||
internal func setIsMuted(call: SignalCall, isMuted: Bool) {
|
||||
// With CallKit, muting is handled by a CXAction, so it must go through the adaptee
|
||||
adaptee.setIsMuted(call: call, isMuted: isMuted)
|
||||
}
|
||||
|
||||
internal func setHasVideo(call: SignalCall, hasVideo: Bool) {
|
||||
adaptee.setHasVideo(call: call, hasVideo: hasVideo)
|
||||
}
|
||||
|
||||
internal func toggleSpeakerphone(call: SignalCall, isEnabled: Bool) {
|
||||
// Speakerphone is not handled by CallKit (e.g. there is no CXAction), so we handle it w/o going through the
|
||||
// adaptee, relying on the AudioService CallObserver to put the system in a state consistent with the call's
|
||||
// assigned property.
|
||||
call.isSpeakerphoneEnabled = isEnabled
|
||||
}
|
||||
|
||||
// CallKit handles ringing state on it's own. But for non-call kit we trigger ringing start/stop manually.
|
||||
internal var hasManualRinger: Bool {
|
||||
return adaptee.hasManualRinger
|
||||
}
|
||||
}
|
||||
|
|
|
@ -94,7 +94,8 @@
|
|||
|
||||
UILocalNotification *notification = [UILocalNotification new];
|
||||
notification.category = PushManagerCategoriesIncomingCall;
|
||||
notification.soundName = @"r.caf";
|
||||
// Rather than using notification sounds, we control the ringtone and repeat vibrations with the CallAudioManager.
|
||||
// notification.soundName = @"r.caf";
|
||||
NSString *localCallId = call.localId.UUIDString;
|
||||
notification.userInfo = @{ PushManagerUserInfoKeysLocalCallId : localCallId };
|
||||
|
||||
|
|
29
Signal/src/util/Weak.swift
Normal file
29
Signal/src/util/Weak.swift
Normal file
|
@ -0,0 +1,29 @@
|
|||
//
|
||||
// Copyright © 2017 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
/**
|
||||
* Container for a weakly referenced object.
|
||||
*
|
||||
* Only use this for |T| with reference-semantic entities
|
||||
* e.g. inheriting from AnyObject or Class-only protocols, but not structs or enums.
|
||||
*
|
||||
*
|
||||
* Based on https://devforums.apple.com/message/981472#981472, but also supports class-only protocols
|
||||
*/
|
||||
struct Weak<T> {
|
||||
private weak var _value: AnyObject?
|
||||
|
||||
var value: T? {
|
||||
get {
|
||||
return _value as? T
|
||||
}
|
||||
set {
|
||||
_value = newValue as AnyObject
|
||||
}
|
||||
}
|
||||
|
||||
init(value: T) {
|
||||
self.value = value
|
||||
}
|
||||
}
|
43
Signal/src/util/WeakTimer.swift
Normal file
43
Signal/src/util/WeakTimer.swift
Normal file
|
@ -0,0 +1,43 @@
|
|||
//
|
||||
// Copyright © 2017 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
/**
|
||||
* As of iOS10, the timer API's take a block, which makes it easy to reference weak self in Swift. This class offers a
|
||||
* similar API that works pre iOS10.
|
||||
*
|
||||
* Solution modified from
|
||||
* http://stackoverflow.com/questions/16821736/weak-reference-to-nstimer-target-to-prevent-retain-cycle/41003985#41003985
|
||||
*/
|
||||
final class WeakTimer {
|
||||
|
||||
fileprivate weak var timer: Timer?
|
||||
fileprivate weak var target: AnyObject?
|
||||
fileprivate let action: (Timer) -> Void
|
||||
|
||||
fileprivate init(timeInterval: TimeInterval, target: AnyObject, userInfo: Any?, repeats: Bool, action: @escaping (Timer) -> Void) {
|
||||
self.target = target
|
||||
self.action = action
|
||||
self.timer = Timer.scheduledTimer(timeInterval: timeInterval,
|
||||
target: self,
|
||||
selector: #selector(fire),
|
||||
userInfo: userInfo,
|
||||
repeats: repeats)
|
||||
}
|
||||
|
||||
class func scheduledTimer(timeInterval: TimeInterval, target: AnyObject, userInfo: Any?, repeats: Bool, action: @escaping (Timer) -> Void) -> Timer {
|
||||
return WeakTimer(timeInterval: timeInterval,
|
||||
target: target,
|
||||
userInfo: userInfo,
|
||||
repeats: repeats,
|
||||
action: action).timer!
|
||||
}
|
||||
|
||||
@objc fileprivate func fire(timer: Timer) {
|
||||
if target != nil {
|
||||
action(timer)
|
||||
} else {
|
||||
timer.invalidate()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -6,117 +6,11 @@ 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()
|
||||
|
||||
// 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)")
|
||||
audioManager.setAudioEnabled(true)
|
||||
audioManager.handleInboundRing()
|
||||
vibrateTimer = Timer.scheduledTimer(timeInterval: vibrateRepeatDuration, target: self, selector: #selector(vibrate), userInfo: nil, repeats: true)
|
||||
}
|
||||
|
||||
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() {
|
||||
// Disables external speaker used for ringing, unless user enables speakerphone.
|
||||
audioManager.setDefaultAudioProfile()
|
||||
audioManager.cancelAllAudio()
|
||||
|
||||
vibrateTimer?.invalidate()
|
||||
vibrateTimer = nil
|
||||
}
|
||||
|
||||
public func vibrate() {
|
||||
AudioServicesPlaySystemSound(kSystemSoundID_Vibrate)
|
||||
DispatchQueue.default.asyncAfter(deadline: DispatchTime.now() + pulseDuration) {
|
||||
AudioServicesPlaySystemSound(kSystemSoundID_Vibrate)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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.
|
||||
@objc(OWSCallViewController)
|
||||
class CallViewController: UIViewController, CallDelegate {
|
||||
class CallViewController: UIViewController, CallObserver {
|
||||
|
||||
enum CallDirection {
|
||||
case unspecified, outgoing, incoming
|
||||
|
@ -128,7 +22,6 @@ class CallViewController: UIViewController, CallDelegate {
|
|||
|
||||
let callUIAdapter: CallUIAdapter
|
||||
let contactsManager: OWSContactsManager
|
||||
let audioService: CallAudioService
|
||||
|
||||
// MARK: Properties
|
||||
|
||||
|
@ -187,7 +80,6 @@ class CallViewController: UIViewController, CallDelegate {
|
|||
contactsManager = Environment.getCurrent().contactsManager
|
||||
let callService = Environment.getCurrent().callService!
|
||||
callUIAdapter = callService.callUIAdapter
|
||||
audioService = CallAudioService()
|
||||
super.init(coder: aDecoder)
|
||||
}
|
||||
|
||||
|
@ -195,7 +87,6 @@ class CallViewController: UIViewController, CallDelegate {
|
|||
contactsManager = Environment.getCurrent().contactsManager
|
||||
let callService = Environment.getCurrent().callService!
|
||||
callUIAdapter = callService.callUIAdapter
|
||||
audioService = CallAudioService()
|
||||
super.init(nibName: nil, bundle: nil)
|
||||
}
|
||||
|
||||
|
@ -239,8 +130,8 @@ class CallViewController: UIViewController, CallDelegate {
|
|||
// No-op, since call service is already set up at this point, the result of which was presenting this viewController.
|
||||
}
|
||||
|
||||
call.delegate = self
|
||||
stateDidChange(call: call, state: call.state)
|
||||
// Subscribe for future call updates
|
||||
call.addObserverAndSyncState(observer: self)
|
||||
}
|
||||
|
||||
func createViews() {
|
||||
|
@ -594,7 +485,11 @@ class CallViewController: UIViewController, CallDelegate {
|
|||
func didPressSpeakerphone(sender speakerphoneButton: UIButton) {
|
||||
Logger.info("\(TAG) called \(#function)")
|
||||
speakerphoneButton.isSelected = !speakerphoneButton.isSelected
|
||||
audioService.isSpeakerphoneEnabled = speakerphoneButton.isSelected
|
||||
if let call = self.call {
|
||||
callUIAdapter.toggleSpeakerphone(call: call, isEnabled: speakerphoneButton.isSelected)
|
||||
} else {
|
||||
Logger.warn("\(TAG) pressed mute, but call was unexpectedly nil")
|
||||
}
|
||||
}
|
||||
|
||||
func didPressTextMessage(sender speakerphoneButton: UIButton) {
|
||||
|
@ -643,14 +538,13 @@ class CallViewController: UIViewController, CallDelegate {
|
|||
self.dismiss(animated: true)
|
||||
}
|
||||
|
||||
// MARK: - CallDelegate
|
||||
// MARK: - CallObserver
|
||||
|
||||
internal func stateDidChange(call: SignalCall, state: CallState) {
|
||||
DispatchQueue.main.async {
|
||||
Logger.info("\(self.TAG) new call status: \(state)")
|
||||
self.updateCallUI(callState: state)
|
||||
}
|
||||
self.audioService.handleState(state)
|
||||
}
|
||||
|
||||
internal func hasVideoDidChange(call: SignalCall, hasVideo: Bool) {
|
||||
|
@ -664,4 +558,10 @@ class CallViewController: UIViewController, CallDelegate {
|
|||
self.updateCallUI(callState: call.state)
|
||||
}
|
||||
}
|
||||
|
||||
internal func speakerphoneDidChange(call: SignalCall, isEnabled: Bool) {
|
||||
DispatchQueue.main.async {
|
||||
self.updateCallUI(callState: call.state)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue