update call message after a call ended

pull/560/head
ryanzhao 4 years ago
parent f019fe7733
commit ff79c58f44

@ -9,7 +9,9 @@ public final class SessionCall: NSObject, WebRTCSessionDelegate {
let sessionID: String let sessionID: String
let mode: Mode let mode: Mode
let webRTCSession: WebRTCSession let webRTCSession: WebRTCSession
let isOutgoing: Bool
var remoteSDP: RTCSessionDescription? = nil var remoteSDP: RTCSessionDescription? = nil
var callMessageTimestamp: UInt64?
var isWaitingForRemoteSDP = false var isWaitingForRemoteSDP = false
var contactName: String { var contactName: String {
let contact = Storage.shared.getContact(with: self.sessionID) let contact = Storage.shared.getContact(with: self.sessionID)
@ -59,6 +61,12 @@ public final class SessionCall: NSObject, WebRTCSessionDelegate {
case answer case answer
} }
// MARK: End call mode
enum EndCallMode {
case local
case remote
}
// MARK: Call State Properties // MARK: Call State Properties
var connectingDate: Date? { var connectingDate: Date? {
didSet { didSet {
@ -115,16 +123,20 @@ public final class SessionCall: NSObject, WebRTCSessionDelegate {
guard let connectedDate = connectedDate else { guard let connectedDate = connectedDate else {
return 0 return 0
} }
if let endDate = endDate {
return endDate.timeIntervalSince(connectedDate)
}
return Date().timeIntervalSince(connectedDate) return Date().timeIntervalSince(connectedDate)
} }
// MARK: Initialization // MARK: Initialization
init(for sessionID: String, uuid: String, mode: Mode) { init(for sessionID: String, uuid: String, mode: Mode, outgoing: Bool = false) {
self.sessionID = sessionID self.sessionID = sessionID
self.uuid = UUID(uuidString: uuid)! self.uuid = UUID(uuidString: uuid)!
self.mode = mode self.mode = mode
self.webRTCSession = WebRTCSession.current ?? WebRTCSession(for: sessionID, with: uuid) self.webRTCSession = WebRTCSession.current ?? WebRTCSession(for: sessionID, with: uuid)
self.isOutgoing = outgoing
WebRTCSession.current = self.webRTCSession WebRTCSession.current = self.webRTCSession
super.init() super.init()
self.webRTCSession.delegate = self self.webRTCSession.delegate = self
@ -160,8 +172,9 @@ public final class SessionCall: NSObject, WebRTCSessionDelegate {
}, completion: { [weak self] in }, completion: { [weak self] in
let _ = promise.done { let _ = promise.done {
Storage.shared.write { transaction in Storage.shared.write { transaction in
self?.webRTCSession.sendOffer(to: self!.sessionID, using: transaction as! YapDatabaseReadWriteTransaction).done { self?.webRTCSession.sendOffer(to: self!.sessionID, using: transaction as! YapDatabaseReadWriteTransaction).done { timestamp in
self?.hasStartedConnecting = true self?.hasStartedConnecting = true
self?.callMessageTimestamp = timestamp
}.retainUntilComplete() }.retainUntilComplete()
} }
} }
@ -186,11 +199,49 @@ public final class SessionCall: NSObject, WebRTCSessionDelegate {
hasEnded = true hasEnded = true
} }
// MARK: Update call message
func updateCallMessage(mode: EndCallMode) {
guard let callMessageTimestamp = callMessageTimestamp else { return }
Storage.write { transaction in
let tsMessage: TSMessage?
if self.isOutgoing {
tsMessage = TSOutgoingMessage.find(withTimestamp: callMessageTimestamp)
} else {
tsMessage = TSIncomingMessage.find(withAuthorId: self.sessionID, timestamp: callMessageTimestamp, transaction: transaction)
}
if let messageToUpdate = tsMessage {
var shouldMarkAsRead = false
let newMessageBody: String
if self.duration > 0 {
let durationString = NSString.formatDurationSeconds(UInt32(self.duration), useShortFormat: true)
newMessageBody = "\(self.isOutgoing ? NSLocalizedString("call_outgoing", comment: "") : NSLocalizedString("call_incoming", comment: "")): \(durationString)"
shouldMarkAsRead = true
} else {
switch mode {
case .local:
newMessageBody = self.isOutgoing ? NSLocalizedString("call_cancelled", comment: "") : NSLocalizedString("call_rejected", comment: "")
shouldMarkAsRead = true
case .remote:
newMessageBody = self.isOutgoing ? NSLocalizedString("call_rejected", comment: "") : NSLocalizedString("call_missing", comment: "")
}
}
messageToUpdate.updateCall(withNewBody: newMessageBody, transaction: transaction)
if let incomingMessage = tsMessage as? TSIncomingMessage, shouldMarkAsRead {
incomingMessage.markAsReadNow(withSendReadReceipt: false, transaction: transaction)
}
}
}
}
// MARK: Renderer // MARK: Renderer
func attachRemoteVideoRenderer(_ renderer: RTCVideoRenderer) { func attachRemoteVideoRenderer(_ renderer: RTCVideoRenderer) {
webRTCSession.attachRemoteRenderer(renderer) webRTCSession.attachRemoteRenderer(renderer)
} }
func removeRemoteVideoRenderer(_ renderer: RTCVideoRenderer) {
webRTCSession.removeRemoteRenderer(renderer)
}
func attachLocalVideoRenderer(_ renderer: RTCVideoRenderer) { func attachLocalVideoRenderer(_ renderer: RTCVideoRenderer) {
webRTCSession.attachLocalRenderer(renderer) webRTCSession.attachLocalRenderer(renderer)
} }

@ -95,6 +95,9 @@ public final class SessionCallManager: NSObject {
guard let call = currentCall else { return } guard let call = currentCall else { return }
if let reason = reason { if let reason = reason {
self.provider.reportCall(with: call.uuid, endedAt: nil, reason: reason) self.provider.reportCall(with: call.uuid, endedAt: nil, reason: reason)
call.updateCallMessage(mode: .remote)
} else {
call.updateCallMessage(mode: .local)
} }
self.currentCall?.webRTCSession.dropConnection() self.currentCall?.webRTCSession.dropConnection()
self.currentCall = nil self.currentCall = nil

@ -125,6 +125,7 @@ final class MiniCallView: UIView {
UIView.animate(withDuration: 0.5, delay: 0, options: [], animations: { UIView.animate(withDuration: 0.5, delay: 0, options: [], animations: {
self.alpha = 0.0 self.alpha = 0.0
}, completion: { _ in }, completion: { _ in
self.callVC.call.removeRemoteVideoRenderer(self.remoteVideoView)
MiniCallView.current = nil MiniCallView.current = nil
self.removeFromSuperview() self.removeFromSuperview()
}) })

@ -32,7 +32,7 @@ extension ConversationVC : InputViewDelegate, MessageCellDelegate, ContextMenuAc
if userDefaults[.hasSeenCallIPExposureWarning] { if userDefaults[.hasSeenCallIPExposureWarning] {
guard let contactSessionID = (thread as? TSContactThread)?.contactSessionID() else { return } guard let contactSessionID = (thread as? TSContactThread)?.contactSessionID() else { return }
guard AppEnvironment.shared.callManager.currentCall == nil else { return } guard AppEnvironment.shared.callManager.currentCall == nil else { return }
let call = SessionCall(for: contactSessionID, uuid: UUID().uuidString, mode: .offer) let call = SessionCall(for: contactSessionID, uuid: UUID().uuidString, mode: .offer, outgoing: true)
let callVC = CallVC(for: call) let callVC = CallVC(for: call)
callVC.conversationVC = self callVC.conversationVC = self
self.inputAccessoryView?.isHidden = true self.inputAccessoryView?.isHidden = true

@ -6,9 +6,14 @@ import UIKit
extension AppDelegate { extension AppDelegate {
// MARK: Call handling // MARK: Call handling
func createNewIncomingCall(caller: String, uuid: String) { @objc func setUpCallHandling() {
// Pre offer messages
MessageReceiver.handlePreOfferCallMessage = { message in
guard CurrentAppContext().isMainApp else { return }
DispatchQueue.main.async { DispatchQueue.main.async {
if let caller = message.sender, let uuid = message.uuid {
let call = SessionCall(for: caller, uuid: uuid, mode: .answer) let call = SessionCall(for: caller, uuid: uuid, mode: .answer)
call.callMessageTimestamp = message.sentTimestamp
if CurrentAppContext().isMainAppAndActive { if CurrentAppContext().isMainAppAndActive {
guard let presentingVC = CurrentAppContext().frontmostViewController() else { preconditionFailure() } // TODO: Handle more gracefully guard let presentingVC = CurrentAppContext().frontmostViewController() else { preconditionFailure() } // TODO: Handle more gracefully
if let conversationVC = presentingVC as? ConversationVC, let contactThread = conversationVC.thread as? TSContactThread, contactThread.contactSessionID() == caller { if let conversationVC = presentingVC as? ConversationVC, let contactThread = conversationVC.thread as? TSContactThread, contactThread.contactSessionID() == caller {
@ -27,13 +32,8 @@ extension AppDelegate {
} }
} }
} }
}
@objc func setUpCallHandling() { }
// Pre offer messages
MessageReceiver.handlePreOfferCallMessage = { message in
guard CurrentAppContext().isMainApp else { return }
self.createNewIncomingCall(caller: message.sender!, uuid: message.uuid!)
} }
// Offer messages // Offer messages
MessageReceiver.handleOfferCallMessage = { message in MessageReceiver.handleOfferCallMessage = { message in

@ -577,6 +577,9 @@
"OPEN_SETTINGS_BUTTON" = "Settings"; "OPEN_SETTINGS_BUTTON" = "Settings";
"call_outgoing" = "Outgoing Call"; "call_outgoing" = "Outgoing Call";
"call_incoming" = "Incoming Call"; "call_incoming" = "Incoming Call";
"call_missing" = "Missing Call";
"call_rejected" = "Rejected Call";
"call_cancelled" = "Cancelled Call";
"voice_call" = "Voice Call"; "voice_call" = "Voice Call";
"video_call" = "Video Call"; "video_call" = "Video Call";
"APN_Message" = "You've got a new message"; "APN_Message" = "You've got a new message";

@ -10,6 +10,10 @@ extension WebRTCSession {
remoteVideoTrack?.add(renderer) remoteVideoTrack?.add(renderer)
} }
public func removeRemoteRenderer(_ renderer: RTCVideoRenderer) {
remoteVideoTrack?.remove(renderer)
}
public func handleLocalFrameCaptured(_ videoFrame: RTCVideoFrame) { public func handleLocalFrameCaptured(_ videoFrame: RTCVideoFrame) {
guard let videoCapturer = delegate?.videoCapturer else { return } guard let videoCapturer = delegate?.videoCapturer else { return }
localVideoSource.capturer(videoCapturer, didCapture: videoFrame) localVideoSource.capturer(videoCapturer, didCapture: videoFrame)

@ -128,10 +128,10 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate {
return promise return promise
} }
public func sendOffer(to sessionID: String, using transaction: YapDatabaseReadWriteTransaction) -> Promise<Void> { public func sendOffer(to sessionID: String, using transaction: YapDatabaseReadWriteTransaction) -> Promise<UInt64> {
print("[Calls] Sending offer message.") print("[Calls] Sending offer message.")
guard let thread = TSContactThread.fetch(for: sessionID, using: transaction) else { return Promise(error: Error.noThread) } guard let thread = TSContactThread.fetch(for: sessionID, using: transaction) else { return Promise(error: Error.noThread) }
let (promise, seal) = Promise<Void>.pending() let (promise, seal) = Promise<UInt64>.pending()
peerConnection.offer(for: mediaConstraints) { [weak self] sdp, error in peerConnection.offer(for: mediaConstraints) { [weak self] sdp, error in
if let error = error { if let error = error {
seal.reject(error) seal.reject(error)
@ -152,7 +152,7 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate {
let tsMessage = TSOutgoingMessage.from(message, associatedWith: thread) let tsMessage = TSOutgoingMessage.from(message, associatedWith: thread)
tsMessage.save(with: transaction) tsMessage.save(with: transaction)
MessageSender.sendNonDurably(message, in: thread, using: transaction).done2 { MessageSender.sendNonDurably(message, in: thread, using: transaction).done2 {
seal.fulfill(()) seal.fulfill(tsMessage.timestamp)
}.catch2 { error in }.catch2 { error in
seal.reject(error) seal.reject(error)
} }

@ -86,6 +86,8 @@ extern const NSUInteger kOversizeTextMessageSizeThreshold;
- (void)updateForDeletionWithTransaction:(YapDatabaseReadWriteTransaction *)transaction; - (void)updateForDeletionWithTransaction:(YapDatabaseReadWriteTransaction *)transaction;
- (void)updateCallMessageWithNewBody:(NSString *)newBody transaction:(YapDatabaseReadWriteTransaction *)transaction;
@end @end
NS_ASSUME_NONNULL_END NS_ASSUME_NONNULL_END

@ -442,6 +442,15 @@ const NSUInteger kOversizeTextMessageSizeThreshold = 2 * 1024;
}]; }];
} }
- (void)updateCallMessageWithNewBody:(NSString *)newBody transaction:(YapDatabaseReadWriteTransaction *)transaction
{
if (!_isCallMessage) { return; }
[self applyChangeToSelfAndLatestCopy:transaction
changeBlock:^(TSMessage *message) {
[message setBody:newBody];
}];
}
@end @end
NS_ASSUME_NONNULL_END NS_ASSUME_NONNULL_END

@ -281,13 +281,6 @@ extension MessageReceiver {
// TODO: Call in progress, put the new call on hold/reject // TODO: Call in progress, put the new call on hold/reject
return return
} }
handlePreOfferCallMessage?(message)
case .offer:
print("[Calls] Received offer message.")
if getWebRTCSession().uuid != message.uuid! {
// TODO: Call in progress, put the new call on hold/reject
return
}
let storage = SNMessagingKitConfiguration.shared.storage let storage = SNMessagingKitConfiguration.shared.storage
let transaction = transaction as! YapDatabaseReadWriteTransaction let transaction = transaction as! YapDatabaseReadWriteTransaction
if let threadID = storage.getOrCreateThread(for: message.sender!, groupPublicKey: message.groupPublicKey, openGroupID: nil, using: transaction), if let threadID = storage.getOrCreateThread(for: message.sender!, groupPublicKey: message.groupPublicKey, openGroupID: nil, using: transaction),
@ -295,9 +288,13 @@ extension MessageReceiver {
let tsMessage = TSIncomingMessage.from(message, associatedWith: thread) let tsMessage = TSIncomingMessage.from(message, associatedWith: thread)
tsMessage.save(with: transaction) tsMessage.save(with: transaction)
} }
// Delegate to the main app, which is expected to show a dialog confirming handlePreOfferCallMessage?(message)
// that the user wants to pick up the call. When they do, the SDP contained case .offer:
// in the offer message will be passed to WebRTCSession.handleRemoteSDP(_:from:). print("[Calls] Received offer message.")
if getWebRTCSession().uuid != message.uuid! {
// TODO: Call in progress, put the new call on hold/reject
return
}
handleOfferCallMessage?(message) handleOfferCallMessage?(message)
case .answer: case .answer:
print("[Calls] Received answer message.") print("[Calls] Received answer message.")

Loading…
Cancel
Save