diff --git a/Telegram/NotificationService/NotificationServiceObjC/Sources/Serialization.m b/Telegram/NotificationService/NotificationServiceObjC/Sources/Serialization.m index cce25b19cc..c855db593b 100644 --- a/Telegram/NotificationService/NotificationServiceObjC/Sources/Serialization.m +++ b/Telegram/NotificationService/NotificationServiceObjC/Sources/Serialization.m @@ -3,7 +3,7 @@ @implementation Serialization - (NSUInteger)currentLayer { - return 120; + return 122; } - (id _Nullable)parseMessage:(NSData * _Nullable)data { diff --git a/submodules/AccountContext/Sources/PresentationCallManager.swift b/submodules/AccountContext/Sources/PresentationCallManager.swift index a402df45d4..025b05f462 100644 --- a/submodules/AccountContext/Sources/PresentationCallManager.swift +++ b/submodules/AccountContext/Sources/PresentationCallManager.swift @@ -151,8 +151,13 @@ public protocol PresentationCall: class { func makeOutgoingVideoView(completion: @escaping (PresentationCallVideoView?) -> Void) } +public protocol PresentationGroupCall: class { + +} + public protocol PresentationCallManager: class { var currentCallSignal: Signal { get } func requestCall(context: AccountContext, peerId: PeerId, isVideo: Bool, endCurrentIfAny: Bool) -> RequestCallResult + func requestOrJoinGroupCall(context: AccountContext, peerId: PeerId) } diff --git a/submodules/MergeLists/Sources/MergeLists.swift b/submodules/MergeLists/Sources/MergeLists.swift index 6dad3c27c4..0cd446a615 100644 --- a/submodules/MergeLists/Sources/MergeLists.swift +++ b/submodules/MergeLists/Sources/MergeLists.swift @@ -215,7 +215,7 @@ public func mergeListsStableWithUpdates(leftList: [T], rightList: [T], isLess for item in rightList { rightStableIds.append(getId(item)) } - if Set(leftStableIds) == Set(rightStableIds) && leftStableIds != rightStableIds && !allUpdated { + if false && Set(leftStableIds) == Set(rightStableIds) && leftStableIds != rightStableIds && !allUpdated { var updatedItems: [(T, AnyHashable)] = [] for i in 0 ..< leftList.count { if getId(leftList[i]) != getId(rightList[i]) { diff --git a/submodules/SyncCore/Sources/TelegramMediaAction.swift b/submodules/SyncCore/Sources/TelegramMediaAction.swift index 5e2d6757af..d1178621ad 100644 --- a/submodules/SyncCore/Sources/TelegramMediaAction.swift +++ b/submodules/SyncCore/Sources/TelegramMediaAction.swift @@ -46,6 +46,7 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable { case peerJoined case phoneNumberRequest case geoProximityReached(from: PeerId, to: PeerId, distance: Int32) + case groupPhoneCall(callId: Int64, accessHash: Int64, duration: Int32?) public init(decoder: PostboxDecoder) { let rawValue: Int32 = decoder.decodeInt32ForKey("_rawValue", orElse: 0) @@ -98,6 +99,8 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable { self = .phoneNumberRequest case 21: self = .geoProximityReached(from: PeerId(decoder.decodeInt64ForKey("fromId", orElse: 0)), to: PeerId(decoder.decodeInt64ForKey("toId", orElse: 0)), distance: (decoder.decodeInt32ForKey("dst", orElse: 0))) + case 22: + self = .groupPhoneCall(callId: decoder.decodeInt64ForKey("callId", orElse: 0), accessHash: decoder.decodeInt64ForKey("accessHash", orElse: 0), duration: decoder.decodeOptionalInt32ForKey("duration")) default: self = .unknown } @@ -188,6 +191,15 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable { encoder.encodeInt64(from.toInt64(), forKey: "fromId") encoder.encodeInt64(to.toInt64(), forKey: "toId") encoder.encodeInt32(distance, forKey: "dst") + case let .groupPhoneCall(callId, accessHash, duration): + encoder.encodeInt32(22, forKey: "_rawValue") + encoder.encodeInt64(callId, forKey: "callId") + encoder.encodeInt64(accessHash, forKey: "accessHash") + if let duration = duration { + encoder.encodeInt32(duration, forKey: "duration") + } else { + encoder.encodeNil(forKey: "duration") + } } } diff --git a/submodules/TelegramApi/Sources/Api0.swift b/submodules/TelegramApi/Sources/Api0.swift index 08e6253e2b..9778f2de67 100644 --- a/submodules/TelegramApi/Sources/Api0.swift +++ b/submodules/TelegramApi/Sources/Api0.swift @@ -6,11 +6,14 @@ 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[-857633264] = { 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) } dict[-784000893] = { return Api.payments.ValidatedRequestedInfo.parse_validatedRequestedInfo($0) } dict[461151667] = { return Api.ChatFull.parse_chatFull($0) } - dict[-253335766] = { return Api.ChatFull.parse_channelFull($0) } + dict[-428758403] = { return Api.ChatFull.parse_channelFull($0) } dict[-1159937629] = { return Api.PollResults.parse_pollResults($0) } dict[-925415106] = { return Api.ChatParticipant.parse_chatParticipant($0) } dict[-636267638] = { return Api.ChatParticipant.parse_chatParticipantCreator($0) } @@ -132,6 +135,11 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[1511503333] = { return Api.InputEncryptedFile.parse_inputEncryptedFile($0) } dict[767652808] = { return Api.InputEncryptedFile.parse_inputEncryptedFileBigUploaded($0) } dict[-1456996667] = { return Api.messages.InactiveChats.parse_inactiveChats($0) } + dict[-1513019911] = { return Api.GroupCallParticipant.parse_groupCallParticipantAdmin($0) } + dict[-1985949076] = { return Api.GroupCallParticipant.parse_groupCallParticipant($0) } + dict[1100680690] = { return Api.GroupCallParticipant.parse_groupCallParticipantLeft($0) } + dict[-1648085351] = { return Api.GroupCallParticipant.parse_groupCallParticipantKicked($0) } + dict[-874654354] = { return Api.GroupCallParticipant.parse_groupCallParticipantInvited($0) } dict[1443858741] = { return Api.messages.SentEncryptedMessage.parse_sentEncryptedMessage($0) } dict[-1802240206] = { return Api.messages.SentEncryptedMessage.parse_sentEncryptedFile($0) } dict[1571494644] = { return Api.ExportedMessageLink.parse_exportedMessageLink($0) } @@ -258,6 +266,8 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-13975905] = { return Api.Update.parse_updateChannelUserTyping($0) } dict[-309990731] = { return Api.Update.parse_updatePinnedMessages($0) } dict[-2054649973] = { return Api.Update.parse_updatePinnedChannelMessages($0) } + dict[92188360] = { return Api.Update.parse_updateGroupCallParticipant($0) } + dict[-2046916883] = { return Api.Update.parse_updateGroupCall($0) } dict[136574537] = { return Api.messages.VotesList.parse_votesList($0) } dict[1558266229] = { return Api.PopularContact.parse_popularContact($0) } dict[-373643672] = { return Api.FolderPeer.parse_folderPeer($0) } @@ -329,11 +339,11 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-392411726] = { return Api.WebPage.parse_webPage($0) } dict[1930545681] = { return Api.WebPage.parse_webPageNotModified($0) } dict[1036876423] = { return Api.InputBotInlineMessage.parse_inputBotInlineMessageText($0) } - dict[-190472735] = { return Api.InputBotInlineMessage.parse_inputBotInlineMessageMediaGeo($0) } dict[1262639204] = { return Api.InputBotInlineMessage.parse_inputBotInlineMessageGame($0) } dict[864077702] = { return Api.InputBotInlineMessage.parse_inputBotInlineMessageMediaAuto($0) } dict[1098628881] = { return Api.InputBotInlineMessage.parse_inputBotInlineMessageMediaVenue($0) } dict[-1494368259] = { return Api.InputBotInlineMessage.parse_inputBotInlineMessageMediaContact($0) } + dict[-1768777083] = { return Api.InputBotInlineMessage.parse_inputBotInlineMessageMediaGeo($0) } dict[2002815875] = { return Api.KeyboardButtonRow.parse_keyboardButtonRow($0) } dict[-290164953] = { return Api.StickerSet.parse_stickerSet($0) } dict[354925740] = { return Api.SecureSecretSettings.parse_secureSecretSettings($0) } @@ -392,6 +402,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-666824391] = { return Api.payments.PaymentResult.parse_paymentVerificationNeeded($0) } dict[1694474197] = { return Api.messages.Chats.parse_chats($0) } dict[-1663561404] = { return Api.messages.Chats.parse_chatsSlice($0) } + dict[-659913713] = { return Api.InputGroupCall.parse_inputGroupCall($0) } dict[482797855] = { return Api.InputSingleMedia.parse_inputSingleMedia($0) } dict[1163625789] = { return Api.MessageViews.parse_messageViews($0) } dict[218751099] = { return Api.InputPrivacyRule.parse_inputPrivacyValueAllowContacts($0) } @@ -496,6 +507,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-1495959709] = { return Api.MessageReplyHeader.parse_messageReplyHeader($0) } dict[411017418] = { return Api.SecureValue.parse_secureValue($0) } dict[-316748368] = { return Api.SecureValueHash.parse_secureValueHash($0) } + dict[1731723191] = { return Api.phone.GroupCall.parse_groupCall($0) } dict[-398136321] = { return Api.messages.SearchCounter.parse_searchCounter($0) } dict[-2128698738] = { return Api.auth.CheckedPhone.parse_checkedPhone($0) } dict[-1188055347] = { return Api.PageListItem.parse_pageListItemText($0) } @@ -777,6 +789,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-648257196] = { return Api.MessageAction.parse_messageActionSecureValuesSent($0) } dict[-202219658] = { return Api.MessageAction.parse_messageActionContactSignUp($0) } dict[-1730095465] = { return Api.MessageAction.parse_messageActionGeoProximityReached($0) } + dict[2047704898] = { return Api.MessageAction.parse_messageActionGroupCall($0) } dict[1399245077] = { return Api.PhoneCall.parse_phoneCallEmpty($0) } dict[462375633] = { return Api.PhoneCall.parse_phoneCallWaiting($0) } dict[-2014659757] = { return Api.PhoneCall.parse_phoneCallRequested($0) } @@ -914,6 +927,8 @@ public struct Api { switch object { case let _1 as Api.messages.StickerSet: _1.serialize(buffer, boxed) + case let _1 as Api.GroupCall: + _1.serialize(buffer, boxed) case let _1 as Api.InputGeoPoint: _1.serialize(buffer, boxed) case let _1 as Api.payments.ValidatedRequestedInfo: @@ -994,6 +1009,8 @@ public struct Api { _1.serialize(buffer, boxed) case let _1 as Api.messages.InactiveChats: _1.serialize(buffer, boxed) + case let _1 as Api.GroupCallParticipant: + _1.serialize(buffer, boxed) case let _1 as Api.messages.SentEncryptedMessage: _1.serialize(buffer, boxed) case let _1 as Api.ExportedMessageLink: @@ -1122,6 +1139,8 @@ public struct Api { _1.serialize(buffer, boxed) case let _1 as Api.messages.Chats: _1.serialize(buffer, boxed) + case let _1 as Api.InputGroupCall: + _1.serialize(buffer, boxed) case let _1 as Api.InputSingleMedia: _1.serialize(buffer, boxed) case let _1 as Api.MessageViews: @@ -1206,6 +1225,8 @@ public struct Api { _1.serialize(buffer, boxed) case let _1 as Api.SecureValueHash: _1.serialize(buffer, boxed) + case let _1 as Api.phone.GroupCall: + _1.serialize(buffer, boxed) case let _1 as Api.messages.SearchCounter: _1.serialize(buffer, boxed) case let _1 as Api.auth.CheckedPhone: diff --git a/submodules/TelegramApi/Sources/Api1.swift b/submodules/TelegramApi/Sources/Api1.swift index b500198b76..27f88bba3b 100644 --- a/submodules/TelegramApi/Sources/Api1.swift +++ b/submodules/TelegramApi/Sources/Api1.swift @@ -1909,6 +1909,134 @@ 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, channelId: Int32?, adminId: Int32, reflectorId: Int64, params: Api.DataJSON?) + 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): + if boxed { + buffer.appendInt32(1829443076) + } + 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 channelId, let adminId, let reflectorId, let params): + if boxed { + buffer.appendInt32(-857633264) + } + 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(adminId, buffer: buffer, boxed: false) + serializeInt64(reflectorId, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 1) != 0 {params!.serialize(buffer, true)} + break + case .groupCallDiscarded(let id, let accessHash, let duration): + if boxed { + buffer.appendInt32(2004925620) + } + serializeInt64(id, buffer: buffer, boxed: false) + serializeInt64(accessHash, buffer: buffer, boxed: false) + serializeInt32(duration, buffer: buffer, boxed: false) + break + } + } + + 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 channelId, let adminId, let reflectorId, let params): + return ("groupCall", [("flags", flags), ("id", id), ("accessHash", accessHash), ("channelId", channelId), ("adminId", adminId), ("reflectorId", reflectorId), ("params", params)]) + 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 _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() + 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!) + } + else { + return nil + } + } + public static func parse_groupCall(_ reader: BufferReader) -> GroupCall? { + var _1: Int32? + _1 = reader.readInt32() + 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: Int64? + _6 = reader.readInt64() + var _7: Api.DataJSON? + if Int(_1!) & Int(1 << 1) != 0 {if let signature = reader.readInt32() { + _7 = Api.parse(reader, signature: signature) as? Api.DataJSON + } } + 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 + let _c7 = (Int(_1!) & Int(1 << 1) == 0) || _7 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { + return Api.GroupCall.groupCall(flags: _1!, id: _2!, accessHash: _3!, channelId: _4, adminId: _5!, reflectorId: _6!, params: _7) + } + else { + return nil + } + } + public static func parse_groupCallDiscarded(_ reader: BufferReader) -> GroupCall? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Int64? + _2 = reader.readInt64() + var _3: Int32? + _3 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.GroupCall.groupCallDiscarded(id: _1!, accessHash: _2!, duration: _3!) + } + else { + return nil + } + } + + } public enum InputGeoPoint: TypeConstructorDescription { case inputGeoPointEmpty case inputGeoPoint(flags: Int32, lat: Double, long: Double, accuracyRadius: Int32?) @@ -1969,7 +2097,7 @@ public extension Api { } public enum ChatFull: TypeConstructorDescription { case chatFull(flags: Int32, id: Int32, about: String, participants: Api.ChatParticipants, chatPhoto: Api.Photo?, notifySettings: Api.PeerNotifySettings, exportedInvite: Api.ExportedChatInvite, botInfo: [Api.BotInfo]?, pinnedMsgId: Int32?, folderId: Int32?) - case channelFull(flags: Int32, id: Int32, about: String, participantsCount: Int32?, adminsCount: Int32?, kickedCount: Int32?, bannedCount: Int32?, onlineCount: Int32?, readInboxMaxId: Int32, readOutboxMaxId: Int32, unreadCount: Int32, chatPhoto: Api.Photo, notifySettings: Api.PeerNotifySettings, exportedInvite: Api.ExportedChatInvite, botInfo: [Api.BotInfo], migratedFromChatId: Int32?, migratedFromMaxId: Int32?, pinnedMsgId: Int32?, stickerset: Api.StickerSet?, availableMinId: Int32?, folderId: Int32?, linkedChatId: Int32?, location: Api.ChannelLocation?, slowmodeSeconds: Int32?, slowmodeNextSendDate: Int32?, statsDc: Int32?, pts: Int32) + case channelFull(flags: Int32, id: Int32, about: String, participantsCount: Int32?, adminsCount: Int32?, kickedCount: Int32?, bannedCount: Int32?, onlineCount: Int32?, readInboxMaxId: Int32, readOutboxMaxId: Int32, unreadCount: Int32, chatPhoto: Api.Photo, notifySettings: Api.PeerNotifySettings, exportedInvite: Api.ExportedChatInvite, botInfo: [Api.BotInfo], migratedFromChatId: Int32?, migratedFromMaxId: Int32?, pinnedMsgId: Int32?, stickerset: Api.StickerSet?, availableMinId: Int32?, folderId: Int32?, linkedChatId: Int32?, location: Api.ChannelLocation?, slowmodeSeconds: Int32?, slowmodeNextSendDate: Int32?, statsDc: Int32?, pts: Int32, callMsgId: Int32?) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { @@ -1992,9 +2120,9 @@ public extension Api { if Int(flags) & Int(1 << 6) != 0 {serializeInt32(pinnedMsgId!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 11) != 0 {serializeInt32(folderId!, buffer: buffer, boxed: false)} break - case .channelFull(let flags, let id, let about, let participantsCount, let adminsCount, let kickedCount, let bannedCount, let onlineCount, let readInboxMaxId, let readOutboxMaxId, let unreadCount, let chatPhoto, let notifySettings, let exportedInvite, let botInfo, let migratedFromChatId, let migratedFromMaxId, let pinnedMsgId, let stickerset, let availableMinId, let folderId, let linkedChatId, let location, let slowmodeSeconds, let slowmodeNextSendDate, let statsDc, let pts): + case .channelFull(let flags, let id, let about, let participantsCount, let adminsCount, let kickedCount, let bannedCount, let onlineCount, let readInboxMaxId, let readOutboxMaxId, let unreadCount, let chatPhoto, let notifySettings, let exportedInvite, let botInfo, let migratedFromChatId, let migratedFromMaxId, let pinnedMsgId, let stickerset, let availableMinId, let folderId, let linkedChatId, let location, let slowmodeSeconds, let slowmodeNextSendDate, let statsDc, let pts, let callMsgId): if boxed { - buffer.appendInt32(-253335766) + buffer.appendInt32(-428758403) } serializeInt32(flags, buffer: buffer, boxed: false) serializeInt32(id, buffer: buffer, boxed: false) @@ -2027,6 +2155,7 @@ public extension Api { if Int(flags) & Int(1 << 18) != 0 {serializeInt32(slowmodeNextSendDate!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 12) != 0 {serializeInt32(statsDc!, buffer: buffer, boxed: false)} serializeInt32(pts, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 21) != 0 {serializeInt32(callMsgId!, buffer: buffer, boxed: false)} break } } @@ -2035,8 +2164,8 @@ public extension Api { switch self { case .chatFull(let flags, let id, let about, let participants, let chatPhoto, let notifySettings, let exportedInvite, let botInfo, let pinnedMsgId, let folderId): return ("chatFull", [("flags", flags), ("id", id), ("about", about), ("participants", participants), ("chatPhoto", chatPhoto), ("notifySettings", notifySettings), ("exportedInvite", exportedInvite), ("botInfo", botInfo), ("pinnedMsgId", pinnedMsgId), ("folderId", folderId)]) - case .channelFull(let flags, let id, let about, let participantsCount, let adminsCount, let kickedCount, let bannedCount, let onlineCount, let readInboxMaxId, let readOutboxMaxId, let unreadCount, let chatPhoto, let notifySettings, let exportedInvite, let botInfo, let migratedFromChatId, let migratedFromMaxId, let pinnedMsgId, let stickerset, let availableMinId, let folderId, let linkedChatId, let location, let slowmodeSeconds, let slowmodeNextSendDate, let statsDc, let pts): - return ("channelFull", [("flags", flags), ("id", id), ("about", about), ("participantsCount", participantsCount), ("adminsCount", adminsCount), ("kickedCount", kickedCount), ("bannedCount", bannedCount), ("onlineCount", onlineCount), ("readInboxMaxId", readInboxMaxId), ("readOutboxMaxId", readOutboxMaxId), ("unreadCount", unreadCount), ("chatPhoto", chatPhoto), ("notifySettings", notifySettings), ("exportedInvite", exportedInvite), ("botInfo", botInfo), ("migratedFromChatId", migratedFromChatId), ("migratedFromMaxId", migratedFromMaxId), ("pinnedMsgId", pinnedMsgId), ("stickerset", stickerset), ("availableMinId", availableMinId), ("folderId", folderId), ("linkedChatId", linkedChatId), ("location", location), ("slowmodeSeconds", slowmodeSeconds), ("slowmodeNextSendDate", slowmodeNextSendDate), ("statsDc", statsDc), ("pts", pts)]) + case .channelFull(let flags, let id, let about, let participantsCount, let adminsCount, let kickedCount, let bannedCount, let onlineCount, let readInboxMaxId, let readOutboxMaxId, let unreadCount, let chatPhoto, let notifySettings, let exportedInvite, let botInfo, let migratedFromChatId, let migratedFromMaxId, let pinnedMsgId, let stickerset, let availableMinId, let folderId, let linkedChatId, let location, let slowmodeSeconds, let slowmodeNextSendDate, let statsDc, let pts, let callMsgId): + return ("channelFull", [("flags", flags), ("id", id), ("about", about), ("participantsCount", participantsCount), ("adminsCount", adminsCount), ("kickedCount", kickedCount), ("bannedCount", bannedCount), ("onlineCount", onlineCount), ("readInboxMaxId", readInboxMaxId), ("readOutboxMaxId", readOutboxMaxId), ("unreadCount", unreadCount), ("chatPhoto", chatPhoto), ("notifySettings", notifySettings), ("exportedInvite", exportedInvite), ("botInfo", botInfo), ("migratedFromChatId", migratedFromChatId), ("migratedFromMaxId", migratedFromMaxId), ("pinnedMsgId", pinnedMsgId), ("stickerset", stickerset), ("availableMinId", availableMinId), ("folderId", folderId), ("linkedChatId", linkedChatId), ("location", location), ("slowmodeSeconds", slowmodeSeconds), ("slowmodeNextSendDate", slowmodeNextSendDate), ("statsDc", statsDc), ("pts", pts), ("callMsgId", callMsgId)]) } } @@ -2155,6 +2284,8 @@ public extension Api { if Int(_1!) & Int(1 << 12) != 0 {_26 = reader.readInt32() } var _27: Int32? _27 = reader.readInt32() + var _28: Int32? + if Int(_1!) & Int(1 << 21) != 0 {_28 = reader.readInt32() } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil @@ -2182,8 +2313,9 @@ public extension Api { let _c25 = (Int(_1!) & Int(1 << 18) == 0) || _25 != nil let _c26 = (Int(_1!) & Int(1 << 12) == 0) || _26 != nil let _c27 = _27 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 && _c18 && _c19 && _c20 && _c21 && _c22 && _c23 && _c24 && _c25 && _c26 && _c27 { - return Api.ChatFull.channelFull(flags: _1!, id: _2!, about: _3!, participantsCount: _4, adminsCount: _5, kickedCount: _6, bannedCount: _7, onlineCount: _8, readInboxMaxId: _9!, readOutboxMaxId: _10!, unreadCount: _11!, chatPhoto: _12!, notifySettings: _13!, exportedInvite: _14!, botInfo: _15!, migratedFromChatId: _16, migratedFromMaxId: _17, pinnedMsgId: _18, stickerset: _19, availableMinId: _20, folderId: _21, linkedChatId: _22, location: _23, slowmodeSeconds: _24, slowmodeNextSendDate: _25, statsDc: _26, pts: _27!) + let _c28 = (Int(_1!) & Int(1 << 21) == 0) || _28 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 && _c18 && _c19 && _c20 && _c21 && _c22 && _c23 && _c24 && _c25 && _c26 && _c27 && _c28 { + return Api.ChatFull.channelFull(flags: _1!, id: _2!, about: _3!, participantsCount: _4, adminsCount: _5, kickedCount: _6, bannedCount: _7, onlineCount: _8, readInboxMaxId: _9!, readOutboxMaxId: _10!, unreadCount: _11!, chatPhoto: _12!, notifySettings: _13!, exportedInvite: _14!, botInfo: _15!, migratedFromChatId: _16, migratedFromMaxId: _17, pinnedMsgId: _18, stickerset: _19, availableMinId: _20, folderId: _21, linkedChatId: _22, location: _23, slowmodeSeconds: _24, slowmodeNextSendDate: _25, statsDc: _26, pts: _27!, callMsgId: _28) } else { return nil @@ -5266,6 +5398,148 @@ public extension Api { } } + } + public enum GroupCallParticipant: TypeConstructorDescription { + case groupCallParticipantAdmin(userId: Int32, source: Int32) + case groupCallParticipant(flags: Int32, userId: Int32, date: Int32, source: Int32) + case groupCallParticipantLeft(userId: Int32) + case groupCallParticipantKicked(userId: Int32) + case groupCallParticipantInvited(flags: Int32, userId: Int32, inviterId: Int32, date: Int32) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .groupCallParticipantAdmin(let userId, let source): + if boxed { + buffer.appendInt32(-1513019911) + } + serializeInt32(userId, buffer: buffer, boxed: false) + serializeInt32(source, buffer: buffer, boxed: false) + break + case .groupCallParticipant(let flags, let userId, let date, let source): + if boxed { + buffer.appendInt32(-1985949076) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt32(userId, buffer: buffer, boxed: false) + serializeInt32(date, buffer: buffer, boxed: false) + serializeInt32(source, buffer: buffer, boxed: false) + break + case .groupCallParticipantLeft(let userId): + if boxed { + buffer.appendInt32(1100680690) + } + serializeInt32(userId, buffer: buffer, boxed: false) + break + case .groupCallParticipantKicked(let userId): + if boxed { + buffer.appendInt32(-1648085351) + } + serializeInt32(userId, buffer: buffer, boxed: false) + break + case .groupCallParticipantInvited(let flags, let userId, let inviterId, let date): + if boxed { + buffer.appendInt32(-874654354) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt32(userId, buffer: buffer, boxed: false) + serializeInt32(inviterId, buffer: buffer, boxed: false) + serializeInt32(date, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .groupCallParticipantAdmin(let userId, let source): + return ("groupCallParticipantAdmin", [("userId", userId), ("source", source)]) + case .groupCallParticipant(let flags, let userId, let date, let source): + return ("groupCallParticipant", [("flags", flags), ("userId", userId), ("date", date), ("source", source)]) + case .groupCallParticipantLeft(let userId): + return ("groupCallParticipantLeft", [("userId", userId)]) + case .groupCallParticipantKicked(let userId): + return ("groupCallParticipantKicked", [("userId", userId)]) + case .groupCallParticipantInvited(let flags, let userId, let inviterId, let date): + return ("groupCallParticipantInvited", [("flags", flags), ("userId", userId), ("inviterId", inviterId), ("date", date)]) + } + } + + public static func parse_groupCallParticipantAdmin(_ reader: BufferReader) -> GroupCallParticipant? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.GroupCallParticipant.groupCallParticipantAdmin(userId: _1!, source: _2!) + } + else { + return nil + } + } + public static func parse_groupCallParticipant(_ reader: BufferReader) -> GroupCallParticipant? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() + var _4: Int32? + _4 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.GroupCallParticipant.groupCallParticipant(flags: _1!, userId: _2!, date: _3!, source: _4!) + } + else { + return nil + } + } + public static func parse_groupCallParticipantLeft(_ reader: BufferReader) -> GroupCallParticipant? { + var _1: Int32? + _1 = reader.readInt32() + let _c1 = _1 != nil + if _c1 { + return Api.GroupCallParticipant.groupCallParticipantLeft(userId: _1!) + } + else { + return nil + } + } + public static func parse_groupCallParticipantKicked(_ reader: BufferReader) -> GroupCallParticipant? { + var _1: Int32? + _1 = reader.readInt32() + let _c1 = _1 != nil + if _c1 { + return Api.GroupCallParticipant.groupCallParticipantKicked(userId: _1!) + } + else { + return nil + } + } + public static func parse_groupCallParticipantInvited(_ reader: BufferReader) -> GroupCallParticipant? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() + var _4: Int32? + _4 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.GroupCallParticipant.groupCallParticipantInvited(flags: _1!, userId: _2!, inviterId: _3!, date: _4!) + } + else { + return nil + } + } + } public enum ExportedMessageLink: TypeConstructorDescription { case exportedMessageLink(link: String, html: String) @@ -6193,6 +6467,8 @@ public extension Api { case updateChannelUserTyping(flags: Int32, channelId: Int32, topMsgId: Int32?, userId: Int32, action: Api.SendMessageAction) case updatePinnedMessages(flags: Int32, peer: Api.Peer, messages: [Int32], pts: Int32, ptsCount: Int32) case updatePinnedChannelMessages(flags: Int32, channelId: Int32, messages: [Int32], pts: Int32, ptsCount: Int32) + case updateGroupCallParticipant(call: Api.InputGroupCall, participant: Api.GroupCallParticipant) + case updateGroupCall(call: Api.GroupCall) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { @@ -6928,6 +7204,19 @@ public extension Api { serializeInt32(pts, buffer: buffer, boxed: false) serializeInt32(ptsCount, buffer: buffer, boxed: false) break + case .updateGroupCallParticipant(let call, let participant): + if boxed { + buffer.appendInt32(92188360) + } + call.serialize(buffer, true) + participant.serialize(buffer, true) + break + case .updateGroupCall(let call): + if boxed { + buffer.appendInt32(-2046916883) + } + call.serialize(buffer, true) + break } } @@ -7103,6 +7392,10 @@ public extension Api { return ("updatePinnedMessages", [("flags", flags), ("peer", peer), ("messages", messages), ("pts", pts), ("ptsCount", ptsCount)]) case .updatePinnedChannelMessages(let flags, let channelId, let messages, let pts, let ptsCount): return ("updatePinnedChannelMessages", [("flags", flags), ("channelId", channelId), ("messages", messages), ("pts", pts), ("ptsCount", ptsCount)]) + case .updateGroupCallParticipant(let call, let participant): + return ("updateGroupCallParticipant", [("call", call), ("participant", participant)]) + case .updateGroupCall(let call): + return ("updateGroupCall", [("call", call)]) } } @@ -8575,6 +8868,37 @@ public extension Api { return nil } } + public static func parse_updateGroupCallParticipant(_ reader: BufferReader) -> Update? { + var _1: Api.InputGroupCall? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.InputGroupCall + } + var _2: Api.GroupCallParticipant? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.GroupCallParticipant + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.Update.updateGroupCallParticipant(call: _1!, participant: _2!) + } + else { + return nil + } + } + public static func parse_updateGroupCall(_ reader: BufferReader) -> Update? { + var _1: Api.GroupCall? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.GroupCall + } + let _c1 = _1 != nil + if _c1 { + return Api.Update.updateGroupCall(call: _1!) + } + else { + return nil + } + } } public enum PopularContact: TypeConstructorDescription { @@ -10399,11 +10723,11 @@ public extension Api { } public enum InputBotInlineMessage: TypeConstructorDescription { case inputBotInlineMessageText(flags: Int32, message: String, entities: [Api.MessageEntity]?, replyMarkup: Api.ReplyMarkup?) - case inputBotInlineMessageMediaGeo(flags: Int32, geoPoint: Api.InputGeoPoint, replyMarkup: Api.ReplyMarkup?) case inputBotInlineMessageGame(flags: Int32, replyMarkup: Api.ReplyMarkup?) case inputBotInlineMessageMediaAuto(flags: Int32, message: String, entities: [Api.MessageEntity]?, replyMarkup: Api.ReplyMarkup?) case inputBotInlineMessageMediaVenue(flags: Int32, geoPoint: Api.InputGeoPoint, title: String, address: String, provider: String, venueId: String, venueType: String, replyMarkup: Api.ReplyMarkup?) case inputBotInlineMessageMediaContact(flags: Int32, phoneNumber: String, firstName: String, lastName: String, vcard: String, replyMarkup: Api.ReplyMarkup?) + case inputBotInlineMessageMediaGeo(flags: Int32, geoPoint: Api.InputGeoPoint, heading: Int32?, period: Int32?, proximityNotificationRadius: Int32?, replyMarkup: Api.ReplyMarkup?) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { @@ -10420,14 +10744,6 @@ public extension Api { }} if Int(flags) & Int(1 << 2) != 0 {replyMarkup!.serialize(buffer, true)} break - case .inputBotInlineMessageMediaGeo(let flags, let geoPoint, let replyMarkup): - if boxed { - buffer.appendInt32(-190472735) - } - serializeInt32(flags, buffer: buffer, boxed: false) - geoPoint.serialize(buffer, true) - if Int(flags) & Int(1 << 2) != 0 {replyMarkup!.serialize(buffer, true)} - break case .inputBotInlineMessageGame(let flags, let replyMarkup): if boxed { buffer.appendInt32(1262639204) @@ -10472,6 +10788,17 @@ public extension Api { serializeString(vcard, buffer: buffer, boxed: false) if Int(flags) & Int(1 << 2) != 0 {replyMarkup!.serialize(buffer, true)} break + case .inputBotInlineMessageMediaGeo(let flags, let geoPoint, let heading, let period, let proximityNotificationRadius, let replyMarkup): + if boxed { + buffer.appendInt32(-1768777083) + } + serializeInt32(flags, buffer: buffer, boxed: false) + geoPoint.serialize(buffer, true) + if Int(flags) & Int(1 << 0) != 0 {serializeInt32(heading!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 1) != 0 {serializeInt32(period!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 3) != 0 {serializeInt32(proximityNotificationRadius!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 2) != 0 {replyMarkup!.serialize(buffer, true)} + break } } @@ -10479,8 +10806,6 @@ public extension Api { switch self { case .inputBotInlineMessageText(let flags, let message, let entities, let replyMarkup): return ("inputBotInlineMessageText", [("flags", flags), ("message", message), ("entities", entities), ("replyMarkup", replyMarkup)]) - case .inputBotInlineMessageMediaGeo(let flags, let geoPoint, let replyMarkup): - return ("inputBotInlineMessageMediaGeo", [("flags", flags), ("geoPoint", geoPoint), ("replyMarkup", replyMarkup)]) case .inputBotInlineMessageGame(let flags, let replyMarkup): return ("inputBotInlineMessageGame", [("flags", flags), ("replyMarkup", replyMarkup)]) case .inputBotInlineMessageMediaAuto(let flags, let message, let entities, let replyMarkup): @@ -10489,6 +10814,8 @@ public extension Api { return ("inputBotInlineMessageMediaVenue", [("flags", flags), ("geoPoint", geoPoint), ("title", title), ("address", address), ("provider", provider), ("venueId", venueId), ("venueType", venueType), ("replyMarkup", replyMarkup)]) case .inputBotInlineMessageMediaContact(let flags, let phoneNumber, let firstName, let lastName, let vcard, let replyMarkup): return ("inputBotInlineMessageMediaContact", [("flags", flags), ("phoneNumber", phoneNumber), ("firstName", firstName), ("lastName", lastName), ("vcard", vcard), ("replyMarkup", replyMarkup)]) + case .inputBotInlineMessageMediaGeo(let flags, let geoPoint, let heading, let period, let proximityNotificationRadius, let replyMarkup): + return ("inputBotInlineMessageMediaGeo", [("flags", flags), ("geoPoint", geoPoint), ("heading", heading), ("period", period), ("proximityNotificationRadius", proximityNotificationRadius), ("replyMarkup", replyMarkup)]) } } @@ -10516,27 +10843,6 @@ public extension Api { return nil } } - public static func parse_inputBotInlineMessageMediaGeo(_ reader: BufferReader) -> InputBotInlineMessage? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.InputGeoPoint? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.InputGeoPoint - } - var _3: Api.ReplyMarkup? - if Int(_1!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.ReplyMarkup - } } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 2) == 0) || _3 != nil - if _c1 && _c2 && _c3 { - return Api.InputBotInlineMessage.inputBotInlineMessageMediaGeo(flags: _1!, geoPoint: _2!, replyMarkup: _3) - } - else { - return nil - } - } public static func parse_inputBotInlineMessageGame(_ reader: BufferReader) -> InputBotInlineMessage? { var _1: Int32? _1 = reader.readInt32() @@ -10641,6 +10947,36 @@ public extension Api { return nil } } + public static func parse_inputBotInlineMessageMediaGeo(_ reader: BufferReader) -> InputBotInlineMessage? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.InputGeoPoint? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.InputGeoPoint + } + var _3: Int32? + if Int(_1!) & Int(1 << 0) != 0 {_3 = reader.readInt32() } + var _4: Int32? + if Int(_1!) & Int(1 << 1) != 0 {_4 = reader.readInt32() } + var _5: Int32? + if Int(_1!) & Int(1 << 3) != 0 {_5 = reader.readInt32() } + var _6: Api.ReplyMarkup? + if Int(_1!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() { + _6 = Api.parse(reader, signature: signature) as? Api.ReplyMarkup + } } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil + let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil + let _c5 = (Int(_1!) & Int(1 << 3) == 0) || _5 != nil + let _c6 = (Int(_1!) & Int(1 << 2) == 0) || _6 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { + return Api.InputBotInlineMessage.inputBotInlineMessageMediaGeo(flags: _1!, geoPoint: _2!, heading: _3, period: _4, proximityNotificationRadius: _5, replyMarkup: _6) + } + else { + return nil + } + } } public enum KeyboardButtonRow: TypeConstructorDescription { @@ -12048,6 +12384,44 @@ public extension Api { } } + } + public enum InputGroupCall: TypeConstructorDescription { + case inputGroupCall(id: Int64, accessHash: Int64) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .inputGroupCall(let id, let accessHash): + if boxed { + buffer.appendInt32(-659913713) + } + serializeInt64(id, buffer: buffer, boxed: false) + serializeInt64(accessHash, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .inputGroupCall(let id, let accessHash): + return ("inputGroupCall", [("id", id), ("accessHash", accessHash)]) + } + } + + public static func parse_inputGroupCall(_ reader: BufferReader) -> InputGroupCall? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Int64? + _2 = reader.readInt64() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.InputGroupCall.inputGroupCall(id: _1!, accessHash: _2!) + } + else { + return nil + } + } + } public enum InputSingleMedia: TypeConstructorDescription { case inputSingleMedia(flags: Int32, media: Api.InputMedia, randomId: Int64, message: String, entities: [Api.MessageEntity]?) @@ -21186,6 +21560,7 @@ public extension Api { case messageActionSecureValuesSent(types: [Api.SecureValueType]) case messageActionContactSignUp case messageActionGeoProximityReached(fromId: Api.Peer, toId: Api.Peer, distance: Int32) + case messageActionGroupCall(flags: Int32, call: Api.InputGroupCall, duration: Int32?) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { @@ -21365,6 +21740,14 @@ public extension Api { toId.serialize(buffer, true) serializeInt32(distance, buffer: buffer, boxed: false) break + case .messageActionGroupCall(let flags, let call, let duration): + if boxed { + buffer.appendInt32(2047704898) + } + serializeInt32(flags, buffer: buffer, boxed: false) + call.serialize(buffer, true) + if Int(flags) & Int(1 << 0) != 0 {serializeInt32(duration!, buffer: buffer, boxed: false)} + break } } @@ -21418,6 +21801,8 @@ public extension Api { return ("messageActionContactSignUp", []) case .messageActionGeoProximityReached(let fromId, let toId, let distance): return ("messageActionGeoProximityReached", [("fromId", fromId), ("toId", toId), ("distance", distance)]) + case .messageActionGroupCall(let flags, let call, let duration): + return ("messageActionGroupCall", [("flags", flags), ("call", call), ("duration", duration)]) } } @@ -21707,6 +22092,25 @@ public extension Api { return nil } } + public static func parse_messageActionGroupCall(_ reader: BufferReader) -> MessageAction? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.InputGroupCall? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.InputGroupCall + } + var _3: Int32? + if Int(_1!) & Int(1 << 0) != 0 {_3 = reader.readInt32() } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil + if _c1 && _c2 && _c3 { + return Api.MessageAction.messageActionGroupCall(flags: _1!, call: _2!, duration: _3) + } + else { + return nil + } + } } public enum PhoneCall: TypeConstructorDescription { diff --git a/submodules/TelegramApi/Sources/Api3.swift b/submodules/TelegramApi/Sources/Api3.swift index 73a388d40c..0daa2e9f0e 100644 --- a/submodules/TelegramApi/Sources/Api3.swift +++ b/submodules/TelegramApi/Sources/Api3.swift @@ -1648,6 +1648,72 @@ public struct photos { } public extension Api { public struct phone { + public enum GroupCall: TypeConstructorDescription { + case groupCall(call: Api.GroupCall, participants: [Api.GroupCallParticipant], chats: [Api.Chat], users: [Api.User]) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .groupCall(let call, let participants, let chats, let users): + if boxed { + buffer.appendInt32(1731723191) + } + call.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(participants.count)) + for item in participants { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(chats.count)) + for item in chats { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(users.count)) + for item in users { + item.serialize(buffer, true) + } + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .groupCall(let call, let participants, let chats, let users): + return ("groupCall", [("call", call), ("participants", participants), ("chats", chats), ("users", users)]) + } + } + + public static func parse_groupCall(_ reader: BufferReader) -> GroupCall? { + var _1: Api.GroupCall? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.GroupCall + } + var _2: [Api.GroupCallParticipant]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.GroupCallParticipant.self) + } + var _3: [Api.Chat]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + } + var _4: [Api.User]? + if let _ = reader.readInt32() { + _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.phone.GroupCall.groupCall(call: _1!, participants: _2!, chats: _3!, users: _4!) + } + else { + return nil + } + } + + } public enum PhoneCall: TypeConstructorDescription { case phoneCall(phoneCall: Api.PhoneCall, users: [Api.User]) @@ -7118,6 +7184,111 @@ public extension Api { return result }) } + + public static func createGroupCall(flags: Int32, channel: Api.InputChannel, randomId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1542553507) + serializeInt32(flags, buffer: buffer, boxed: false) + channel.serialize(buffer, true) + serializeInt32(randomId, buffer: buffer, boxed: false) + return (FunctionDescription(name: "phone.createGroupCall", parameters: [("flags", flags), ("channel", channel), ("randomId", randomId)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } + + public static func joinGroupCall(call: Api.InputGroupCall, params: Api.DataJSON) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(893342305) + call.serialize(buffer, true) + params.serialize(buffer, true) + return (FunctionDescription(name: "phone.joinGroupCall", parameters: [("call", call), ("params", params)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } + + public static func leaveGroupCall(call: Api.InputGroupCall) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1625919071) + call.serialize(buffer, true) + return (FunctionDescription(name: "phone.leaveGroupCall", parameters: [("call", call)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } + + public static func editGroupCallMember(flags: Int32, call: Api.InputGroupCall, userId: Api.InputUser) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1662282468) + serializeInt32(flags, buffer: buffer, boxed: false) + call.serialize(buffer, true) + userId.serialize(buffer, true) + return (FunctionDescription(name: "phone.editGroupCallMember", parameters: [("flags", flags), ("call", call), ("userId", userId)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } + + public static func kickGroupCallMember(flags: Int32, call: Api.InputGroupCall, userId: Api.InputUser) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1731080446) + serializeInt32(flags, buffer: buffer, boxed: false) + call.serialize(buffer, true) + userId.serialize(buffer, true) + return (FunctionDescription(name: "phone.kickGroupCallMember", parameters: [("flags", flags), ("call", call), ("userId", userId)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } + + public static func discardGroupCall(call: Api.InputGroupCall) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(2054648117) + call.serialize(buffer, true) + return (FunctionDescription(name: "phone.discardGroupCall", parameters: [("call", call)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } + + public static func getGroupCall(call: Api.InputGroupCall) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(209498135) + call.serialize(buffer, true) + return (FunctionDescription(name: "phone.getGroupCall", parameters: [("call", call)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.phone.GroupCall? in + let reader = BufferReader(buffer) + var result: Api.phone.GroupCall? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.phone.GroupCall + } + return result + }) + } } } } diff --git a/submodules/TelegramCallsUI/Sources/PresentationCallManager.swift b/submodules/TelegramCallsUI/Sources/PresentationCallManager.swift index 3c9274bc4b..2686847069 100644 --- a/submodules/TelegramCallsUI/Sources/PresentationCallManager.swift +++ b/submodules/TelegramCallsUI/Sources/PresentationCallManager.swift @@ -60,6 +60,11 @@ public final class PresentationCallManagerImpl: PresentationCallManager { private var currentCallDisposable = MetaDisposable() private let removeCurrentCallDisposable = MetaDisposable() + private var currentGroupCallValue: PresentationGroupCallImpl? + private var currentGroupCall: PresentationGroupCallImpl? { + return self.currentGroupCallValue + } + private var ringingStatesDisposable: Disposable? private let hasActiveCallsPromise = ValuePromise(false, ignoreRepeated: true) @@ -72,6 +77,11 @@ public final class PresentationCallManagerImpl: PresentationCallManager { return self.currentCallPromise.get() } + private let currentGroupCallPromise = Promise(nil) + public var currentGroupCallSignal: Signal { + return self.currentGroupCallPromise.get() + } + private let startCallDisposable = MetaDisposable() private var proxyServer: ProxyServerSettings? @@ -566,4 +576,100 @@ public final class PresentationCallManagerImpl: PresentationCallManager { self.resumeMediaPlayback() } } + + private func updateCurrentGroupCall(_ value: PresentationGroupCallImpl?) { + let wasEmpty = self.currentGroupCallValue == nil + let isEmpty = value == nil + if wasEmpty && !isEmpty { + self.resumeMedia = self.isMediaPlaying() + } + + self.currentGroupCallValue = value + + if !wasEmpty && isEmpty && self.resumeMedia { + self.resumeMedia = false + self.resumeMediaPlayback() + } + } + + public func requestOrJoinGroupCall(context: AccountContext, peerId: PeerId) { + let begin: () -> Void = { [weak self] in + guard let strongSelf = self else { + return + } + let _ = strongSelf.startGroupCall(account: context.account, peerId: peerId).start() + } + begin() + } + + private func startGroupCall( + account: Account, + peerId: PeerId, + internalId: CallSessionInternalId = CallSessionInternalId() + ) -> Signal { + let (presentationData, present, openSettings) = self.getDeviceAccessData() + + let isVideo = false + + let accessEnabledSignal: Signal = Signal { subscriber in + DeviceAccess.authorizeAccess(to: .microphone(.voiceCall), presentationData: presentationData, present: { c, a in + present(c, a) + }, openSettings: { + openSettings() + }, { value in + if isVideo && value { + DeviceAccess.authorizeAccess(to: .camera(.videoCall), presentationData: presentationData, present: { c, a in + present(c, a) + }, openSettings: { + openSettings() + }, { value in + subscriber.putNext(value) + subscriber.putCompletion() + }) + } else { + subscriber.putNext(value) + subscriber.putCompletion() + } + }) + return EmptyDisposable + } + |> runOn(Queue.mainQueue()) + + return accessEnabledSignal + |> deliverOnMainQueue + |> mapToSignal { [weak self] accessEnabled -> Signal in + guard let strongSelf = self else { + return .single(false) + } + + if !accessEnabled { + return .single(false) + } + + let call = PresentationGroupCallImpl( + account: account, + audioSession: strongSelf.audioSession, + callKitIntegration: nil, + getDeviceAccessData: strongSelf.getDeviceAccessData, + internalId: internalId, + peerId: peerId, + peer: nil + ) + strongSelf.updateCurrentGroupCall(call) + strongSelf.currentGroupCallPromise.set(.single(call)) + strongSelf.hasActiveCallsPromise.set(true) + strongSelf.removeCurrentCallDisposable.set((call.canBeRemoved.get() + |> deliverOnMainQueue).start(next: { [weak call] value in + if value, let strongSelf = self, let call = call { + if strongSelf.currentGroupCall === call { + strongSelf.updateCurrentGroupCall(nil) + strongSelf.currentGroupCallPromise.set(.single(nil)) + strongSelf.hasActiveCallsPromise.set(false) + } + } + })) + + return .single(true) + } + } } diff --git a/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift b/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift new file mode 100644 index 0000000000..7125a26e99 --- /dev/null +++ b/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift @@ -0,0 +1,346 @@ +import Foundation +import UIKit +import Postbox +import TelegramCore +import SyncCore +import SwiftSignalKit +import Display +import AVFoundation +import TelegramVoip +import TelegramAudio +import TelegramUIPreferences +import TelegramPresentationData +import DeviceAccess +import UniversalMediaPlayer +import AccountContext + +public final class PresentationGroupCallImpl: PresentationGroupCall { + private enum InternalState { + case requesting + case active(GroupCallInfo) + case estabilished(GroupCallInfo, String, [Int32]) + + var callInfo: GroupCallInfo? { + switch self { + case .requesting: + return nil + case let .active(info): + return info + case let .estabilished(info, _, _): + return info + } + } + } + + public let account: Account + private let audioSession: ManagedAudioSession + private let callKitIntegration: CallKitIntegration? + public var isIntegratedWithCallKit: Bool { + return self.callKitIntegration != nil + } + + private let getDeviceAccessData: () -> (presentationData: PresentationData, present: (ViewController, Any?) -> Void, openSettings: () -> Void) + + public let internalId: CallSessionInternalId + public let peerId: PeerId + public let peer: Peer? + + private var internalState: InternalState = .requesting + + private var callContext: OngoingGroupCallContext? + + private var sessionStateDisposable: Disposable? + + private let isMutedPromise = ValuePromise(false) + private var isMutedValue = false + public var isMuted: Signal { + return self.isMutedPromise.get() + } + + private let audioOutputStatePromise = Promise<([AudioSessionOutput], AudioSessionOutput?)>(([], nil)) + private var audioOutputStateValue: ([AudioSessionOutput], AudioSessionOutput?) = ([], nil) + private var currentAudioOutputValue: AudioSessionOutput = .builtin + public var audioOutputState: Signal<([AudioSessionOutput], AudioSessionOutput?), NoError> { + return self.audioOutputStatePromise.get() + } + + private var audioSessionControl: ManagedAudioSessionControl? + private var audioSessionDisposable: Disposable? + private let audioSessionShouldBeActive = ValuePromise(false, ignoreRepeated: true) + private var audioSessionShouldBeActiveDisposable: Disposable? + private let audioSessionActive = Promise(false) + private var audioSessionActiveDisposable: Disposable? + private var isAudioSessionActive = false + + let canBeRemoved = Promise(false) + + private let requestDisposable = MetaDisposable() + private var groupCallParticipantUpdatesDisposable: Disposable? + + init( + account: Account, + audioSession: ManagedAudioSession, + callKitIntegration: CallKitIntegration?, + getDeviceAccessData: @escaping () -> (presentationData: PresentationData, present: (ViewController, Any?) -> Void, openSettings: () -> Void), + internalId: CallSessionInternalId, + peerId: PeerId, + peer: Peer? + ) { + self.account = account + self.audioSession = audioSession + self.callKitIntegration = callKitIntegration + self.getDeviceAccessData = getDeviceAccessData + + self.internalId = internalId + self.peerId = peerId + self.peer = peer + + var didReceiveAudioOutputs = false + + self.audioSessionDisposable = audioSession.push(audioSessionType: .voiceCall, manualActivate: { [weak self] control in + Queue.mainQueue().async { + if let strongSelf = self { + strongSelf.updateSessionState(internalState: strongSelf.internalState, audioSessionControl: control) + } + } + }, deactivate: { [weak self] in + return Signal { subscriber in + Queue.mainQueue().async { + if let strongSelf = self { + strongSelf.updateIsAudioSessionActive(false) + strongSelf.updateSessionState(internalState: strongSelf.internalState, audioSessionControl: nil) + } + subscriber.putCompletion() + } + return EmptyDisposable + } + }, availableOutputsChanged: { [weak self] availableOutputs, currentOutput in + Queue.mainQueue().async { + guard let strongSelf = self else { + return + } + strongSelf.audioOutputStateValue = (availableOutputs, currentOutput) + + var signal: Signal<([AudioSessionOutput], AudioSessionOutput?), NoError> = .single((availableOutputs, currentOutput)) + if !didReceiveAudioOutputs { + didReceiveAudioOutputs = true + if currentOutput == .speaker { + signal = .single((availableOutputs, .builtin)) + |> then( + signal + |> delay(1.0, queue: Queue.mainQueue()) + ) + } + } + strongSelf.audioOutputStatePromise.set(signal) + } + }) + + self.audioSessionShouldBeActiveDisposable = (self.audioSessionShouldBeActive.get() + |> deliverOnMainQueue).start(next: { [weak self] value in + if let strongSelf = self { + if value { + if let audioSessionControl = strongSelf.audioSessionControl { + let audioSessionActive: Signal + if let callKitIntegration = strongSelf.callKitIntegration { + audioSessionActive = callKitIntegration.audioSessionActive + |> filter { $0 } + |> timeout(2.0, queue: Queue.mainQueue(), alternate: Signal { subscriber in + if let strongSelf = self, let _ = strongSelf.audioSessionControl { + } + subscriber.putNext(true) + subscriber.putCompletion() + return EmptyDisposable + }) + } else { + audioSessionControl.activate({ _ in }) + audioSessionActive = .single(true) + } + strongSelf.audioSessionActive.set(audioSessionActive) + } else { + strongSelf.audioSessionActive.set(.single(false)) + } + } else { + strongSelf.audioSessionActive.set(.single(false)) + } + } + }) + + self.audioSessionActiveDisposable = (self.audioSessionActive.get() + |> deliverOnMainQueue).start(next: { [weak self] value in + if let strongSelf = self { + strongSelf.updateIsAudioSessionActive(value) + } + }) + + self.requestCall() + + self.groupCallParticipantUpdatesDisposable = (self.account.stateManager.groupCallParticipantUpdates + |> deliverOnMainQueue).start(next: { [weak self] updates in + guard let strongSelf = self else { + return + } + if case let .estabilished(callInfo, _, _) = strongSelf.internalState { + var addedSsrc: [Int32] = [] + for (callId, ssrc) in updates { + if callId == callInfo.id { + addedSsrc.append(ssrc) + } + } + if !addedSsrc.isEmpty { + strongSelf.callContext?.addSsrcs(ssrcs: addedSsrc) + } + } + }) + } + + deinit { + self.audioSessionShouldBeActiveDisposable?.dispose() + self.audioSessionActiveDisposable?.dispose() + self.sessionStateDisposable?.dispose() + self.audioSessionDisposable?.dispose() + self.requestDisposable.dispose() + self.groupCallParticipantUpdatesDisposable?.dispose() + } + + private func updateSessionState(internalState: InternalState, audioSessionControl: ManagedAudioSessionControl?) { + let previousControl = self.audioSessionControl + self.audioSessionControl = audioSessionControl + + let previousInternalState = self.internalState + self.internalState = internalState + + if let audioSessionControl = audioSessionControl, previousControl == nil { + audioSessionControl.setOutputMode(.custom(self.currentAudioOutputValue)) + audioSessionControl.setup(synchronous: true) + } + + self.audioSessionShouldBeActive.set(true) + + switch previousInternalState { + case .active: + break + default: + if case let .active(callInfo) = internalState { + let callContext = OngoingGroupCallContext() + self.callContext = callContext + self.requestDisposable.set((callContext.joinPayload + |> take(1) + |> deliverOnMainQueue).start(next: { [weak self] joinPayload in + guard let strongSelf = self else { + return + } + strongSelf.requestDisposable.set((joinGroupCall( + account: strongSelf.account, + callId: callInfo.id, + accessHash: callInfo.accessHash, + joinPayload: joinPayload + ) + |> deliverOnMainQueue).start(next: { joinCallResult in + guard let strongSelf = self else { + return + } + if let clientParams = joinCallResult.callInfo.clientParams { + strongSelf.updateSessionState(internalState: .estabilished(joinCallResult.callInfo, clientParams, joinCallResult.ssrcs), audioSessionControl: strongSelf.audioSessionControl) + } + })) + })) + } + } + + switch previousInternalState { + case .estabilished: + break + default: + if case let .estabilished(_, clientParams, ssrcs) = internalState { + self.callContext?.setJoinResponse(payload: clientParams, ssrcs: ssrcs) + } + } + } + + private func updateIsAudioSessionActive(_ value: Bool) { + if self.isAudioSessionActive != value { + self.isAudioSessionActive = value + } + } + + public func hangUp() -> Signal { + return .single(true) + } + + public func toggleIsMuted() { + self.setIsMuted(!self.isMutedValue) + } + + public func setIsMuted(_ value: Bool) { + self.isMutedValue = value + self.isMutedPromise.set(self.isMutedValue) + self.callContext?.setIsMuted(self.isMutedValue) + } + + public func setCurrentAudioOutput(_ output: AudioSessionOutput) { + guard self.currentAudioOutputValue != output else { + return + } + self.currentAudioOutputValue = output + + self.audioOutputStatePromise.set(.single((self.audioOutputStateValue.0, output)) + |> then( + .single(self.audioOutputStateValue) + |> delay(1.0, queue: Queue.mainQueue()) + )) + + if let audioSessionControl = self.audioSessionControl { + audioSessionControl.setOutputMode(.custom(output)) + } + } + + private func requestCall() { + self.internalState = .requesting + + enum CallError { + case generic + } + + let account = self.account + let peerId = self.peerId + + let currentCall = getCurrentGroupCall(account: account, peerId: peerId) + |> mapError { _ -> CallError in + return .generic + } + + let currentOrRequestedCall = currentCall + |> mapToSignal { callInfo -> Signal in + if let callInfo = callInfo { + return .single(callInfo) + } else { + return createGroupCall(account: account, peerId: peerId) + |> mapError { _ -> CallError in + return .generic + } + } + } + + let restartedCall = currentOrRequestedCall + |> mapToSignal { value -> Signal in + let stopped: Signal = stopGroupCall(account: account, callId: value.id, accessHash: value.accessHash) + |> mapError { _ -> CallError in + return .generic + } + |> map { _ -> GroupCallInfo in + } + + return stopped + |> then(currentOrRequestedCall) + } + + self.requestDisposable.set((currentOrRequestedCall + |> deliverOnMainQueue).start(next: { [weak self] value in + guard let strongSelf = self else { + return + } + strongSelf.updateSessionState(internalState: .active(value), audioSessionControl: strongSelf.audioSessionControl) + })) + } +} diff --git a/submodules/TelegramCore/Sources/AccountIntermediateState.swift b/submodules/TelegramCore/Sources/AccountIntermediateState.swift index 0da4669378..2bf78f3456 100644 --- a/submodules/TelegramCore/Sources/AccountIntermediateState.swift +++ b/submodules/TelegramCore/Sources/AccountIntermediateState.swift @@ -110,6 +110,7 @@ enum AccountStateMutationOperation { case UpdateChatListFilterOrder(order: [Int32]) case UpdateChatListFilter(id: Int32, filter: Api.DialogFilter?) case UpdateReadThread(threadMessageId: MessageId, readMaxId: Int32, isIncoming: Bool, mainChannelMessage: MessageId?) + case UpdateGroupCallParticipant(id: Int64, accessHash: Int64, participant: Api.GroupCallParticipant) } struct HoleFromPreviousState { @@ -276,6 +277,10 @@ struct AccountMutableState { self.addOperation(.UpdateReadThread(threadMessageId: threadMessageId, readMaxId: readMaxId, isIncoming: isIncoming, mainChannelMessage: mainChannelMessage)) } + mutating func updateGroupCallParticipant(id: Int64, accessHash: Int64, participant: Api.GroupCallParticipant) { + self.addOperation(.UpdateGroupCallParticipant(id: id, accessHash: accessHash, participant: participant)) + } + mutating func readGroupFeedInbox(groupId: PeerGroupId, index: MessageIndex) { self.addOperation(.ReadGroupFeedInbox(groupId, index)) } @@ -484,7 +489,7 @@ struct AccountMutableState { mutating func addOperation(_ operation: AccountStateMutationOperation) { switch operation { - case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMessagePoll/*, .UpdateMessageReactions*/, .UpdateMedia, .ReadOutbox, .ReadGroupFeedInbox, .MergePeerPresences, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .UpdateCachedPeerData, .UpdatePinnedItemIds, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateMessageForwardsCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .AddCallSignalingData, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdatePeerChatUnreadMark, .UpdateIsContact, .UpdatePeerChatInclusion, .UpdatePeersNearby, .UpdateTheme, .SyncChatListFilters, .UpdateChatListFilterOrder, .UpdateChatListFilter, .UpdateReadThread, .UpdateMessagesPinned: + case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMessagePoll/*, .UpdateMessageReactions*/, .UpdateMedia, .ReadOutbox, .ReadGroupFeedInbox, .MergePeerPresences, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .UpdateCachedPeerData, .UpdatePinnedItemIds, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateMessageForwardsCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .AddCallSignalingData, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdatePeerChatUnreadMark, .UpdateIsContact, .UpdatePeerChatInclusion, .UpdatePeersNearby, .UpdateTheme, .SyncChatListFilters, .UpdateChatListFilterOrder, .UpdateChatListFilter, .UpdateReadThread, .UpdateGroupCallParticipant, .UpdateMessagesPinned: break case let .AddMessages(messages, location): for message in messages { @@ -602,6 +607,7 @@ struct AccountReplayedFinalState { let updatedWebpages: [MediaId: TelegramMediaWebpage] let updatedCalls: [Api.PhoneCall] let addedCallSignalingData: [(Int64, Data)] + let updatedGroupCallParticipants: [(Int64, Int32)] let updatedPeersNearby: [PeerNearby]? let isContactUpdates: [(PeerId, Bool)] let delayNotificatonsUntil: Int32? @@ -617,6 +623,7 @@ struct AccountFinalStateEvents { let updatedWebpages: [MediaId: TelegramMediaWebpage] let updatedCalls: [Api.PhoneCall] let addedCallSignalingData: [(Int64, Data)] + let updatedGroupCallParticipants: [(Int64, Int32)] let updatedPeersNearby: [PeerNearby]? let isContactUpdates: [(PeerId, Bool)] let displayAlerts: [(text: String, isDropAuth: Bool)] @@ -629,10 +636,10 @@ struct AccountFinalStateEvents { let updatedOutgoingThreadReadStates: [MessageId: MessageId.Id] var isEmpty: Bool { - return self.addedIncomingMessageIds.isEmpty && self.wasScheduledMessageIds.isEmpty && self.deletedMessageIds.isEmpty && self.updatedTypingActivities.isEmpty && self.updatedWebpages.isEmpty && self.updatedCalls.isEmpty && self.addedCallSignalingData.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 + 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)] = [], 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, Int32)] = [], 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 @@ -640,6 +647,7 @@ struct AccountFinalStateEvents { self.updatedWebpages = updatedWebpages self.updatedCalls = updatedCalls self.addedCallSignalingData = addedCallSignalingData + self.updatedGroupCallParticipants = updatedGroupCallParticipants self.updatedPeersNearby = updatedPeersNearby self.isContactUpdates = isContactUpdates self.displayAlerts = displayAlerts @@ -660,6 +668,7 @@ struct AccountFinalStateEvents { self.updatedWebpages = state.updatedWebpages self.updatedCalls = state.updatedCalls self.addedCallSignalingData = state.addedCallSignalingData + self.updatedGroupCallParticipants = state.updatedGroupCallParticipants self.updatedPeersNearby = state.updatedPeersNearby self.isContactUpdates = state.isContactUpdates self.displayAlerts = state.state.state.displayAlerts @@ -695,6 +704,6 @@ struct AccountFinalStateEvents { let externallyUpdatedPeerId = self.externallyUpdatedPeerId.union(other.externallyUpdatedPeerId) let authorizationListUpdated = self.authorizationListUpdated || other.authorizationListUpdated - return AccountFinalStateEvents(addedIncomingMessageIds: self.addedIncomingMessageIds + other.addedIncomingMessageIds, wasScheduledMessageIds: self.wasScheduledMessageIds + other.wasScheduledMessageIds, deletedMessageIds: self.deletedMessageIds + other.deletedMessageIds, updatedTypingActivities: self.updatedTypingActivities, updatedWebpages: self.updatedWebpages, updatedCalls: self.updatedCalls + other.updatedCalls, addedCallSignalingData: self.addedCallSignalingData + other.addedCallSignalingData, isContactUpdates: self.isContactUpdates + other.isContactUpdates, displayAlerts: self.displayAlerts + other.displayAlerts, delayNotificatonsUntil: delayNotificatonsUntil, updatedMaxMessageId: updatedMaxMessageId, updatedQts: updatedQts, externallyUpdatedPeerId: externallyUpdatedPeerId, authorizationListUpdated: authorizationListUpdated, updatedIncomingThreadReadStates: self.updatedIncomingThreadReadStates.merging(other.updatedIncomingThreadReadStates, uniquingKeysWith: { lhs, _ in lhs })) + return AccountFinalStateEvents(addedIncomingMessageIds: self.addedIncomingMessageIds + other.addedIncomingMessageIds, wasScheduledMessageIds: self.wasScheduledMessageIds + other.wasScheduledMessageIds, deletedMessageIds: self.deletedMessageIds + other.deletedMessageIds, updatedTypingActivities: self.updatedTypingActivities, updatedWebpages: self.updatedWebpages, updatedCalls: self.updatedCalls + other.updatedCalls, addedCallSignalingData: self.addedCallSignalingData + other.addedCallSignalingData, updatedGroupCallParticipants: self.updatedGroupCallParticipants + other.updatedGroupCallParticipants, isContactUpdates: self.isContactUpdates + other.isContactUpdates, displayAlerts: self.displayAlerts + other.displayAlerts, delayNotificatonsUntil: delayNotificatonsUntil, updatedMaxMessageId: updatedMaxMessageId, updatedQts: updatedQts, externallyUpdatedPeerId: externallyUpdatedPeerId, authorizationListUpdated: authorizationListUpdated, updatedIncomingThreadReadStates: self.updatedIncomingThreadReadStates.merging(other.updatedIncomingThreadReadStates, uniquingKeysWith: { lhs, _ in lhs })) } } diff --git a/submodules/TelegramCore/Sources/AccountStateManagementUtils.swift b/submodules/TelegramCore/Sources/AccountStateManagementUtils.swift index b053ab26c8..da488ada74 100644 --- a/submodules/TelegramCore/Sources/AccountStateManagementUtils.swift +++ b/submodules/TelegramCore/Sources/AccountStateManagementUtils.swift @@ -1306,6 +1306,11 @@ private func finalStateWithUpdatesAndServerTime(postbox: Postbox, network: Netwo updatedState.addUpdateCall(phoneCall) case let .updatePhoneCallSignalingData(phoneCallId, data): updatedState.addCallSignalingData(callId: phoneCallId, data: data.makeData()) + case let .updateGroupCallParticipant(call, participant): + switch call { + case let .inputGroupCall(id, accessHash): + updatedState.updateGroupCallParticipant(id: id, accessHash: accessHash, participant: participant) + } case let .updateLangPackTooLong(langCode): updatedState.updateLangPack(langCode: langCode, difference: nil) case let .updateLangPack(difference): @@ -2109,7 +2114,7 @@ private func optimizedOperations(_ operations: [AccountStateMutationOperation]) var currentAddScheduledMessages: OptimizeAddMessagesState? for operation in operations { switch operation { - case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMessagePoll/*, .UpdateMessageReactions*/, .UpdateMedia, .MergeApiChats, .MergeApiUsers, .MergePeerPresences, .UpdatePeer, .ReadInbox, .ReadOutbox, .ReadGroupFeedInbox, .ResetReadState, .ResetIncomingReadState, .UpdatePeerChatUnreadMark, .ResetMessageTagSummary, .UpdateNotificationSettings, .UpdateGlobalNotificationSettings, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .UpdateCachedPeerData, .UpdatePinnedItemIds, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateMessageForwardsCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .AddCallSignalingData, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdateIsContact, .UpdatePeerChatInclusion, .UpdatePeersNearby, .UpdateTheme, .SyncChatListFilters, .UpdateChatListFilter, .UpdateChatListFilterOrder, .UpdateReadThread, .UpdateMessagesPinned: + case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMessagePoll/*, .UpdateMessageReactions*/, .UpdateMedia, .MergeApiChats, .MergeApiUsers, .MergePeerPresences, .UpdatePeer, .ReadInbox, .ReadOutbox, .ReadGroupFeedInbox, .ResetReadState, .ResetIncomingReadState, .UpdatePeerChatUnreadMark, .ResetMessageTagSummary, .UpdateNotificationSettings, .UpdateGlobalNotificationSettings, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .UpdateCachedPeerData, .UpdatePinnedItemIds, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateMessageForwardsCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .AddCallSignalingData, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdateIsContact, .UpdatePeerChatInclusion, .UpdatePeersNearby, .UpdateTheme, .SyncChatListFilters, .UpdateChatListFilter, .UpdateChatListFilterOrder, .UpdateReadThread, .UpdateMessagesPinned, .UpdateGroupCallParticipant: if let currentAddMessages = currentAddMessages, !currentAddMessages.messages.isEmpty { result.append(.AddMessages(currentAddMessages.messages, currentAddMessages.location)) } @@ -2196,6 +2201,7 @@ func replayFinalState(accountManager: AccountManager, postbox: Postbox, accountP var updatedWebpages: [MediaId: TelegramMediaWebpage] = [:] var updatedCalls: [Api.PhoneCall] = [] var addedCallSignalingData: [(Int64, Data)] = [] + var updatedGroupCallParticipants: [(Int64, Int32)] = [] var updatedPeersNearby: [PeerNearby]? var isContactUpdates: [(PeerId, Bool)] = [] var stickerPackOperations: [AccountStateUpdateStickerPacksOperation] = [] @@ -2925,6 +2931,23 @@ func replayFinalState(accountManager: AccountManager, postbox: Postbox, accountP updatedCalls.append(call) case let .AddCallSignalingData(callId, data): addedCallSignalingData.append((callId, data)) + case let .UpdateGroupCallParticipant(callId, _, participant): + var ssrc: Int32? + switch participant { + case let .groupCallParticipantAdmin(_, source): + ssrc = source + case let .groupCallParticipant(_, _, _, source): + ssrc = source + case .groupCallParticipantLeft: + break + case .groupCallParticipantKicked: + break + case .groupCallParticipantInvited: + break + } + if let ssrc = ssrc { + updatedGroupCallParticipants.append((callId, ssrc)) + } case let .UpdateLangPack(langCode, difference): if let difference = difference { if langPackDifferences[langCode] == nil { @@ -3347,5 +3370,5 @@ func replayFinalState(accountManager: AccountManager, postbox: Postbox, accountP requestChatListFiltersSync(transaction: transaction) } - return AccountReplayedFinalState(state: finalState, addedIncomingMessageIds: addedIncomingMessageIds, wasScheduledMessageIds: wasScheduledMessageIds, addedSecretMessageIds: addedSecretMessageIds, deletedMessageIds: deletedMessageIds, updatedTypingActivities: updatedTypingActivities, updatedWebpages: updatedWebpages, updatedCalls: updatedCalls, addedCallSignalingData: addedCallSignalingData, updatedPeersNearby: updatedPeersNearby, isContactUpdates: isContactUpdates, delayNotificatonsUntil: delayNotificatonsUntil, updatedIncomingThreadReadStates: updatedIncomingThreadReadStates, updatedOutgoingThreadReadStates: updatedOutgoingThreadReadStates) + return AccountReplayedFinalState(state: finalState, addedIncomingMessageIds: addedIncomingMessageIds, wasScheduledMessageIds: wasScheduledMessageIds, addedSecretMessageIds: addedSecretMessageIds, deletedMessageIds: deletedMessageIds, updatedTypingActivities: updatedTypingActivities, updatedWebpages: updatedWebpages, updatedCalls: updatedCalls, addedCallSignalingData: addedCallSignalingData, updatedGroupCallParticipants: updatedGroupCallParticipants, updatedPeersNearby: updatedPeersNearby, isContactUpdates: isContactUpdates, delayNotificatonsUntil: delayNotificatonsUntil, updatedIncomingThreadReadStates: updatedIncomingThreadReadStates, updatedOutgoingThreadReadStates: updatedOutgoingThreadReadStates) } diff --git a/submodules/TelegramCore/Sources/AccountStateManager.swift b/submodules/TelegramCore/Sources/AccountStateManager.swift index a7cf0516cb..26265cf1d5 100644 --- a/submodules/TelegramCore/Sources/AccountStateManager.swift +++ b/submodules/TelegramCore/Sources/AccountStateManager.swift @@ -148,6 +148,11 @@ public final class AccountStateManager { return self.threadReadStateUpdatesPipe.signal() } + private let groupCallParticipantUpdatesPipe = ValuePipe<[(Int64, Int32)]>() + public var groupCallParticipantUpdates: Signal<[(Int64, Int32)], NoError> { + return self.groupCallParticipantUpdatesPipe.signal() + } + private let deletedMessagesPipe = ValuePipe<[DeletedMessageId]>() public var deletedMessages: Signal<[DeletedMessageId], NoError> { return self.deletedMessagesPipe.signal() @@ -673,6 +678,9 @@ public final class AccountStateManager { strongSelf.callSessionManager.addCallSignalingData(id: id, data: data) } } + if !events.updatedGroupCallParticipants.isEmpty { + strongSelf.groupCallParticipantUpdatesPipe.putNext(events.updatedGroupCallParticipants) + } if !events.updatedIncomingThreadReadStates.isEmpty || !events.updatedOutgoingThreadReadStates.isEmpty { strongSelf.threadReadStateUpdatesPipe.putNext((events.updatedIncomingThreadReadStates, events.updatedOutgoingThreadReadStates)) } diff --git a/submodules/TelegramCore/Sources/GroupCalls.swift b/submodules/TelegramCore/Sources/GroupCalls.swift new file mode 100644 index 0000000000..3808a406fc --- /dev/null +++ b/submodules/TelegramCore/Sources/GroupCalls.swift @@ -0,0 +1,287 @@ +import Postbox +import SwiftSignalKit +import TelegramApi +import MtProtoKit +import SyncCore + +public struct GroupCallInfo: Equatable { + public var id: Int64 + public var accessHash: Int64 + public var peerId: PeerId? + public var clientParams: String? +} + +private extension GroupCallInfo { + init?(_ call: Api.GroupCall) { + switch call { + case let .groupCallPrivate(_, id, accessHash, channelId, _, _): + self.init( + id: id, + accessHash: accessHash, + peerId: channelId.flatMap { PeerId(namespace: Namespaces.Peer.CloudChannel, id: $0) }, + clientParams: nil + ) + case let .groupCall(_, id, accessHash, channelId, _, _, params): + var clientParams: String? + if let params = params { + switch params { + case let .dataJSON(data): + clientParams = data + } + } + self.init( + id: id, + accessHash: accessHash, + peerId: channelId.flatMap { PeerId(namespace: Namespaces.Peer.CloudChannel, id: $0) }, + clientParams: clientParams + ) + case .groupCallDiscarded: + return nil + } + } +} + +public enum GetCurrentGroupCallError { + case generic +} + +public func getCurrentGroupCall(account: Account, peerId: PeerId) -> Signal { + return account.postbox.transaction { transaction -> Api.InputChannel? in + transaction.getPeer(peerId).flatMap(apiInputChannel) + } + |> castError(GetCurrentGroupCallError.self) + |> mapToSignal { inputPeer -> Signal in + guard let inputPeer = inputPeer else { + return .fail(.generic) + } + return account.network.request(Api.functions.channels.getFullChannel(channel: inputPeer)) + |> mapError { _ -> GetCurrentGroupCallError in + return .generic + } + |> mapToSignal { result -> Signal in + switch result { + case let .chatFull(fullChat, _, _): + switch fullChat { + case let .channelFull(_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, callMsgId): + return .single(callMsgId.flatMap { callMsgId in + MessageId(peerId: peerId, namespace: Namespaces.Peer.CloudChannel, id: callMsgId) + }) + default: + return .single(nil) + } + default: + return .single(nil) + } + } + } + |> mapToSignal { messageId -> Signal in + guard let messageId = messageId else { + return .single(nil) + } + return account.postbox.transaction { transaction -> Api.InputChannel? in + return transaction.getPeer(peerId).flatMap(apiInputChannel) + } + |> castError(GetCurrentGroupCallError.self) + |> mapToSignal { inputPeer -> Signal in + guard let inputPeer = inputPeer else { + return .fail(.generic) + } + return account.network.request(Api.functions.channels.getMessages(channel: inputPeer, id: [.inputMessageID(id: messageId.id)])) + |> mapError { _ -> GetCurrentGroupCallError in + return .generic + } + |> mapToSignal { result -> Signal in + let messages: [Api.Message] + let chats: [Api.Chat] + let users: [Api.User] + + switch result { + case let .messages(apiMessages, apiChats, apiUsers): + messages = apiMessages + chats = apiChats + users = apiUsers + case let .messagesSlice(_, _, _, _, messages: apiMessages, chats: apiChats, users: apiUsers): + messages = apiMessages + chats = apiChats + users = apiUsers + case let .channelMessages(_, _, _, _, apiMessages, apiChats, apiUsers): + messages = apiMessages + chats = apiChats + users = apiUsers + case .messagesNotModified: + return .fail(.generic) + } + + guard let apiMessage = messages.first else { + return .single(nil) + } + guard let message = StoreMessage(apiMessage: apiMessage) else { + return .fail(.generic) + } + + var maybeInputCall: Api.InputGroupCall? + loop: for media in message.media { + if let action = media as? TelegramMediaAction { + switch action.action { + case let .groupPhoneCall(callId, accessHash, _): + maybeInputCall = .inputGroupCall(id: callId, accessHash: accessHash) + break loop + default: + break + } + } + } + + guard let inputCall = maybeInputCall else { + return .fail(.generic) + } + + return account.network.request(Api.functions.phone.getGroupCall(call: inputCall)) + |> mapError { _ -> GetCurrentGroupCallError in + return .generic + } + |> mapToSignal { result -> Signal in + switch result { + case let .groupCall(call, participants, chats, users): + return account.postbox.transaction { transaction -> GroupCallInfo? in + return GroupCallInfo(call) + } + |> mapError { _ -> GetCurrentGroupCallError in + return .generic + } + } + } + } + } + } +} + +public enum CreateGroupCallError { + case generic +} + +public func createGroupCall(account: Account, peerId: PeerId) -> Signal { + return account.postbox.transaction { transaction -> Api.InputChannel? in + return transaction.getPeer(peerId).flatMap(apiInputChannel) + } + |> castError(CreateGroupCallError.self) + |> mapToSignal { inputPeer -> Signal in + guard let inputPeer = inputPeer else { + return .fail(.generic) + } + + return account.network.request(Api.functions.phone.createGroupCall(flags: 0, channel: inputPeer, randomId: Int32.random(in: Int32.min ... Int32.max))) + |> mapError { _ -> CreateGroupCallError in + return .generic + } + |> mapToSignal { result -> Signal in + account.stateManager.addUpdates(result) + + var parsedCall: GroupCallInfo? + loop: for update in result.allUpdates { + switch update { + case let .updateGroupCall(call): + parsedCall = GroupCallInfo(call) + break loop + default: + break + } + } + + if let parsedCall = parsedCall { + return .single(parsedCall) + } else { + return .fail(.generic) + } + } + } +} + +public enum JoinGroupCallError { + case generic +} + +public struct JoinGroupCallResult { + public var callInfo: GroupCallInfo + public var ssrcs: [Int32] +} + +public func joinGroupCall(account: Account, callId: Int64, accessHash: Int64, joinPayload: String) -> Signal { + return account.network.request(Api.functions.phone.joinGroupCall(call: .inputGroupCall(id: callId, accessHash: accessHash), params: .dataJSON(data: joinPayload))) + |> mapError { _ -> JoinGroupCallError in + return .generic + } + |> mapToSignal { updates -> Signal in + return account.network.request(Api.functions.phone.getGroupCall(call: .inputGroupCall(id: callId, accessHash: accessHash))) + |> mapError { _ -> JoinGroupCallError in + return .generic + } + |> mapToSignal { result -> Signal in + account.stateManager.addUpdates(updates) + + var maybeParsedCall: GroupCallInfo? + loop: for update in updates.allUpdates { + switch update { + case let .updateGroupCall(call): + maybeParsedCall = GroupCallInfo(call) + break loop + default: + break + } + } + + guard let parsedCall = maybeParsedCall else { + return .fail(.generic) + } + + switch result { + case let .groupCall(call, participants, chats, users): + guard let _ = GroupCallInfo(call) else { + return .fail(.generic) + } + var ssrcs: [Int32] = [] + for participant in participants { + var ssrc: Int32? + switch participant { + case let .groupCallParticipantAdmin(_, source): + ssrc = source + case let .groupCallParticipant(_, _, _, source): + ssrc = source + case .groupCallParticipantLeft: + break + case .groupCallParticipantKicked: + break + case .groupCallParticipantInvited: + break + } + if let ssrc = ssrc { + ssrcs.append(ssrc) + } + } + return account.postbox.transaction { transaction -> JoinGroupCallResult in + return JoinGroupCallResult( + callInfo: parsedCall, + ssrcs: ssrcs + ) + } + |> castError(JoinGroupCallError.self) + } + } + } +} + +public enum StopGroupCallError { + case generic +} + +public func stopGroupCall(account: Account, callId: Int64, accessHash: Int64) -> Signal { + return account.network.request(Api.functions.phone.discardGroupCall(call: .inputGroupCall(id: callId, accessHash: accessHash))) + |> mapError { _ -> StopGroupCallError in + return .generic + } + |> mapToSignal { result -> Signal in + account.stateManager.addUpdates(result) + + return .complete() + } +} diff --git a/submodules/TelegramCore/Sources/Serialization.swift b/submodules/TelegramCore/Sources/Serialization.swift index b87bcca4ed..034fd4b593 100644 --- a/submodules/TelegramCore/Sources/Serialization.swift +++ b/submodules/TelegramCore/Sources/Serialization.swift @@ -210,7 +210,7 @@ public class BoxedMessage: NSObject { public class Serialization: NSObject, MTSerialization { public func currentLayer() -> UInt { - return 121 + return 122 } public func parseMessage(_ data: Data!) -> Any! { diff --git a/submodules/TelegramCore/Sources/StoreMessage_Telegram.swift b/submodules/TelegramCore/Sources/StoreMessage_Telegram.swift index 817959bea2..698fe5d4f8 100644 --- a/submodules/TelegramCore/Sources/StoreMessage_Telegram.swift +++ b/submodules/TelegramCore/Sources/StoreMessage_Telegram.swift @@ -188,7 +188,7 @@ func apiMessagePeerIds(_ message: Api.Message) -> [PeerId] { } switch action { - case .messageActionChannelCreate, .messageActionChatDeletePhoto, .messageActionChatEditPhoto, .messageActionChatEditTitle, .messageActionEmpty, .messageActionPinMessage, .messageActionHistoryClear, .messageActionGameScore, .messageActionPaymentSent, .messageActionPaymentSentMe, .messageActionPhoneCall, .messageActionScreenshotTaken, .messageActionCustomAction, .messageActionBotAllowed, .messageActionSecureValuesSent, .messageActionSecureValuesSentMe, .messageActionContactSignUp: + case .messageActionChannelCreate, .messageActionChatDeletePhoto, .messageActionChatEditPhoto, .messageActionChatEditTitle, .messageActionEmpty, .messageActionPinMessage, .messageActionHistoryClear, .messageActionGameScore, .messageActionPaymentSent, .messageActionPaymentSentMe, .messageActionPhoneCall, .messageActionScreenshotTaken, .messageActionCustomAction, .messageActionBotAllowed, .messageActionSecureValuesSent, .messageActionSecureValuesSentMe, .messageActionContactSignUp, .messageActionGroupCall: break case let .messageActionChannelMigrateFrom(_, chatId): result.append(PeerId(namespace: Namespaces.Peer.CloudGroup, id: chatId)) diff --git a/submodules/TelegramCore/Sources/TelegramMediaAction.swift b/submodules/TelegramCore/Sources/TelegramMediaAction.swift index 3af839d70e..eeb04af985 100644 --- a/submodules/TelegramCore/Sources/TelegramMediaAction.swift +++ b/submodules/TelegramCore/Sources/TelegramMediaAction.swift @@ -59,6 +59,11 @@ func telegramMediaActionFromApiAction(_ action: Api.MessageAction) -> TelegramMe return TelegramMediaAction(action: .peerJoined) case let .messageActionGeoProximityReached(fromId, toId, distance): return TelegramMediaAction(action: .geoProximityReached(from: fromId.peerId, to: toId.peerId, distance: distance)) + case let .messageActionGroupCall(_, call, duration): + switch call { + case let .inputGroupCall(id, accessHash): + return TelegramMediaAction(action: .groupPhoneCall(callId: id, accessHash: accessHash, duration: duration)) + } } } diff --git a/submodules/TelegramCore/Sources/UpdateCachedPeerData.swift b/submodules/TelegramCore/Sources/UpdateCachedPeerData.swift index 6a3f8e49d5..70c6d43f0b 100644 --- a/submodules/TelegramCore/Sources/UpdateCachedPeerData.swift +++ b/submodules/TelegramCore/Sources/UpdateCachedPeerData.swift @@ -346,7 +346,7 @@ func fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPeerId: PeerI } switch fullChat { - case let .channelFull(flags, _, about, participantsCount, adminsCount, kickedCount, bannedCount, _, _, _, _, chatPhoto, _, apiExportedInvite, apiBotInfos, migratedFromChatId, migratedFromMaxId, pinnedMsgId, stickerSet, minAvailableMsgId, folderId, linkedChatId, location, slowmodeSeconds, slowmodeNextSendDate, statsDc, pts): + case let .channelFull(flags, _, about, participantsCount, adminsCount, kickedCount, bannedCount, _, _, _, _, chatPhoto, _, apiExportedInvite, apiBotInfos, migratedFromChatId, migratedFromMaxId, pinnedMsgId, stickerSet, minAvailableMsgId, folderId, linkedChatId, location, slowmodeSeconds, slowmodeNextSendDate, statsDc, pts, callMsgId): var channelFlags = CachedChannelFlags() if (flags & (1 << 3)) != 0 { channelFlags.insert(.canDisplayParticipants) diff --git a/submodules/TelegramCore/Sources/UpdatesApiUtils.swift b/submodules/TelegramCore/Sources/UpdatesApiUtils.swift index acf5d469a5..44c7a8bc40 100644 --- a/submodules/TelegramCore/Sources/UpdatesApiUtils.swift +++ b/submodules/TelegramCore/Sources/UpdatesApiUtils.swift @@ -338,6 +338,21 @@ extension Api.Update { } } +extension Api.Updates { + var allUpdates: [Api.Update] { + switch self { + case let .updates(updates, _, _, _, _): + return updates + case let .updatesCombined(updates, _, _, _, _, _): + return updates + case let .updateShort(update, _): + return [update] + default: + return [] + } + } +} + extension Api.Updates { var rawMessageIds: [Int32] { switch self { diff --git a/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift b/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift index a8cad395f3..dc8bd8b0d9 100644 --- a/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift +++ b/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift @@ -404,6 +404,15 @@ public func universalServiceMessageString(presentationData: (PresentationTheme, } } attributedString = NSAttributedString(string: titleString, font: titleFont, textColor: primaryTextColor) + case let .groupPhoneCall(_, _, duration): + //TODO:localize + let titleString: String + if let duration = duration { + titleString = "Group Call \(duration)s" + } else { + titleString = "Group Call" + } + attributedString = NSAttributedString(string: titleString, font: titleFont, textColor: primaryTextColor) case let .customText(text, entities): attributedString = stringWithAppliedEntities(text, entities: entities, baseColor: primaryTextColor, linkColor: primaryTextColor, baseFont: titleFont, linkFont: titleBoldFont, boldFont: titleBoldFont, italicFont: titleFont, boldItalicFont: titleBoldFont, fixedFont: titleFont, blockQuoteFont: titleFont, underlineLinks: false) case let .botDomainAccessGranted(domain): diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoData.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoData.swift index 510dee6cb2..ff273acc3e 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoData.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoData.swift @@ -949,6 +949,7 @@ func peerInfoHeaderButtons(peer: Peer?, cachedData: CachedPeerData?, isOpenedFro displayLeave = false if channel.flags.contains(.isCreator) || channel.hasPermission(.inviteMembers) { result.append(.addMember) + result.append(.call) } } switch channel.participationStatus { diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift index b4ef92e948..806b7838d0 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift @@ -3163,6 +3163,12 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD } private func requestCall(isVideo: Bool) { + if let peer = self.data?.peer as? TelegramChannel { + self.context.sharedContext.callManager?.requestOrJoinGroupCall(context: self.context, peerId: peer.id) + + return + } + guard let peer = self.data?.peer as? TelegramUser, let cachedUserData = self.data?.cachedData as? CachedUserData else { return } diff --git a/submodules/TelegramVoip/Sources/GroupCallContext.swift b/submodules/TelegramVoip/Sources/GroupCallContext.swift index e1d6c09ca4..c5e838c05e 100644 --- a/submodules/TelegramVoip/Sources/GroupCallContext.swift +++ b/submodules/TelegramVoip/Sources/GroupCallContext.swift @@ -732,7 +732,7 @@ private extension ConferenceDescription { let videoMid: String? if stream.isMain { audioMid = "0" - if let videoSsrc = stream.videoSsrc { + if let _ = stream.videoSsrc { videoMid = "1" } else { videoMid = nil @@ -740,7 +740,7 @@ private extension ConferenceDescription { } else { audioMid = "audio\(stream.audioSsrc)" if let videoSsrc = stream.videoSsrc { - videoMid = "video\(stream.videoSsrc)" + videoMid = "video\(videoSsrc)" } else { videoMid = nil } @@ -1590,17 +1590,6 @@ public final class GroupCallContext { strongSelf.relaySdpAnswer(sdpAnswer: offerSdp) } }) - - /*guard let offer = conference.offerSdp(sessionId: strongSelf.sessionId, bundleId: bundleId, bridgeHost: strongSelf.colibriHost, transport: transport, currentState: strongSelf.currentOfferState) else { - return - } - strongSelf.currentOfferState = offer.state - - strongSelf.memberCount.set(offer.state.items.filter({ !$0.isRemoved }).count) - - for sdp in offer.sdpList { - strongSelf.context.setOfferSdp(sdp, isPartial: false) - }*/ })) } @@ -1698,8 +1687,6 @@ public final class GroupCallContext { if let offer = conference.offerSdp(sessionId: strongSelf.sessionId, bundleId: localBundleId, bridgeHost: strongSelf.colibriHost, transport: localTransport, currentState: strongSelf.currentOfferState, isAnswer: false) { strongSelf.currentOfferState = offer.state - let queue = strongSelf.queue - strongSelf.memberCount.set(offer.state.items.filter({ !$0.isRemoved }).count) for sdp in offer.sdpList { @@ -1837,6 +1824,14 @@ public final class GroupCallContext { self.context.setIsMuted(self.isMutedValue) } + func setIsMuted(_ isMuted: Bool) { + if self.isMutedValue != isMuted { + self.isMutedValue = isMuted + self.isMuted.set(self.isMutedValue) + self.context.setIsMuted(self.isMutedValue) + } + } + func makeIncomingVideoView(id: String, completion: @escaping (OngoingCallContextPresentationCallVideoView?) -> Void) { self.context.makeIncomingVideoView(withStreamId: id, completion: { view in if let view = view { @@ -1929,6 +1924,12 @@ public final class GroupCallContext { } } + public func setIsMuted(_ isMuted: Bool) { + self.impl.with { impl in + impl.setIsMuted(isMuted) + } + } + public func makeIncomingVideoView(id: String, completion: @escaping (OngoingCallContextPresentationCallVideoView?) -> Void) { self.impl.with { impl in impl.makeIncomingVideoView(id: id, completion: completion) @@ -1936,3 +1937,527 @@ public final class GroupCallContext { } } +private struct ParsedJoinPayload { + var payload: String + var audioSsrc: UInt32 +} + +private func parseSdpIntoJoinPayload(sdp: String) -> ParsedJoinPayload? { + let lines = sdp.components(separatedBy: "\n") + + var videoLines: [String] = [] + var audioLines: [String] = [] + var isAudioLine = false + var isVideoLine = false + for line in lines { + if line.hasPrefix("m=audio") { + isAudioLine = true + isVideoLine = false + } else if line.hasPrefix("m=video") { + isVideoLine = true + isAudioLine = false + } + + if isAudioLine { + audioLines.append(line) + } else if isVideoLine { + videoLines.append(line) + } + } + + func getLines(prefix: String) -> [String] { + var result: [String] = [] + for line in lines { + if line.hasPrefix(prefix) { + var cleanLine = String(line[line.index(line.startIndex, offsetBy: prefix.count)...]) + if cleanLine.hasSuffix("\r") { + cleanLine.removeLast() + } + result.append(cleanLine) + } + } + return result + } + + func getLines(prefix: String, isAudio: Bool) -> [String] { + var result: [String] = [] + for line in (isAudio ? audioLines : videoLines) { + if line.hasPrefix(prefix) { + var cleanLine = String(line[line.index(line.startIndex, offsetBy: prefix.count)...]) + if cleanLine.hasSuffix("\r") { + cleanLine.removeLast() + } + result.append(cleanLine) + } + } + return result + } + + var audioSources: [Int] = [] + for line in getLines(prefix: "a=ssrc:", isAudio: true) { + let scanner = Scanner(string: line) + if #available(iOS 13.0, *) { + if let ssrc = scanner.scanInt() { + if !audioSources.contains(ssrc) { + audioSources.append(ssrc) + } + } + } + } + + guard let ssrc = audioSources.first else { + return nil + } + + guard let ufrag = getLines(prefix: "a=ice-ufrag:").first else { + return nil + } + guard let pwd = getLines(prefix: "a=ice-pwd:").first else { + return nil + } + + var resultPayload: [String: Any] = [:] + + var fingerprints: [[String: Any]] = [] + for line in getLines(prefix: "a=fingerprint:") { + let components = line.components(separatedBy: " ") + if components.count != 2 { + continue + } + fingerprints.append([ + "hash": components[0], + "fingerprint": components[1], + "setup": "active" + ]) + } + + resultPayload["fingerprints"] = fingerprints + + resultPayload["ufrag"] = ufrag + resultPayload["pwd"] = pwd + + resultPayload["ssrc"] = ssrc + + guard let payloadData = try? JSONSerialization.data(withJSONObject: resultPayload, options: []) else { + return nil + } + guard let payloadString = String(data: payloadData, encoding: .utf8) else { + return nil + } + + return ParsedJoinPayload( + payload: payloadString, + audioSsrc: UInt32(ssrc) + ) +} + +private func parseJoinResponseIntoSdp(sessionId: UInt32, mainStreamAudioSsrc: UInt32, payload: String, isAnswer: Bool, otherSsrcs: [UInt32]) -> String? { + guard let payloadData = payload.data(using: .utf8) else { + return nil + } + guard let jsonPayload = try? JSONSerialization.jsonObject(with: payloadData, options: []) as? [String: Any] else { + return nil + } + + guard let transport = jsonPayload["transport"] as? [String: Any] else { + return nil + } + guard let pwd = transport["pwd"] as? String else { + return nil + } + guard let ufrag = transport["ufrag"] as? String else { + return nil + } + + struct ParsedFingerprint { + var hashValue: String + var fingerprint: String + var setup: String + } + + var fingerprints: [ParsedFingerprint] = [] + guard let fingerprintsValue = transport["fingerprints"] as? [[String: Any]] else { + return nil + } + for fingerprintValue in fingerprintsValue { + guard let hashValue = fingerprintValue["hash"] as? String else { + continue + } + guard let fingerprint = fingerprintValue["fingerprint"] as? String else { + continue + } + guard let setup = fingerprintValue["setup"] as? String else { + continue + } + fingerprints.append(ParsedFingerprint( + hashValue: hashValue, + fingerprint: fingerprint, + setup: setup + )) + } + + struct ParsedCandidate { + var port: String + var `protocol`: String + var network: String + var generation: String + var id: String + var component: String + var foundation: String + var priority: String + var ip: String + var type: String + var tcpType: String? + var relAddr: String? + var relPort: String? + } + + var candidates: [ParsedCandidate] = [] + guard let candidatesValue = transport["candidates"] as? [[String: Any]] else { + return nil + } + for candidateValue in candidatesValue { + guard let port = candidateValue["port"] as? String else { + continue + } + guard let `protocol` = candidateValue["protocol"] as? String else { + continue + } + guard let network = candidateValue["network"] as? String else { + continue + } + guard let generation = candidateValue["generation"] as? String else { + continue + } + guard let id = candidateValue["id"] as? String else { + continue + } + guard let component = candidateValue["component"] as? String else { + continue + } + guard let foundation = candidateValue["foundation"] as? String else { + continue + } + guard let priority = candidateValue["priority"] as? String else { + continue + } + guard let ip = candidateValue["ip"] as? String else { + continue + } + guard let type = candidateValue["type"] as? String else { + continue + } + + let tcpType = candidateValue["tcptype"] as? String + + let relAddr = candidateValue["rel-addr"] as? String + let relPort = candidateValue["rel-port"] as? String + + candidates.append(ParsedCandidate( + port: port, + protocol: `protocol`, + network: network, + generation: generation, + id: id, + component: component, + foundation: foundation, + priority: priority, + ip: ip, + type: type, + tcpType: tcpType, + relAddr: relAddr, + relPort: relPort + )) + } + + struct StreamSpec { + var isMain: Bool + var audioSsrc: Int + var isRemoved: Bool + } + + func createSdp(sessionId: UInt32, bundleStreams: [StreamSpec]) -> String { + var sdp = "" + func appendSdp(_ string: String) { + if !sdp.isEmpty { + sdp.append("\n") + } + sdp.append(string) + } + + appendSdp("v=0") + appendSdp("o=- \(sessionId) 2 IN IP4 0.0.0.0") + appendSdp("s=-") + appendSdp("t=0 0") + + var bundleString = "a=group:BUNDLE" + for stream in bundleStreams { + bundleString.append(" ") + let audioMid: String + if stream.isMain { + audioMid = "0" + } else { + audioMid = "audio\(stream.audioSsrc)" + } + bundleString.append("\(audioMid)") + } + appendSdp(bundleString) + + appendSdp("a=ice-lite") + + for stream in bundleStreams { + let audioMid: String + if stream.isMain { + audioMid = "0" + } else { + audioMid = "audio\(stream.audioSsrc)" + } + + appendSdp("m=audio \(stream.isMain ? "1" : "0") RTP/SAVPF 111 126") + if stream.isMain { + appendSdp("c=IN IP4 0.0.0.0") + } + appendSdp("a=mid:\(audioMid)") + if stream.isRemoved { + appendSdp("a=inactive") + } else { + if stream.isMain { + appendSdp("a=ice-ufrag:\(ufrag)") + appendSdp("a=ice-pwd:\(pwd)") + + for fingerprint in fingerprints { + appendSdp("a=fingerprint:\(fingerprint.hashValue) \(fingerprint.fingerprint)") + appendSdp("a=setup:passive") + } + + for candidate in candidates { + var candidateString = "a=candidate:" + candidateString.append("\(candidate.foundation) ") + candidateString.append("\(candidate.component) ") + var protocolValue = candidate.protocol + if protocolValue == "ssltcp" { + protocolValue = "tcp" + } + candidateString.append("\(protocolValue) ") + candidateString.append("\(candidate.priority) ") + + let ip = candidate.ip + candidateString.append("\(ip) ") + candidateString.append("\(candidate.port) ") + + candidateString.append("typ \(candidate.type) ") + + switch candidate.type { + case "srflx", "prflx", "relay": + if let relAddr = candidate.relAddr, let relPort = candidate.relPort { + candidateString.append("raddr \(relAddr) rport \(relPort) ") + } + break + default: + break + } + + if protocolValue == "tcp" { + guard let tcpType = candidate.tcpType else { + continue + } + candidateString.append("tcptype \(tcpType) ") + } + + candidateString.append("generation \(candidate.generation)") + + appendSdp(candidateString) + } + } + + appendSdp("a=rtpmap:111 opus/48000/2") + appendSdp("a=rtpmap:126 telephone-event/8000") + appendSdp("a=fmtp:111 minptime=10; useinbandfec=1; usedtx=1") + appendSdp("a=rtcp:1 IN IP4 0.0.0.0") + appendSdp("a=rtcp-mux") + appendSdp("a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level") + appendSdp("a=extmap:3 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time") + appendSdp("a=extmap:5 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01") + appendSdp("a=rtcp-fb:111 transport-cc") + + if isAnswer { + appendSdp("a=recvonly") + } else { + if stream.isMain { + appendSdp("a=sendrecv") + } else { + appendSdp("a=sendonly") + appendSdp("a=bundle-only") + } + + appendSdp("a=ssrc-group:FID \(stream.audioSsrc)") + appendSdp("a=ssrc:\(stream.audioSsrc) cname:stream\(stream.audioSsrc)") + appendSdp("a=ssrc:\(stream.audioSsrc) msid:stream\(stream.audioSsrc) audio\(stream.audioSsrc)") + appendSdp("a=ssrc:\(stream.audioSsrc) mslabel:audio\(stream.audioSsrc)") + appendSdp("a=ssrc:\(stream.audioSsrc) label:audio\(stream.audioSsrc)") + } + } + } + + appendSdp("") + + return sdp + } + + var bundleStreams: [StreamSpec] = [] + bundleStreams.append(StreamSpec( + isMain: true, + audioSsrc: Int(mainStreamAudioSsrc), + isRemoved: false + )) + + for ssrc in otherSsrcs { + bundleStreams.append(StreamSpec( + isMain: false, + audioSsrc: Int(ssrc), + isRemoved: false + )) + } + + /*var bundleStreams: [StreamSpec] = [] + if let currentState = currentState { + for item in currentState.items { + let isRemoved = !streams.contains(where: { $0.audioSsrc == item.audioSsrc }) + bundleStreams.append(StreamSpec( + isMain: item.audioSsrc == mainStreamAudioSsrc, + audioSsrc: item.audioSsrc, + videoSsrc: item.videoSsrc, + isRemoved: isRemoved + )) + } + } + + for stream in streams { + if bundleStreams.contains(where: { $0.audioSsrc == stream.audioSsrc }) { + continue + } + bundleStreams.append(stream) + }*/ + + return createSdp(sessionId: sessionId, bundleStreams: bundleStreams) +} + +public final class OngoingGroupCallContext { + private final class Impl { + let queue: Queue + let context: GroupCallThreadLocalContext + + let sessionId = UInt32.random(in: 0 ..< UInt32(Int32.max)) + var mainStreamAudioSsrc: UInt32? + var initialAnswerPayload: String? + var otherSsrcs: [UInt32] = [] + + let joinPayload = Promise() + + init(queue: Queue) { + self.queue = queue + + self.context = GroupCallThreadLocalContext(queue: ContextQueueImpl(queue: queue), relaySdpAnswer: { _ in + }, incomingVideoStreamListUpdated: { _ in + }, videoCapturer: nil) + + let queue = self.queue + self.context.emitOffer(adjustSdp: { sdp in + return sdp + }, completion: { [weak self] offerSdp in + queue.async { + guard let strongSelf = self else { + return + } + if let payload = parseSdpIntoJoinPayload(sdp: offerSdp) { + strongSelf.mainStreamAudioSsrc = payload.audioSsrc + strongSelf.joinPayload.set(.single(payload.payload)) + } + } + }) + } + + func setJoinResponse(payload: String, ssrcs: [Int32]) { + guard let mainStreamAudioSsrc = self.mainStreamAudioSsrc else { + return + } + if let sdp = parseJoinResponseIntoSdp(sessionId: self.sessionId, mainStreamAudioSsrc: mainStreamAudioSsrc, payload: payload, isAnswer: true, otherSsrcs: []) { + self.initialAnswerPayload = payload + self.context.setOfferSdp(sdp, isPartial: true) + self.addSsrcs(ssrcs: ssrcs) + } + } + + func addSsrcs(ssrcs: [Int32]) { + if ssrcs.isEmpty { + return + } + guard let mainStreamAudioSsrc = self.mainStreamAudioSsrc else { + return + } + guard let initialAnswerPayload = self.initialAnswerPayload else { + return + } + let mappedSsrcs = ssrcs.map(UInt32.init(bitPattern:)) + var otherSsrcs = self.otherSsrcs + for ssrc in mappedSsrcs { + if ssrc == mainStreamAudioSsrc { + continue + } + if !otherSsrcs.contains(ssrc) { + otherSsrcs.append(ssrc) + } + } + if self.otherSsrcs != otherSsrcs { + self.otherSsrcs = otherSsrcs + + if let sdp = parseJoinResponseIntoSdp(sessionId: self.sessionId, mainStreamAudioSsrc: mainStreamAudioSsrc, payload: initialAnswerPayload, isAnswer: false, otherSsrcs: self.otherSsrcs) { + self.context.setOfferSdp(sdp, isPartial: false) + } + } + } + + func setIsMuted(_ isMuted: Bool) { + } + } + + private let queue = Queue() + private let impl: QueueLocalObject + + public var joinPayload: Signal { + return Signal { subscriber in + let disposable = MetaDisposable() + self.impl.with { impl in + disposable.set(impl.joinPayload.get().start(next: { value in + subscriber.putNext(value) + })) + } + return disposable + } + } + + public init() { + let queue = self.queue + self.impl = QueueLocalObject(queue: queue, generate: { + return Impl(queue: queue) + }) + } + + public func setIsMuted(_ isMuted: Bool) { + self.impl.with { impl in + impl.setIsMuted(isMuted) + } + } + + public func setJoinResponse(payload: String, ssrcs: [Int32]) { + self.impl.with { impl in + impl.setJoinResponse(payload: payload, ssrcs: ssrcs) + } + } + + public func addSsrcs(ssrcs: [Int32]) { + self.impl.with { impl in + impl.addSsrcs(ssrcs: ssrcs) + } + } +}