Start work on video.

// FREEBIE
This commit is contained in:
Matthew Chen 2017-01-18 17:29:47 -05:00
parent dbb29d7d7e
commit 9e739433c5
15 changed files with 133 additions and 40 deletions

View File

@ -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

View File

@ -2,7 +2,7 @@
"images" : [
{
"idiom" : "universal",
"filename" : "video-inactive.png",
"filename" : "mute-unselected-wide.png",
"scale" : "1x"
},
{

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

View File

@ -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

View File

@ -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.
*

View File

@ -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)
}
}
}

View File

@ -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)
}

View File

@ -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)")

View File

@ -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)

View File

@ -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()
}
}

View File

@ -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)
}
}

View File

@ -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)
}
}
}