Sync "mute" controls between CallKit
In the process, extracted the CallDelegate to allow the CAllViewController to observe useful call state properties (call.state and call.isMuted) // FREEBIE
This commit is contained in:
parent
33db2715f3
commit
947a637669
|
@ -347,6 +347,8 @@
|
|||
<outlet property="contactAvatarView" destination="id3-xi-PFz" id="CUV-hJ-Qcp"/>
|
||||
<outlet property="contactNameLabel" destination="UbL-8Z-oK1" id="h9V-l9-JVF"/>
|
||||
<outlet property="incomingCallControls" destination="5E5-dq-23I" id="fWz-1n-pjI"/>
|
||||
<outlet property="muteButton" destination="NES-Ce-PcK" id="Eku-Tx-cMN"/>
|
||||
<outlet property="speakerPhoneButton" destination="Bb2-w8-mPU" id="GYN-Z6-i5I"/>
|
||||
</connections>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="5mi-rT-gg5" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
|
|
|
@ -718,6 +718,13 @@ fileprivate let timeoutSeconds = 60
|
|||
handleFailedCall(error: .assertionError(description:"\(TAG) peerConnectionClient unexpectedly nil in \(#function)"))
|
||||
return
|
||||
}
|
||||
|
||||
guard let call = self.call else {
|
||||
handleFailedCall(error: .assertionError(description:"\(TAG) call unexpectedly nil in \(#function)"))
|
||||
return
|
||||
}
|
||||
|
||||
call.isMuted = isMuted
|
||||
peerConnectionClient.setAudioEnabled(enabled: !isMuted)
|
||||
}
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@ class NonCallKitCallUIAdaptee: CallUIAdaptee {
|
|||
self.notificationsAdapter = notificationsAdapter
|
||||
}
|
||||
|
||||
public func startOutgoingCall(_ call: SignalCall) {
|
||||
internal func startOutgoingCall(_ call: SignalCall) {
|
||||
CallService.signalingQueue.async {
|
||||
_ = self.callService.handleOutgoingCall(call).then {
|
||||
Logger.debug("\(self.TAG) handleOutgoingCall succeeded")
|
||||
|
@ -28,7 +28,7 @@ class NonCallKitCallUIAdaptee: CallUIAdaptee {
|
|||
}
|
||||
}
|
||||
|
||||
public func reportIncomingCall(_ call: SignalCall, callerName: String) {
|
||||
internal func reportIncomingCall(_ call: SignalCall, callerName: String) {
|
||||
Logger.debug("\(TAG) \(#function)")
|
||||
|
||||
// present Call View controller
|
||||
|
@ -43,23 +43,30 @@ class NonCallKitCallUIAdaptee: CallUIAdaptee {
|
|||
}
|
||||
}
|
||||
|
||||
public func reportMissedCall(_ call: SignalCall, callerName: String) {
|
||||
internal func reportMissedCall(_ call: SignalCall, callerName: String) {
|
||||
notificationsAdapter.presentMissedCall(call, callerName: callerName)
|
||||
}
|
||||
|
||||
public func answerCall(_ call: SignalCall) {
|
||||
internal func answerCall(_ call: SignalCall) {
|
||||
// NO-OP
|
||||
}
|
||||
|
||||
public func declineCall(_ call: SignalCall) {
|
||||
internal func declineCall(_ call: SignalCall) {
|
||||
CallService.signalingQueue.async {
|
||||
self.callService.handleDeclineCall(call)
|
||||
}
|
||||
}
|
||||
|
||||
public func endCall(_ call: SignalCall) {
|
||||
internal func endCall(_ call: SignalCall) {
|
||||
CallService.signalingQueue.async {
|
||||
self.callService.handleLocalHungupCall(call)
|
||||
}
|
||||
}
|
||||
|
||||
internal func toggleMute(call: SignalCall, isMuted: Bool) {
|
||||
CallService.signalingQueue.async {
|
||||
self.callService.handleToggledMute(isMuted: isMuted)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -16,6 +16,11 @@ enum CallState: String {
|
|||
case remoteBusy // terminal
|
||||
}
|
||||
|
||||
protocol CallDelegate {
|
||||
func stateDidChange(call: SignalCall, state: CallState)
|
||||
func muteDidChange(call: SignalCall, isMuted: Bool)
|
||||
}
|
||||
|
||||
/**
|
||||
* Data model for a WebRTC backed voice/video call.
|
||||
*/
|
||||
|
@ -23,21 +28,30 @@ enum CallState: String {
|
|||
|
||||
let TAG = "[SignalCall]"
|
||||
|
||||
var delegate: CallDelegate?
|
||||
let remotePhoneNumber: String
|
||||
|
||||
// Signal Service identifier for this Call. Used to coordinate the call across remote clients.
|
||||
let signalingId: UInt64
|
||||
|
||||
// Distinguishes between calls locally, e.g. in CallKit
|
||||
let localId: UUID
|
||||
var hasVideo = false
|
||||
var state: CallState {
|
||||
didSet {
|
||||
Logger.debug("\(TAG) state changed: \(oldValue) -> \(state)")
|
||||
stateDidChange?(state)
|
||||
delegate?.stateDidChange(call: self, state: state)
|
||||
}
|
||||
}
|
||||
var isMuted = false {
|
||||
didSet {
|
||||
Logger.debug("\(TAG) muted changed: \(oldValue) -> \(isMuted)")
|
||||
delegate?.muteDidChange(call: self, isMuted: isMuted)
|
||||
}
|
||||
}
|
||||
|
||||
let signalingId: UInt64
|
||||
let remotePhoneNumber: String
|
||||
let localId: UUID
|
||||
var hasVideo = false
|
||||
var error: CallError?
|
||||
|
||||
var stateDidChange: ((_ newState: CallState) -> Void)?
|
||||
|
||||
init(localId: UUID, signalingId: UInt64, state: CallState, remotePhoneNumber: String) {
|
||||
self.localId = localId
|
||||
self.signalingId = signalingId
|
||||
|
|
|
@ -5,12 +5,13 @@ import UIKit
|
|||
import CallKit
|
||||
|
||||
/**
|
||||
* Based on SpeakerboxCallManager, from the Apple CallKit Example app. Though, it's responsibilities are mostly mirrored (and delegated from) CallUIAdapter?
|
||||
* Based on SpeakerboxCallManager, from the Apple CallKit Example app. Though, it's responsibilities are mostly mirrored (and delegated from) CallKitCallUIAdaptee.
|
||||
* TODO: Would it simplify things to merge this into CallKitCallUIAdaptee?
|
||||
*/
|
||||
@available(iOS 10.0, *)
|
||||
final class CallKitCallManager: NSObject {
|
||||
|
||||
let TAG = "[CallKitCallManager]"
|
||||
let callController = CXCallController()
|
||||
|
||||
// MARK: Actions
|
||||
|
@ -43,12 +44,20 @@ final class CallKitCallManager: NSObject {
|
|||
requestTransaction(transaction)
|
||||
}
|
||||
|
||||
func toggleMute(call: SignalCall, isMuted: Bool) {
|
||||
let muteCallAction = CXSetMutedCallAction(call: call.localId, muted: isMuted)
|
||||
let transaction = CXTransaction()
|
||||
transaction.addAction(muteCallAction)
|
||||
|
||||
requestTransaction(transaction)
|
||||
}
|
||||
|
||||
private func requestTransaction(_ transaction: CXTransaction) {
|
||||
callController.request(transaction) { error in
|
||||
if let error = error {
|
||||
print("Error requesting transaction: \(error)")
|
||||
Logger.error("\(self.TAG) Error requesting transaction: \(error)")
|
||||
} else {
|
||||
print("Requested transaction successfully")
|
||||
Logger.debug("\(self.TAG) Requested transaction successfully")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -95,6 +95,10 @@ final class CallKitCallUIAdaptee: NSObject, CallUIAdaptee, CXProviderDelegate {
|
|||
callManager.end(call: call)
|
||||
}
|
||||
|
||||
internal func toggleMute(call: SignalCall, isMuted: Bool) {
|
||||
callManager.toggleMute(call: call, isMuted: isMuted)
|
||||
}
|
||||
|
||||
// MARK: CXProviderDelegate
|
||||
|
||||
func providerDidReset(_ provider: CXProvider) {
|
||||
|
@ -187,7 +191,7 @@ final class CallKitCallUIAdaptee: NSObject, CallUIAdaptee, CXProviderDelegate {
|
|||
action.fulfill()
|
||||
}
|
||||
|
||||
func provider(_ provider: CXProvider, perform action: CXEndCallAction) {
|
||||
public func provider(_ provider: CXProvider, perform action: CXEndCallAction) {
|
||||
Logger.debug("\(TAG) Received \(#function) CXEndCallAction")
|
||||
guard let call = callManager.callWithLocalId(action.callUUID) else {
|
||||
action.fail()
|
||||
|
@ -212,13 +216,13 @@ final class CallKitCallUIAdaptee: NSObject, CallUIAdaptee, CXProviderDelegate {
|
|||
callManager.removeCall(call)
|
||||
}
|
||||
|
||||
func provider(_ provider: CXProvider, perform action: CXSetHeldCallAction) {
|
||||
public func provider(_ provider: CXProvider, perform action: CXSetHeldCallAction) {
|
||||
Logger.debug("\(TAG) Received \(#function) CXSetHeldCallAction")
|
||||
guard let call = callManager.callWithLocalId(action.callUUID) else {
|
||||
action.fail()
|
||||
return
|
||||
}
|
||||
Logger.warn("TODO, set held call: \(call)")
|
||||
Logger.warn("TODO, unimplemented set held call: \(call)")
|
||||
|
||||
// TODO FIXME
|
||||
// // Update the SpeakerboxCall's underlying hold state.
|
||||
|
@ -235,6 +239,28 @@ final class CallKitCallUIAdaptee: NSObject, CallUIAdaptee, CXProviderDelegate {
|
|||
action.fulfill()
|
||||
}
|
||||
|
||||
public func provider(_ provider: CXProvider, perform action: CXSetMutedCallAction) {
|
||||
Logger.debug("\(TAG) Received \(#function) CXSetMutedCallAction")
|
||||
guard callManager.callWithLocalId(action.callUUID) != nil else {
|
||||
Logger.error("\(TAG) Failing CXSetMutedCallAction for unknown call: \(action.callUUID)")
|
||||
action.fail()
|
||||
return
|
||||
}
|
||||
|
||||
CallService.signalingQueue.async {
|
||||
self.callService.handleToggledMute(isMuted: action.isMuted)
|
||||
action.fulfill()
|
||||
}
|
||||
}
|
||||
|
||||
public func provider(_ provider: CXProvider, perform action: CXSetGroupCallAction) {
|
||||
Logger.warn("\(TAG) unimplemented \(#function) for CXSetGroupCallAction")
|
||||
}
|
||||
|
||||
public func provider(_ provider: CXProvider, perform action: CXPlayDTMFCallAction) {
|
||||
Logger.warn("\(TAG) unimplemented \(#function) for CXPlayDTMFCallAction")
|
||||
}
|
||||
|
||||
func provider(_ provider: CXProvider, timedOutPerforming action: CXAction) {
|
||||
Logger.debug("\(TAG) Timed out \(#function)")
|
||||
|
||||
|
|
|
@ -14,6 +14,7 @@ protocol CallUIAdaptee {
|
|||
func answerCall(_ call: SignalCall)
|
||||
func declineCall(_ call: SignalCall)
|
||||
func endCall(_ call: SignalCall)
|
||||
func toggleMute(call: SignalCall, isMuted: Bool)
|
||||
}
|
||||
|
||||
// Shared default implementations
|
||||
|
@ -86,4 +87,8 @@ class CallUIAdapter {
|
|||
internal func showCall(_ call: SignalCall) {
|
||||
adaptee.showCall(call)
|
||||
}
|
||||
|
||||
internal func toggleMute(call: SignalCall, isMuted: Bool) {
|
||||
adaptee.toggleMute(call: call, isMuted: isMuted)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -126,7 +126,7 @@ import PromiseKit
|
|||
}
|
||||
|
||||
@objc(OWSCallViewController)
|
||||
class CallViewController: UIViewController {
|
||||
class CallViewController: UIViewController, CallDelegate {
|
||||
|
||||
enum CallDirection {
|
||||
case unspecified, outgoing, incoming
|
||||
|
@ -201,8 +201,8 @@ class CallViewController: UIViewController {
|
|||
// No-op, since call service is already set up at this point, the result of which was presenting this viewController.
|
||||
}
|
||||
|
||||
call.stateDidChange = callStateDidChange
|
||||
callStateDidChange(call.state)
|
||||
call.delegate = self
|
||||
stateDidChange(call: call, state: call.state)
|
||||
}
|
||||
|
||||
// objc accessible way to set our swift enum.
|
||||
|
@ -266,13 +266,6 @@ class CallViewController: UIViewController {
|
|||
|
||||
// MARK: - Actions
|
||||
|
||||
func callStateDidChange(_ newState: CallState) {
|
||||
DispatchQueue.main.async {
|
||||
self.updateCallUI(callState: newState)
|
||||
}
|
||||
self.audioService.handleState(newState)
|
||||
}
|
||||
|
||||
/**
|
||||
* Ends a connected call. Do not confuse with `didPressDeclineCall`.
|
||||
*/
|
||||
|
@ -290,8 +283,10 @@ class CallViewController: UIViewController {
|
|||
@IBAction func didPressMute(sender muteButton: UIButton) {
|
||||
Logger.info("\(TAG) called \(#function)")
|
||||
muteButton.isSelected = !muteButton.isSelected
|
||||
CallService.signalingQueue.async {
|
||||
self.callService.handleToggledMute(isMuted: muteButton.isSelected)
|
||||
if let call = self.call {
|
||||
callUIAdapter.toggleMute(call: call, isMuted: muteButton.isSelected)
|
||||
} else {
|
||||
Logger.warn("\(TAG) hung up, but call was unexpectedly nil")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -332,4 +327,20 @@ class CallViewController: UIViewController {
|
|||
|
||||
self.dismiss(animated: true)
|
||||
}
|
||||
|
||||
// MARK: - Call Delegate
|
||||
|
||||
internal func stateDidChange(call: SignalCall, state: CallState) {
|
||||
DispatchQueue.main.async {
|
||||
self.updateCallUI(callState: state)
|
||||
}
|
||||
self.audioService.handleState(state)
|
||||
}
|
||||
|
||||
internal func muteDidChange(call: SignalCall, isMuted: Bool) {
|
||||
Logger.debug("\(TAG) in \(#function)")
|
||||
DispatchQueue.main.async {
|
||||
self.muteButton.isSelected = call.isMuted
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue