mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
[WIP] Conference calls
This commit is contained in:
parent
0bb33c215a
commit
1f517e187a
@ -401,6 +401,22 @@ public extension GroupCallParticipantsContext.Participant {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public struct PresentationGroupCallInvitedPeer: Equatable {
|
||||||
|
public enum State {
|
||||||
|
case requesting
|
||||||
|
case ringing
|
||||||
|
case connecting
|
||||||
|
}
|
||||||
|
|
||||||
|
public var id: EnginePeer.Id
|
||||||
|
public var state: State?
|
||||||
|
|
||||||
|
public init(id: EnginePeer.Id, state: State?) {
|
||||||
|
self.id = id
|
||||||
|
self.state = state
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public protocol PresentationGroupCall: AnyObject {
|
public protocol PresentationGroupCall: AnyObject {
|
||||||
var account: Account { get }
|
var account: Account { get }
|
||||||
var accountContext: AccountContext { get }
|
var accountContext: AccountContext { get }
|
||||||
@ -468,7 +484,7 @@ public protocol PresentationGroupCall: AnyObject {
|
|||||||
|
|
||||||
func invitePeer(_ peerId: EnginePeer.Id) -> Bool
|
func invitePeer(_ peerId: EnginePeer.Id) -> Bool
|
||||||
func removedPeer(_ peerId: EnginePeer.Id)
|
func removedPeer(_ peerId: EnginePeer.Id)
|
||||||
var invitedPeers: Signal<[EnginePeer.Id], NoError> { get }
|
var invitedPeers: Signal<[PresentationGroupCallInvitedPeer], NoError> { get }
|
||||||
|
|
||||||
var inviteLinks: Signal<GroupCallInviteLinks?, NoError> { get }
|
var inviteLinks: Signal<GroupCallInviteLinks?, NoError> { get }
|
||||||
|
|
||||||
|
@ -470,7 +470,7 @@ final class CallControllerNodeV2: ViewControllerTracingNode, CallControllerNodeP
|
|||||||
}
|
}
|
||||||
case .busy:
|
case .busy:
|
||||||
mappedReason = .busy
|
mappedReason = .busy
|
||||||
case .hungUp:
|
case .hungUp, .switchedToConference:
|
||||||
if self.callStartTimestamp != nil {
|
if self.callStartTimestamp != nil {
|
||||||
mappedReason = .hangUp
|
mappedReason = .hangUp
|
||||||
} else {
|
} else {
|
||||||
@ -687,12 +687,12 @@ final class CallControllerNodeV2: ViewControllerTracingNode, CallControllerNodeP
|
|||||||
func animateOutToGroupChat(completion: @escaping () -> Void) -> CallController.AnimateOutToGroupChat {
|
func animateOutToGroupChat(completion: @escaping () -> Void) -> CallController.AnimateOutToGroupChat {
|
||||||
self.callScreen.animateOutToGroupChat(completion: completion)
|
self.callScreen.animateOutToGroupChat(completion: completion)
|
||||||
|
|
||||||
let takenIncomingVideoLayer = self.callScreen.takeIncomingVideoLayer()
|
let takeSource = self.callScreen.takeIncomingVideoLayer()
|
||||||
return CallController.AnimateOutToGroupChat(
|
return CallController.AnimateOutToGroupChat(
|
||||||
containerView: self.containerView,
|
containerView: self.containerView,
|
||||||
incomingPeerId: self.call.peerId,
|
incomingPeerId: (takeSource?.1 ?? true) ? self.call.peerId : self.call.context.account.peerId,
|
||||||
incomingVideoLayer: takenIncomingVideoLayer?.0,
|
incomingVideoLayer: takeSource?.0.0,
|
||||||
incomingVideoPlaceholder: takenIncomingVideoLayer?.1
|
incomingVideoPlaceholder: takeSource?.0.1
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -225,7 +225,7 @@ final class LegacyCallControllerNode: ASDisplayNode, CallControllerNodeProtocol
|
|||||||
switch type {
|
switch type {
|
||||||
case .busy:
|
case .busy:
|
||||||
statusValue = .text(self.presentationData.strings.Call_StatusBusy)
|
statusValue = .text(self.presentationData.strings.Call_StatusBusy)
|
||||||
case .hungUp, .missed:
|
case .hungUp, .missed, .switchedToConference:
|
||||||
statusValue = .text(self.presentationData.strings.Call_StatusEnded)
|
statusValue = .text(self.presentationData.strings.Call_StatusEnded)
|
||||||
}
|
}
|
||||||
case .error:
|
case .error:
|
||||||
|
@ -909,7 +909,7 @@ public final class PresentationCallImpl: PresentationCall {
|
|||||||
self.conferenceCallImpl = conferenceCall
|
self.conferenceCallImpl = conferenceCall
|
||||||
conferenceCall.upgradedConferenceCall = self
|
conferenceCall.upgradedConferenceCall = self
|
||||||
|
|
||||||
conferenceCall.setInvitedPeers(self.pendingInviteToConferencePeerIds)
|
conferenceCall.setConferenceInvitedPeers(self.pendingInviteToConferencePeerIds)
|
||||||
for peerId in self.pendingInviteToConferencePeerIds {
|
for peerId in self.pendingInviteToConferencePeerIds {
|
||||||
let _ = conferenceCall.invitePeer(peerId)
|
let _ = conferenceCall.invitePeer(peerId)
|
||||||
}
|
}
|
||||||
@ -990,8 +990,6 @@ public final class PresentationCallImpl: PresentationCall {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
self.ongoingContext?.stop(debugLogValue: Promise())
|
|
||||||
self.ongoingContext = nil
|
|
||||||
self.ongoingContextStateDisposable?.dispose()
|
self.ongoingContextStateDisposable?.dispose()
|
||||||
|
|
||||||
self.conferenceStateValue = .ready
|
self.conferenceStateValue = .ready
|
||||||
@ -1197,6 +1195,8 @@ public final class PresentationCallImpl: PresentationCall {
|
|||||||
tone = .busy
|
tone = .busy
|
||||||
case .hungUp, .missed:
|
case .hungUp, .missed:
|
||||||
tone = .ended
|
tone = .ended
|
||||||
|
case .switchedToConference:
|
||||||
|
tone = nil
|
||||||
}
|
}
|
||||||
case .error:
|
case .error:
|
||||||
tone = .failed
|
tone = .failed
|
||||||
|
@ -731,7 +731,7 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
|
|||||||
guard let groupCall = conferenceSource.conferenceCall else {
|
guard let groupCall = conferenceSource.conferenceCall else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
(conferenceSource as! PresentationCallImpl).resetAsMovedToConference()
|
(groupCall as! PresentationGroupCallImpl).moveConferenceCall(source: conferenceSource)
|
||||||
self.updateCurrentGroupCall(.group(groupCall))
|
self.updateCurrentGroupCall(.group(groupCall))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -601,6 +601,10 @@ private final class ScreencastEmbeddedIPCContext: ScreencastIPCContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private final class PendingConferenceInvitationContext {
|
private final class PendingConferenceInvitationContext {
|
||||||
|
enum State {
|
||||||
|
case ringing
|
||||||
|
}
|
||||||
|
|
||||||
private let callSessionManager: CallSessionManager
|
private let callSessionManager: CallSessionManager
|
||||||
private var requestDisposable: Disposable?
|
private var requestDisposable: Disposable?
|
||||||
private var stateDisposable: Disposable?
|
private var stateDisposable: Disposable?
|
||||||
@ -608,7 +612,7 @@ private final class PendingConferenceInvitationContext {
|
|||||||
|
|
||||||
private var didNotifyEnded: Bool = false
|
private var didNotifyEnded: Bool = false
|
||||||
|
|
||||||
init(callSessionManager: CallSessionManager, groupCall: GroupCallReference, encryptionKey: Data, peerId: PeerId, onEnded: @escaping () -> Void) {
|
init(callSessionManager: CallSessionManager, groupCall: GroupCallReference, encryptionKey: Data, peerId: PeerId, onStateUpdated: @escaping (State) -> Void, onEnded: @escaping (Bool) -> Void) {
|
||||||
self.callSessionManager = callSessionManager
|
self.callSessionManager = callSessionManager
|
||||||
|
|
||||||
self.requestDisposable = (callSessionManager.request(peerId: peerId, isVideo: false, enableVideo: true, conferenceCall: (groupCall, encryptionKey))
|
self.requestDisposable = (callSessionManager.request(peerId: peerId, isVideo: false, enableVideo: true, conferenceCall: (groupCall, encryptionKey))
|
||||||
@ -624,10 +628,14 @@ private final class PendingConferenceInvitationContext {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
switch state.state {
|
switch state.state {
|
||||||
case .dropping, .terminated:
|
case let .requesting(ringing, _):
|
||||||
|
if ringing {
|
||||||
|
onStateUpdated(.ringing)
|
||||||
|
}
|
||||||
|
case let .dropping(reason), let .terminated(_, reason, _):
|
||||||
if !self.didNotifyEnded {
|
if !self.didNotifyEnded {
|
||||||
self.didNotifyEnded = true
|
self.didNotifyEnded = true
|
||||||
onEnded()
|
onEnded(reason == .ended(.switchedToConference))
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
@ -972,15 +980,15 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
|||||||
return self.membersPromise.get()
|
return self.membersPromise.get()
|
||||||
}
|
}
|
||||||
|
|
||||||
private var invitedPeersValue: [EnginePeer.Id] = [] {
|
private var invitedPeersValue: [PresentationGroupCallInvitedPeer] = [] {
|
||||||
didSet {
|
didSet {
|
||||||
if self.invitedPeersValue != oldValue {
|
if self.invitedPeersValue != oldValue {
|
||||||
self.inivitedPeersPromise.set(self.invitedPeersValue)
|
self.inivitedPeersPromise.set(self.invitedPeersValue)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private let inivitedPeersPromise = ValuePromise<[EnginePeer.Id]>([])
|
private let inivitedPeersPromise = ValuePromise<[PresentationGroupCallInvitedPeer]>([])
|
||||||
public var invitedPeers: Signal<[EnginePeer.Id], NoError> {
|
public var invitedPeers: Signal<[PresentationGroupCallInvitedPeer], NoError> {
|
||||||
return self.inivitedPeersPromise.get()
|
return self.inivitedPeersPromise.get()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1062,14 +1070,13 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
|||||||
return self.conferenceSourceId
|
return self.conferenceSourceId
|
||||||
}
|
}
|
||||||
|
|
||||||
var internal_isRemoteConnected = Promise<Bool>()
|
|
||||||
private var internal_isRemoteConnectedDisposable: Disposable?
|
|
||||||
|
|
||||||
public var onMutedSpeechActivityDetected: ((Bool) -> Void)?
|
public var onMutedSpeechActivityDetected: ((Bool) -> Void)?
|
||||||
|
|
||||||
let debugLog = Promise<String?>()
|
let debugLog = Promise<String?>()
|
||||||
|
|
||||||
public weak var upgradedConferenceCall: PresentationCallImpl?
|
public weak var upgradedConferenceCall: PresentationCallImpl?
|
||||||
|
public var pendingDisconnedUpgradedConferenceCall: PresentationCallImpl?
|
||||||
|
private var pendingDisconnedUpgradedConferenceCallTimer: Foundation.Timer?
|
||||||
private var conferenceInvitationContexts: [PeerId: PendingConferenceInvitationContext] = [:]
|
private var conferenceInvitationContexts: [PeerId: PendingConferenceInvitationContext] = [:]
|
||||||
|
|
||||||
init(
|
init(
|
||||||
@ -1428,14 +1435,10 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
|||||||
}
|
}
|
||||||
|
|
||||||
self.audioOutputStateDisposable?.dispose()
|
self.audioOutputStateDisposable?.dispose()
|
||||||
|
|
||||||
self.removedChannelMembersDisposable?.dispose()
|
self.removedChannelMembersDisposable?.dispose()
|
||||||
|
|
||||||
self.peerUpdatesSubscription?.dispose()
|
self.peerUpdatesSubscription?.dispose()
|
||||||
|
|
||||||
self.screencastStateDisposable?.dispose()
|
self.screencastStateDisposable?.dispose()
|
||||||
|
self.pendingDisconnedUpgradedConferenceCallTimer?.invalidate()
|
||||||
self.internal_isRemoteConnectedDisposable?.dispose()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func switchToTemporaryParticipantsContext(sourceContext: GroupCallParticipantsContext?, oldMyPeerId: PeerId) {
|
private func switchToTemporaryParticipantsContext(sourceContext: GroupCallParticipantsContext?, oldMyPeerId: PeerId) {
|
||||||
@ -1529,7 +1532,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
|||||||
topParticipants.append(participant)
|
topParticipants.append(participant)
|
||||||
}
|
}
|
||||||
|
|
||||||
if let index = updatedInvitedPeers.firstIndex(of: participant.peer.id) {
|
if let index = updatedInvitedPeers.firstIndex(where: { $0.id == participant.peer.id }) {
|
||||||
updatedInvitedPeers.remove(at: index)
|
updatedInvitedPeers.remove(at: index)
|
||||||
didUpdateInvitedPeers = true
|
didUpdateInvitedPeers = true
|
||||||
}
|
}
|
||||||
@ -1986,6 +1989,8 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
|||||||
}
|
}
|
||||||
lastTimestamp = CFAbsoluteTimeGetCurrent()
|
lastTimestamp = CFAbsoluteTimeGetCurrent()
|
||||||
self.hasActiveIncomingDataValue = true
|
self.hasActiveIncomingDataValue = true
|
||||||
|
|
||||||
|
self.activateIncomingAudioIfNeeded()
|
||||||
})
|
})
|
||||||
|
|
||||||
self.hasActiveIncomingDataTimer?.invalidate()
|
self.hasActiveIncomingDataTimer?.invalidate()
|
||||||
@ -2002,15 +2007,6 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
|||||||
})
|
})
|
||||||
|
|
||||||
self.signalBarsPromise.set(callContext.signalBars)
|
self.signalBarsPromise.set(callContext.signalBars)
|
||||||
|
|
||||||
self.internal_isRemoteConnectedDisposable = (self.internal_isRemoteConnected.get()
|
|
||||||
|> distinctUntilChanged
|
|
||||||
|> deliverOnMainQueue).startStrict(next: { [weak callContext] isRemoteConnected in
|
|
||||||
guard let callContext else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
callContext.addRemoteConnectedEvent(isRemoteConntected: isRemoteConnected)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2645,7 +2641,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let index = updatedInvitedPeers.firstIndex(of: participant.peer.id) {
|
if let index = updatedInvitedPeers.firstIndex(where: { $0.id == participant.peer.id }) {
|
||||||
updatedInvitedPeers.remove(at: index)
|
updatedInvitedPeers.remove(at: index)
|
||||||
didUpdateInvitedPeers = true
|
didUpdateInvitedPeers = true
|
||||||
}
|
}
|
||||||
@ -2741,6 +2737,12 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func activateIncomingAudioIfNeeded() {
|
||||||
|
if let genericCallContext = self.genericCallContext, case let .call(groupCall) = genericCallContext {
|
||||||
|
groupCall.activateIncomingAudio()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private func requestMediaChannelDescriptions(ssrcs: Set<UInt32>, completion: @escaping ([OngoingGroupCallContext.MediaChannelDescription]) -> Void) -> Disposable {
|
private func requestMediaChannelDescriptions(ssrcs: Set<UInt32>, completion: @escaping ([OngoingGroupCallContext.MediaChannelDescription]) -> Void) -> Disposable {
|
||||||
func extractMediaChannelDescriptions(remainingSsrcs: inout Set<UInt32>, participants: [GroupCallParticipantsContext.Participant], into result: inout [OngoingGroupCallContext.MediaChannelDescription]) {
|
func extractMediaChannelDescriptions(remainingSsrcs: inout Set<UInt32>, participants: [GroupCallParticipantsContext.Participant], into result: inout [OngoingGroupCallContext.MediaChannelDescription]) {
|
||||||
for participant in participants {
|
for participant in participants {
|
||||||
@ -3643,42 +3645,68 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
|||||||
if conferenceInvitationContexts[peerId] != nil {
|
if conferenceInvitationContexts[peerId] != nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
var onEnded: (() -> Void)?
|
var onStateUpdated: ((PendingConferenceInvitationContext.State) -> Void)?
|
||||||
|
var onEnded: ((Bool) -> Void)?
|
||||||
var didEndAlready = false
|
var didEndAlready = false
|
||||||
let invitationContext = PendingConferenceInvitationContext(
|
let invitationContext = PendingConferenceInvitationContext(
|
||||||
callSessionManager: self.accountContext.account.callSessionManager,
|
callSessionManager: self.accountContext.account.callSessionManager,
|
||||||
groupCall: GroupCallReference(id: initialCall.id, accessHash: initialCall.accessHash),
|
groupCall: GroupCallReference(id: initialCall.id, accessHash: initialCall.accessHash),
|
||||||
encryptionKey: encryptionKey.key,
|
encryptionKey: encryptionKey.key,
|
||||||
peerId: peerId,
|
peerId: peerId,
|
||||||
onEnded: {
|
onStateUpdated: { state in
|
||||||
|
onStateUpdated?(state)
|
||||||
|
},
|
||||||
|
onEnded: { success in
|
||||||
didEndAlready = true
|
didEndAlready = true
|
||||||
onEnded?()
|
onEnded?(success)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
if !didEndAlready {
|
if !didEndAlready {
|
||||||
conferenceInvitationContexts[peerId] = invitationContext
|
conferenceInvitationContexts[peerId] = invitationContext
|
||||||
if !self.invitedPeersValue.contains(peerId) {
|
if !self.invitedPeersValue.contains(where: { $0.id == peerId }) {
|
||||||
self.invitedPeersValue.append(peerId)
|
self.invitedPeersValue.append(PresentationGroupCallInvitedPeer(id: peerId, state: .requesting))
|
||||||
}
|
}
|
||||||
onEnded = { [weak self, weak invitationContext] in
|
onStateUpdated = { [weak self] state in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if let index = self.invitedPeersValue.firstIndex(where: { $0.id == peerId }) {
|
||||||
|
var invitedPeer = self.invitedPeersValue[index]
|
||||||
|
switch state {
|
||||||
|
case .ringing:
|
||||||
|
invitedPeer.state = .ringing
|
||||||
|
}
|
||||||
|
self.invitedPeersValue[index] = invitedPeer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onEnded = { [weak self, weak invitationContext] success in
|
||||||
guard let self, let invitationContext else {
|
guard let self, let invitationContext else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if self.conferenceInvitationContexts[peerId] === invitationContext {
|
if self.conferenceInvitationContexts[peerId] === invitationContext {
|
||||||
self.conferenceInvitationContexts.removeValue(forKey: peerId)
|
self.conferenceInvitationContexts.removeValue(forKey: peerId)
|
||||||
self.invitedPeersValue.removeAll(where: { $0 == peerId })
|
|
||||||
|
if success {
|
||||||
|
if let index = self.invitedPeersValue.firstIndex(where: { $0.id == peerId }) {
|
||||||
|
var invitedPeer = self.invitedPeersValue[index]
|
||||||
|
invitedPeer.state = .connecting
|
||||||
|
self.invitedPeersValue[index] = invitedPeer
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.invitedPeersValue.removeAll(where: { $0.id == peerId })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
return false
|
||||||
} else {
|
} else {
|
||||||
guard let callInfo = self.internalState.callInfo, !self.invitedPeersValue.contains(peerId) else {
|
guard let callInfo = self.internalState.callInfo, !self.invitedPeersValue.contains(where: { $0.id == peerId }) else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
var updatedInvitedPeers = self.invitedPeersValue
|
var updatedInvitedPeers = self.invitedPeersValue
|
||||||
updatedInvitedPeers.insert(peerId, at: 0)
|
updatedInvitedPeers.insert(PresentationGroupCallInvitedPeer(id: peerId, state: nil), at: 0)
|
||||||
self.invitedPeersValue = updatedInvitedPeers
|
self.invitedPeersValue = updatedInvitedPeers
|
||||||
|
|
||||||
let _ = self.accountContext.engine.calls.inviteToGroupCall(callId: callInfo.id, accessHash: callInfo.accessHash, peerId: peerId).start()
|
let _ = self.accountContext.engine.calls.inviteToGroupCall(callId: callInfo.id, accessHash: callInfo.accessHash, peerId: peerId).start()
|
||||||
@ -3687,13 +3715,15 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func setInvitedPeers(_ peerIds: [PeerId]) {
|
func setConferenceInvitedPeers(_ peerIds: [PeerId]) {
|
||||||
self.invitedPeersValue = peerIds
|
self.invitedPeersValue = peerIds.map {
|
||||||
|
PresentationGroupCallInvitedPeer(id: $0, state: .requesting)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func removedPeer(_ peerId: PeerId) {
|
public func removedPeer(_ peerId: PeerId) {
|
||||||
var updatedInvitedPeers = self.invitedPeersValue
|
var updatedInvitedPeers = self.invitedPeersValue
|
||||||
updatedInvitedPeers.removeAll(where: { $0 == peerId})
|
updatedInvitedPeers.removeAll(where: { $0.id == peerId})
|
||||||
self.invitedPeersValue = updatedInvitedPeers
|
self.invitedPeersValue = updatedInvitedPeers
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3859,6 +3889,26 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
|||||||
}
|
}
|
||||||
|> runOn(.mainQueue())
|
|> runOn(.mainQueue())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func moveConferenceCall(source: PresentationCall) {
|
||||||
|
guard let source = source as? PresentationCallImpl else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
self.pendingDisconnedUpgradedConferenceCall?.resetAsMovedToConference()
|
||||||
|
self.pendingDisconnedUpgradedConferenceCall = source
|
||||||
|
|
||||||
|
self.pendingDisconnedUpgradedConferenceCallTimer?.invalidate()
|
||||||
|
self.pendingDisconnedUpgradedConferenceCallTimer = Foundation.Timer.scheduledTimer(withTimeInterval: 5.0, repeats: false, block: { [weak self] _ in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if let pendingDisconnedUpgradedConferenceCall = self.pendingDisconnedUpgradedConferenceCall {
|
||||||
|
self.pendingDisconnedUpgradedConferenceCall = nil
|
||||||
|
pendingDisconnedUpgradedConferenceCall.resetAsMovedToConference()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private protocol ScreencastContext: AnyObject {
|
private protocol ScreencastContext: AnyObject {
|
||||||
|
@ -428,6 +428,15 @@ final class VideoChatParticipantVideoComponent: Component {
|
|||||||
alphaTransition.setAlpha(view: titleView, alpha: controlsAlpha)
|
alphaTransition.setAlpha(view: titleView, alpha: controlsAlpha)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var previousVideoDescription: GroupCallParticipantsContext.Participant.VideoDescription?
|
||||||
|
if let previousComponent {
|
||||||
|
if previousComponent.isMyPeer && previousComponent.isPresentation {
|
||||||
|
previousVideoDescription = nil
|
||||||
|
} else {
|
||||||
|
previousVideoDescription = previousComponent.maxVideoQuality == 0 ? nil : (previousComponent.isPresentation ? previousComponent.participant.presentationDescription : previousComponent.participant.videoDescription)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let videoDescription: GroupCallParticipantsContext.Participant.VideoDescription?
|
let videoDescription: GroupCallParticipantsContext.Participant.VideoDescription?
|
||||||
if component.isMyPeer && component.isPresentation {
|
if component.isMyPeer && component.isPresentation {
|
||||||
videoDescription = nil
|
videoDescription = nil
|
||||||
@ -506,8 +515,13 @@ final class VideoChatParticipantVideoComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let videoLayer: PrivateCallVideoLayer
|
let videoLayer: PrivateCallVideoLayer
|
||||||
|
var resetVideoSource = false
|
||||||
if let current = self.videoLayer {
|
if let current = self.videoLayer {
|
||||||
videoLayer = current
|
videoLayer = current
|
||||||
|
|
||||||
|
if let previousVideoDescription, previousVideoDescription.endpointId != videoDescription.endpointId {
|
||||||
|
resetVideoSource = true
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
videoLayer = PrivateCallVideoLayer()
|
videoLayer = PrivateCallVideoLayer()
|
||||||
self.videoLayer = videoLayer
|
self.videoLayer = videoLayer
|
||||||
@ -517,6 +531,10 @@ final class VideoChatParticipantVideoComponent: Component {
|
|||||||
|
|
||||||
videoLayer.blurredLayer.opacity = 0.0
|
videoLayer.blurredLayer.opacity = 0.0
|
||||||
|
|
||||||
|
resetVideoSource = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if resetVideoSource {
|
||||||
if let input = component.call.video(endpointId: videoDescription.endpointId) {
|
if let input = component.call.video(endpointId: videoDescription.endpointId) {
|
||||||
let videoSource = AdaptedCallVideoSource(videoStreamSignal: input)
|
let videoSource = AdaptedCallVideoSource(videoStreamSignal: input)
|
||||||
self.videoSource = videoSource
|
self.videoSource = videoSource
|
||||||
|
@ -128,7 +128,7 @@ final class VideoChatParticipantsComponent: Component {
|
|||||||
|
|
||||||
let call: VideoChatCall
|
let call: VideoChatCall
|
||||||
let participants: Participants?
|
let participants: Participants?
|
||||||
let invitedPeers: [EnginePeer]
|
let invitedPeers: [VideoChatScreenComponent.InvitedPeer]
|
||||||
let speakingParticipants: Set<EnginePeer.Id>
|
let speakingParticipants: Set<EnginePeer.Id>
|
||||||
let expandedVideoState: ExpandedVideoState?
|
let expandedVideoState: ExpandedVideoState?
|
||||||
let maxVideoQuality: Int
|
let maxVideoQuality: Int
|
||||||
@ -148,7 +148,7 @@ final class VideoChatParticipantsComponent: Component {
|
|||||||
init(
|
init(
|
||||||
call: VideoChatCall,
|
call: VideoChatCall,
|
||||||
participants: Participants?,
|
participants: Participants?,
|
||||||
invitedPeers: [EnginePeer],
|
invitedPeers: [VideoChatScreenComponent.InvitedPeer],
|
||||||
speakingParticipants: Set<EnginePeer.Id>,
|
speakingParticipants: Set<EnginePeer.Id>,
|
||||||
expandedVideoState: ExpandedVideoState?,
|
expandedVideoState: ExpandedVideoState?,
|
||||||
maxVideoQuality: Int,
|
maxVideoQuality: Int,
|
||||||
@ -1259,10 +1259,20 @@ final class VideoChatParticipantsComponent: Component {
|
|||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
let invitedPeer = component.invitedPeers[i - self.listParticipants.count]
|
let invitedPeer = component.invitedPeers[i - self.listParticipants.count]
|
||||||
participantPeerId = invitedPeer.id
|
participantPeerId = invitedPeer.peer.id
|
||||||
|
|
||||||
let subtitle: PeerListItemComponent.Subtitle
|
let subtitle: PeerListItemComponent.Subtitle
|
||||||
subtitle = PeerListItemComponent.Subtitle(text: component.strings.VoiceChat_StatusInvited, color: .neutral)
|
//TODO:localize
|
||||||
|
switch invitedPeer.state {
|
||||||
|
case .none:
|
||||||
|
subtitle = PeerListItemComponent.Subtitle(text: component.strings.VoiceChat_StatusInvited, color: .neutral)
|
||||||
|
case .connecting:
|
||||||
|
subtitle = PeerListItemComponent.Subtitle(text: "connecting...", color: .neutral)
|
||||||
|
case .requesting:
|
||||||
|
subtitle = PeerListItemComponent.Subtitle(text: "requesting...", color: .neutral)
|
||||||
|
case .ringing:
|
||||||
|
subtitle = PeerListItemComponent.Subtitle(text: "ringing...", color: .neutral)
|
||||||
|
}
|
||||||
|
|
||||||
peerItemComponent = PeerListItemComponent(
|
peerItemComponent = PeerListItemComponent(
|
||||||
context: component.call.accountContext,
|
context: component.call.accountContext,
|
||||||
@ -1270,15 +1280,15 @@ final class VideoChatParticipantsComponent: Component {
|
|||||||
strings: component.strings,
|
strings: component.strings,
|
||||||
style: .generic,
|
style: .generic,
|
||||||
sideInset: 0.0,
|
sideInset: 0.0,
|
||||||
title: invitedPeer.displayTitle(strings: component.strings, displayOrder: .firstLast),
|
title: invitedPeer.peer.displayTitle(strings: component.strings, displayOrder: .firstLast),
|
||||||
avatarComponent: AnyComponent(VideoChatParticipantAvatarComponent(
|
avatarComponent: AnyComponent(VideoChatParticipantAvatarComponent(
|
||||||
call: component.call,
|
call: component.call,
|
||||||
peer: invitedPeer,
|
peer: invitedPeer.peer,
|
||||||
myPeerId: component.participants?.myPeerId ?? component.call.accountContext.account.peerId,
|
myPeerId: component.participants?.myPeerId ?? component.call.accountContext.account.peerId,
|
||||||
isSpeaking: false,
|
isSpeaking: false,
|
||||||
theme: component.theme
|
theme: component.theme
|
||||||
)),
|
)),
|
||||||
peer: invitedPeer,
|
peer: invitedPeer.peer,
|
||||||
subtitle: subtitle,
|
subtitle: subtitle,
|
||||||
subtitleAccessory: .none,
|
subtitleAccessory: .none,
|
||||||
presence: nil,
|
presence: nil,
|
||||||
|
@ -190,6 +190,16 @@ final class VideoChatScreenComponent: Component {
|
|||||||
self.scrollView = scrollView
|
self.scrollView = scrollView
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct InvitedPeer: Equatable {
|
||||||
|
var peer: EnginePeer
|
||||||
|
var state: PresentationGroupCallInvitedPeer.State?
|
||||||
|
|
||||||
|
init(peer: EnginePeer, state: PresentationGroupCallInvitedPeer.State?) {
|
||||||
|
self.peer = peer
|
||||||
|
self.state = state
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
final class View: UIView, UIGestureRecognizerDelegate {
|
final class View: UIView, UIGestureRecognizerDelegate {
|
||||||
let containerView: UIView
|
let containerView: UIView
|
||||||
@ -242,7 +252,7 @@ final class VideoChatScreenComponent: Component {
|
|||||||
var members: PresentationGroupCallMembers?
|
var members: PresentationGroupCallMembers?
|
||||||
var membersDisposable: Disposable?
|
var membersDisposable: Disposable?
|
||||||
|
|
||||||
var invitedPeers: [EnginePeer] = []
|
var invitedPeers: [InvitedPeer] = []
|
||||||
var invitedPeersDisposable: Disposable?
|
var invitedPeersDisposable: Disposable?
|
||||||
|
|
||||||
var speakingParticipantPeers: [EnginePeer] = []
|
var speakingParticipantPeers: [EnginePeer] = []
|
||||||
@ -323,8 +333,13 @@ final class VideoChatScreenComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var expandedPeer: (id: EnginePeer.Id, isPresentation: Bool)?
|
var expandedPeer: (id: EnginePeer.Id, isPresentation: Bool)?
|
||||||
if let animateOutData, animateOutData.incomingVideoLayer != nil {
|
if let animateOutData, animateOutData.incomingVideoLayer != nil, let members = self.members {
|
||||||
if let members = self.members, let participant = members.participants.first(where: { $0.peer.id == animateOutData.incomingPeerId }) {
|
if let participant = members.participants.first(where: { $0.peer.id == animateOutData.incomingPeerId }) {
|
||||||
|
if let _ = participant.videoDescription {
|
||||||
|
expandedPeer = (participant.peer.id, false)
|
||||||
|
self.expandedParticipantsVideoState = VideoChatParticipantsComponent.ExpandedVideoState(mainParticipant: VideoChatParticipantsComponent.VideoParticipantKey(id: participant.peer.id, isPresentation: false), isMainParticipantPinned: false, isUIHidden: true)
|
||||||
|
}
|
||||||
|
} else if let participant = members.participants.first(where: { $0.peer.id == sourceCallController.call.context.account.peerId }) {
|
||||||
if let _ = participant.videoDescription {
|
if let _ = participant.videoDescription {
|
||||||
expandedPeer = (participant.peer.id, false)
|
expandedPeer = (participant.peer.id, false)
|
||||||
self.expandedParticipantsVideoState = VideoChatParticipantsComponent.ExpandedVideoState(mainParticipant: VideoChatParticipantsComponent.VideoParticipantKey(id: participant.peer.id, isPresentation: false), isMainParticipantPinned: false, isUIHidden: true)
|
self.expandedParticipantsVideoState = VideoChatParticipantsComponent.ExpandedVideoState(mainParticipant: VideoChatParticipantsComponent.VideoParticipantKey(id: participant.peer.id, isPresentation: false), isMainParticipantPinned: false, isUIHidden: true)
|
||||||
@ -969,7 +984,7 @@ final class VideoChatScreenComponent: Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static func groupCallStateForConferenceSource(conferenceSource: PresentationCall) -> Signal<(state: PresentationGroupCallState, invitedPeers: [EnginePeer]), NoError> {
|
static func groupCallStateForConferenceSource(conferenceSource: PresentationCall) -> Signal<(state: PresentationGroupCallState, invitedPeers: [InvitedPeer]), NoError> {
|
||||||
let invitedPeers = conferenceSource.context.engine.data.subscribe(
|
let invitedPeers = conferenceSource.context.engine.data.subscribe(
|
||||||
EngineDataList((conferenceSource as! PresentationCallImpl).pendingInviteToConferencePeerIds.map { TelegramEngine.EngineData.Item.Peer.Peer(id: $0) })
|
EngineDataList((conferenceSource as! PresentationCallImpl).pendingInviteToConferencePeerIds.map { TelegramEngine.EngineData.Item.Peer.Peer(id: $0) })
|
||||||
)
|
)
|
||||||
@ -982,7 +997,7 @@ final class VideoChatScreenComponent: Component {
|
|||||||
conferenceSource.isMuted,
|
conferenceSource.isMuted,
|
||||||
invitedPeers
|
invitedPeers
|
||||||
)
|
)
|
||||||
|> mapToSignal { state, isMuted, invitedPeers -> Signal<(state: PresentationGroupCallState, invitedPeers: [EnginePeer]), NoError> in
|
|> mapToSignal { state, isMuted, invitedPeers -> Signal<(state: PresentationGroupCallState, invitedPeers: [VideoChatScreenComponent.InvitedPeer]), NoError> in
|
||||||
let mappedNetworkState: PresentationGroupCallState.NetworkState
|
let mappedNetworkState: PresentationGroupCallState.NetworkState
|
||||||
switch state.state {
|
switch state.state {
|
||||||
case .active:
|
case .active:
|
||||||
@ -1007,7 +1022,12 @@ final class VideoChatScreenComponent: Component {
|
|||||||
isVideoWatchersLimitReached: false
|
isVideoWatchersLimitReached: false
|
||||||
)
|
)
|
||||||
|
|
||||||
return .single((callState, invitedPeers.compactMap({ $0 })))
|
return .single((callState, invitedPeers.compactMap({ peer -> VideoChatScreenComponent.InvitedPeer? in
|
||||||
|
guard let peer else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return VideoChatScreenComponent.InvitedPeer(peer: peer, state: .requesting)
|
||||||
|
})))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1023,10 +1043,18 @@ final class VideoChatScreenComponent: Component {
|
|||||||
var participants: [GroupCallParticipantsContext.Participant] = []
|
var participants: [GroupCallParticipantsContext.Participant] = []
|
||||||
let (myPeer, remotePeer) = peers
|
let (myPeer, remotePeer) = peers
|
||||||
if let myPeer {
|
if let myPeer {
|
||||||
|
var myVideoDescription: GroupCallParticipantsContext.Participant.VideoDescription?
|
||||||
|
switch state.videoState {
|
||||||
|
case .active:
|
||||||
|
myVideoDescription = GroupCallParticipantsContext.Participant.VideoDescription(endpointId: "temp-local", ssrcGroups: [], audioSsrc: nil, isPaused: false)
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
participants.append(GroupCallParticipantsContext.Participant(
|
participants.append(GroupCallParticipantsContext.Participant(
|
||||||
peer: myPeer._asPeer(),
|
peer: myPeer._asPeer(),
|
||||||
ssrc: nil,
|
ssrc: nil,
|
||||||
videoDescription: nil,
|
videoDescription: myVideoDescription,
|
||||||
presentationDescription: nil,
|
presentationDescription: nil,
|
||||||
joinTimestamp: 0,
|
joinTimestamp: 0,
|
||||||
raiseHandRating: nil,
|
raiseHandRating: nil,
|
||||||
@ -1040,10 +1068,18 @@ final class VideoChatScreenComponent: Component {
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
if let remotePeer {
|
if let remotePeer {
|
||||||
|
var remoteVideoDescription: GroupCallParticipantsContext.Participant.VideoDescription?
|
||||||
|
switch state.remoteVideoState {
|
||||||
|
case .active:
|
||||||
|
remoteVideoDescription = GroupCallParticipantsContext.Participant.VideoDescription(endpointId: "temp-remote", ssrcGroups: [], audioSsrc: nil, isPaused: false)
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
participants.append(GroupCallParticipantsContext.Participant(
|
participants.append(GroupCallParticipantsContext.Participant(
|
||||||
peer: remotePeer._asPeer(),
|
peer: remotePeer._asPeer(),
|
||||||
ssrc: nil,
|
ssrc: nil,
|
||||||
videoDescription: nil,
|
videoDescription: remoteVideoDescription,
|
||||||
presentationDescription: nil,
|
presentationDescription: nil,
|
||||||
joinTimestamp: 0,
|
joinTimestamp: 0,
|
||||||
raiseHandRating: nil,
|
raiseHandRating: nil,
|
||||||
@ -1087,7 +1123,7 @@ final class VideoChatScreenComponent: Component {
|
|||||||
self.members = component.initialData.members
|
self.members = component.initialData.members
|
||||||
self.invitedPeers = component.initialData.invitedPeers
|
self.invitedPeers = component.initialData.invitedPeers
|
||||||
if let members = self.members {
|
if let members = self.members {
|
||||||
self.invitedPeers.removeAll(where: { invitedPeer in members.participants.contains(where: { $0.peer.id == invitedPeer.id }) })
|
self.invitedPeers.removeAll(where: { invitedPeer in members.participants.contains(where: { $0.peer.id == invitedPeer.peer.id }) })
|
||||||
}
|
}
|
||||||
self.callState = component.initialData.callState
|
self.callState = component.initialData.callState
|
||||||
}
|
}
|
||||||
@ -1128,7 +1164,7 @@ final class VideoChatScreenComponent: Component {
|
|||||||
|
|
||||||
self.members = members
|
self.members = members
|
||||||
if let members {
|
if let members {
|
||||||
self.invitedPeers.removeAll(where: { invitedPeer in members.participants.contains(where: { $0.peer.id == invitedPeer.id }) })
|
self.invitedPeers.removeAll(where: { invitedPeer in members.participants.contains(where: { $0.peer.id == invitedPeer.peer.id }) })
|
||||||
}
|
}
|
||||||
|
|
||||||
if let members, let expandedParticipantsVideoState = self.expandedParticipantsVideoState, !expandedParticipantsVideoState.isUIHidden {
|
if let members, let expandedParticipantsVideoState = self.expandedParticipantsVideoState, !expandedParticipantsVideoState.isUIHidden {
|
||||||
@ -1234,10 +1270,16 @@ final class VideoChatScreenComponent: Component {
|
|||||||
self.invitedPeersDisposable = (groupCall.invitedPeers
|
self.invitedPeersDisposable = (groupCall.invitedPeers
|
||||||
|> mapToSignal { invitedPeers in
|
|> mapToSignal { invitedPeers in
|
||||||
return accountContext.engine.data.get(
|
return accountContext.engine.data.get(
|
||||||
EngineDataList(invitedPeers.map({ TelegramEngine.EngineData.Item.Peer.Peer(id: $0) }))
|
EngineDataMap(invitedPeers.map({ TelegramEngine.EngineData.Item.Peer.Peer(id: $0.id) }))
|
||||||
)
|
)
|
||||||
|> map { peers in
|
|> map { peers -> [InvitedPeer] in
|
||||||
return peers.compactMap { $0 }
|
var result: [InvitedPeer] = []
|
||||||
|
for invitedPeer in invitedPeers {
|
||||||
|
if let maybePeer = peers[invitedPeer.id], let peer = maybePeer {
|
||||||
|
result.append(InvitedPeer(peer: peer, state: invitedPeer.state))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|> deliverOnMainQueue).startStrict(next: { [weak self] invitedPeers in
|
|> deliverOnMainQueue).startStrict(next: { [weak self] invitedPeers in
|
||||||
@ -1247,7 +1289,7 @@ final class VideoChatScreenComponent: Component {
|
|||||||
|
|
||||||
var invitedPeers = invitedPeers
|
var invitedPeers = invitedPeers
|
||||||
if let members {
|
if let members {
|
||||||
invitedPeers.removeAll(where: { invitedPeer in members.participants.contains(where: { $0.peer.id == invitedPeer.id }) })
|
invitedPeers.removeAll(where: { invitedPeer in members.participants.contains(where: { $0.peer.id == invitedPeer.peer.id }) })
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.invitedPeers != invitedPeers {
|
if self.invitedPeers != invitedPeers {
|
||||||
@ -2461,13 +2503,13 @@ final class VideoChatScreenV2Impl: ViewControllerComponentContainer, VoiceChatCo
|
|||||||
let peer: EnginePeer?
|
let peer: EnginePeer?
|
||||||
let members: PresentationGroupCallMembers?
|
let members: PresentationGroupCallMembers?
|
||||||
let callState: PresentationGroupCallState
|
let callState: PresentationGroupCallState
|
||||||
let invitedPeers: [EnginePeer]
|
let invitedPeers: [VideoChatScreenComponent.InvitedPeer]
|
||||||
|
|
||||||
init(
|
init(
|
||||||
peer: EnginePeer?,
|
peer: EnginePeer?,
|
||||||
members: PresentationGroupCallMembers?,
|
members: PresentationGroupCallMembers?,
|
||||||
callState: PresentationGroupCallState,
|
callState: PresentationGroupCallState,
|
||||||
invitedPeers: [EnginePeer]
|
invitedPeers: [VideoChatScreenComponent.InvitedPeer]
|
||||||
) {
|
) {
|
||||||
self.peer = peer
|
self.peer = peer
|
||||||
self.members = members
|
self.members = members
|
||||||
@ -2633,7 +2675,7 @@ final class VideoChatScreenV2Impl: ViewControllerComponentContainer, VoiceChatCo
|
|||||||
let accountContext = groupCall.accountContext
|
let accountContext = groupCall.accountContext
|
||||||
let invitedPeers = groupCall.invitedPeers |> take(1) |> mapToSignal { invitedPeers in
|
let invitedPeers = groupCall.invitedPeers |> take(1) |> mapToSignal { invitedPeers in
|
||||||
return accountContext.engine.data.get(
|
return accountContext.engine.data.get(
|
||||||
EngineDataList(invitedPeers.map({ TelegramEngine.EngineData.Item.Peer.Peer(id: $0) }))
|
EngineDataList(invitedPeers.map(\.id).map({ TelegramEngine.EngineData.Item.Peer.Peer(id: $0) }))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return combineLatest(
|
return combineLatest(
|
||||||
@ -2647,7 +2689,12 @@ final class VideoChatScreenV2Impl: ViewControllerComponentContainer, VoiceChatCo
|
|||||||
peer: peer,
|
peer: peer,
|
||||||
members: members,
|
members: members,
|
||||||
callState: callState,
|
callState: callState,
|
||||||
invitedPeers: invitedPeers.compactMap { $0 }
|
invitedPeers: invitedPeers.compactMap { peer -> VideoChatScreenComponent.InvitedPeer? in
|
||||||
|
guard let peer else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return VideoChatScreenComponent.InvitedPeer(peer: peer, state: nil)
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
case let .conferenceSource(conferenceSource):
|
case let .conferenceSource(conferenceSource):
|
||||||
|
@ -1891,7 +1891,8 @@ final class VoiceChatControllerImpl: ViewController, VoiceChatController {
|
|||||||
self.updateDecorationsColors()
|
self.updateDecorationsColors()
|
||||||
|
|
||||||
let invitedPeers: Signal<[EnginePeer], NoError> = self.call.invitedPeers
|
let invitedPeers: Signal<[EnginePeer], NoError> = self.call.invitedPeers
|
||||||
|> mapToSignal { ids -> Signal<[EnginePeer], NoError> in
|
|> mapToSignal { peers -> Signal<[EnginePeer], NoError> in
|
||||||
|
let ids = peers.map(\.id)
|
||||||
return context.engine.data.get(EngineDataList(
|
return context.engine.data.get(EngineDataList(
|
||||||
ids.map(TelegramEngine.EngineData.Item.Peer.Peer.init)
|
ids.map(TelegramEngine.EngineData.Item.Peer.Peer.init)
|
||||||
))
|
))
|
||||||
|
@ -19,6 +19,7 @@ public enum CallSessionEndedType {
|
|||||||
case hungUp
|
case hungUp
|
||||||
case busy
|
case busy
|
||||||
case missed
|
case missed
|
||||||
|
case switchedToConference
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum CallSessionTerminationReason: Equatable {
|
public enum CallSessionTerminationReason: Equatable {
|
||||||
@ -709,7 +710,7 @@ private final class CallSessionManagerContext {
|
|||||||
case .missed:
|
case .missed:
|
||||||
mappedReason = .ended(.missed)
|
mappedReason = .ended(.missed)
|
||||||
case .switchToConference:
|
case .switchToConference:
|
||||||
mappedReason = .ended(.hungUp)
|
mappedReason = .ended(.switchedToConference)
|
||||||
}
|
}
|
||||||
context.state = .dropping(reason: mappedReason, disposable: (dropCallSession(network: self.network, addUpdates: self.addUpdates, stableId: id, accessHash: accessHash, isVideo: isVideo, reason: reason)
|
context.state = .dropping(reason: mappedReason, disposable: (dropCallSession(network: self.network, addUpdates: self.addUpdates, stableId: id, accessHash: accessHash, isVideo: isVideo, reason: reason)
|
||||||
|> deliverOn(self.queue)).start(next: { [weak self] reportRating, sendDebugLogs in
|
|> deliverOn(self.queue)).start(next: { [weak self] reportRating, sendDebugLogs in
|
||||||
@ -763,7 +764,7 @@ private final class CallSessionManagerContext {
|
|||||||
|
|
||||||
if let (id, accessHash) = dropData {
|
if let (id, accessHash) = dropData {
|
||||||
self.contextIdByStableId.removeValue(forKey: id)
|
self.contextIdByStableId.removeValue(forKey: id)
|
||||||
context.state = .dropping(reason: .ended(.hungUp), disposable: (dropCallSession(network: self.network, addUpdates: self.addUpdates, stableId: id, accessHash: accessHash, isVideo: isVideo, reason: .switchToConference(encryptedGroupKey: encryptedGroupKey))
|
context.state = .dropping(reason: .ended(.switchedToConference), disposable: (dropCallSession(network: self.network, addUpdates: self.addUpdates, stableId: id, accessHash: accessHash, isVideo: isVideo, reason: .switchToConference(encryptedGroupKey: encryptedGroupKey))
|
||||||
|> deliverOn(self.queue)).start(next: { [weak self] reportRating, sendDebugLogs in
|
|> deliverOn(self.queue)).start(next: { [weak self] reportRating, sendDebugLogs in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
if let context = strongSelf.contexts[internalId] {
|
if let context = strongSelf.contexts[internalId] {
|
||||||
@ -962,11 +963,15 @@ private final class CallSessionManagerContext {
|
|||||||
case .phoneCallDiscardReasonDisconnect:
|
case .phoneCallDiscardReasonDisconnect:
|
||||||
parsedReason = .error(.disconnected)
|
parsedReason = .error(.disconnected)
|
||||||
case .phoneCallDiscardReasonHangup:
|
case .phoneCallDiscardReasonHangup:
|
||||||
parsedReason = .ended(.hungUp)
|
if context.pendingConference != nil {
|
||||||
|
parsedReason = .ended(.switchedToConference)
|
||||||
|
} else {
|
||||||
|
parsedReason = .ended(.hungUp)
|
||||||
|
}
|
||||||
case .phoneCallDiscardReasonMissed:
|
case .phoneCallDiscardReasonMissed:
|
||||||
parsedReason = .ended(.missed)
|
parsedReason = .ended(.missed)
|
||||||
case .phoneCallDiscardReasonAllowGroupCall:
|
case .phoneCallDiscardReasonAllowGroupCall:
|
||||||
parsedReason = .ended(.hungUp)
|
parsedReason = .ended(.switchedToConference)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
parsedReason = .ended(.hungUp)
|
parsedReason = .ended(.hungUp)
|
||||||
|
@ -503,21 +503,27 @@ public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictu
|
|||||||
self.update(transition: .easeInOut(duration: 0.25))
|
self.update(transition: .easeInOut(duration: 0.25))
|
||||||
}
|
}
|
||||||
|
|
||||||
public func takeIncomingVideoLayer() -> (CALayer, VideoSource.Output?)? {
|
public func takeIncomingVideoLayer() -> ((CALayer, VideoSource.Output?), Bool)? {
|
||||||
var remoteVideoContainerKey: VideoContainerView.Key?
|
var activeVideoSources: [(VideoContainerView.Key, Bool)] = []
|
||||||
if self.swapLocalAndRemoteVideo {
|
if self.swapLocalAndRemoteVideo {
|
||||||
|
if let _ = self.activeLocalVideoSource {
|
||||||
|
activeVideoSources.append((.background, false))
|
||||||
|
}
|
||||||
if let _ = self.activeRemoteVideoSource {
|
if let _ = self.activeRemoteVideoSource {
|
||||||
remoteVideoContainerKey = .foreground
|
activeVideoSources.append((.foreground, true))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if let _ = self.activeRemoteVideoSource {
|
if let _ = self.activeRemoteVideoSource {
|
||||||
remoteVideoContainerKey = .background
|
activeVideoSources.append((.background, true))
|
||||||
|
}
|
||||||
|
if let _ = self.activeLocalVideoSource {
|
||||||
|
activeVideoSources.append((.foreground, false))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let remoteVideoContainerKey, let videoContainerView = self.videoContainerViews.first(where: { $0.key == remoteVideoContainerKey }) {
|
if let videoSource = activeVideoSources.first, let videoContainerView = self.videoContainerViews.first(where: { $0.key == videoSource.0 }) {
|
||||||
videoContainerView.videoContainerLayerTaken = true
|
videoContainerView.videoContainerLayerTaken = true
|
||||||
return (videoContainerView.videoContainerLayer, videoContainerView.currentVideoOutput)
|
return ((videoContainerView.videoContainerLayer, videoContainerView.currentVideoOutput), videoSource.1)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -1087,10 +1087,8 @@ public final class OngoingGroupCallContext {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func addRemoteConnectedEvent(isRemoteConntected: Bool) {
|
func activateIncomingAudio() {
|
||||||
#if os(iOS)
|
self.context.activateIncomingAudio()
|
||||||
self.context.addRemoteConnectedEvent(isRemoteConntected)
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1314,9 +1312,9 @@ public final class OngoingGroupCallContext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func addRemoteConnectedEvent(isRemoteConntected: Bool) {
|
public func activateIncomingAudio() {
|
||||||
self.impl.with { impl in
|
self.impl.with { impl in
|
||||||
impl.addRemoteConnectedEvent(isRemoteConntected: isRemoteConntected)
|
impl.activateIncomingAudio()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -453,7 +453,7 @@ isConference:(bool)isConference;
|
|||||||
|
|
||||||
- (void)getStats:(void (^ _Nonnull)(OngoingGroupCallStats * _Nonnull))completion;
|
- (void)getStats:(void (^ _Nonnull)(OngoingGroupCallStats * _Nonnull))completion;
|
||||||
|
|
||||||
- (void)addRemoteConnectedEvent:(bool)isRemoteConnected;
|
- (void)activateIncomingAudio;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
|
@ -66,6 +66,29 @@
|
|||||||
|
|
||||||
namespace tgcalls {
|
namespace tgcalls {
|
||||||
|
|
||||||
|
class WrappedChildAudioDeviceModuleControl {
|
||||||
|
public:
|
||||||
|
WrappedChildAudioDeviceModuleControl() {
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual ~WrappedChildAudioDeviceModuleControl() {
|
||||||
|
_mutex.Lock();
|
||||||
|
_mutex.Unlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
void setActive() {
|
||||||
|
_mutex.Lock();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
_mutex.Unlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
webrtc::Mutex _mutex;
|
||||||
|
};
|
||||||
|
|
||||||
class SharedAudioDeviceModule {
|
class SharedAudioDeviceModule {
|
||||||
public:
|
public:
|
||||||
virtual ~SharedAudioDeviceModule() = default;
|
virtual ~SharedAudioDeviceModule() = default;
|
||||||
@ -93,12 +116,15 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
void UpdateAudioCallback(webrtc::AudioTransport *previousAudioCallback, webrtc::AudioTransport *audioCallback) {
|
void UpdateAudioCallback(webrtc::AudioTransport *previousAudioCallback, webrtc::AudioTransport *audioCallback) {
|
||||||
if (audioCallback == nil) {
|
if (audioCallback) {
|
||||||
if (_currentAudioTransport == previousAudioCallback) {
|
_audioTransports.push_back(audioCallback);
|
||||||
_currentAudioTransport = nil;
|
} else if (previousAudioCallback) {
|
||||||
|
for (size_t i = 0; i < _audioTransports.size(); i++) {
|
||||||
|
if (_audioTransports[i] == previousAudioCallback) {
|
||||||
|
_audioTransports.erase(_audioTransports.begin() + i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
_currentAudioTransport = audioCallback;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -409,19 +435,26 @@ public:
|
|||||||
bool keyPressed,
|
bool keyPressed,
|
||||||
uint32_t& newMicLevel
|
uint32_t& newMicLevel
|
||||||
) override {
|
) override {
|
||||||
if (_currentAudioTransport) {
|
if (!_audioTransports.empty()) {
|
||||||
return _currentAudioTransport->RecordedDataIsAvailable(
|
for (size_t i = 0; i < _audioTransports.size(); i++) {
|
||||||
audioSamples,
|
auto result = _audioTransports[_audioTransports.size() - 1]->RecordedDataIsAvailable(
|
||||||
nSamples,
|
audioSamples,
|
||||||
nBytesPerSample,
|
nSamples,
|
||||||
nChannels,
|
nBytesPerSample,
|
||||||
samplesPerSec,
|
nChannels,
|
||||||
totalDelayMS,
|
samplesPerSec,
|
||||||
clockDrift,
|
totalDelayMS,
|
||||||
currentMicLevel,
|
clockDrift,
|
||||||
keyPressed,
|
currentMicLevel,
|
||||||
newMicLevel
|
keyPressed,
|
||||||
);
|
newMicLevel
|
||||||
|
);
|
||||||
|
if (i == _audioTransports.size() - 1) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
} else {
|
} else {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@ -440,20 +473,26 @@ public:
|
|||||||
uint32_t& newMicLevel,
|
uint32_t& newMicLevel,
|
||||||
absl::optional<int64_t> estimatedCaptureTimeNS
|
absl::optional<int64_t> estimatedCaptureTimeNS
|
||||||
) override {
|
) override {
|
||||||
if (_currentAudioTransport) {
|
if (!_audioTransports.empty()) {
|
||||||
return _currentAudioTransport->RecordedDataIsAvailable(
|
for (size_t i = 0; i < _audioTransports.size(); i++) {
|
||||||
audioSamples,
|
auto result = _audioTransports[_audioTransports.size() - 1]->RecordedDataIsAvailable(
|
||||||
nSamples,
|
audioSamples,
|
||||||
nBytesPerSample,
|
nSamples,
|
||||||
nChannels,
|
nBytesPerSample,
|
||||||
samplesPerSec,
|
nChannels,
|
||||||
totalDelayMS,
|
samplesPerSec,
|
||||||
clockDrift,
|
totalDelayMS,
|
||||||
currentMicLevel,
|
clockDrift,
|
||||||
keyPressed,
|
currentMicLevel,
|
||||||
newMicLevel,
|
keyPressed,
|
||||||
estimatedCaptureTimeNS
|
newMicLevel,
|
||||||
);
|
estimatedCaptureTimeNS
|
||||||
|
);
|
||||||
|
if (i == _audioTransports.size() - 1) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
} else {
|
} else {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@ -470,8 +509,8 @@ public:
|
|||||||
int64_t* elapsed_time_ms,
|
int64_t* elapsed_time_ms,
|
||||||
int64_t* ntp_time_ms
|
int64_t* ntp_time_ms
|
||||||
) override {
|
) override {
|
||||||
if (_currentAudioTransport) {
|
if (!_audioTransports.empty()) {
|
||||||
return _currentAudioTransport->NeedMorePlayData(
|
return _audioTransports[_audioTransports.size() - 1]->NeedMorePlayData(
|
||||||
nSamples,
|
nSamples,
|
||||||
nBytesPerSample,
|
nBytesPerSample,
|
||||||
nChannels,
|
nChannels,
|
||||||
@ -496,8 +535,8 @@ public:
|
|||||||
int64_t* elapsed_time_ms,
|
int64_t* elapsed_time_ms,
|
||||||
int64_t* ntp_time_ms
|
int64_t* ntp_time_ms
|
||||||
) override {
|
) override {
|
||||||
if (_currentAudioTransport) {
|
if (!_audioTransports.empty()) {
|
||||||
_currentAudioTransport->PullRenderData(
|
_audioTransports[_audioTransports.size() - 1]->PullRenderData(
|
||||||
bits_per_sample,
|
bits_per_sample,
|
||||||
sample_rate,
|
sample_rate,
|
||||||
number_of_channels,
|
number_of_channels,
|
||||||
@ -540,7 +579,7 @@ public:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
bool _isStarted = false;
|
bool _isStarted = false;
|
||||||
webrtc::AudioTransport *_currentAudioTransport = nullptr;
|
std::vector<webrtc::AudioTransport *> _audioTransports;
|
||||||
};
|
};
|
||||||
|
|
||||||
class WrappedChildAudioDeviceModule : public tgcalls::DefaultWrappedAudioDeviceModule {
|
class WrappedChildAudioDeviceModule : public tgcalls::DefaultWrappedAudioDeviceModule {
|
||||||
@ -556,13 +595,28 @@ public:
|
|||||||
auto previousAudioCallback = _audioCallback;
|
auto previousAudioCallback = _audioCallback;
|
||||||
_audioCallback = audioCallback;
|
_audioCallback = audioCallback;
|
||||||
|
|
||||||
((WrappedAudioDeviceModuleIOS *)WrappedInstance().get())->UpdateAudioCallback(previousAudioCallback, audioCallback);
|
if (_isActive) {
|
||||||
|
((WrappedAudioDeviceModuleIOS *)WrappedInstance().get())->UpdateAudioCallback(previousAudioCallback, audioCallback);
|
||||||
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
void setIsActive() {
|
||||||
|
if (_isActive) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_isActive = true;
|
||||||
|
|
||||||
|
if (_audioCallback) {
|
||||||
|
((WrappedAudioDeviceModuleIOS *)WrappedInstance().get())->UpdateAudioCallback(nullptr, _audioCallback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
webrtc::AudioTransport *_audioCallback = nullptr;
|
webrtc::AudioTransport *_audioCallback = nullptr;
|
||||||
|
bool _isActive = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
class SharedAudioDeviceModuleImpl: public tgcalls::SharedAudioDeviceModule {
|
class SharedAudioDeviceModuleImpl: public tgcalls::SharedAudioDeviceModule {
|
||||||
@ -1785,7 +1839,9 @@ static void (*InternalVoipLoggingFunction)(NSString *) = NULL;
|
|||||||
},
|
},
|
||||||
.createWrappedAudioDeviceModule = [audioDeviceModule](webrtc::TaskQueueFactory *taskQueueFactory) -> rtc::scoped_refptr<tgcalls::WrappedAudioDeviceModule> {
|
.createWrappedAudioDeviceModule = [audioDeviceModule](webrtc::TaskQueueFactory *taskQueueFactory) -> rtc::scoped_refptr<tgcalls::WrappedAudioDeviceModule> {
|
||||||
if (audioDeviceModule) {
|
if (audioDeviceModule) {
|
||||||
return audioDeviceModule->getSyncAssumingSameThread()->makeChildAudioDeviceModule();
|
auto result = audioDeviceModule->getSyncAssumingSameThread()->makeChildAudioDeviceModule();
|
||||||
|
((WrappedChildAudioDeviceModule *)result.get())->setIsActive();
|
||||||
|
return result;
|
||||||
} else {
|
} else {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
@ -2493,7 +2549,9 @@ isConference:(bool)isConference {
|
|||||||
},
|
},
|
||||||
.createWrappedAudioDeviceModule = [audioDeviceModule](webrtc::TaskQueueFactory *taskQueueFactory) -> rtc::scoped_refptr<tgcalls::WrappedAudioDeviceModule> {
|
.createWrappedAudioDeviceModule = [audioDeviceModule](webrtc::TaskQueueFactory *taskQueueFactory) -> rtc::scoped_refptr<tgcalls::WrappedAudioDeviceModule> {
|
||||||
if (audioDeviceModule) {
|
if (audioDeviceModule) {
|
||||||
return audioDeviceModule->getSyncAssumingSameThread()->makeChildAudioDeviceModule();
|
auto result = audioDeviceModule->getSyncAssumingSameThread()->makeChildAudioDeviceModule();
|
||||||
|
((WrappedChildAudioDeviceModule *)result.get())->setIsActive();
|
||||||
|
return result;
|
||||||
} else {
|
} else {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
@ -2835,10 +2893,7 @@ isConference:(bool)isConference {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)addRemoteConnectedEvent:(bool)isRemoteConnected {
|
- (void)activateIncomingAudio {
|
||||||
if (_instance) {
|
|
||||||
_instance->internal_addCustomNetworkEvent(isRemoteConnected);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
Loading…
x
Reference in New Issue
Block a user