Start work on video.

// FREEBIE
pull/1/head
Matthew Chen 8 years ago
parent dbb29d7d7e
commit 9e739433c5

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

@ -2,7 +2,7 @@
"images" : [ "images" : [
{ {
"idiom" : "universal", "idiom" : "universal",
"filename" : "video-inactive.png", "filename" : "mute-unselected-wide.png",
"scale" : "1x" "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

@ -317,7 +317,13 @@ static NSString *const kURLHostVerifyPrefix = @"verify";
*/ */
- (BOOL)application:(UIApplication *)application continueUserActivity:(nonnull NSUserActivity *)userActivity restorationHandler:(nonnull void (^)(NSArray * _Nullable))restorationHandler - (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... // TODO Something like...
// *phoneNumber = [[[[[[userActivity interaction] intent] contacts] firstObject] personHandle] value] // *phoneNumber = [[[[[[userActivity interaction] intent] contacts] firstObject] personHandle] value]
// thread = blah // 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 Foundation
import PromiseKit import PromiseKit
import WebRTC 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 * ## Signaling
* *
@ -119,7 +121,7 @@ fileprivate let timeoutSeconds = 60
var incomingCallPromise: Promise<Void>? var incomingCallPromise: Promise<Void>?
// Used to coordinate promises across delegate methods // Used to coordinate promises across delegate methods
var fulfillCallConnectedPromise: (()->())? var fulfillCallConnectedPromise: (() -> Void)?
required init(accountManager: AccountManager, contactsManager: OWSContactsManager, messageSender: MessageSender, notificationsAdapter: CallNotificationsAdapter) { required init(accountManager: AccountManager, contactsManager: OWSContactsManager, messageSender: MessageSender, notificationsAdapter: CallNotificationsAdapter) {
self.accountManager = accountManager self.accountManager = accountManager
@ -608,7 +610,7 @@ fileprivate let timeoutSeconds = 60
call.state = .connected call.state = .connected
// We don't risk transmitting any media until the remote client has admitted to being 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) peerConnectionClient.setVideoEnabled(enabled: call.hasVideo)
} }
@ -713,7 +715,7 @@ fileprivate let timeoutSeconds = 60
* *
* Can be used for Incoming and Outgoing calls. * Can be used for Incoming and Outgoing calls.
*/ */
func handleToggledMute(isMuted: Bool) { func setIsMuted(isMuted: Bool) {
assertOnSignalingQueue() assertOnSignalingQueue()
guard let peerConnectionClient = self.peerConnectionClient else { guard let peerConnectionClient = self.peerConnectionClient else {
@ -730,6 +732,34 @@ fileprivate let timeoutSeconds = 60
peerConnectionClient.setAudioEnabled(enabled: !isMuted) 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. * 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 import Foundation
@ -70,10 +71,15 @@ class NonCallKitCallUIAdaptee: CallUIAdaptee {
} }
} }
func toggleMute(call: SignalCall, isMuted: Bool) { func setIsMuted(call: SignalCall, isMuted: Bool) {
CallService.signalingQueue.async { 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 Foundation
import PromiseKit import PromiseKit
@ -154,7 +155,7 @@ class PeerConnectionClient: NSObject, RTCPeerConnectionDelegate, RTCDataChannelD
public func setVideoEnabled(enabled: Bool) { public func setVideoEnabled(enabled: Bool) {
guard let videoTrack = self.videoTrack else { guard let videoTrack = self.videoTrack else {
let action = enabled ? "enable" : "disable" 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 return
} }
@ -198,7 +199,7 @@ class PeerConnectionClient: NSObject, RTCPeerConnectionDelegate, RTCDataChannelD
var defaultOfferConstraints: RTCMediaConstraints { var defaultOfferConstraints: RTCMediaConstraints {
let mandatoryConstraints = [ let mandatoryConstraints = [
"OfferToReceiveAudio": "true", "OfferToReceiveAudio": "true",
"OfferToReceiveVideo" : "true" "OfferToReceiveVideo": "true"
] ]
return RTCMediaConstraints(mandatoryConstraints:mandatoryConstraints, optionalConstraints:nil) return RTCMediaConstraints(mandatoryConstraints:mandatoryConstraints, optionalConstraints:nil)
} }

@ -19,6 +19,7 @@ enum CallState: String {
protocol CallDelegate: class { protocol CallDelegate: class {
func stateDidChange(call: SignalCall, state: CallState) func stateDidChange(call: SignalCall, state: CallState)
func hasVideoDidChange(call: SignalCall, hasVideo: Bool)
func muteDidChange(call: SignalCall, isMuted: Bool) func muteDidChange(call: SignalCall, isMuted: Bool)
} }
@ -37,7 +38,12 @@ protocol CallDelegate: class {
// Distinguishes between calls locally, e.g. in CallKit // Distinguishes between calls locally, e.g. in CallKit
let localId: UUID 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 { var state: CallState {
didSet { didSet {
Logger.debug("\(TAG) state changed: \(oldValue) -> \(state)") 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 UIKit
import CallKit import CallKit
@ -48,7 +49,7 @@ final class CallKitCallManager: NSObject {
requestTransaction(transaction) requestTransaction(transaction)
} }
func toggleMute(call: SignalCall, isMuted: Bool) { func setIsMuted(call: SignalCall, isMuted: Bool) {
let muteCallAction = CXSetMutedCallAction(call: call.localId, muted: isMuted) let muteCallAction = CXSetMutedCallAction(call: call.localId, muted: isMuted)
let transaction = CXTransaction() let transaction = CXTransaction()
transaction.addAction(muteCallAction) 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 Foundation
import UIKit import UIKit
@ -99,8 +100,21 @@ final class CallKitCallUIAdaptee: NSObject, CallUIAdaptee, CXProviderDelegate {
callManager.end(call: call) callManager.end(call: call)
} }
func toggleMute(call: SignalCall, isMuted: Bool) { func setIsMuted(call: SignalCall, isMuted: Bool) {
callManager.toggleMute(call: call, isMuted: isMuted) 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 // MARK: CXProviderDelegate
@ -139,7 +153,7 @@ final class CallKitCallUIAdaptee: NSObject, CallUIAdaptee, CXProviderDelegate {
CallService.signalingQueue.async { CallService.signalingQueue.async {
self.callService.handleOutgoingCall(call).then { self.callService.handleOutgoingCall(call).then {
action.fulfill() action.fulfill()
}.catch { error in }.catch { _ in
self.callManager.removeCall(call) self.callManager.removeCall(call)
action.fail() action.fail()
} }
@ -243,7 +257,7 @@ final class CallKitCallUIAdaptee: NSObject, CallUIAdaptee, CXProviderDelegate {
} }
CallService.signalingQueue.async { CallService.signalingQueue.async {
self.callService.handleToggledMute(isMuted: action.isMuted) self.callService.setIsMuted(isMuted: action.isMuted)
action.fulfill() 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 Foundation
import PromiseKit import PromiseKit
@ -15,7 +16,8 @@ protocol CallUIAdaptee {
func declineCall(_ call: SignalCall) func declineCall(_ call: SignalCall)
func recipientAcceptedCall(_ call: SignalCall) func recipientAcceptedCall(_ call: SignalCall)
func endCall(_ 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 // Shared default implementations
@ -93,7 +95,11 @@ class CallUIAdapter {
adaptee.showCall(call) adaptee.showCall(call)
} }
internal func toggleMute(call: SignalCall, isMuted: Bool) { internal func setIsMuted(call: SignalCall, isMuted: Bool) {
adaptee.toggleMute(call: call, isMuted: isMuted) 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", textMessageButton = createButton(imageName:"message-active-wide",
action:#selector(didPressTextMessage)) action:#selector(didPressTextMessage))
muteButton = createButton(imageName:"mute-active-wide", muteButton = createButton(imageName:"mute-unselected-wide",
action:#selector(didPressMute)) action:#selector(didPressMute))
speakerPhoneButton = createButton(imageName:"speaker-active-wide", speakerPhoneButton = createButton(imageName:"speaker-active-wide",
action:#selector(didPressSpeakerphone)) action:#selector(didPressSpeakerphone))
videoButton = createButton(imageName:"video-active-wide", videoButton = createButton(imageName:"video-inactive-wide",
action:#selector(didPressVideo)) action:#selector(didPressVideo))
hangUpButton = createButton(imageName:"hangup-active-wide", hangUpButton = createButton(imageName:"hangup-active-wide",
action:#selector(didPressHangup)) 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 : [ ongoingCallView = createContainerForCallControls(controlGroups : [
[textMessageButton, videoButton], [textMessageButton, videoButton],
[muteButton, speakerPhoneButton ], [muteButton, speakerPhoneButton ],
@ -335,6 +343,7 @@ class CallViewController: UIViewController, CallDelegate {
func createButton(imageName: String, action: Selector) -> UIButton { func createButton(imageName: String, action: Selector) -> UIButton {
let image = UIImage(named:imageName) let image = UIImage(named:imageName)
assert(image != nil)
let button = UIButton() let button = UIButton()
button.setImage(image, for:.normal) button.setImage(image, for:.normal)
button.imageEdgeInsets = UIEdgeInsets(top: buttonInset(), button.imageEdgeInsets = UIEdgeInsets(top: buttonInset(),
@ -513,6 +522,9 @@ class CallViewController: UIViewController, CallDelegate {
assert(Thread.isMainThread) assert(Thread.isMainThread)
updateCallStatusLabel(callState: callState) updateCallStatusLabel(callState: callState)
videoButton.isSelected = call.hasVideo
muteButton.isSelected = call.isMuted
// Show Incoming vs. Ongoing call controls // Show Incoming vs. Ongoing call controls
let isRinging = callState == .localRinging let isRinging = callState == .localRinging
incomingCallView.isHidden = !isRinging incomingCallView.isHidden = !isRinging
@ -573,7 +585,7 @@ class CallViewController: UIViewController, CallDelegate {
Logger.info("\(TAG) called \(#function)") Logger.info("\(TAG) called \(#function)")
muteButton.isSelected = !muteButton.isSelected muteButton.isSelected = !muteButton.isSelected
if let call = self.call { if let call = self.call {
callUIAdapter.toggleMute(call: call, isMuted: muteButton.isSelected) callUIAdapter.setIsMuted(call: call, isMuted: muteButton.isSelected)
} else { } else {
Logger.warn("\(TAG) pressed mute, but call was unexpectedly nil") Logger.warn("\(TAG) pressed mute, but call was unexpectedly nil")
} }
@ -608,8 +620,12 @@ class CallViewController: UIViewController, CallDelegate {
func didPressVideo(sender: UIButton) { func didPressVideo(sender: UIButton) {
Logger.info("\(TAG) called \(#function)") Logger.info("\(TAG) called \(#function)")
videoButton.isSelected = !videoButton.isSelected
// TODO: 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) self.dismiss(animated: true)
} }
// MARK: - Call Delegate // MARK: - CallDelegate
internal func stateDidChange(call: SignalCall, state: CallState) { internal func stateDidChange(call: SignalCall, state: CallState) {
DispatchQueue.main.async { DispatchQueue.main.async {
self.updateCallUI(callState: state) self.updateCallUI(callState: state)
Logger.info("\(self.TAG) new call status: \(call.state)")
} }
self.audioService.handleState(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) { internal func muteDidChange(call: SignalCall, isMuted: Bool) {
DispatchQueue.main.async { DispatchQueue.main.async {
self.muteButton.isSelected = call.isMuted self.updateCallUI(callState: call.state)
} }
} }
} }

Loading…
Cancel
Save