-
-
Notifications
You must be signed in to change notification settings - Fork 3.2k
/
Copy pathSignalCall.swift
211 lines (189 loc) · 6.47 KB
/
SignalCall.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
//
// Copyright 2020 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//
import SignalRingRTC
import SignalServiceKit
import SignalUI
enum CallMode {
case individual(IndividualCall)
case groupThread(GroupThreadCall)
case callLink(CallLinkCall)
init(groupCall: GroupCall) {
switch groupCall.concreteType {
case .groupThread(let call): self = .groupThread(call)
case .callLink(let call): self = .callLink(call)
}
}
var commonState: CommonCallState {
switch self {
case .individual(let call):
return call.commonState
case .groupThread(let call as GroupCall), .callLink(let call as GroupCall):
return call.commonState
}
}
@MainActor
var hasTerminated: Bool {
switch self {
case .individual(let call): return call.hasTerminated
case .groupThread(let call): return call.hasTerminated
case .callLink: return false
}
}
@MainActor
var isOutgoingAudioMuted: Bool {
switch self {
case .individual(let call):
return call.isMuted
case .groupThread(let call as GroupCall), .callLink(let call as GroupCall):
return call.ringRtcCall.isOutgoingAudioMuted
}
}
@MainActor
var isOutgoingVideoMuted: Bool {
switch self {
case .individual(let call):
return !call.hasLocalVideo
case .groupThread(let call as GroupCall), .callLink(let call as GroupCall):
return call.ringRtcCall.isOutgoingVideoMuted
}
}
@MainActor
var joinState: JoinState {
switch self {
case .individual(let call):
/// `JoinState` is a group call concept, but we want to bridge
/// between the two call types.
/// TODO: Continue to tweak this as we unify the individual and
/// group call UIs.
switch call.state {
case .idle,
.remoteHangup,
.remoteHangupNeedPermission,
.localHangup,
.remoteRinging,
.localRinging_Anticipatory,
.localRinging_ReadyToAnswer,
.remoteBusy,
.localFailure,
.busyElsewhere,
.answeredElsewhere,
.declinedElsewhere:
return .notJoined
case .connected,
.accepting,
.answering,
.reconnecting,
.dialing:
return .joined
}
case .groupThread(let call as GroupCall), .callLink(let call as GroupCall):
return call.joinState
}
}
var isFull: Bool {
switch self {
case .individual:
return false
case .groupThread(let call as GroupCall), .callLink(let call as GroupCall):
return call.ringRtcCall.isFull
}
}
/// Returns the remote party for an incoming 1:1 call, or the ringer for a group call ring.
///
/// Returns `nil` for an outgoing 1:1 call, a manually-entered group call,
/// or a group call that has already been joined.
var caller: SignalServiceAddress? {
switch self {
case .individual(let call):
guard call.direction == .incoming else {
return nil
}
return call.remoteAddress
case .groupThread(let call):
guard case .incomingRing(let caller, _) = call.groupCallRingState else {
return nil
}
return caller
case .callLink:
return nil
}
}
var videoCaptureController: VideoCaptureController {
switch self {
case .individual(let call):
return call.videoCaptureController
case .groupThread(let call as GroupCall), .callLink(let call as GroupCall):
return call.videoCaptureController
}
}
func matches(_ callTarget: CallTarget) -> Bool {
switch (self, callTarget) {
case (.individual(let call), .individual(let thread)) where call.thread.uniqueId == thread.uniqueId:
return true
case (.groupThread(let call), .groupThread(let groupId)) where call.groupId.serialize() == groupId.serialize():
return true
case (.callLink(let call), .callLink(let callLink)) where call.callLink.rootKey.bytes == callLink.rootKey.bytes:
return true
case (.individual, _), (.groupThread, _), (.callLink, _):
return false
}
}
}
enum CallError: Error {
case providerReset
case disconnected
case externalError(underlyingError: Error)
case timeout(description: String)
case signaling
case doNotDisturbEnabled
case contactIsBlocked
func shouldSilentlyDropCall() -> Bool {
switch self {
case .providerReset, .disconnected, .externalError, .timeout, .signaling:
return false
case .doNotDisturbEnabled, .contactIsBlocked:
return true
}
}
static func wrapErrorIfNeeded(_ error: any Error) -> CallError {
switch error {
case let callError as CallError:
return callError
default:
return .externalError(underlyingError: error)
}
}
}
/// Represents a call happening on this device.
class SignalCall: CallManagerCallReference {
let mode: CallMode
var commonState: CommonCallState { mode.commonState }
@MainActor var hasTerminated: Bool { mode.hasTerminated }
@MainActor var isOutgoingAudioMuted: Bool { mode.isOutgoingAudioMuted }
@MainActor var isOutgoingVideoMuted: Bool { mode.isOutgoingVideoMuted }
@MainActor var joinState: JoinState { mode.joinState }
var isFull: Bool { mode.isFull }
var caller: SignalServiceAddress? { mode.caller }
var videoCaptureController: VideoCaptureController { mode.videoCaptureController }
init(groupThreadCall: GroupThreadCall) {
self.mode = .groupThread(groupThreadCall)
}
init(callLinkCall: CallLinkCall) {
self.mode = .callLink(callLinkCall)
}
init(individualCall: IndividualCall) {
self.mode = .individual(individualCall)
}
var localId: UUID {
return self.mode.commonState.localId
}
var offerMediaType: TSRecentCallOfferType {
switch mode {
case .individual(let call): return call.offerMediaType
case .groupThread: return .video
case .callLink: return .video
}
}
}