mirror of https://github.com/oxen-io/session-ios
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
236 lines
6.7 KiB
Swift
236 lines
6.7 KiB
Swift
//
|
|
// Copyright (c) 2021 Open Whisper Systems. All rights reserved.
|
|
//
|
|
|
|
import Foundation
|
|
import SignalRingRTC
|
|
|
|
public enum CallState: String {
|
|
case idle
|
|
case dialing
|
|
case answering
|
|
case remoteRinging
|
|
case localRinging
|
|
case connected
|
|
case reconnecting
|
|
case localFailure // terminal
|
|
case localHangup // terminal
|
|
case remoteHangup // terminal
|
|
case remoteHangupNeedPermission // terminal
|
|
case remoteBusy // terminal
|
|
case answeredElsewhere // terminal
|
|
case declinedElsewhere // terminal
|
|
case busyElsewhere // terminal
|
|
}
|
|
|
|
public enum CallAdapterType {
|
|
case `default`, nonCallKit
|
|
}
|
|
|
|
public enum CallDirection {
|
|
case outgoing, incoming
|
|
}
|
|
|
|
public protocol IndividualCallDelegate: AnyObject {
|
|
func individualCallStateDidChange(_ call: IndividualCall, state: CallState)
|
|
func individualCallLocalVideoMuteDidChange(_ call: IndividualCall, isVideoMuted: Bool)
|
|
func individualCallLocalAudioMuteDidChange(_ call: IndividualCall, isAudioMuted: Bool)
|
|
func individualCallHoldDidChange(_ call: IndividualCall, isOnHold: Bool)
|
|
func individualCallRemoteVideoMuteDidChange(_ call: IndividualCall, isVideoMuted: Bool)
|
|
func individualCallRemoteSharingScreenDidChange(_ call: IndividualCall, isRemoteSharingScreen: Bool)
|
|
}
|
|
|
|
/**
|
|
* Data model for a WebRTC backed voice/video call.
|
|
*
|
|
* This class' state should only be accessed on the main queue.
|
|
*/
|
|
@objc
|
|
public class IndividualCall: NSObject, IndividualCallNotificationInfo {
|
|
|
|
// Mark -
|
|
|
|
var backgroundTask: OWSBackgroundTask? {
|
|
didSet {
|
|
AssertIsOnMainThread()
|
|
|
|
Logger.info("")
|
|
}
|
|
}
|
|
|
|
var callId: UInt64? {
|
|
didSet {
|
|
AssertIsOnMainThread()
|
|
|
|
Logger.info("")
|
|
}
|
|
}
|
|
|
|
let callAdapterType: CallAdapterType
|
|
|
|
weak var remoteVideoTrack: RTCVideoTrack? {
|
|
didSet {
|
|
AssertIsOnMainThread()
|
|
|
|
Logger.info("")
|
|
delegate?.individualCallRemoteVideoMuteDidChange(self, isVideoMuted: !isRemoteVideoEnabled)
|
|
}
|
|
}
|
|
|
|
var isRemoteVideoEnabled = false {
|
|
didSet {
|
|
AssertIsOnMainThread()
|
|
|
|
Logger.info("\(isRemoteVideoEnabled)")
|
|
delegate?.individualCallRemoteVideoMuteDidChange(self, isVideoMuted: !isRemoteVideoEnabled)
|
|
}
|
|
}
|
|
|
|
var isRemoteSharingScreen = false {
|
|
didSet {
|
|
AssertIsOnMainThread()
|
|
|
|
Logger.info("\(isRemoteSharingScreen)")
|
|
delegate?.individualCallRemoteSharingScreenDidChange(self, isRemoteSharingScreen: isRemoteSharingScreen)
|
|
}
|
|
}
|
|
|
|
// MARK: -
|
|
|
|
// tracking cleanup
|
|
var wasReportedToSystem = false
|
|
var wasRemovedFromSystem = false
|
|
|
|
@objc
|
|
public let remoteAddress: SignalServiceAddress
|
|
|
|
public var isEnded: Bool {
|
|
switch state {
|
|
case .localFailure, .localHangup, .remoteHangup, .remoteHangupNeedPermission, .remoteBusy, .answeredElsewhere, .declinedElsewhere, .busyElsewhere:
|
|
return true
|
|
case .idle, .dialing, .answering, .remoteRinging, .localRinging, .connected, .reconnecting:
|
|
return false
|
|
}
|
|
}
|
|
|
|
public let direction: CallDirection
|
|
|
|
// Distinguishes between calls locally, e.g. in CallKit
|
|
@objc
|
|
public let localId: UUID
|
|
|
|
public let thread: TSContactThread
|
|
|
|
public let sentAtTimestamp: UInt64
|
|
|
|
public var callRecord: TSCall? {
|
|
didSet {
|
|
AssertIsOnMainThread()
|
|
assert(oldValue == nil)
|
|
|
|
updateCallRecordType()
|
|
}
|
|
}
|
|
|
|
public lazy var hasLocalVideo = offerMediaType == .video {
|
|
didSet {
|
|
AssertIsOnMainThread()
|
|
|
|
delegate?.individualCallLocalVideoMuteDidChange(self, isVideoMuted: !hasLocalVideo)
|
|
}
|
|
}
|
|
|
|
public var state: CallState {
|
|
didSet {
|
|
AssertIsOnMainThread()
|
|
Logger.debug("state changed: \(oldValue) -> \(self.state) for call: \(self)")
|
|
|
|
updateCallRecordType()
|
|
|
|
delegate?.individualCallStateDidChange(self, state: state)
|
|
}
|
|
}
|
|
|
|
public var offerMediaType: TSRecentCallOfferType = .audio
|
|
|
|
// We start out muted if the record permission isn't granted. This should generally
|
|
// only happen for incoming calls, because we proactively ask about it before you
|
|
// can make an outgoing call.
|
|
public var isMuted = AVAudioSession.sharedInstance().recordPermission != .granted {
|
|
didSet {
|
|
AssertIsOnMainThread()
|
|
|
|
Logger.debug("muted changed: \(oldValue) -> \(self.isMuted)")
|
|
|
|
delegate?.individualCallLocalAudioMuteDidChange(self, isAudioMuted: isMuted)
|
|
}
|
|
}
|
|
|
|
public var isOnHold = false {
|
|
didSet {
|
|
AssertIsOnMainThread()
|
|
Logger.debug("isOnHold changed: \(oldValue) -> \(self.isOnHold)")
|
|
|
|
delegate?.individualCallHoldDidChange(self, isOnHold: isOnHold)
|
|
}
|
|
}
|
|
|
|
public weak var delegate: IndividualCallDelegate?
|
|
|
|
// MARK: Initializers and Factory Methods
|
|
|
|
init(direction: CallDirection, localId: UUID, state: CallState, remoteAddress: SignalServiceAddress, sentAtTimestamp: UInt64, callAdapterType: CallAdapterType) {
|
|
self.direction = direction
|
|
self.localId = localId
|
|
self.state = state
|
|
self.remoteAddress = remoteAddress
|
|
self.thread = TSContactThread.getOrCreateThread(contactAddress: remoteAddress)
|
|
self.sentAtTimestamp = sentAtTimestamp
|
|
self.callAdapterType = callAdapterType
|
|
}
|
|
|
|
deinit {
|
|
Logger.debug("")
|
|
if !isEnded {
|
|
owsFailDebug("isEnded was unexpectedly false")
|
|
}
|
|
if wasReportedToSystem {
|
|
if !wasRemovedFromSystem {
|
|
owsFailDebug("wasRemovedFromSystem was unexpectedly false")
|
|
}
|
|
} else {
|
|
if wasRemovedFromSystem {
|
|
owsFailDebug("wasRemovedFromSystem was unexpectedly true")
|
|
}
|
|
}
|
|
}
|
|
|
|
override public var description: String {
|
|
return "IndividualCall: {\(remoteAddress), localId: \(localId), signalingId: \(callId as Optional)))}"
|
|
}
|
|
|
|
private func updateCallRecordType() {
|
|
AssertIsOnMainThread()
|
|
|
|
guard let callRecord = self.callRecord else {
|
|
return
|
|
}
|
|
|
|
// Mark incomplete calls as completed if call has connected.
|
|
if state == .connected &&
|
|
callRecord.callType == .outgoingIncomplete {
|
|
callRecord.updateCallType(.outgoing)
|
|
}
|
|
if state == .connected &&
|
|
callRecord.callType == .incomingIncomplete {
|
|
callRecord.updateCallType(.incoming)
|
|
}
|
|
}
|
|
|
|
// MARK: Equatable
|
|
|
|
static func == (lhs: IndividualCall, rhs: IndividualCall) -> Bool {
|
|
return lhs.localId == rhs.localId
|
|
}
|
|
}
|