From 2c7185763ed73184d5b9a0ea38fcbda102c26e28 Mon Sep 17 00:00:00 2001 From: Ali <> Date: Tue, 24 Nov 2020 19:50:25 +0400 Subject: [PATCH] WIP --- .bazelrc | 5 + build-system/generate-xcode-project.sh | 1 + .../Sources/PresentationCallManager.swift | 7 +- .../Sources/Node/ChatListTypingNode.swift | 8 + submodules/Display/Source/WindowContent.swift | 3 + submodules/TelegramApi/Sources/Api0.swift | 7 +- submodules/TelegramApi/Sources/Api1.swift | 78 ++- submodules/TelegramApi/Sources/Api3.swift | 18 +- .../Sources/PresentationGroupCall.swift | 94 ++-- .../Sources/VoiceChatController.swift | 38 +- .../Sources/AccountIntermediateState.swift | 6 +- .../Sources/AccountStateManagementUtils.swift | 22 +- .../Sources/AccountStateManager.swift | 4 +- .../TelegramCore/Sources/GroupCalls.swift | 481 ++++++++++++++++-- .../Sources/ManagedLocalInputActivities.swift | 2 + .../Sources/PeerInputActivity.swift | 72 +-- .../TelegramUI/Sources/ChatTitleView.swift | 4 + submodules/TgVoipWebrtc/tgcalls | 2 +- third-party/webrtc/BUILD | 52 +- third-party/webrtc/webrtc-ios | 2 +- 20 files changed, 620 insertions(+), 286 deletions(-) diff --git a/.bazelrc b/.bazelrc index 2c58590827..8d23f76c8c 100644 --- a/.bazelrc +++ b/.bazelrc @@ -4,6 +4,11 @@ build --action_env=ZERO_AR_DATE=1 build --strategy=Genrule=local build --apple_platform_type=ios build --cxxopt='-std=c++14' +build --per_file_copt="third-party/webrtc/.*\.m$","@-fno-stack-protector" +build --per_file_copt="third-party/webrtc/.*\.mm$","@-fno-stack-protector" +build --per_file_copt="third-party/webrtc/.*\.cpp$","@-std=c++14" +build --per_file_copt="third-party/webrtc/.*\.cc$","@-std=c++14" +build --per_file_copt="third-party/webrtc/.*\.mm$","@-std=c++14" build --spawn_strategy=local build --strategy=SwiftCompile=local build --features=debug_prefix_map_pwd_is_dot diff --git a/build-system/generate-xcode-project.sh b/build-system/generate-xcode-project.sh index 9910f9d3b9..eb1e5538c8 100755 --- a/build-system/generate-xcode-project.sh +++ b/build-system/generate-xcode-project.sh @@ -51,6 +51,7 @@ BAZEL_OPTIONS=(\ --spawn_strategy=standalone \ --strategy=SwiftCompile=standalone \ --features=swift.enable_batch_mode \ + --apple_generate_dsym \ --swiftcopt=-j${CORE_COUNT_MINUS_ONE} \ ) diff --git a/submodules/AccountContext/Sources/PresentationCallManager.swift b/submodules/AccountContext/Sources/PresentationCallManager.swift index 431d10c532..80849f7862 100644 --- a/submodules/AccountContext/Sources/PresentationCallManager.swift +++ b/submodules/AccountContext/Sources/PresentationCallManager.swift @@ -176,14 +176,14 @@ public struct PresentationGroupCallState: Equatable { public struct PresentationGroupCallMemberState: Equatable { public var ssrc: UInt32 - public var isSpeaking: Bool + public var muteState: GroupCallParticipantsContext.Participant.MuteState? public init( ssrc: UInt32, - isSpeaking: Bool + muteState: GroupCallParticipantsContext.Participant.MuteState? ) { self.ssrc = ssrc - self.isSpeaking = isSpeaking + self.muteState = muteState } } @@ -205,6 +205,7 @@ public protocol PresentationGroupCall: class { func toggleIsMuted() func setIsMuted(_ value: Bool) func setCurrentAudioOutput(_ output: AudioSessionOutput) + func updateMuteState(peerId: PeerId, isMuted: Bool) } public protocol PresentationCallManager: class { diff --git a/submodules/ChatListUI/Sources/Node/ChatListTypingNode.swift b/submodules/ChatListUI/Sources/Node/ChatListTypingNode.swift index e67e3f05c9..ca664a419d 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListTypingNode.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListTypingNode.swift @@ -60,6 +60,8 @@ final class ChatListInputActivitiesNode: ASDisplayNode { text = strings.Activity_PlayingGame case .typingText: text = strings.DialogList_Typing + case .speakingInGroupCall: + text = "" } let string = NSAttributedString(string: text, font: textFont, textColor: color) @@ -74,6 +76,8 @@ final class ChatListInputActivitiesNode: ASDisplayNode { state = .uploading(string, lightColor) case .playingGame: state = .playingGame(string, lightColor) + case .speakingInGroupCall: + state = .typingText(string, lightColor) } } else { let text: String @@ -96,6 +100,8 @@ final class ChatListInputActivitiesNode: ASDisplayNode { text = strings.DialogList_SinglePlayingGameSuffix(peerTitle).0 case .typingText: text = strings.DialogList_SingleTypingSuffix(peerTitle).0 + case .speakingInGroupCall: + text = "" } } else { text = activities[0].0.compactDisplayTitle @@ -113,6 +119,8 @@ final class ChatListInputActivitiesNode: ASDisplayNode { state = .uploading(string, lightColor) case .playingGame: state = .playingGame(string, lightColor) + case .speakingInGroupCall: + state = .typingText(string, lightColor) } } } else { diff --git a/submodules/Display/Source/WindowContent.swift b/submodules/Display/Source/WindowContent.swift index 91db3affaa..df841641e4 100644 --- a/submodules/Display/Source/WindowContent.swift +++ b/submodules/Display/Source/WindowContent.swift @@ -3,6 +3,9 @@ import UIKit import AsyncDisplayKit import SwiftSignalKit +public func qewfqewfq() { +} + private struct WindowLayout: Equatable { let size: CGSize let metrics: LayoutMetrics diff --git a/submodules/TelegramApi/Sources/Api0.swift b/submodules/TelegramApi/Sources/Api0.swift index 3e2cffef91..c9fc696abd 100644 --- a/submodules/TelegramApi/Sources/Api0.swift +++ b/submodules/TelegramApi/Sources/Api0.swift @@ -6,8 +6,8 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[571523412] = { return $0.readDouble() } dict[-1255641564] = { return parseString($0) } dict[-1240849242] = { return Api.messages.StickerSet.parse_stickerSet($0) } - dict[1829443076] = { return Api.GroupCall.parse_groupCallPrivate($0) } - dict[2083222527] = { return Api.GroupCall.parse_groupCall($0) } + dict[-1331534976] = { return Api.GroupCall.parse_groupCallPrivate($0) } + dict[1435512961] = { return Api.GroupCall.parse_groupCall($0) } dict[2004925620] = { return Api.GroupCall.parse_groupCallDiscarded($0) } dict[-457104426] = { return Api.InputGeoPoint.parse_inputGeoPointEmpty($0) } dict[1210199983] = { return Api.InputGeoPoint.parse_inputGeoPoint($0) } @@ -169,6 +169,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-580219064] = { return Api.SendMessageAction.parse_sendMessageGamePlayAction($0) } dict[-1997373508] = { return Api.SendMessageAction.parse_sendMessageRecordRoundAction($0) } dict[608050278] = { return Api.SendMessageAction.parse_sendMessageUploadRoundAction($0) } + dict[-651419003] = { return Api.SendMessageAction.parse_speakingInGroupCallAction($0) } dict[-1137792208] = { return Api.PrivacyKey.parse_privacyKeyStatusTimestamp($0) } dict[1343122938] = { return Api.PrivacyKey.parse_privacyKeyChatInvite($0) } dict[1030105979] = { return Api.PrivacyKey.parse_privacyKeyPhoneCall($0) } @@ -531,7 +532,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-2042159726] = { return Api.SecurePasswordKdfAlgo.parse_securePasswordKdfAlgoSHA512($0) } dict[-1032140601] = { return Api.BotCommand.parse_botCommand($0) } dict[1474462241] = { return Api.account.ContentSettings.parse_contentSettings($0) } - dict[1325740111] = { return Api.phone.GroupParticipants.parse_groupParticipants($0) } + dict[1021016465] = { return Api.phone.GroupParticipants.parse_groupParticipants($0) } dict[-2066640507] = { return Api.messages.AffectedMessages.parse_affectedMessages($0) } dict[-402498398] = { return Api.messages.SavedGifs.parse_savedGifsNotModified($0) } dict[772213157] = { return Api.messages.SavedGifs.parse_savedGifs($0) } diff --git a/submodules/TelegramApi/Sources/Api1.swift b/submodules/TelegramApi/Sources/Api1.swift index b228a213c0..bef5b0139a 100644 --- a/submodules/TelegramApi/Sources/Api1.swift +++ b/submodules/TelegramApi/Sources/Api1.swift @@ -1910,32 +1910,28 @@ public struct messages { } public extension Api { public enum GroupCall: TypeConstructorDescription { - case groupCallPrivate(flags: Int32, id: Int64, accessHash: Int64, channelId: Int32?, participantsCount: Int32, adminId: Int32) - case groupCall(flags: Int32, id: Int64, accessHash: Int64, adminId: Int32, reflectorId: Int64, params: Api.DataJSON?, version: Int32) + case groupCallPrivate(id: Int64, accessHash: Int64, participantsCount: Int32) + case groupCall(flags: Int32, id: Int64, accessHash: Int64, participantsCount: Int32, params: Api.DataJSON?, version: Int32) case groupCallDiscarded(id: Int64, accessHash: Int64, duration: Int32) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .groupCallPrivate(let flags, let id, let accessHash, let channelId, let participantsCount, let adminId): + case .groupCallPrivate(let id, let accessHash, let participantsCount): if boxed { - buffer.appendInt32(1829443076) + buffer.appendInt32(-1331534976) } - serializeInt32(flags, buffer: buffer, boxed: false) serializeInt64(id, buffer: buffer, boxed: false) serializeInt64(accessHash, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {serializeInt32(channelId!, buffer: buffer, boxed: false)} serializeInt32(participantsCount, buffer: buffer, boxed: false) - serializeInt32(adminId, buffer: buffer, boxed: false) break - case .groupCall(let flags, let id, let accessHash, let adminId, let reflectorId, let params, let version): + case .groupCall(let flags, let id, let accessHash, let participantsCount, let params, let version): if boxed { - buffer.appendInt32(2083222527) + buffer.appendInt32(1435512961) } serializeInt32(flags, buffer: buffer, boxed: false) serializeInt64(id, buffer: buffer, boxed: false) serializeInt64(accessHash, buffer: buffer, boxed: false) - serializeInt32(adminId, buffer: buffer, boxed: false) - serializeInt64(reflectorId, buffer: buffer, boxed: false) + serializeInt32(participantsCount, buffer: buffer, boxed: false) if Int(flags) & Int(1 << 0) != 0 {params!.serialize(buffer, true)} serializeInt32(version, buffer: buffer, boxed: false) break @@ -1952,36 +1948,27 @@ public extension Api { public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .groupCallPrivate(let flags, let id, let accessHash, let channelId, let participantsCount, let adminId): - return ("groupCallPrivate", [("flags", flags), ("id", id), ("accessHash", accessHash), ("channelId", channelId), ("participantsCount", participantsCount), ("adminId", adminId)]) - case .groupCall(let flags, let id, let accessHash, let adminId, let reflectorId, let params, let version): - return ("groupCall", [("flags", flags), ("id", id), ("accessHash", accessHash), ("adminId", adminId), ("reflectorId", reflectorId), ("params", params), ("version", version)]) + case .groupCallPrivate(let id, let accessHash, let participantsCount): + return ("groupCallPrivate", [("id", id), ("accessHash", accessHash), ("participantsCount", participantsCount)]) + case .groupCall(let flags, let id, let accessHash, let participantsCount, let params, let version): + return ("groupCall", [("flags", flags), ("id", id), ("accessHash", accessHash), ("participantsCount", participantsCount), ("params", params), ("version", version)]) case .groupCallDiscarded(let id, let accessHash, let duration): return ("groupCallDiscarded", [("id", id), ("accessHash", accessHash), ("duration", duration)]) } } public static func parse_groupCallPrivate(_ reader: BufferReader) -> GroupCall? { - var _1: Int32? - _1 = reader.readInt32() + var _1: Int64? + _1 = reader.readInt64() var _2: Int64? _2 = reader.readInt64() - var _3: Int64? - _3 = reader.readInt64() - var _4: Int32? - if Int(_1!) & Int(1 << 0) != 0 {_4 = reader.readInt32() } - var _5: Int32? - _5 = reader.readInt32() - var _6: Int32? - _6 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil - let _c5 = _5 != nil - let _c6 = _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.GroupCall.groupCallPrivate(flags: _1!, id: _2!, accessHash: _3!, channelId: _4, participantsCount: _5!, adminId: _6!) + if _c1 && _c2 && _c3 { + return Api.GroupCall.groupCallPrivate(id: _1!, accessHash: _2!, participantsCount: _3!) } else { return nil @@ -1996,23 +1983,20 @@ public extension Api { _3 = reader.readInt64() var _4: Int32? _4 = reader.readInt32() - var _5: Int64? - _5 = reader.readInt64() - var _6: Api.DataJSON? + var _5: Api.DataJSON? if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() { - _6 = Api.parse(reader, signature: signature) as? Api.DataJSON + _5 = Api.parse(reader, signature: signature) as? Api.DataJSON } } - var _7: Int32? - _7 = reader.readInt32() + var _6: Int32? + _6 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = (Int(_1!) & Int(1 << 0) == 0) || _6 != nil - let _c7 = _7 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { - return Api.GroupCall.groupCall(flags: _1!, id: _2!, accessHash: _3!, adminId: _4!, reflectorId: _5!, params: _6, version: _7!) + let _c5 = (Int(_1!) & Int(1 << 0) == 0) || _5 != nil + let _c6 = _6 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { + return Api.GroupCall.groupCall(flags: _1!, id: _2!, accessHash: _3!, participantsCount: _4!, params: _5, version: _6!) } else { return nil @@ -5981,6 +5965,7 @@ public extension Api { case sendMessageGamePlayAction case sendMessageRecordRoundAction case sendMessageUploadRoundAction(progress: Int32) + case speakingInGroupCallAction public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { @@ -6061,6 +6046,12 @@ public extension Api { buffer.appendInt32(608050278) } serializeInt32(progress, buffer: buffer, boxed: false) + break + case .speakingInGroupCallAction: + if boxed { + buffer.appendInt32(-651419003) + } + break } } @@ -6093,6 +6084,8 @@ public extension Api { return ("sendMessageRecordRoundAction", []) case .sendMessageUploadRoundAction(let progress): return ("sendMessageUploadRoundAction", [("progress", progress)]) + case .speakingInGroupCallAction: + return ("speakingInGroupCallAction", []) } } @@ -6175,6 +6168,9 @@ public extension Api { return nil } } + public static func parse_speakingInGroupCallAction(_ reader: BufferReader) -> SendMessageAction? { + return Api.SendMessageAction.speakingInGroupCallAction + } } public enum PrivacyKey: TypeConstructorDescription { diff --git a/submodules/TelegramApi/Sources/Api3.swift b/submodules/TelegramApi/Sources/Api3.swift index 44f25f5feb..ad02177b6d 100644 --- a/submodules/TelegramApi/Sources/Api3.swift +++ b/submodules/TelegramApi/Sources/Api3.swift @@ -1715,13 +1715,13 @@ public struct phone { } public enum GroupParticipants: TypeConstructorDescription { - case groupParticipants(count: Int32, participants: [Api.GroupCallParticipant], users: [Api.User]) + case groupParticipants(count: Int32, participants: [Api.GroupCallParticipant], users: [Api.User], version: Int32) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .groupParticipants(let count, let participants, let users): + case .groupParticipants(let count, let participants, let users, let version): if boxed { - buffer.appendInt32(1325740111) + buffer.appendInt32(1021016465) } serializeInt32(count, buffer: buffer, boxed: false) buffer.appendInt32(481674261) @@ -1734,14 +1734,15 @@ public struct phone { for item in users { item.serialize(buffer, true) } + serializeInt32(version, buffer: buffer, boxed: false) break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .groupParticipants(let count, let participants, let users): - return ("groupParticipants", [("count", count), ("participants", participants), ("users", users)]) + case .groupParticipants(let count, let participants, let users, let version): + return ("groupParticipants", [("count", count), ("participants", participants), ("users", users), ("version", version)]) } } @@ -1756,11 +1757,14 @@ public struct phone { if let _ = reader.readInt32() { _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) } + var _4: Int32? + _4 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.phone.GroupParticipants.groupParticipants(count: _1!, participants: _2!, users: _3!) + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.phone.GroupParticipants.groupParticipants(count: _1!, participants: _2!, users: _3!, version: _4!) } else { return nil diff --git a/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift b/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift index e5f2d4e78b..c5868973c6 100644 --- a/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift +++ b/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift @@ -27,7 +27,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { private enum InternalState { case requesting case active(GroupCallInfo) - case estabilished(GroupCallInfo, String, UInt32, [UInt32], [UInt32: PeerId]) + case estabilished(info: GroupCallInfo, clientParams: String, localSsrc: UInt32, initialState: GroupCallParticipantsContext.State) var callInfo: GroupCallInfo? { switch self { @@ -35,7 +35,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { return nil case let .active(info): return info - case let .estabilished(info, _, _, _, _): + case let .estabilished(info, _, _, _): return info } } @@ -81,6 +81,9 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { } private var audioLevelsDisposable = MetaDisposable() + private var participantsContextStateDisposable = MetaDisposable() + private var participantsContext: GroupCallParticipantsContext? + private var audioSessionControl: ManagedAudioSessionControl? private var audioSessionDisposable: Disposable? private let audioSessionShouldBeActive = ValuePromise(false, ignoreRepeated: true) @@ -231,26 +234,20 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { guard let strongSelf = self else { return } - if case let .estabilished(callInfo, _, _, _, _) = strongSelf.internalState { - var addedSsrc: [UInt32] = [] - var removedSsrc: [UInt32] = [] - for (callId, peerId, ssrc, isAdded) in updates { + if case let .estabilished(callInfo, _, _, _) = strongSelf.internalState { + /*var addedSsrc: [UInt32] = [] + var removedSsrc: [UInt32] = []*/ + for (callId, update) in updates { if callId == callInfo.id { - let mappedSsrc = UInt32(bitPattern: ssrc) - if isAdded { - addedSsrc.append(mappedSsrc) - strongSelf.ssrcMapping[mappedSsrc] = peerId - } else { - removedSsrc.append(mappedSsrc) - } + strongSelf.participantsContext?.addUpdates(updates: [update]) } } - if !addedSsrc.isEmpty { + /*if !addedSsrc.isEmpty { strongSelf.callContext?.addSsrcs(ssrcs: addedSsrc) } if !removedSsrc.isEmpty { strongSelf.callContext?.removeSsrcs(ssrcs: removedSsrc) - } + }*/ } }) @@ -270,6 +267,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { self.networkStateDisposable.dispose() self.checkCallDisposable?.dispose() self.audioLevelsDisposable.dispose() + self.participantsContextStateDisposable.dispose() } private func updateSessionState(internalState: InternalState, audioSessionControl: ManagedAudioSessionControl?) { @@ -319,7 +317,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { return } if let clientParams = joinCallResult.callInfo.clientParams { - strongSelf.updateSessionState(internalState: .estabilished(joinCallResult.callInfo, clientParams, ssrc, joinCallResult.ssrcs, joinCallResult.ssrcMapping), audioSessionControl: strongSelf.audioSessionControl) + strongSelf.updateSessionState(internalState: .estabilished(info: joinCallResult.callInfo, clientParams: clientParams, localSsrc: ssrc, initialState: joinCallResult.state), audioSessionControl: strongSelf.audioSessionControl) } })) })) @@ -361,23 +359,6 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { } })) - self.memberStatesDisposable.set((callContext.memberStates - |> deliverOnMainQueue).start(next: { [weak self] memberStates in - guard let strongSelf = self else { - return - } - var result: [PeerId: PresentationGroupCallMemberState] = [:] - for (ssrc, _) in memberStates { - if let peerId = strongSelf.ssrcMapping[ssrc] { - result[peerId] = PresentationGroupCallMemberState( - ssrc: ssrc, - isSpeaking: false - ) - } - } - strongSelf.membersValue = result - })) - self.audioLevelsDisposable.set((callContext.audioLevels |> deliverOnMainQueue).start(next: { [weak self] levels in guard let strongSelf = self else { @@ -400,9 +381,40 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { case .estabilished: break default: - if case let .estabilished(_, clientParams, _, ssrcs, ssrcMapping) = internalState { - self.ssrcMapping = ssrcMapping + if case let .estabilished(callInfo, clientParams, _, initialState) = internalState { + self.ssrcMapping.removeAll() + var ssrcs: [UInt32] = [] + for participant in initialState.participants { + self.ssrcMapping[participant.ssrc] = participant.peer.id + ssrcs.append(participant.ssrc) + } self.callContext?.setJoinResponse(payload: clientParams, ssrcs: ssrcs) + + let participantsContext = GroupCallParticipantsContext( + account: self.accountContext.account, + id: callInfo.id, + accessHash: callInfo.accessHash, + state: initialState + ) + self.participantsContext = participantsContext + self.participantsContextStateDisposable.set((participantsContext.state + |> deliverOnMainQueue).start(next: { [weak self] state in + guard let strongSelf = self else { + return + } + + var memberStates: [PeerId: PresentationGroupCallMemberState] = [:] + for participant in state.participants { + strongSelf.ssrcMapping[participant.ssrc] = participant.peer.id + + memberStates[participant.peer.id] = PresentationGroupCallMemberState( + ssrc: participant.ssrc, + muteState: participant.muteState + ) + } + strongSelf.membersValue = memberStates + })) + if let isCurrentlyConnecting = self.isCurrentlyConnecting, isCurrentlyConnecting { self.startCheckingCallIfNeeded() } @@ -414,7 +426,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { if self.checkCallDisposable != nil { return } - if case let .estabilished(callInfo, _, ssrc, _, _) = self.internalState { + if case let .estabilished(callInfo, _, ssrc, _) = self.internalState { let checkSignal = checkGroupCall(account: self.account, callId: callInfo.id, accessHash: callInfo.accessHash, ssrc: Int32(bitPattern: ssrc)) self.checkCallDisposable = (( @@ -448,7 +460,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { } public func leave() -> Signal { - if case let .estabilished(callInfo, _, _, _, _) = self.internalState { + if case let .estabilished(callInfo, _, _, _) = self.internalState { self.leaveDisposable.set((leaveGroupCall(account: self.account, callId: callInfo.id, accessHash: callInfo.accessHash) |> deliverOnMainQueue).start(completed: { [weak self] in self?._canBeRemoved.set(.single(true)) @@ -485,6 +497,10 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { } } + public func updateMuteState(peerId: PeerId, isMuted: Bool) { + self.participantsContext?.updateMuteState(peerId: peerId, muteState: isMuted ? GroupCallParticipantsContext.Participant.MuteState(canUnmute: peerId == self.accountContext.account.peerId) : nil) + } + private func requestCall() { self.callContext?.stop() self.callContext = nil @@ -516,7 +532,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { } } - let restartedCall = currentOrRequestedCall + /*let restartedCall = currentOrRequestedCall |> mapToSignal { value -> Signal in let stopped: Signal = stopGroupCall(account: account, callId: value.id, accessHash: value.accessHash) |> mapError { _ -> CallError in @@ -527,7 +543,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { return stopped |> then(currentOrRequestedCall) - } + }*/ self.requestDisposable.set((currentOrRequestedCall |> deliverOnMainQueue).start(next: { [weak self] value in diff --git a/submodules/TelegramCallsUI/Sources/VoiceChatController.swift b/submodules/TelegramCallsUI/Sources/VoiceChatController.swift index c625b1e63f..2fb3ea5065 100644 --- a/submodules/TelegramCallsUI/Sources/VoiceChatController.swift +++ b/submodules/TelegramCallsUI/Sources/VoiceChatController.swift @@ -129,7 +129,12 @@ public final class VoiceChatController: ViewController { private final class Interaction { private var audioLevels: [PeerId: ValuePipe] = [:] - init() { + let updateIsMuted: (PeerId, Bool) -> Void + + init( + updateIsMuted: @escaping (PeerId, Bool) -> Void + ) { + self.updateIsMuted = updateIsMuted } func getAudioLevel(_ peerId: PeerId) -> Signal? { @@ -161,6 +166,7 @@ public final class VoiceChatController: ViewController { var participant: RenderedChannelParticipant var activityTimestamp: Int32 var state: State + var muteState: GroupCallParticipantsContext.Participant.MuteState? var stableId: PeerId { return self.participant.peer.id @@ -182,7 +188,13 @@ public final class VoiceChatController: ViewController { text = .presence case .listening: //TODO:localize - text = .text("listening", .accent) + let muteString: String + if self.muteState != nil { + muteString = " [muted]" + } else { + muteString = "" + } + text = .text("listening\(muteString)", .accent) case .speaking: //TODO:localize text = .text("speaking", .constructive) @@ -190,7 +202,16 @@ public final class VoiceChatController: ViewController { return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: PresentationDateTimeFormat(timeFormat: .regular, dateFormat: .monthFirst, dateSeparator: ".", decimalSeparator: ".", groupingSeparator: "."), nameDisplayOrder: .firstLast, context: context, peer: peer, height: .peerList, presence: self.participant.presences[self.participant.peer.id], text: text, label: .none, editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: false), revealOptions: ItemListPeerItemRevealOptions(options: [ItemListPeerItemRevealOption(type: .destructive, title: presentationData.strings.Common_Delete, action: { //arguments.deleteIncludePeer(peer.peerId) - })]), switchValue: nil, enabled: true, selectable: false, sectionId: 0, action: nil, setPeerIdWithRevealedOptions: { lhs, rhs in + })]), switchValue: nil, enabled: true, selectable: true, sectionId: 0, action: { + switch self.state { + case .inactive: + break + default: + if self.participant.peer.id != context.account.peerId { + interaction.updateIsMuted(self.participant.peer.id, self.muteState != nil ? false : true) + } + } + }, setPeerIdWithRevealedOptions: { lhs, rhs in //arguments.setItemIdWithRevealedOptions(lhs.flatMap { .peer($0) }, rhs.flatMap { .peer($0) }) }, removePeer: { id in //arguments.deleteIncludePeer(id) @@ -277,7 +298,9 @@ public final class VoiceChatController: ViewController { super.init() - self.itemInteraction = Interaction() + self.itemInteraction = Interaction(updateIsMuted: { [weak self] peerId, isMuted in + self?.call.updateMuteState(peerId: peerId, isMuted: isMuted) + }) self.backgroundColor = .black @@ -684,14 +707,16 @@ public final class VoiceChatController: ViewController { for member in members { let memberState: PeerEntry.State + var memberMuteState: GroupCallParticipantsContext.Participant.MuteState? if member.peer.id == self.context.account.peerId { if !isMuted { memberState = .speaking } else { memberState = .listening } - } else if let _ = memberStates[member.peer.id] { + } else if let state = memberStates[member.peer.id] { memberState = .listening + memberMuteState = state.muteState } else { memberState = .inactive } @@ -699,7 +724,8 @@ public final class VoiceChatController: ViewController { entries.append(PeerEntry( participant: member, activityTimestamp: Int32.max - 1 - index, - state: memberState + state: memberState, + muteState: memberMuteState )) index += 1 } diff --git a/submodules/TelegramCore/Sources/AccountIntermediateState.swift b/submodules/TelegramCore/Sources/AccountIntermediateState.swift index b107d56d60..5425e150ae 100644 --- a/submodules/TelegramCore/Sources/AccountIntermediateState.swift +++ b/submodules/TelegramCore/Sources/AccountIntermediateState.swift @@ -607,7 +607,7 @@ struct AccountReplayedFinalState { let updatedWebpages: [MediaId: TelegramMediaWebpage] let updatedCalls: [Api.PhoneCall] let addedCallSignalingData: [(Int64, Data)] - let updatedGroupCallParticipants: [(Int64, PeerId, Int32, Bool)] + let updatedGroupCallParticipants: [(Int64, GroupCallParticipantsContext.StateUpdate)] let updatedPeersNearby: [PeerNearby]? let isContactUpdates: [(PeerId, Bool)] let delayNotificatonsUntil: Int32? @@ -623,7 +623,7 @@ struct AccountFinalStateEvents { let updatedWebpages: [MediaId: TelegramMediaWebpage] let updatedCalls: [Api.PhoneCall] let addedCallSignalingData: [(Int64, Data)] - let updatedGroupCallParticipants: [(Int64, PeerId, Int32, Bool)] + let updatedGroupCallParticipants: [(Int64, GroupCallParticipantsContext.StateUpdate)] let updatedPeersNearby: [PeerNearby]? let isContactUpdates: [(PeerId, Bool)] let displayAlerts: [(text: String, isDropAuth: Bool)] @@ -639,7 +639,7 @@ struct AccountFinalStateEvents { return self.addedIncomingMessageIds.isEmpty && self.wasScheduledMessageIds.isEmpty && self.deletedMessageIds.isEmpty && self.updatedTypingActivities.isEmpty && self.updatedWebpages.isEmpty && self.updatedCalls.isEmpty && self.addedCallSignalingData.isEmpty && self.updatedGroupCallParticipants.isEmpty && self.updatedPeersNearby?.isEmpty ?? true && self.isContactUpdates.isEmpty && self.displayAlerts.isEmpty && self.delayNotificatonsUntil == nil && self.updatedMaxMessageId == nil && self.updatedQts == nil && self.externallyUpdatedPeerId.isEmpty && !authorizationListUpdated && self.updatedIncomingThreadReadStates.isEmpty && self.updatedOutgoingThreadReadStates.isEmpty } - init(addedIncomingMessageIds: [MessageId] = [], wasScheduledMessageIds: [MessageId] = [], deletedMessageIds: [DeletedMessageId] = [], updatedTypingActivities: [PeerActivitySpace: [PeerId: PeerInputActivity?]] = [:], updatedWebpages: [MediaId: TelegramMediaWebpage] = [:], updatedCalls: [Api.PhoneCall] = [], addedCallSignalingData: [(Int64, Data)] = [], updatedGroupCallParticipants: [(Int64, PeerId, Int32, Bool)] = [], updatedPeersNearby: [PeerNearby]? = nil, isContactUpdates: [(PeerId, Bool)] = [], displayAlerts: [(text: String, isDropAuth: Bool)] = [], delayNotificatonsUntil: Int32? = nil, updatedMaxMessageId: Int32? = nil, updatedQts: Int32? = nil, externallyUpdatedPeerId: Set = Set(), authorizationListUpdated: Bool = false, updatedIncomingThreadReadStates: [MessageId: MessageId.Id] = [:], updatedOutgoingThreadReadStates: [MessageId: MessageId.Id] = [:]) { + init(addedIncomingMessageIds: [MessageId] = [], wasScheduledMessageIds: [MessageId] = [], deletedMessageIds: [DeletedMessageId] = [], updatedTypingActivities: [PeerActivitySpace: [PeerId: PeerInputActivity?]] = [:], updatedWebpages: [MediaId: TelegramMediaWebpage] = [:], updatedCalls: [Api.PhoneCall] = [], addedCallSignalingData: [(Int64, Data)] = [], updatedGroupCallParticipants: [(Int64, GroupCallParticipantsContext.StateUpdate)] = [], updatedPeersNearby: [PeerNearby]? = nil, isContactUpdates: [(PeerId, Bool)] = [], displayAlerts: [(text: String, isDropAuth: Bool)] = [], delayNotificatonsUntil: Int32? = nil, updatedMaxMessageId: Int32? = nil, updatedQts: Int32? = nil, externallyUpdatedPeerId: Set = Set(), authorizationListUpdated: Bool = false, updatedIncomingThreadReadStates: [MessageId: MessageId.Id] = [:], updatedOutgoingThreadReadStates: [MessageId: MessageId.Id] = [:]) { self.addedIncomingMessageIds = addedIncomingMessageIds self.wasScheduledMessageIds = wasScheduledMessageIds self.deletedMessageIds = deletedMessageIds diff --git a/submodules/TelegramCore/Sources/AccountStateManagementUtils.swift b/submodules/TelegramCore/Sources/AccountStateManagementUtils.swift index 5bb47d6498..821824b844 100644 --- a/submodules/TelegramCore/Sources/AccountStateManagementUtils.swift +++ b/submodules/TelegramCore/Sources/AccountStateManagementUtils.swift @@ -2201,7 +2201,7 @@ func replayFinalState(accountManager: AccountManager, postbox: Postbox, accountP var updatedWebpages: [MediaId: TelegramMediaWebpage] = [:] var updatedCalls: [Api.PhoneCall] = [] var addedCallSignalingData: [(Int64, Data)] = [] - var updatedGroupCallParticipants: [(Int64, PeerId, Int32, Bool)] = [] + var updatedGroupCallParticipants: [(Int64, GroupCallParticipantsContext.StateUpdate)] = [] var updatedPeersNearby: [PeerNearby]? var isContactUpdates: [(PeerId, Bool)] = [] var stickerPackOperations: [AccountStateUpdateStickerPacksOperation] = [] @@ -2932,22 +2932,10 @@ func replayFinalState(accountManager: AccountManager, postbox: Postbox, accountP case let .AddCallSignalingData(callId, data): addedCallSignalingData.append((callId, data)) case let .UpdateGroupCallParticipants(callId, _, participants, version): - for participant in participants { - var peerId: PeerId? - var ssrc: Int32? - var isAdded = true - switch participant { - case let .groupCallParticipant(flags, userId, date, source): - peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: userId) - ssrc = source - if flags & (1 << 1) != 0 { - isAdded = false - } - } - if let peerId = peerId, let ssrc = ssrc { - updatedGroupCallParticipants.append((callId, peerId, ssrc, isAdded)) - } - } + updatedGroupCallParticipants.append(( + callId, + GroupCallParticipantsContext.StateUpdate(participants: participants, version: version) + )) case let .UpdateLangPack(langCode, difference): if let difference = difference { if langPackDifferences[langCode] == nil { diff --git a/submodules/TelegramCore/Sources/AccountStateManager.swift b/submodules/TelegramCore/Sources/AccountStateManager.swift index 2b96b1273b..16885dbb7d 100644 --- a/submodules/TelegramCore/Sources/AccountStateManager.swift +++ b/submodules/TelegramCore/Sources/AccountStateManager.swift @@ -148,8 +148,8 @@ public final class AccountStateManager { return self.threadReadStateUpdatesPipe.signal() } - private let groupCallParticipantUpdatesPipe = ValuePipe<[(Int64, PeerId, Int32, Bool)]>() - public var groupCallParticipantUpdates: Signal<[(Int64, PeerId, Int32, Bool)], NoError> { + private let groupCallParticipantUpdatesPipe = ValuePipe<[(Int64, GroupCallParticipantsContext.StateUpdate)]>() + public var groupCallParticipantUpdates: Signal<[(Int64, GroupCallParticipantsContext.StateUpdate)], NoError> { return self.groupCallParticipantUpdatesPipe.signal() } diff --git a/submodules/TelegramCore/Sources/GroupCalls.swift b/submodules/TelegramCore/Sources/GroupCalls.swift index c59fdd5fa8..10a8b1a64f 100644 --- a/submodules/TelegramCore/Sources/GroupCalls.swift +++ b/submodules/TelegramCore/Sources/GroupCalls.swift @@ -7,23 +7,19 @@ import SyncCore public struct GroupCallInfo: Equatable { public var id: Int64 public var accessHash: Int64 - public var peerId: PeerId? public var clientParams: String? - public var version: Int32? } private extension GroupCallInfo { init?(_ call: Api.GroupCall) { switch call { - case let .groupCallPrivate(_, id, accessHash, channelId, _, _): + case let .groupCallPrivate(id, accessHash, _): self.init( id: id, accessHash: accessHash, - peerId: channelId.flatMap { PeerId(namespace: Namespaces.Peer.CloudChannel, id: $0) }, - clientParams: nil, - version: nil + clientParams: nil ) - case let .groupCall(_, id, accessHash, _, _, params, version): + case let .groupCall(_, id, accessHash, _, params, _): var clientParams: String? if let params = params { switch params { @@ -34,9 +30,7 @@ private extension GroupCallInfo { self.init( id: id, accessHash: accessHash, - peerId: nil, - clientParams: clientParams, - version: version + clientParams: clientParams ) case .groupCallDiscarded: return nil @@ -84,7 +78,7 @@ public func getCurrentGroupCall(account: Account, peerId: PeerId) -> Signal mapToSignal { result -> Signal in switch result { - case let .groupCall(call, sources, participants, users): + case let .groupCall(call, _, _, _): return account.postbox.transaction { transaction -> GroupCallInfo? in return GroupCallInfo(call) } @@ -137,41 +131,72 @@ public func createGroupCall(account: Account, peerId: PeerId) -> Signal Signal { +public func getGroupCallParticipants(account: Account, callId: Int64, accessHash: Int64, maxDate: Int32, limit: Int32) -> Signal { return account.network.request(Api.functions.phone.getGroupParticipants(call: .inputGroupCall(id: callId, accessHash: accessHash), maxDate: maxDate, limit: limit)) |> mapError { _ -> GetGroupCallParticipantsError in return .generic } - |> map { result -> GetGroupCallParticipantsResult in - var ssrcMapping: [UInt32: PeerId] = [:] - - switch result { - case let .groupParticipants(count, participants, users): - for participant in participants { - var peerId: PeerId? - var ssrc: UInt32? - switch participant { - case let .groupCallParticipant(flags, userId, date, source): - peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: userId) - ssrc = UInt32(bitPattern: source) + |> mapToSignal { result -> Signal in + return account.postbox.transaction { transaction -> GroupCallParticipantsContext.State in + var parsedParticipants: [GroupCallParticipantsContext.Participant] = [] + let totalCount: Int + let version: Int32 + + switch result { + case let .groupParticipants(count, participants, users, apiVersion): + totalCount = Int(count) + version = apiVersion + + var peers: [Peer] = [] + var peerPresences: [PeerId: PeerPresence] = [:] + + for user in users { + let telegramUser = TelegramUser(user: user) + peers.append(telegramUser) + if let presence = TelegramUserPresence(apiUser: user) { + peerPresences[telegramUser.id] = presence + } } - if let peerId = peerId, let ssrc = ssrc { - ssrcMapping[ssrc] = peerId + + updatePeers(transaction: transaction, peers: peers, update: { _, updated -> Peer in + return updated + }) + updatePeerPresences(transaction: transaction, accountPeerId: account.peerId, peerPresences: peerPresences) + + loop: for participant in participants { + switch participant { + case let .groupCallParticipant(flags, userId, date, source): + let peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: userId) + let ssrc = UInt32(bitPattern: source) + guard let peer = transaction.getPeer(peerId) else { + continue loop + } + var muteState: GroupCallParticipantsContext.Participant.MuteState? + if (flags & (1 << 0)) != 0 { + let canUnmute = (flags & (1 << 2)) != 0 + muteState = GroupCallParticipantsContext.Participant.MuteState(canUnmute: canUnmute) + } + parsedParticipants.append(GroupCallParticipantsContext.Participant( + peer: peer, + ssrc: ssrc, + joinTimestamp: date, + muteState: muteState + )) + } } } + + return GroupCallParticipantsContext.State( + participants: parsedParticipants, + totalCount: totalCount, + version: version + ) } - - return GetGroupCallParticipantsResult( - ssrcMapping: ssrcMapping - ) + |> castError(GetGroupCallParticipantsError.self) } } @@ -181,8 +206,7 @@ public enum JoinGroupCallError { public struct JoinGroupCallResult { public var callInfo: GroupCallInfo - public var ssrcs: [UInt32] - public var ssrcMapping: [UInt32: PeerId] + public var state: GroupCallParticipantsContext.State } public func joinGroupCall(account: Account, callId: Int64, accessHash: Int64, joinPayload: String) -> Signal { @@ -201,7 +225,7 @@ public func joinGroupCall(account: Account, callId: Int64, accessHash: Int64, jo return .generic } ) - |> mapToSignal { result, participantsResult -> Signal in + |> mapToSignal { result, state -> Signal in account.stateManager.addUpdates(updates) var maybeParsedCall: GroupCallInfo? @@ -220,28 +244,14 @@ public func joinGroupCall(account: Account, callId: Int64, accessHash: Int64, jo } switch result { - case let .groupCall(call, sources, participants, users): + case let .groupCall(call, sources, _, users): guard let _ = GroupCallInfo(call) else { return .fail(.generic) } - var ssrcMapping: [UInt32: PeerId] = participantsResult.ssrcMapping - for participant in participants { - var peerId: PeerId? - var ssrc: UInt32? - switch participant { - case let .groupCallParticipant(flags, userId, date, source): - peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: userId) - ssrc = UInt32(bitPattern: source) - } - if let peerId = peerId, let ssrc = ssrc { - ssrcMapping[ssrc] = peerId - } - } return account.postbox.transaction { transaction -> JoinGroupCallResult in return JoinGroupCallResult( callInfo: parsedCall, - ssrcs: sources.map(UInt32.init(bitPattern:)), - ssrcMapping: ssrcMapping + state: state ) } |> castError(JoinGroupCallError.self) @@ -297,3 +307,368 @@ public func checkGroupCall(account: Account, callId: Int64, accessHash: Int64, s } } } + +private func binaryInsertionIndex(_ inputArr: [GroupCallParticipantsContext.Participant], searchItem: Int32) -> Int { + var lo = 0 + var hi = inputArr.count - 1 + while lo <= hi { + let mid = (lo + hi) / 2 + if inputArr[mid].joinTimestamp < searchItem { + lo = mid + 1 + } else if searchItem < inputArr[mid].joinTimestamp { + hi = mid - 1 + } else { + return mid + } + } + return lo +} + +public final class GroupCallParticipantsContext { + public struct Participant: Equatable { + public struct MuteState: Equatable { + public var canUnmute: Bool + + public init(canUnmute: Bool) { + self.canUnmute = canUnmute + } + } + + public var peer: Peer + public var ssrc: UInt32 + public var joinTimestamp: Int32 + public var muteState: MuteState? + + public static func ==(lhs: Participant, rhs: Participant) -> Bool { + if !lhs.peer.isEqual(rhs.peer) { + return false + } + if lhs.ssrc != rhs.ssrc { + return false + } + if lhs.joinTimestamp != rhs.joinTimestamp { + return false + } + if lhs.muteState != rhs.muteState { + return false + } + return true + } + } + + public struct State: Equatable { + public var participants: [Participant] + public var totalCount: Int + public var version: Int32 + } + + private struct OverlayState: Equatable { + struct MuteStateChange: Equatable { + var state: Participant.MuteState? + var disposable: Disposable + + static func ==(lhs: MuteStateChange, rhs: MuteStateChange) -> Bool { + if lhs.state != rhs.state { + return false + } + if lhs.disposable !== rhs.disposable { + return false + } + return true + } + } + + var pendingMuteStateChanges: [PeerId: MuteStateChange] = [:] + + var isEmpty: Bool { + if !self.pendingMuteStateChanges.isEmpty { + return false + } + return true + } + } + + private struct InternalState: Equatable { + var state: State + var overlayState: OverlayState + } + + public struct StateUpdate { + public struct ParticipantUpdate { + public var peerId: PeerId + public var ssrc: UInt32 + public var joinTimestamp: Int32 + public var muteState: Participant.MuteState? + public var isRemoved: Bool + } + + public var participantUpdates: [ParticipantUpdate] + public var version: Int32 + + public var removePendingMuteStates: Set + } + + private let account: Account + private let id: Int64 + private let accessHash: Int64 + + private var stateValue: InternalState { + didSet { + self.statePromise.set(self.stateValue) + } + } + private let statePromise: ValuePromise + + public var state: Signal { + return self.statePromise.get() + |> map { state -> State in + if state.overlayState.isEmpty { + return state.state + } + var publicState = state.state + for i in 0 ..< publicState.participants.count { + if let pendingMuteState = state.overlayState.pendingMuteStateChanges[publicState.participants[i].peer.id] { + publicState.participants[i].muteState = pendingMuteState.state + } + } + return publicState + } + } + + private var updateQueue: [StateUpdate] = [] + private var isProcessingUpdate: Bool = false + private let disposable = MetaDisposable() + + public init(account: Account, id: Int64, accessHash: Int64, state: State) { + self.account = account + self.id = id + self.accessHash = accessHash + self.stateValue = InternalState(state: state, overlayState: OverlayState()) + self.statePromise = ValuePromise(self.stateValue) + } + + deinit { + self.disposable.dispose() + } + + public func addUpdates(updates: [StateUpdate]) { + self.updateQueue.append(contentsOf: updates) + self.beginProcessingUpdatesIfNeeded() + } + + private func beginProcessingUpdatesIfNeeded() { + if self.isProcessingUpdate { + return + } + if self.updateQueue.isEmpty { + return + } + self.isProcessingUpdate = true + let update = self.updateQueue.removeFirst() + self.processUpdate(update: update) + } + + private func endedProcessingUpdate() { + assert(self.isProcessingUpdate) + self.isProcessingUpdate = false + self.beginProcessingUpdatesIfNeeded() + } + + private func processUpdate(update: StateUpdate) { + if update.version < self.stateValue.state.version { + for peerId in update.removePendingMuteStates { + self.stateValue.overlayState.pendingMuteStateChanges.removeValue(forKey: peerId) + } + self.endedProcessingUpdate() + return + } + + if update.version > self.stateValue.state.version + 1 { + for peerId in update.removePendingMuteStates { + self.stateValue.overlayState.pendingMuteStateChanges.removeValue(forKey: peerId) + } + self.resetStateFromServer() + return + } + + let _ = (self.account.postbox.transaction { transaction -> [PeerId: Peer] in + var peers: [PeerId: Peer] = [:] + + for participantUpdate in update.participantUpdates { + if let peer = transaction.getPeer(participantUpdate.peerId) { + peers[peer.id] = peer + } + } + + return peers + } + |> deliverOnMainQueue).start(next: { [weak self] peers in + guard let strongSelf = self else { + return + } + + var updatedParticipants = Array(strongSelf.stateValue.state.participants.reversed()) + var updatedTotalCount = strongSelf.stateValue.state.totalCount + + for participantUpdate in update.participantUpdates { + if participantUpdate.isRemoved { + if let index = updatedParticipants.firstIndex(where: { $0.peer.id == participantUpdate.peerId }) { + updatedParticipants.remove(at: index) + updatedTotalCount -= 1 + } + } else { + guard let peer = peers[participantUpdate.peerId] else { + assertionFailure() + continue + } + if let index = updatedParticipants.firstIndex(where: { $0.peer.id == participantUpdate.peerId }) { + updatedParticipants.remove(at: index) + } else { + updatedTotalCount += 1 + } + + let participant = Participant( + peer: peer, + ssrc: participantUpdate.ssrc, + joinTimestamp: participantUpdate.joinTimestamp, + muteState: participantUpdate.muteState + ) + let index = binaryInsertionIndex(updatedParticipants, searchItem: participant.joinTimestamp) + updatedParticipants.insert(participant, at: index) + } + } + + var updatedOverlayState = strongSelf.stateValue.overlayState + for peerId in update.removePendingMuteStates { + updatedOverlayState.pendingMuteStateChanges.removeValue(forKey: peerId) + } + + strongSelf.stateValue = InternalState( + state: State( + participants: Array(updatedParticipants.reversed()), + totalCount: updatedTotalCount, + version: update.version + ), + overlayState: updatedOverlayState + ) + + strongSelf.endedProcessingUpdate() + }) + } + + private func resetStateFromServer() { + self.updateQueue.removeAll() + + self.disposable.set(( + getGroupCallParticipants(account: self.account, callId: self.id, accessHash: self.accessHash, maxDate: 0, limit: 100) + |> deliverOnMainQueue).start(next: { [weak self] state in + guard let strongSelf = self else { + return + } + strongSelf.stateValue.state = state + strongSelf.endedProcessingUpdate() + })) + } + + public func updateMuteState(peerId: PeerId, muteState: Participant.MuteState?) { + if let current = self.stateValue.overlayState.pendingMuteStateChanges[peerId] { + if current.state == muteState { + return + } + current.disposable.dispose() + self.stateValue.overlayState.pendingMuteStateChanges.removeValue(forKey: peerId) + } + + let disposable = MetaDisposable() + self.stateValue.overlayState.pendingMuteStateChanges[peerId] = OverlayState.MuteStateChange( + state: muteState, + disposable: disposable + ) + + let account = self.account + let id = self.id + let accessHash = self.accessHash + + let signal: Signal = self.account.postbox.transaction { transaction -> Api.InputUser? in + return transaction.getPeer(peerId).flatMap(apiInputUser) + } + |> mapToSignal { inputUser -> Signal in + guard let inputUser = inputUser else { + return .single(nil) + } + var flags: Int32 = 0 + if muteState != nil { + flags |= 1 << 0 + } + return account.network.request(Api.functions.phone.editGroupCallMember(flags: flags, call: .inputGroupCall(id: id, accessHash: accessHash), userId: inputUser)) + |> map(Optional.init) + |> `catch` { _ -> Signal in + return .single(nil) + } + } + + disposable.set((signal + |> deliverOnMainQueue).start(next: { [weak self] updates in + guard let strongSelf = self else { + return + } + + if let updates = updates { + var stateUpdates: [GroupCallParticipantsContext.StateUpdate] = [] + + loop: for update in updates.allUpdates { + switch update { + case let .updateGroupCallParticipants(call, participants, version): + switch call { + case let .inputGroupCall(updateCallId, _): + if updateCallId != id { + continue loop + } + } + stateUpdates.append(GroupCallParticipantsContext.StateUpdate(participants: participants, version: version, removePendingMuteStates: [peerId])) + default: + break + } + } + + strongSelf.addUpdates(updates: stateUpdates) + + strongSelf.account.stateManager.addUpdates(updates) + } else { + strongSelf.stateValue.overlayState.pendingMuteStateChanges.removeValue(forKey: peerId) + } + })) + } +} + +extension GroupCallParticipantsContext.StateUpdate { + init(participants: [Api.GroupCallParticipant], version: Int32, removePendingMuteStates: Set = Set()) { + var participantUpdates: [GroupCallParticipantsContext.StateUpdate.ParticipantUpdate] = [] + for participant in participants { + switch participant { + case let .groupCallParticipant(flags, userId, date, source): + let peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: userId) + let ssrc = UInt32(bitPattern: source) + var muteState: GroupCallParticipantsContext.Participant.MuteState? + if (flags & (1 << 0)) != 0 { + let canUnmute = (flags & (1 << 2)) != 0 + muteState = GroupCallParticipantsContext.Participant.MuteState(canUnmute: canUnmute) + } + let isRemoved = (flags & (1 << 1)) != 0 + participantUpdates.append(GroupCallParticipantsContext.StateUpdate.ParticipantUpdate( + peerId: peerId, + ssrc: ssrc, + joinTimestamp: date, + muteState: muteState, + isRemoved: isRemoved + )) + } + } + + self.init( + participantUpdates: participantUpdates, + version: version, + removePendingMuteStates: removePendingMuteStates + ) + } +} diff --git a/submodules/TelegramCore/Sources/ManagedLocalInputActivities.swift b/submodules/TelegramCore/Sources/ManagedLocalInputActivities.swift index 2f435c6c29..661d36379c 100644 --- a/submodules/TelegramCore/Sources/ManagedLocalInputActivities.swift +++ b/submodules/TelegramCore/Sources/ManagedLocalInputActivities.swift @@ -115,6 +115,8 @@ private func actionFromActivity(_ activity: PeerInputActivity?) -> Api.SendMessa return .sendMessageRecordRoundAction case let .uploadingInstantVideo(progress): return .sendMessageUploadRoundAction(progress: progress) + case .speakingInGroupCall: + return .speakingInGroupCallAction } } else { return .sendMessageCancelAction diff --git a/submodules/TelegramCore/Sources/PeerInputActivity.swift b/submodules/TelegramCore/Sources/PeerInputActivity.swift index a7ab25a7bd..369bae488c 100644 --- a/submodules/TelegramCore/Sources/PeerInputActivity.swift +++ b/submodules/TelegramCore/Sources/PeerInputActivity.swift @@ -10,78 +10,28 @@ public enum PeerInputActivity: Comparable { case playingGame case recordingInstantVideo case uploadingInstantVideo(progress: Int32) - - public static func ==(lhs: PeerInputActivity, rhs: PeerInputActivity) -> Bool { - switch lhs { - case .typingText: - if case .typingText = rhs { - return true - } else { - return false - } - case let .uploadingFile(progress): - if case .uploadingFile(progress) = rhs { - return true - } else { - return false - } - case .recordingVoice: - if case .recordingVoice = rhs { - return true - } else { - return false - } - case .playingGame: - if case .playingGame = rhs { - return true - } else { - return false - } - case .uploadingPhoto(let progress): - if case .uploadingPhoto(progress) = rhs { - return true - } else { - return false - } - case .uploadingVideo(let progress): - if case .uploadingVideo(progress) = rhs { - return true - } else { - return false - } - case .recordingInstantVideo: - if case .recordingInstantVideo = rhs { - return true - } else { - return false - } - case .uploadingInstantVideo(let progress): - if case .uploadingInstantVideo(progress) = rhs { - return true - } else { - return false - } - } - } + case speakingInGroupCall public var key: Int32 { switch self { case .typingText: return 0 - case .uploadingFile: + case .speakingInGroupCall: return 1 - case .recordingVoice: + case .uploadingFile: return 2 - case .uploadingPhoto: + case .recordingVoice: return 3 - case .uploadingVideo: + case .uploadingPhoto: return 4 - case .recordingInstantVideo: + case .uploadingVideo: return 5 - case .uploadingInstantVideo: + case .recordingInstantVideo: return 6 - case .playingGame: + case .uploadingInstantVideo: return 7 + case .playingGame: + return 8 } } @@ -111,6 +61,8 @@ extension PeerInputActivity { self = .recordingInstantVideo case let .sendMessageUploadRoundAction(progress): self = .uploadingInstantVideo(progress: progress) + case .speakingInGroupCallAction: + self = .speakingInGroupCall } } } diff --git a/submodules/TelegramUI/Sources/ChatTitleView.swift b/submodules/TelegramUI/Sources/ChatTitleView.swift index cdb02185db..6a969ed90e 100644 --- a/submodules/TelegramUI/Sources/ChatTitleView.swift +++ b/submodules/TelegramUI/Sources/ChatTitleView.swift @@ -318,6 +318,8 @@ final class ChatTitleView: UIView, NavigationBarTitleView { stringValue = strings.Activity_RecordingVideoMessage case .uploadingInstantVideo: stringValue = strings.Activity_UploadingVideoMessage + case .speakingInGroupCall: + stringValue = "" } } else { for (peer, _) in inputActivities { @@ -345,6 +347,8 @@ final class ChatTitleView: UIView, NavigationBarTitleView { state = .uploading(string, color) case .playingGame: state = .playingGame(string, color) + case .speakingInGroupCall: + state = .typingText(string, color) } } else { if let titleContent = self.titleContent { diff --git a/submodules/TgVoipWebrtc/tgcalls b/submodules/TgVoipWebrtc/tgcalls index c0a1340e81..b245c575f3 160000 --- a/submodules/TgVoipWebrtc/tgcalls +++ b/submodules/TgVoipWebrtc/tgcalls @@ -1 +1 @@ -Subproject commit c0a1340e81d4c4ee27307164afc2f43b4183851a +Subproject commit b245c575f350e13186d34b9ae38f6af555c6fe14 diff --git a/third-party/webrtc/BUILD b/third-party/webrtc/BUILD index 1f5cb9b8b7..9c2f3f7db7 100644 --- a/third-party/webrtc/BUILD +++ b/third-party/webrtc/BUILD @@ -625,6 +625,7 @@ webrtc_sources = [ "pc/data_channel.cc", "pc/data_channel_controller.cc", "pc/datagram_rtp_transport.cc", + "pc/dtls_srtp_transport.h", "pc/dtls_srtp_transport.cc", "pc/dtls_transport.cc", "pc/dtmf_sender.cc", @@ -2091,58 +2092,11 @@ cc_library( visibility = ["//visibility:public"], ) -objc_library( - name = "webrtc_objc_sdk", - enable_modules = True, - module_name = "webrtc_objc_sdk", - srcs = ["webrtc-ios/src/sdk/" + path for path in ios_objc_sources], - copts = [ - "-Ithird-party/webrtc/webrtc-ios/src", - "-Ithird-party/webrtc/webrtc-ios/src/third_party/abseil-cpp", - "-Ithird-party/webrtc/webrtc-ios/src/third_party/usrsctp/usrsctplib", - "-Ithird-party/webrtc/webrtc-ios/src/third_party/usrsctp/usrsctplib/usrsctplib", - "-Ithird-party/webrtc/webrtc-ios/src/third_party/libsrtp/include", - "-Ithird-party/webrtc/webrtc-ios/src/third_party/libsrtp/crypto/include", - "-Ithird-party/webrtc/webrtc-ios/src/third_party/libyuv/include", - "-Ithird-party/webrtc/webrtc-ios/src/third_party/libvpx/source/libvpx", - "-Ithird-party/webrtc/webrtc-ios/src/testing/gtest/include", - "-Ithird-party/webrtc/webrtc-ios/src/sdk/objc", - "-Ithird-party/webrtc/webrtc-ios/src/sdk/objc/base", - "-Ithird-party/webrtc/additional-files", - "-DWEBRTC_IOS", - "-DWEBRTC_MAC", - "-DWEBRTC_POSIX", - "-DRTC_ENABLE_VP9", - "-DBSD=1", - "-DUSE_KISS_FFT", - "-DHAVE_PTHREAD", - "-DWEBRTC_APM_DEBUG_DUMP=0", - "-DWEBRTC_USE_BUILTIN_ISAC_FLOAT", - "-DWEBRTC_OPUS_VARIABLE_COMPLEXITY=0", - "-DHAVE_NETINET_IN_H", - "-DWEBRTC_INCLUDE_INTERNAL_AUDIO_DEVICE", - "-DSCTP_SIMPLE_ALLOCATOR", - "-DSCTP_PROCESS_LEVEL_LOCKS", - "-D__Userspace__", - "-D__Userspace_os_Darwin", - "-DPACKAGE_VERSION='\"\"'", - ] + arch_specific_cflags, - deps = [ - "//third-party/boringssl:crypto", - "//third-party/boringssl:ssl", - "//submodules/Opus:opus", - ":usrsctp", - ":libsrtp", - ":libevent", - ], - visibility = ["//visibility:public"], -) - objc_library( name = "webrtc_objcpp_sdk", enable_modules = True, module_name = "webrtc_objcpp_sdk", - srcs = ["webrtc-ios/src/sdk/" + path for path in ios_sources], + srcs = ["webrtc-ios/src/sdk/" + path for path in ios_sources + ios_objc_sources], copts = [ "-Ithird-party/webrtc/webrtc-ios/src", "-Ithird-party/webrtc/webrtc-ios/src/third_party/abseil-cpp", @@ -2173,7 +2127,6 @@ objc_library( "-D__Userspace__", "-D__Userspace_os_Darwin", "-DPACKAGE_VERSION='\"\"'", - "-std=c++14", ] + arch_specific_cflags, deps = [ "//third-party/boringssl:crypto", @@ -2225,7 +2178,6 @@ objc_library( ":libsrtp", ":libevent", ":libyuv", - ":webrtc_objc_sdk", ":webrtc_objcpp_sdk", "//third-party/libvpx:vpx", ], diff --git a/third-party/webrtc/webrtc-ios b/third-party/webrtc/webrtc-ios index 782743c793..facc5cdcc8 160000 --- a/third-party/webrtc/webrtc-ios +++ b/third-party/webrtc/webrtc-ios @@ -1 +1 @@ -Subproject commit 782743c7931d09c1d2e4a0cf6cd349ee45452f1d +Subproject commit facc5cdcc8792fd1a83f39b14ce5d719c6f44625