parent
dbb29d7d7e
commit
9e739433c5
|
@ -2,7 +2,7 @@
|
|||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "video-active.png",
|
||||
"filename" : "mute-selected-wide.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
Binary file not shown.
After Width: | Height: | Size: 24 KiB |
|
@ -2,7 +2,7 @@
|
|||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "video-inactive.png",
|
||||
"filename" : "mute-unselected-wide.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
BIN
Signal/Images.xcassets/mute-unselected-wide.imageset/mute-unselected-wide.png
vendored
Normal file
BIN
Signal/Images.xcassets/mute-unselected-wide.imageset/mute-unselected-wide.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 20 KiB |
Binary file not shown.
Before Width: | Height: | Size: 7.6 KiB |
Binary file not shown.
Before Width: | Height: | Size: 12 KiB |
|
@ -317,7 +317,13 @@ static NSString *const kURLHostVerifyPrefix = @"verify";
|
|||
*/
|
||||
- (BOOL)application:(UIApplication *)application continueUserActivity:(nonnull NSUserActivity *)userActivity restorationHandler:(nonnull void (^)(NSArray * _Nullable))restorationHandler
|
||||
{
|
||||
DDLogWarn(@"%@ called %s with userActivity: %@, but not yet supported.", self.tag, __PRETTY_FUNCTION__, userActivity);
|
||||
if ([userActivity.activityType isEqualToString:@"INStartVideoCallIntent"]) {
|
||||
[[Environment getCurrent].callService handleCallKitStartVideo];
|
||||
} else {
|
||||
DDLogWarn(
|
||||
@"%@ called %s with userActivity: %@, but not yet supported.", self.tag, __PRETTY_FUNCTION__, userActivity);
|
||||
}
|
||||
|
||||
// TODO Something like...
|
||||
// *phoneNumber = [[[[[[userActivity interaction] intent] contacts] firstObject] personHandle] value]
|
||||
// thread = blah
|
||||
|
|
|
@ -1,14 +1,16 @@
|
|||
// Created by Michael Kirk on 11/11/16.
|
||||
// Copyright © 2016 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import PromiseKit
|
||||
import WebRTC
|
||||
|
||||
/**
|
||||
* `CallService` manages the state of a WebRTC backed Signal Call (as opposed to the legacy "RedPhone Call").
|
||||
* `CallService` is a global singleton that manages the state of WebRTC-backed Signal Calls
|
||||
* (as opposed to legacy "RedPhone Calls").
|
||||
*
|
||||
* It serves as connection from the `CallUIAdapater` to the `PeerConnectionClient`.
|
||||
* It serves as a connection between the `CallUIAdapter` and the `PeerConnectionClient`.
|
||||
*
|
||||
* ## Signaling
|
||||
*
|
||||
|
@ -119,7 +121,7 @@ fileprivate let timeoutSeconds = 60
|
|||
var incomingCallPromise: Promise<Void>?
|
||||
|
||||
// Used to coordinate promises across delegate methods
|
||||
var fulfillCallConnectedPromise: (()->())?
|
||||
var fulfillCallConnectedPromise: (() -> Void)?
|
||||
|
||||
required init(accountManager: AccountManager, contactsManager: OWSContactsManager, messageSender: MessageSender, notificationsAdapter: CallNotificationsAdapter) {
|
||||
self.accountManager = accountManager
|
||||
|
@ -608,7 +610,7 @@ fileprivate let timeoutSeconds = 60
|
|||
call.state = .connected
|
||||
|
||||
// We don't risk transmitting any media until the remote client has admitted to being connected.
|
||||
peerConnectionClient.setAudioEnabled(enabled: true)
|
||||
peerConnectionClient.setAudioEnabled(enabled: !call.isMuted)
|
||||
peerConnectionClient.setVideoEnabled(enabled: call.hasVideo)
|
||||
}
|
||||
|
||||
|
@ -713,7 +715,7 @@ fileprivate let timeoutSeconds = 60
|
|||
*
|
||||
* Can be used for Incoming and Outgoing calls.
|
||||
*/
|
||||
func handleToggledMute(isMuted: Bool) {
|
||||
func setIsMuted(isMuted: Bool) {
|
||||
assertOnSignalingQueue()
|
||||
|
||||
guard let peerConnectionClient = self.peerConnectionClient else {
|
||||
|
@ -730,6 +732,34 @@ fileprivate let timeoutSeconds = 60
|
|||
peerConnectionClient.setAudioEnabled(enabled: !isMuted)
|
||||
}
|
||||
|
||||
/**
|
||||
* Local user toggled video.
|
||||
*
|
||||
* Can be used for Incoming and Outgoing calls.
|
||||
*/
|
||||
func setHasVideo(hasVideo: Bool) {
|
||||
assertOnSignalingQueue()
|
||||
|
||||
guard let peerConnectionClient = self.peerConnectionClient else {
|
||||
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.hasVideo = hasVideo
|
||||
peerConnectionClient.setVideoEnabled(enabled: hasVideo)
|
||||
}
|
||||
|
||||
func handleCallKitStartVideo() {
|
||||
CallService.signalingQueue.async {
|
||||
self.setHasVideo(hasVideo:true)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Local client received a message on the WebRTC data channel.
|
||||
*
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
// Created by Michael Kirk on 1/3/17.
|
||||
// Copyright © 2017 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
|
@ -70,10 +71,15 @@ class NonCallKitCallUIAdaptee: CallUIAdaptee {
|
|||
}
|
||||
}
|
||||
|
||||
func toggleMute(call: SignalCall, isMuted: Bool) {
|
||||
func setIsMuted(call: SignalCall, isMuted: Bool) {
|
||||
CallService.signalingQueue.async {
|
||||
self.callService.handleToggledMute(isMuted: isMuted)
|
||||
self.callService.setIsMuted(isMuted: isMuted)
|
||||
}
|
||||
}
|
||||
|
||||
func setHasVideo(call: SignalCall, hasVideo: Bool) {
|
||||
CallService.signalingQueue.async {
|
||||
self.callService.setHasVideo(hasVideo: hasVideo)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
// Created by Michael Kirk on 11/29/16.
|
||||
// Copyright © 2016 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import PromiseKit
|
||||
|
@ -154,7 +155,7 @@ class PeerConnectionClient: NSObject, RTCPeerConnectionDelegate, RTCDataChannelD
|
|||
public func setVideoEnabled(enabled: Bool) {
|
||||
guard let videoTrack = self.videoTrack else {
|
||||
let action = enabled ? "enable" : "disable"
|
||||
Logger.error("\(TAG)) trying to \(action) videoTack which doesn't exist")
|
||||
Logger.error("\(TAG)) trying to \(action) videoTrack which doesn't exist")
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -198,7 +199,7 @@ class PeerConnectionClient: NSObject, RTCPeerConnectionDelegate, RTCDataChannelD
|
|||
var defaultOfferConstraints: RTCMediaConstraints {
|
||||
let mandatoryConstraints = [
|
||||
"OfferToReceiveAudio": "true",
|
||||
"OfferToReceiveVideo" : "true"
|
||||
"OfferToReceiveVideo": "true"
|
||||
]
|
||||
return RTCMediaConstraints(mandatoryConstraints:mandatoryConstraints, optionalConstraints:nil)
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ enum CallState: String {
|
|||
|
||||
protocol CallDelegate: class {
|
||||
func stateDidChange(call: SignalCall, state: CallState)
|
||||
func hasVideoDidChange(call: SignalCall, hasVideo: Bool)
|
||||
func muteDidChange(call: SignalCall, isMuted: Bool)
|
||||
}
|
||||
|
||||
|
@ -37,7 +38,12 @@ protocol CallDelegate: class {
|
|||
|
||||
// Distinguishes between calls locally, e.g. in CallKit
|
||||
let localId: UUID
|
||||
var hasVideo = false
|
||||
var hasVideo = false {
|
||||
didSet {
|
||||
Logger.debug("\(TAG) hasVideo changed: \(oldValue) -> \(hasVideo)")
|
||||
delegate?.hasVideoDidChange(call: self, hasVideo: hasVideo)
|
||||
}
|
||||
}
|
||||
var state: CallState {
|
||||
didSet {
|
||||
Logger.debug("\(TAG) state changed: \(oldValue) -> \(state)")
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
// Created by Michael Kirk on 12/13/16.
|
||||
// Copyright © 2016 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import CallKit
|
||||
|
@ -48,7 +49,7 @@ final class CallKitCallManager: NSObject {
|
|||
requestTransaction(transaction)
|
||||
}
|
||||
|
||||
func toggleMute(call: SignalCall, isMuted: Bool) {
|
||||
func setIsMuted(call: SignalCall, isMuted: Bool) {
|
||||
let muteCallAction = CXSetMutedCallAction(call: call.localId, muted: isMuted)
|
||||
let transaction = CXTransaction()
|
||||
transaction.addAction(muteCallAction)
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
// Created by Michael Kirk on 12/23/16.
|
||||
// Copyright © 2016 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import UIKit
|
||||
|
@ -99,8 +100,21 @@ final class CallKitCallUIAdaptee: NSObject, CallUIAdaptee, CXProviderDelegate {
|
|||
callManager.end(call: call)
|
||||
}
|
||||
|
||||
func toggleMute(call: SignalCall, isMuted: Bool) {
|
||||
callManager.toggleMute(call: call, isMuted: isMuted)
|
||||
func setIsMuted(call: SignalCall, isMuted: Bool) {
|
||||
callManager.setIsMuted(call: call, isMuted: isMuted)
|
||||
}
|
||||
|
||||
func setHasVideo(call: SignalCall, hasVideo: Bool) {
|
||||
let update = CXCallUpdate()
|
||||
update.remoteHandle = CXHandle(type: .phoneNumber, value: call.remotePhoneNumber)
|
||||
update.hasVideo = hasVideo
|
||||
|
||||
// Update the CallKit UI.
|
||||
provider.reportCall(with: call.localId, updated: update)
|
||||
|
||||
CallService.signalingQueue.async {
|
||||
self.callService.setHasVideo(hasVideo: hasVideo)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: CXProviderDelegate
|
||||
|
@ -139,7 +153,7 @@ final class CallKitCallUIAdaptee: NSObject, CallUIAdaptee, CXProviderDelegate {
|
|||
CallService.signalingQueue.async {
|
||||
self.callService.handleOutgoingCall(call).then {
|
||||
action.fulfill()
|
||||
}.catch { error in
|
||||
}.catch { _ in
|
||||
self.callManager.removeCall(call)
|
||||
action.fail()
|
||||
}
|
||||
|
@ -243,7 +257,7 @@ final class CallKitCallUIAdaptee: NSObject, CallUIAdaptee, CXProviderDelegate {
|
|||
}
|
||||
|
||||
CallService.signalingQueue.async {
|
||||
self.callService.handleToggledMute(isMuted: action.isMuted)
|
||||
self.callService.setIsMuted(isMuted: action.isMuted)
|
||||
action.fulfill()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
// Created by Michael Kirk on 12/13/16.
|
||||
// Copyright © 2016 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import PromiseKit
|
||||
|
@ -15,7 +16,8 @@ protocol CallUIAdaptee {
|
|||
func declineCall(_ call: SignalCall)
|
||||
func recipientAcceptedCall(_ call: SignalCall)
|
||||
func endCall(_ call: SignalCall)
|
||||
func toggleMute(call: SignalCall, isMuted: Bool)
|
||||
func setIsMuted(call: SignalCall, isMuted: Bool)
|
||||
func setHasVideo(call: SignalCall, hasVideo: Bool)
|
||||
}
|
||||
|
||||
// Shared default implementations
|
||||
|
@ -93,7 +95,11 @@ class CallUIAdapter {
|
|||
adaptee.showCall(call)
|
||||
}
|
||||
|
||||
internal func toggleMute(call: SignalCall, isMuted: Bool) {
|
||||
adaptee.toggleMute(call: call, isMuted: isMuted)
|
||||
internal func setIsMuted(call: SignalCall, isMuted: Bool) {
|
||||
adaptee.setIsMuted(call: call, isMuted: isMuted)
|
||||
}
|
||||
|
||||
internal func setHasVideo(call: SignalCall, hasVideo: Bool) {
|
||||
adaptee.setHasVideo(call: call, hasVideo: hasVideo)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -281,15 +281,23 @@ class CallViewController: UIViewController, CallDelegate {
|
|||
|
||||
textMessageButton = createButton(imageName:"message-active-wide",
|
||||
action:#selector(didPressTextMessage))
|
||||
muteButton = createButton(imageName:"mute-active-wide",
|
||||
muteButton = createButton(imageName:"mute-unselected-wide",
|
||||
action:#selector(didPressMute))
|
||||
speakerPhoneButton = createButton(imageName:"speaker-active-wide",
|
||||
action:#selector(didPressSpeakerphone))
|
||||
videoButton = createButton(imageName:"video-active-wide",
|
||||
videoButton = createButton(imageName:"video-inactive-wide",
|
||||
action:#selector(didPressVideo))
|
||||
hangUpButton = createButton(imageName:"hangup-active-wide",
|
||||
action:#selector(didPressHangup))
|
||||
|
||||
let muteSelectedImage = UIImage(named:"mute-selected-wide")
|
||||
assert(muteSelectedImage != nil)
|
||||
muteButton.setImage(muteSelectedImage, for:.selected)
|
||||
|
||||
let videoSelectedImage = UIImage(named:"video-active-wide")
|
||||
assert(videoSelectedImage != nil)
|
||||
videoButton.setImage(videoSelectedImage, for:.selected)
|
||||
|
||||
ongoingCallView = createContainerForCallControls(controlGroups : [
|
||||
[textMessageButton, videoButton],
|
||||
[muteButton, speakerPhoneButton ],
|
||||
|
@ -335,6 +343,7 @@ class CallViewController: UIViewController, CallDelegate {
|
|||
|
||||
func createButton(imageName: String, action: Selector) -> UIButton {
|
||||
let image = UIImage(named:imageName)
|
||||
assert(image != nil)
|
||||
let button = UIButton()
|
||||
button.setImage(image, for:.normal)
|
||||
button.imageEdgeInsets = UIEdgeInsets(top: buttonInset(),
|
||||
|
@ -513,6 +522,9 @@ class CallViewController: UIViewController, CallDelegate {
|
|||
assert(Thread.isMainThread)
|
||||
updateCallStatusLabel(callState: callState)
|
||||
|
||||
videoButton.isSelected = call.hasVideo
|
||||
muteButton.isSelected = call.isMuted
|
||||
|
||||
// Show Incoming vs. Ongoing call controls
|
||||
let isRinging = callState == .localRinging
|
||||
incomingCallView.isHidden = !isRinging
|
||||
|
@ -573,7 +585,7 @@ class CallViewController: UIViewController, CallDelegate {
|
|||
Logger.info("\(TAG) called \(#function)")
|
||||
muteButton.isSelected = !muteButton.isSelected
|
||||
if let call = self.call {
|
||||
callUIAdapter.toggleMute(call: call, isMuted: muteButton.isSelected)
|
||||
callUIAdapter.setIsMuted(call: call, isMuted: muteButton.isSelected)
|
||||
} else {
|
||||
Logger.warn("\(TAG) pressed mute, but call was unexpectedly nil")
|
||||
}
|
||||
|
@ -608,8 +620,12 @@ class CallViewController: UIViewController, CallDelegate {
|
|||
|
||||
func didPressVideo(sender: UIButton) {
|
||||
Logger.info("\(TAG) called \(#function)")
|
||||
|
||||
// TODO:
|
||||
videoButton.isSelected = !videoButton.isSelected
|
||||
if let call = self.call {
|
||||
callUIAdapter.setHasVideo(call: call, hasVideo: videoButton.isSelected)
|
||||
} else {
|
||||
Logger.warn("\(TAG) pressed video, but call was unexpectedly nil")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -627,18 +643,25 @@ class CallViewController: UIViewController, CallDelegate {
|
|||
self.dismiss(animated: true)
|
||||
}
|
||||
|
||||
// MARK: - Call Delegate
|
||||
// MARK: - CallDelegate
|
||||
|
||||
internal func stateDidChange(call: SignalCall, state: CallState) {
|
||||
DispatchQueue.main.async {
|
||||
self.updateCallUI(callState: state)
|
||||
Logger.info("\(self.TAG) new call status: \(call.state)")
|
||||
}
|
||||
self.audioService.handleState(state)
|
||||
}
|
||||
|
||||
internal func hasVideoDidChange(call: SignalCall, hasVideo: Bool) {
|
||||
DispatchQueue.main.async {
|
||||
self.updateCallUI(callState: call.state)
|
||||
}
|
||||
}
|
||||
|
||||
internal func muteDidChange(call: SignalCall, isMuted: Bool) {
|
||||
DispatchQueue.main.async {
|
||||
self.muteButton.isSelected = call.isMuted
|
||||
self.updateCallUI(callState: call.state)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue