Merge branch 'beta'

This commit is contained in:
Isaac 2025-04-14 21:16:36 +04:00
commit 77bfc86c6f
24 changed files with 458 additions and 7064 deletions

View File

@ -389,7 +389,10 @@ public extension GroupCallParticipantsContext.Participant {
guard let videoDescription = self.videoDescription else { guard let videoDescription = self.videoDescription else {
return nil return nil
} }
return PresentationGroupCallRequestedVideo(audioSsrc: audioSsrc, peerId: self.peer.id.id._internalGetInt64Value(), endpointId: videoDescription.endpointId, ssrcGroups: videoDescription.ssrcGroups.map { group in guard let peer = self.peer else {
return nil
}
return PresentationGroupCallRequestedVideo(audioSsrc: audioSsrc, peerId: peer.id.id._internalGetInt64Value(), endpointId: videoDescription.endpointId, ssrcGroups: videoDescription.ssrcGroups.map { group in
PresentationGroupCallRequestedVideo.SsrcGroup(semantics: group.semantics, ssrcs: group.ssrcs) PresentationGroupCallRequestedVideo.SsrcGroup(semantics: group.semantics, ssrcs: group.ssrcs)
}, minQuality: minQuality, maxQuality: maxQuality) }, minQuality: minQuality, maxQuality: maxQuality)
} }
@ -401,7 +404,10 @@ public extension GroupCallParticipantsContext.Participant {
guard let presentationDescription = self.presentationDescription else { guard let presentationDescription = self.presentationDescription else {
return nil return nil
} }
return PresentationGroupCallRequestedVideo(audioSsrc: audioSsrc, peerId: self.peer.id.id._internalGetInt64Value(), endpointId: presentationDescription.endpointId, ssrcGroups: presentationDescription.ssrcGroups.map { group in guard let peer = self.peer else {
return nil
}
return PresentationGroupCallRequestedVideo(audioSsrc: audioSsrc, peerId: peer.id.id._internalGetInt64Value(), endpointId: presentationDescription.endpointId, ssrcGroups: presentationDescription.ssrcGroups.map { group in
PresentationGroupCallRequestedVideo.SsrcGroup(semantics: group.semantics, ssrcs: group.ssrcs) PresentationGroupCallRequestedVideo.SsrcGroup(semantics: group.semantics, ssrcs: group.ssrcs)
}, minQuality: minQuality, maxQuality: maxQuality) }, minQuality: minQuality, maxQuality: maxQuality)
} }

View File

@ -57,7 +57,8 @@ public final class AccountGroupCallContextImpl: AccountGroupCallContext {
id: call.id, id: call.id,
reference: .id(id: call.id, accessHash: call.accessHash), reference: .id(id: call.id, accessHash: call.accessHash),
state: state, state: state,
previousServiceState: nil previousServiceState: nil,
e2eContext: nil
) )
self.participantsContext = context self.participantsContext = context

View File

@ -425,8 +425,8 @@ public class CallStatusBarNodeImpl: CallStatusBarNode {
if let members = currentMembers { if let members = currentMembers {
var speakingPeers: [Peer] = [] var speakingPeers: [Peer] = []
for member in members.participants { for member in members.participants {
if members.speakingParticipants.contains(member.peer.id) { if let memberPeer = member.peer, members.speakingParticipants.contains(memberPeer.id) {
speakingPeers.append(member.peer) speakingPeers.append(memberPeer._asPeer())
} }
} }
speakingPeer = speakingPeers.first speakingPeer = speakingPeers.first

View File

@ -395,7 +395,7 @@ public final class GroupCallNavigationAccessoryPanel: ASDisplayNode {
if data.info.isStream { if data.info.isStream {
self.avatarsContent = self.avatarsContext.update(peers: [], animated: false) self.avatarsContent = self.avatarsContext.update(peers: [], animated: false)
} else { } else {
self.avatarsContent = self.avatarsContext.update(peers: data.topParticipants.map { EnginePeer($0.peer) }, animated: false) self.avatarsContent = self.avatarsContext.update(peers: data.topParticipants.compactMap { $0.peer }, animated: false)
if let imageDisposable = self.imageDisposable { if let imageDisposable = self.imageDisposable {
self.imageDisposable = nil self.imageDisposable = nil
@ -430,7 +430,7 @@ public final class GroupCallNavigationAccessoryPanel: ASDisplayNode {
if let info = summaryState.info, info.isStream { if let info = summaryState.info, info.isStream {
strongSelf.avatarsContent = strongSelf.avatarsContext.update(peers: [], animated: false) strongSelf.avatarsContent = strongSelf.avatarsContext.update(peers: [], animated: false)
} else { } else {
strongSelf.avatarsContent = strongSelf.avatarsContext.update(peers: summaryState.topParticipants.map { EnginePeer($0.peer) }, animated: false) strongSelf.avatarsContent = strongSelf.avatarsContext.update(peers: summaryState.topParticipants.compactMap { $0.peer }, animated: false)
} }
if let (size, leftInset, rightInset, isHidden) = strongSelf.validLayout { if let (size, leftInset, rightInset, isHidden) = strongSelf.validLayout {
@ -513,7 +513,7 @@ public final class GroupCallNavigationAccessoryPanel: ASDisplayNode {
if data.info.isStream { if data.info.isStream {
self.avatarsContent = self.avatarsContext.update(peers: [], animated: false) self.avatarsContent = self.avatarsContext.update(peers: [], animated: false)
} else { } else {
self.avatarsContent = self.avatarsContext.update(peers: data.topParticipants.map { EnginePeer($0.peer) }, animated: false) self.avatarsContent = self.avatarsContext.update(peers: data.topParticipants.compactMap { $0.peer }, animated: false)
} }
updateAudioLevels = true updateAudioLevels = true

View File

@ -908,7 +908,7 @@ public final class PresentationCallImpl: PresentationCall {
var found = false var found = false
if let members { if let members {
for participant in members.participants { for participant in members.participants {
if participant.peer.id == waitForRemotePeerId { if participant.id == .peer(waitForRemotePeerId) {
found = true found = true
break break
} }
@ -921,7 +921,7 @@ public final class PresentationCallImpl: PresentationCall {
if waitForLocalVideo { if waitForLocalVideo {
if let members { if let members {
for participant in members.participants { for participant in members.participants {
if participant.peer.id == state.myPeerId { if participant.id == .peer(state.myPeerId) {
if participant.videoDescription == nil { if participant.videoDescription == nil {
return false return false
} }
@ -932,7 +932,7 @@ public final class PresentationCallImpl: PresentationCall {
if let waitForRemoteVideo { if let waitForRemoteVideo {
if let members { if let members {
for participant in members.participants { for participant in members.participants {
if participant.peer.id == waitForRemoteVideo { if participant.id == .peer(waitForRemoteVideo) {
if participant.videoDescription == nil { if participant.videoDescription == nil {
return false return false
} }

View File

@ -331,9 +331,13 @@ private final class ConferenceCallE2EContextStateImpl: ConferenceCallE2EContextS
func getEmojiState() -> Data? { func getEmojiState() -> Data? {
return self.call.emojiState() return self.call.emojiState()
} }
func getParticipants() -> [ConferenceCallE2EContext.BlockchainParticipant] {
return self.call.participants().map { ConferenceCallE2EContext.BlockchainParticipant(userId: $0.userId, internalId: $0.internalId) }
}
func getParticipantIds() -> [Int64] { func getParticipantIds() -> [Int64] {
return self.call.participantIds().compactMap { $0.int64Value } return self.call.participants().map { $0.userId }
} }
func applyBlock(block: Data) { func applyBlock(block: Data) {
@ -1246,7 +1250,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
} }
if let sourceContext = sourceContext, let initialState = sourceContext.immediateState { if let sourceContext = sourceContext, let initialState = sourceContext.immediateState {
let temporaryParticipantsContext = self.accountContext.engine.calls.groupCall(peerId: self.peerId, myPeerId: myPeerId, id: sourceContext.id, reference: sourceContext.reference, state: initialState, previousServiceState: sourceContext.serviceState) let temporaryParticipantsContext = self.accountContext.engine.calls.groupCall(peerId: self.peerId, myPeerId: myPeerId, id: sourceContext.id, reference: sourceContext.reference, state: initialState, previousServiceState: sourceContext.serviceState, e2eContext: self.e2eContext)
self.temporaryParticipantsContext = temporaryParticipantsContext self.temporaryParticipantsContext = temporaryParticipantsContext
self.participantsContextStateDisposable.set((combineLatest(queue: .mainQueue(), self.participantsContextStateDisposable.set((combineLatest(queue: .mainQueue(),
myPeerData, myPeerData,
@ -1274,14 +1278,14 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
if oldMyPeerId != myPeerId { if oldMyPeerId != myPeerId {
for i in 0 ..< participants.count { for i in 0 ..< participants.count {
if participants[i].peer.id == oldMyPeerId { if participants[i].id == .peer(oldMyPeerId) {
participants.remove(at: i) participants.remove(at: i)
break break
} }
} }
} }
if !participants.contains(where: { $0.peer.id == myPeerId }) { if !participants.contains(where: { $0.id == .peer(myPeerId) }) {
if let (myPeer, aboutText) = myPeerData { if let (myPeer, aboutText) = myPeerData {
let about: String? let about: String?
if let aboutText = aboutText { if let aboutText = aboutText {
@ -1290,7 +1294,8 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
about = " " about = " "
} }
participants.append(GroupCallParticipantsContext.Participant( participants.append(GroupCallParticipantsContext.Participant(
peer: myPeer._asPeer(), id: .peer(myPeer.id),
peer: myPeer,
ssrc: nil, ssrc: nil,
videoDescription: nil, videoDescription: nil,
presentationDescription: nil, presentationDescription: nil,
@ -1315,7 +1320,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
topParticipants.append(participant) topParticipants.append(participant)
} }
if let index = updatedInvitedPeers.firstIndex(where: { $0.id == participant.peer.id }) { if let index = updatedInvitedPeers.firstIndex(where: { participant.id == .peer($0.id) }) {
updatedInvitedPeers.remove(at: index) updatedInvitedPeers.remove(at: index)
didUpdateInvitedPeers = true didUpdateInvitedPeers = true
} }
@ -1370,7 +1375,8 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
about = " " about = " "
} }
participants.append(GroupCallParticipantsContext.Participant( participants.append(GroupCallParticipantsContext.Participant(
peer: myPeer._asPeer(), id: .peer(myPeer.id),
peer: myPeer,
ssrc: nil, ssrc: nil,
videoDescription: nil, videoDescription: nil,
presentationDescription: nil, presentationDescription: nil,
@ -1495,7 +1501,8 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
isStream: callInfo.isStream, isStream: callInfo.isStream,
version: 0 version: 0
), ),
previousServiceState: nil previousServiceState: nil,
e2eContext: self.e2eContext
) )
self.temporaryParticipantsContext = nil self.temporaryParticipantsContext = nil
self.participantsContext = participantsContext self.participantsContext = participantsContext
@ -1555,7 +1562,8 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
about = " " about = " "
} }
participants.append(GroupCallParticipantsContext.Participant( participants.append(GroupCallParticipantsContext.Participant(
peer: myPeer._asPeer(), id: .peer(myPeer.id),
peer: myPeer,
ssrc: nil, ssrc: nil,
videoDescription: nil, videoDescription: nil,
presentationDescription: nil, presentationDescription: nil,
@ -1967,11 +1975,11 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
self.ssrcMapping.removeAll() self.ssrcMapping.removeAll()
for participant in joinCallResult.state.participants { for participant in joinCallResult.state.participants {
if let ssrc = participant.ssrc { if let ssrc = participant.ssrc, let participantPeer = participant.peer {
self.ssrcMapping[ssrc] = SsrcMapping(peerId: participant.peer.id, isPresentation: false) self.ssrcMapping[ssrc] = SsrcMapping(peerId: participantPeer.id, isPresentation: false)
} }
if let presentationSsrc = participant.presentationDescription?.audioSsrc { if let presentationSsrc = participant.presentationDescription?.audioSsrc, let participantPeer = participant.peer {
self.ssrcMapping[presentationSsrc] = SsrcMapping(peerId: participant.peer.id, isPresentation: true) self.ssrcMapping[presentationSsrc] = SsrcMapping(peerId: participantPeer.id, isPresentation: true)
} }
} }
@ -2268,7 +2276,8 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
id: callInfo.id, id: callInfo.id,
reference: reference, reference: reference,
state: initialState, state: initialState,
previousServiceState: serviceState previousServiceState: serviceState,
e2eContext: self.e2eContext
) )
self.temporaryParticipantsContext = nil self.temporaryParticipantsContext = nil
self.participantsContext = participantsContext self.participantsContext = participantsContext
@ -2367,14 +2376,14 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
if let (ignorePeerId, ignoreSsrc) = self.ignorePreviousJoinAsPeerId { if let (ignorePeerId, ignoreSsrc) = self.ignorePreviousJoinAsPeerId {
for i in 0 ..< participants.count { for i in 0 ..< participants.count {
if participants[i].peer.id == ignorePeerId && participants[i].ssrc == ignoreSsrc { if participants[i].id == .peer(ignorePeerId) && participants[i].ssrc == ignoreSsrc {
participants.remove(at: i) participants.remove(at: i)
break break
} }
} }
} }
if !participants.contains(where: { $0.peer.id == myPeerId }) && !self.leaving { if !participants.contains(where: { $0.id == .peer(myPeerId) }) && !self.leaving {
if let (myPeer, cachedData) = myPeerAndCachedData { if let (myPeer, cachedData) = myPeerAndCachedData {
let about: String? let about: String?
if let cachedData = cachedData as? CachedUserData { if let cachedData = cachedData as? CachedUserData {
@ -2386,7 +2395,8 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
} }
participants.append(GroupCallParticipantsContext.Participant( participants.append(GroupCallParticipantsContext.Participant(
peer: myPeer, id: .peer(myPeer.id),
peer: EnginePeer(myPeer),
ssrc: nil, ssrc: nil,
videoDescription: nil, videoDescription: nil,
presentationDescription: nil, presentationDescription: nil,
@ -2415,13 +2425,17 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
} }
if let ssrc = participant.ssrc { if let ssrc = participant.ssrc {
self.ssrcMapping[ssrc] = SsrcMapping(peerId: participant.peer.id, isPresentation: false) if let participantPeer = participant.peer {
self.ssrcMapping[ssrc] = SsrcMapping(peerId: participantPeer.id, isPresentation: false)
}
} }
if let presentationSsrc = participant.presentationDescription?.audioSsrc { if let presentationSsrc = participant.presentationDescription?.audioSsrc {
self.ssrcMapping[presentationSsrc] = SsrcMapping(peerId: participant.peer.id, isPresentation: true) if let participantPeer = participant.peer {
self.ssrcMapping[presentationSsrc] = SsrcMapping(peerId: participantPeer.id, isPresentation: true)
}
} }
if participant.peer.id == self.joinAsPeerId { if participant.id == .peer(self.joinAsPeerId) {
if let (myPeer, cachedData) = myPeerAndCachedData { if let (myPeer, cachedData) = myPeerAndCachedData {
let about: String? let about: String?
if let cachedData = cachedData as? CachedUserData { if let cachedData = cachedData as? CachedUserData {
@ -2431,7 +2445,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
} else { } else {
about = " " about = " "
} }
participant.peer = myPeer participant.peer = EnginePeer(myPeer)
participant.about = about participant.about = about
} }
@ -2531,7 +2545,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
} }
} }
if let index = updatedInvitedPeers.firstIndex(where: { $0.id == participant.peer.id }) { if let index = updatedInvitedPeers.firstIndex(where: { participant.id == .peer($0.id) }) {
updatedInvitedPeers.remove(at: index) updatedInvitedPeers.remove(at: index)
didUpdateInvitedPeers = true didUpdateInvitedPeers = true
} }
@ -2646,24 +2660,28 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
if remainingSsrcs.contains(audioSsrc) { if remainingSsrcs.contains(audioSsrc) {
remainingSsrcs.remove(audioSsrc) remainingSsrcs.remove(audioSsrc)
result.append(OngoingGroupCallContext.MediaChannelDescription( if let participantPeer = participant.peer {
kind: .audio, result.append(OngoingGroupCallContext.MediaChannelDescription(
peerId: participant.peer.id.id._internalGetInt64Value(), kind: .audio,
audioSsrc: audioSsrc, peerId: participantPeer.id.id._internalGetInt64Value(),
videoDescription: nil audioSsrc: audioSsrc,
)) videoDescription: nil
))
}
} }
if let screencastSsrc = participant.presentationDescription?.audioSsrc { if let screencastSsrc = participant.presentationDescription?.audioSsrc {
if remainingSsrcs.contains(screencastSsrc) { if remainingSsrcs.contains(screencastSsrc) {
remainingSsrcs.remove(screencastSsrc) remainingSsrcs.remove(screencastSsrc)
result.append(OngoingGroupCallContext.MediaChannelDescription( if let participantPeer = participant.peer {
kind: .audio, result.append(OngoingGroupCallContext.MediaChannelDescription(
peerId: participant.peer.id.id._internalGetInt64Value(), kind: .audio,
audioSsrc: screencastSsrc, peerId: participantPeer.id.id._internalGetInt64Value(),
videoDescription: nil audioSsrc: screencastSsrc,
)) videoDescription: nil
))
}
} }
} }
} }
@ -2849,7 +2867,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
if let participantsContext = self.participantsContext, let immediateState = participantsContext.immediateState { if let participantsContext = self.participantsContext, let immediateState = participantsContext.immediateState {
for participant in immediateState.participants { for participant in immediateState.participants {
if participant.peer.id == previousPeerId { if participant.id == .peer(previousPeerId) {
self.temporaryJoinTimestamp = participant.joinTimestamp self.temporaryJoinTimestamp = participant.joinTimestamp
self.temporaryActivityTimestamp = participant.activityTimestamp self.temporaryActivityTimestamp = participant.activityTimestamp
self.temporaryActivityRank = participant.activityRank self.temporaryActivityRank = participant.activityRank
@ -3008,7 +3026,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
return return
} }
for participant in membersValue.participants { for participant in membersValue.participants {
if participant.peer.id == self.joinAsPeerId { if participant.id == .peer(self.joinAsPeerId) {
if participant.hasRaiseHand { if participant.hasRaiseHand {
return return
} }
@ -3024,7 +3042,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
return return
} }
for participant in membersValue.participants { for participant in membersValue.participants {
if participant.peer.id == self.joinAsPeerId { if participant.id == .peer(self.joinAsPeerId) {
if !participant.hasRaiseHand { if !participant.hasRaiseHand {
return return
} }

View File

@ -143,7 +143,9 @@ final class VideoChatParticipantThumbnailComponent: Component {
gesture.cancel() gesture.cancel()
return return
} }
component.contextAction?(EnginePeer(component.participant.peer), self.extractedContainerView, gesture) if let participantPeer = component.participant.peer {
component.contextAction?(participantPeer, self.extractedContainerView, gesture)
}
} }
} }
@ -213,10 +215,10 @@ final class VideoChatParticipantThumbnailComponent: Component {
let avatarFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - avatarSize.width) * 0.5), y: 7.0), size: avatarSize) let avatarFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - avatarSize.width) * 0.5), y: 7.0), size: avatarSize)
transition.setFrame(view: avatarNode.view, frame: avatarFrame) transition.setFrame(view: avatarNode.view, frame: avatarFrame)
avatarNode.updateSize(size: avatarSize) avatarNode.updateSize(size: avatarSize)
if component.participant.peer.smallProfileImage != nil { if component.participant.peer?.smallProfileImage != nil {
avatarNode.setPeerV2(context: component.call.accountContext, theme: component.theme, peer: EnginePeer(component.participant.peer), displayDimensions: avatarSize) avatarNode.setPeerV2(context: component.call.accountContext, theme: component.theme, peer: component.participant.peer, displayDimensions: avatarSize)
} else { } else {
avatarNode.setPeer(context: component.call.accountContext, theme: component.theme, peer: EnginePeer(component.participant.peer), displayDimensions: avatarSize) avatarNode.setPeer(context: component.call.accountContext, theme: component.theme, peer: component.participant.peer, displayDimensions: avatarSize)
} }
let muteStatusSize = self.muteStatus.update( let muteStatusSize = self.muteStatus.update(
@ -241,7 +243,7 @@ final class VideoChatParticipantThumbnailComponent: Component {
let titleSize = self.title.update( let titleSize = self.title.update(
transition: .immediate, transition: .immediate,
component: AnyComponent(MultilineTextComponent( component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(string: EnginePeer(component.participant.peer).compactDisplayTitle, font: Font.semibold(13.0), textColor: .white)) text: .plain(NSAttributedString(string: component.participant.peer?.compactDisplayTitle ?? "User \(component.participant.id)", font: Font.semibold(13.0), textColor: .white))
)), )),
environment: {}, environment: {},
containerSize: CGSize(width: availableSize.width - 6.0 * 2.0 - 12.0, height: 100.0) containerSize: CGSize(width: availableSize.width - 6.0 * 2.0 - 12.0, height: 100.0)
@ -436,10 +438,10 @@ final class VideoChatParticipantThumbnailComponent: Component {
final class VideoChatExpandedParticipantThumbnailsComponent: Component { final class VideoChatExpandedParticipantThumbnailsComponent: Component {
final class Participant: Equatable { final class Participant: Equatable {
struct Key: Hashable { struct Key: Hashable {
var id: EnginePeer.Id var id: GroupCallParticipantsContext.Participant.Id
var isPresentation: Bool var isPresentation: Bool
init(id: EnginePeer.Id, isPresentation: Bool) { init(id: GroupCallParticipantsContext.Participant.Id, isPresentation: Bool) {
self.id = id self.id = id
self.isPresentation = isPresentation self.isPresentation = isPresentation
} }
@ -449,7 +451,7 @@ final class VideoChatExpandedParticipantThumbnailsComponent: Component {
let isPresentation: Bool let isPresentation: Bool
var key: Key { var key: Key {
return Key(id: self.participant.peer.id, isPresentation: self.isPresentation) return Key(id: self.participant.id, isPresentation: self.isPresentation)
} }
init( init(
@ -657,6 +659,12 @@ final class VideoChatExpandedParticipantThumbnailsComponent: Component {
let itemFrame = itemLayout.frame(at: i) let itemFrame = itemLayout.frame(at: i)
let participantKey = participant.key let participantKey = participant.key
var isSpeaking = false
if let participantPeer = participant.participant.peer {
isSpeaking = component.speakingParticipants.contains(participantPeer.id)
}
let _ = itemView.view.update( let _ = itemView.view.update(
transition: itemTransition, transition: itemTransition,
component: AnyComponent(VideoChatParticipantThumbnailComponent( component: AnyComponent(VideoChatParticipantThumbnailComponent(
@ -665,7 +673,7 @@ final class VideoChatExpandedParticipantThumbnailsComponent: Component {
participant: participant.participant, participant: participant.participant,
isPresentation: participant.isPresentation, isPresentation: participant.isPresentation,
isSelected: component.selectedParticipant == participant.key, isSelected: component.selectedParticipant == participant.key,
isSpeaking: component.speakingParticipants.contains(participant.participant.peer.id), isSpeaking: isSpeaking,
displayVideo: component.displayVideo, displayVideo: component.displayVideo,
interfaceOrientation: component.interfaceOrientation, interfaceOrientation: component.interfaceOrientation,
action: { [weak self] in action: { [weak self] in

View File

@ -133,14 +133,14 @@ private final class BlobView: UIView {
final class VideoChatParticipantAvatarComponent: Component { final class VideoChatParticipantAvatarComponent: Component {
let call: VideoChatCall let call: VideoChatCall
let peer: EnginePeer let peer: EnginePeer?
let myPeerId: EnginePeer.Id let myPeerId: EnginePeer.Id
let isSpeaking: Bool let isSpeaking: Bool
let theme: PresentationTheme let theme: PresentationTheme
init( init(
call: VideoChatCall, call: VideoChatCall,
peer: EnginePeer, peer: EnginePeer?,
myPeerId: EnginePeer.Id, myPeerId: EnginePeer.Id,
isSpeaking: Bool, isSpeaking: Bool,
theme: PresentationTheme theme: PresentationTheme
@ -267,7 +267,7 @@ final class VideoChatParticipantAvatarComponent: Component {
tintTransition.setTintColor(layer: blobView.blobsLayer, color: component.isSpeaking ? UIColor(rgb: 0x33C758) : component.theme.list.itemAccentColor) tintTransition.setTintColor(layer: blobView.blobsLayer, color: component.isSpeaking ? UIColor(rgb: 0x33C758) : component.theme.list.itemAccentColor)
} }
if component.peer.smallProfileImage != nil { if component.peer?.smallProfileImage != nil {
avatarNode.setPeerV2( avatarNode.setPeerV2(
context: component.call.accountContext, context: component.call.accountContext,
theme: component.theme, theme: component.theme,
@ -296,7 +296,7 @@ final class VideoChatParticipantAvatarComponent: Component {
var isSpeaking: Bool var isSpeaking: Bool
} }
let peerId = component.peer.id let peerId = component.peer?.id
let levelSignal: Signal<Level?, NoError> let levelSignal: Signal<Level?, NoError>
if peerId == component.myPeerId { if peerId == component.myPeerId {
levelSignal = component.call.myAudioLevelAndSpeaking levelSignal = component.call.myAudioLevelAndSpeaking

View File

@ -245,7 +245,9 @@ final class VideoChatParticipantVideoComponent: Component {
gesture.cancel() gesture.cancel()
return return
} }
component.contextAction?(EnginePeer(component.participant.peer), self.extractedContainerView, gesture) if let participantPeer = component.participant.peer {
component.contextAction?(participantPeer, self.extractedContainerView, gesture)
}
} }
} }
@ -316,12 +318,12 @@ final class VideoChatParticipantVideoComponent: Component {
let controlsAlpha: CGFloat = component.isUIHidden ? 0.0 : 1.0 let controlsAlpha: CGFloat = component.isUIHidden ? 0.0 : 1.0
if previousComponent == nil { if previousComponent == nil {
let colors = calculateAvatarColors(context: component.call.accountContext, explicitColorIndex: nil, peerId: component.participant.peer.id, nameColor: component.participant.peer.nameColor, icon: .none, theme: component.theme) let colors = calculateAvatarColors(context: component.call.accountContext, explicitColorIndex: nil, peerId: component.participant.peer?.id, nameColor: component.participant.peer?.nameColor, icon: .none, theme: component.theme)
self.backgroundGradientView.image = generateGradientImage(size: CGSize(width: 8.0, height: 32.0), colors: colors.reversed(), locations: [0.0, 1.0], direction: .vertical) self.backgroundGradientView.image = generateGradientImage(size: CGSize(width: 8.0, height: 32.0), colors: colors.reversed(), locations: [0.0, 1.0], direction: .vertical)
} }
if let smallProfileImage = component.participant.peer.smallProfileImage { if let smallProfileImage = component.participant.peer?.smallProfileImage {
let blurredAvatarView: UIImageView let blurredAvatarView: UIImageView
if let current = self.blurredAvatarView { if let current = self.blurredAvatarView {
blurredAvatarView = current blurredAvatarView = current
@ -338,8 +340,8 @@ final class VideoChatParticipantVideoComponent: Component {
if self.blurredAvatarDisposable == nil { if self.blurredAvatarDisposable == nil {
//TODO:release synchronous //TODO:release synchronous
if let imageCache = component.call.accountContext.imageCache as? DirectMediaImageCache, let peerReference = PeerReference(component.participant.peer) { if let participantPeer = component.participant.peer, let imageCache = component.call.accountContext.imageCache as? DirectMediaImageCache, let peerReference = PeerReference(participantPeer._asPeer()) {
if let result = imageCache.getAvatarImage(peer: peerReference, resource: MediaResourceReference.avatar(peer: peerReference, resource: smallProfileImage.resource), immediateThumbnail: component.participant.peer.profileImageRepresentations.first?.immediateThumbnailData, size: 64, synchronous: false) { if let result = imageCache.getAvatarImage(peer: peerReference, resource: MediaResourceReference.avatar(peer: peerReference, resource: smallProfileImage.resource), immediateThumbnail: participantPeer.profileImageRepresentations.first?.immediateThumbnailData, size: 64, synchronous: false) {
if let image = result.image { if let image = result.image {
blurredAvatarView.image = blurredAvatarImage(image) blurredAvatarView.image = blurredAvatarImage(image)
} }
@ -402,7 +404,7 @@ final class VideoChatParticipantVideoComponent: Component {
let titleSize = self.title.update( let titleSize = self.title.update(
transition: .immediate, transition: .immediate,
component: AnyComponent(MultilineTextComponent( component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(string: component.participant.peer.debugDisplayTitle, font: Font.semibold(16.0), textColor: .white)), text: .plain(NSAttributedString(string: component.participant.peer?.debugDisplayTitle ?? "User \(component.participant.id)", font: Font.semibold(16.0), textColor: .white)),
insets: titleInnerInsets, insets: titleInnerInsets,
textShadowColor: UIColor(white: 0.0, alpha: 0.7), textShadowColor: UIColor(white: 0.0, alpha: 0.7),
textShadowBlur: 8.0 textShadowBlur: 8.0

View File

@ -92,10 +92,10 @@ final class VideoChatParticipantsComponent: Component {
} }
struct VideoParticipantKey: Hashable { struct VideoParticipantKey: Hashable {
var id: EnginePeer.Id var id: GroupCallParticipantsContext.Participant.Id
var isPresentation: Bool var isPresentation: Bool
init(id: EnginePeer.Id, isPresentation: Bool) { init(id: GroupCallParticipantsContext.Participant.Id, isPresentation: Bool) {
self.id = id self.id = id
self.isPresentation = isPresentation self.isPresentation = isPresentation
} }
@ -608,7 +608,7 @@ final class VideoChatParticipantsComponent: Component {
let isPresentation: Bool let isPresentation: Bool
var key: VideoParticipantKey { var key: VideoParticipantKey {
return VideoParticipantKey(id: self.participant.peer.id, isPresentation: self.isPresentation) return VideoParticipantKey(id: self.participant.id, isPresentation: self.isPresentation)
} }
init(participant: GroupCallParticipantsContext.Participant, isPresentation: Bool) { init(participant: GroupCallParticipantsContext.Participant, isPresentation: Bool) {
@ -673,7 +673,7 @@ final class VideoChatParticipantsComponent: Component {
private var expandedThumbnailsView: ComponentView<Empty>? private var expandedThumbnailsView: ComponentView<Empty>?
private var expandedSpeakingToast: ComponentView<Empty>? private var expandedSpeakingToast: ComponentView<Empty>?
private var listItemViews: [EnginePeer.Id: ListItem] = [:] private var listItemViews: [GroupCallParticipantsContext.Participant.Id: ListItem] = [:]
private let listItemViewContainer: UIView private let listItemViewContainer: UIView
private let listItemViewSeparatorContainer: SimpleLayer private let listItemViewSeparatorContainer: SimpleLayer
private let listItemsBackground = ComponentView<Empty>() private let listItemsBackground = ComponentView<Empty>()
@ -959,7 +959,7 @@ final class VideoChatParticipantsComponent: Component {
} }
} }
var visibleParticipants: [EnginePeer.Id] = [] var visibleParticipants: [GroupCallParticipantsContext.Participant.Id] = []
for index in validGridItemIndices { for index in validGridItemIndices {
let videoParticipant = self.gridParticipants[index] let videoParticipant = self.gridParticipants[index]
@ -988,7 +988,7 @@ final class VideoChatParticipantsComponent: Component {
} }
if isItemExpanded || (index >= clippedVisibleGridItemRange.minIndex && index <= clippedVisibleGridItemRange.maxIndex) { if isItemExpanded || (index >= clippedVisibleGridItemRange.minIndex && index <= clippedVisibleGridItemRange.maxIndex) {
visibleParticipants.append(videoParticipant.key.id) visibleParticipants.append(videoParticipant.participant.id)
} }
var suppressItemExpansionCollapseAnimation = false var suppressItemExpansionCollapseAnimation = false
@ -1048,6 +1048,11 @@ final class VideoChatParticipantsComponent: Component {
} else { } else {
itemAlpha = 1.0 itemAlpha = 1.0
} }
var isSpeaking = false
if let participantPeer = videoParticipant.participant.peer {
isSpeaking = component.speakingParticipants.contains(participantPeer.id)
}
let _ = itemView.view.update( let _ = itemView.view.update(
transition: itemTransition, transition: itemTransition,
@ -1056,9 +1061,9 @@ final class VideoChatParticipantsComponent: Component {
strings: component.strings, strings: component.strings,
call: component.call, call: component.call,
participant: videoParticipant.participant, participant: videoParticipant.participant,
isMyPeer: videoParticipant.participant.peer.id == component.participants?.myPeerId, isMyPeer: videoParticipant.participant.peer?.id == component.participants?.myPeerId,
isPresentation: videoParticipant.isPresentation, isPresentation: videoParticipant.isPresentation,
isSpeaking: component.speakingParticipants.contains(videoParticipant.participant.peer.id), isSpeaking: isSpeaking,
maxVideoQuality: component.maxVideoQuality, maxVideoQuality: component.maxVideoQuality,
isExpanded: isItemExpanded, isExpanded: isItemExpanded,
isUIHidden: isItemUIHidden || self.isPinchToZoomActive, isUIHidden: isItemUIHidden || self.isPinchToZoomActive,
@ -1205,23 +1210,23 @@ final class VideoChatParticipantsComponent: Component {
self.gridItemViews.removeValue(forKey: itemId) self.gridItemViews.removeValue(forKey: itemId)
} }
var validListItemIds: [EnginePeer.Id] = [] var validListItemIds: [GroupCallParticipantsContext.Participant.Id] = []
let visibleListItemRange = itemLayout.visibleListItemRange(for: self.scrollView.bounds) let visibleListItemRange = itemLayout.visibleListItemRange(for: self.scrollView.bounds)
let clippedVisibleListItemRange = itemLayout.visibleListItemRange(for: clippedScrollViewBounds) let clippedVisibleListItemRange = itemLayout.visibleListItemRange(for: clippedScrollViewBounds)
if visibleListItemRange.maxIndex >= visibleListItemRange.minIndex { if visibleListItemRange.maxIndex >= visibleListItemRange.minIndex {
for i in visibleListItemRange.minIndex ... visibleListItemRange.maxIndex { for i in visibleListItemRange.minIndex ... visibleListItemRange.maxIndex {
let itemFrame = itemLayout.listItemFrame(at: i) let itemFrame = itemLayout.listItemFrame(at: i)
let participantPeerId: EnginePeer.Id let participantItemId: GroupCallParticipantsContext.Participant.Id
let peerItemComponent: PeerListItemComponent let peerItemComponent: PeerListItemComponent
if i < self.listParticipants.count { if i < self.listParticipants.count {
let participant = self.listParticipants[i] let participant = self.listParticipants[i]
participantPeerId = participant.peer.id participantItemId = participant.id
let subtitle: PeerListItemComponent.Subtitle let subtitle: PeerListItemComponent.Subtitle
if participant.peer.id == component.call.accountContext.account.peerId { if participant.id == .peer(component.call.accountContext.account.peerId) {
subtitle = PeerListItemComponent.Subtitle(text: component.strings.VoiceChat_You, color: .accent) subtitle = PeerListItemComponent.Subtitle(text: component.strings.VoiceChat_You, color: .accent)
} else if component.speakingParticipants.contains(participant.peer.id) { } else if let participantPeer = participant.peer, component.speakingParticipants.contains(participantPeer.id) {
if let volume = participant.volume, volume / 100 != 100 { if let volume = participant.volume, volume / 100 != 100 {
subtitle = PeerListItemComponent.Subtitle(text: component.strings.VoiceChat_StatusSpeakingVolume("\(volume / 100)%").string, color: .constructive) subtitle = PeerListItemComponent.Subtitle(text: component.strings.VoiceChat_StatusSpeakingVolume("\(volume / 100)%").string, color: .constructive)
} else { } else {
@ -1233,10 +1238,14 @@ final class VideoChatParticipantsComponent: Component {
subtitle = PeerListItemComponent.Subtitle(text: component.strings.VoiceChat_StatusListening, color: .neutral) subtitle = PeerListItemComponent.Subtitle(text: component.strings.VoiceChat_StatusListening, color: .neutral)
} }
var isSpeaking = false
if let participantPeer = participant.peer {
isSpeaking = component.speakingParticipants.contains(participantPeer.id)
}
let rightAccessoryComponent: AnyComponent<Empty> = AnyComponent(VideoChatParticipantStatusComponent( let rightAccessoryComponent: AnyComponent<Empty> = AnyComponent(VideoChatParticipantStatusComponent(
muteState: participant.muteState, muteState: participant.muteState,
hasRaiseHand: participant.hasRaiseHand, hasRaiseHand: participant.hasRaiseHand,
isSpeaking: component.speakingParticipants.contains(participant.peer.id), isSpeaking: isSpeaking,
theme: component.theme theme: component.theme
)) ))
@ -1246,15 +1255,15 @@ final class VideoChatParticipantsComponent: Component {
strings: component.strings, strings: component.strings,
style: .generic, style: .generic,
sideInset: 0.0, sideInset: 0.0,
title: EnginePeer(participant.peer).displayTitle(strings: component.strings, displayOrder: .firstLast), title: participant.peer?.displayTitle(strings: component.strings, displayOrder: .firstLast) ?? "User \(participant.id)",
avatarComponent: AnyComponent(VideoChatParticipantAvatarComponent( avatarComponent: AnyComponent(VideoChatParticipantAvatarComponent(
call: component.call, call: component.call,
peer: EnginePeer(participant.peer), peer: participant.peer,
myPeerId: component.participants?.myPeerId ?? component.call.accountContext.account.peerId, myPeerId: component.participants?.myPeerId ?? component.call.accountContext.account.peerId,
isSpeaking: component.speakingParticipants.contains(participant.peer.id), isSpeaking: isSpeaking,
theme: component.theme theme: component.theme
)), )),
peer: EnginePeer(participant.peer), peer: participant.peer,
subtitle: subtitle, subtitle: subtitle,
subtitleAccessory: .none, subtitleAccessory: .none,
presence: nil, presence: nil,
@ -1280,7 +1289,7 @@ final class VideoChatParticipantsComponent: Component {
) )
} else { } else {
let invitedPeer = component.invitedPeers[i - self.listParticipants.count] let invitedPeer = component.invitedPeers[i - self.listParticipants.count]
participantPeerId = invitedPeer.peer.id participantItemId = .peer(invitedPeer.peer.id)
let subtitle: PeerListItemComponent.Subtitle = PeerListItemComponent.Subtitle(text: component.strings.VoiceChat_StatusInvited, color: .neutral) let subtitle: PeerListItemComponent.Subtitle = PeerListItemComponent.Subtitle(text: component.strings.VoiceChat_StatusInvited, color: .neutral)
@ -1328,20 +1337,20 @@ final class VideoChatParticipantsComponent: Component {
) )
} }
validListItemIds.append(participantPeerId) validListItemIds.append(participantItemId)
if i >= clippedVisibleListItemRange.minIndex && i <= clippedVisibleListItemRange.maxIndex { if i >= clippedVisibleListItemRange.minIndex && i <= clippedVisibleListItemRange.maxIndex {
visibleParticipants.append(participantPeerId) visibleParticipants.append(participantItemId)
} }
var itemTransition = transition var itemTransition = transition
let itemView: ListItem let itemView: ListItem
if let current = self.listItemViews[participantPeerId] { if let current = self.listItemViews[participantItemId] {
itemView = current itemView = current
} else { } else {
itemTransition = itemTransition.withAnimation(.none) itemTransition = itemTransition.withAnimation(.none)
itemView = ListItem() itemView = ListItem()
self.listItemViews[participantPeerId] = itemView self.listItemViews[participantItemId] = itemView
} }
let _ = itemView.view.update( let _ = itemView.view.update(
@ -1375,7 +1384,7 @@ final class VideoChatParticipantsComponent: Component {
} }
} }
var removedListItemIds: [EnginePeer.Id] = [] var removedListItemIds: [GroupCallParticipantsContext.Participant.Id] = []
for (itemId, itemView) in self.listItemViews { for (itemId, itemView) in self.listItemViews {
if !validListItemIds.contains(itemId) { if !validListItemIds.contains(itemId) {
removedListItemIds.append(itemId) removedListItemIds.append(itemId)
@ -1512,7 +1521,7 @@ final class VideoChatParticipantsComponent: Component {
expandedThumbnailsComponentView.alpha = expandedThumbnailsAlpha expandedThumbnailsComponentView.alpha = expandedThumbnailsAlpha
let fromReferenceFrame: CGRect let fromReferenceFrame: CGRect
if let index = self.gridParticipants.firstIndex(where: { $0.participant.peer.id == expandedVideoState.mainParticipant.id && $0.isPresentation == expandedVideoState.mainParticipant.isPresentation }) { if let index = self.gridParticipants.firstIndex(where: { $0.participant.id == expandedVideoState.mainParticipant.id && $0.isPresentation == expandedVideoState.mainParticipant.isPresentation }) {
fromReferenceFrame = self.gridItemViewContainer.convert(itemLayout.gridItemFrame(at: index), to: self.expandedGridItemContainer) fromReferenceFrame = self.gridItemViewContainer.convert(itemLayout.gridItemFrame(at: index), to: self.expandedGridItemContainer)
} else { } else {
fromReferenceFrame = previousExpandedGridItemContainerFrame fromReferenceFrame = previousExpandedGridItemContainerFrame
@ -1571,7 +1580,7 @@ final class VideoChatParticipantsComponent: Component {
expandedControlsComponentView.alpha = expandedControlsAlpha expandedControlsComponentView.alpha = expandedControlsAlpha
let fromReferenceFrame: CGRect let fromReferenceFrame: CGRect
if let index = self.gridParticipants.firstIndex(where: { $0.participant.peer.id == expandedVideoState.mainParticipant.id && $0.isPresentation == expandedVideoState.mainParticipant.isPresentation }) { if let index = self.gridParticipants.firstIndex(where: { $0.participant.id == expandedVideoState.mainParticipant.id && $0.isPresentation == expandedVideoState.mainParticipant.isPresentation }) {
fromReferenceFrame = self.gridItemViewContainer.convert(itemLayout.gridItemFrame(at: index), to: self.expandedGridItemContainer) fromReferenceFrame = self.gridItemViewContainer.convert(itemLayout.gridItemFrame(at: index), to: self.expandedGridItemContainer)
} else { } else {
fromReferenceFrame = previousExpandedGridItemContainerFrame fromReferenceFrame = previousExpandedGridItemContainerFrame
@ -1591,7 +1600,7 @@ final class VideoChatParticipantsComponent: Component {
self.expandedThumbnailsView = nil self.expandedThumbnailsView = nil
if transition.containedViewLayoutTransition.isAnimated, let expandedThumbnailsComponentView = expandedThumbnailsView.view { if transition.containedViewLayoutTransition.isAnimated, let expandedThumbnailsComponentView = expandedThumbnailsView.view {
if let collapsingItemView = self.gridItemViews.values.first(where: { $0.isCollapsing }), let index = self.gridParticipants.firstIndex(where: { $0.participant.peer.id == collapsingItemView.key.id && $0.isPresentation == collapsingItemView.key.isPresentation }) { if let collapsingItemView = self.gridItemViews.values.first(where: { $0.isCollapsing }), let index = self.gridParticipants.firstIndex(where: { $0.participant.id == collapsingItemView.key.id && $0.isPresentation == collapsingItemView.key.isPresentation }) {
let targetLocalItemFrame = itemLayout.gridItemFrame(at: index) let targetLocalItemFrame = itemLayout.gridItemFrame(at: index)
var targetItemFrame = self.gridItemViewContainer.convert(targetLocalItemFrame, to: self) var targetItemFrame = self.gridItemViewContainer.convert(targetLocalItemFrame, to: self)
targetItemFrame.origin.y -= expandedGridItemContainerFrame.minY targetItemFrame.origin.y -= expandedGridItemContainerFrame.minY
@ -1612,7 +1621,7 @@ final class VideoChatParticipantsComponent: Component {
self.expandedControlsView = nil self.expandedControlsView = nil
if transition.containedViewLayoutTransition.isAnimated, let expandedControlsComponentView = expandedControlsView.view { if transition.containedViewLayoutTransition.isAnimated, let expandedControlsComponentView = expandedControlsView.view {
if let collapsingItemView = self.gridItemViews.values.first(where: { $0.isCollapsing }), let index = self.gridParticipants.firstIndex(where: { $0.participant.peer.id == collapsingItemView.key.id && $0.isPresentation == collapsingItemView.key.isPresentation }) { if let collapsingItemView = self.gridItemViews.values.first(where: { $0.isCollapsing }), let index = self.gridParticipants.firstIndex(where: { $0.participant.id == collapsingItemView.key.id && $0.isPresentation == collapsingItemView.key.isPresentation }) {
let targetLocalItemFrame = itemLayout.gridItemFrame(at: index) let targetLocalItemFrame = itemLayout.gridItemFrame(at: index)
var targetItemFrame = self.gridItemViewContainer.convert(targetLocalItemFrame, to: self) var targetItemFrame = self.gridItemViewContainer.convert(targetLocalItemFrame, to: self)
targetItemFrame.origin.y -= expandedGridItemContainerFrame.minY targetItemFrame.origin.y -= expandedGridItemContainerFrame.minY
@ -1630,7 +1639,7 @@ final class VideoChatParticipantsComponent: Component {
} }
} }
if let expandedVideoState = component.expandedVideoState, expandedVideoState.isMainParticipantPinned, let participants = component.participants, !component.speakingParticipants.isEmpty, let firstOther = component.speakingParticipants.first(where: { $0 != expandedVideoState.mainParticipant.id }), let speakingPeer = participants.participants.first(where: { $0.peer.id == firstOther })?.peer { if let expandedVideoState = component.expandedVideoState, expandedVideoState.isMainParticipantPinned, let participants = component.participants, !component.speakingParticipants.isEmpty, let firstOther = component.speakingParticipants.first(where: { expandedVideoState.mainParticipant.id != .peer($0) }), let speakingPeer = participants.participants.first(where: { $0.id == .peer(firstOther) })?.peer {
let expandedSpeakingToast: ComponentView<Empty> let expandedSpeakingToast: ComponentView<Empty>
var expandedSpeakingToastTransition = transition var expandedSpeakingToastTransition = transition
if let current = self.expandedSpeakingToast { if let current = self.expandedSpeakingToast {
@ -1644,21 +1653,21 @@ final class VideoChatParticipantsComponent: Component {
transition: expandedSpeakingToastTransition, transition: expandedSpeakingToastTransition,
component: AnyComponent(VideoChatExpandedSpeakingToastComponent( component: AnyComponent(VideoChatExpandedSpeakingToastComponent(
context: component.call.accountContext, context: component.call.accountContext,
peer: EnginePeer(speakingPeer), peer: speakingPeer,
strings: component.strings, strings: component.strings,
theme: component.theme, theme: component.theme,
action: { [weak self] peer in action: { [weak self] peer in
guard let self, let component = self.component, let participants = component.participants else { guard let self, let component = self.component, let participants = component.participants else {
return return
} }
guard let participant = participants.participants.first(where: { $0.peer.id == peer.id }) else { guard let participant = participants.participants.first(where: { $0.id == .peer(peer.id) }) else {
return return
} }
var key: VideoParticipantKey? var key: VideoParticipantKey?
if participant.presentationDescription != nil { if participant.presentationDescription != nil {
key = VideoParticipantKey(id: peer.id, isPresentation: true) key = VideoParticipantKey(id: .peer(peer.id), isPresentation: true)
} else if participant.videoDescription != nil { } else if participant.videoDescription != nil {
key = VideoParticipantKey(id: peer.id, isPresentation: false) key = VideoParticipantKey(id: .peer(peer.id), isPresentation: false)
} }
if let key { if let key {
component.updateMainParticipant(key, nil) component.updateMainParticipant(key, nil)
@ -1701,7 +1710,13 @@ final class VideoChatParticipantsComponent: Component {
} }
} }
component.visibleParticipantsUpdated(Set(visibleParticipants)) component.visibleParticipantsUpdated(Set(visibleParticipants.compactMap {
if case let .peer(id) = $0 {
return id
} else {
return nil
}
}))
} }
func setEventCycleState(scrollView: UIScrollView, eventCycleState: EventCycleState?) { func setEventCycleState(scrollView: UIScrollView, eventCycleState: EventCycleState?) {
@ -1712,7 +1727,7 @@ final class VideoChatParticipantsComponent: Component {
} }
} }
func itemFrame(peerId: EnginePeer.Id, isPresentation: Bool) -> CGRect? { func itemFrame(peerId: GroupCallParticipantsContext.Participant.Id, isPresentation: Bool) -> CGRect? {
for (key, itemView) in self.gridItemViews { for (key, itemView) in self.gridItemViews {
if key.id == peerId && key.isPresentation == isPresentation { if key.id == peerId && key.isPresentation == isPresentation {
if let itemComponentView = itemView.view.view { if let itemComponentView = itemView.view.view {
@ -1723,7 +1738,7 @@ final class VideoChatParticipantsComponent: Component {
return nil return nil
} }
func updateItemPlaceholder(peerId: EnginePeer.Id, isPresentation: Bool, placeholder: VideoSource.Output) { func updateItemPlaceholder(peerId: GroupCallParticipantsContext.Participant.Id, isPresentation: Bool, placeholder: VideoSource.Output) {
for (key, itemView) in self.gridItemViews { for (key, itemView) in self.gridItemViews {
if key.id == peerId && key.isPresentation == isPresentation { if key.id == peerId && key.isPresentation == isPresentation {
if let itemComponentView = itemView.view.view as? VideoChatParticipantVideoComponent.View { if let itemComponentView = itemView.view.view as? VideoChatParticipantVideoComponent.View {
@ -1781,7 +1796,7 @@ final class VideoChatParticipantsComponent: Component {
if participant.videoDescription != nil { if participant.videoDescription != nil {
hasVideo = true hasVideo = true
let videoParticipant = VideoParticipant(participant: participant, isPresentation: false) let videoParticipant = VideoParticipant(participant: participant, isPresentation: false)
if participant.peer.id == participants.myPeerId { if participant.id == .peer(participants.myPeerId) {
gridParticipants.insert(videoParticipant, at: 0) gridParticipants.insert(videoParticipant, at: 0)
} else { } else {
gridParticipants.append(videoParticipant) gridParticipants.append(videoParticipant)
@ -1790,14 +1805,14 @@ final class VideoChatParticipantsComponent: Component {
if participant.presentationDescription != nil { if participant.presentationDescription != nil {
hasVideo = true hasVideo = true
let videoParticipant = VideoParticipant(participant: participant, isPresentation: true) let videoParticipant = VideoParticipant(participant: participant, isPresentation: true)
if participant.peer.id == participants.myPeerId { if participant.id == .peer(participants.myPeerId) {
gridParticipants.insert(videoParticipant, at: 0) gridParticipants.insert(videoParticipant, at: 0)
} else { } else {
gridParticipants.append(videoParticipant) gridParticipants.append(videoParticipant)
} }
} }
if !hasVideo || component.layout.videoColumn != nil { if !hasVideo || component.layout.videoColumn != nil {
if participant.peer.id == participants.myPeerId && !isFullyMuted { if participant.id == .peer(participants.myPeerId) && !isFullyMuted {
listParticipants.insert(participant, at: 0) listParticipants.insert(participant, at: 0)
} else { } else {
listParticipants.append(participant) listParticipants.append(participant)
@ -1927,7 +1942,7 @@ final class VideoChatParticipantsComponent: Component {
for participant in participants.participants { for participant in participants.participants {
var maxVideoQuality: PresentationGroupCallRequestedVideo.Quality = .medium var maxVideoQuality: PresentationGroupCallRequestedVideo.Quality = .medium
if let expandedVideoState = component.expandedVideoState { if let expandedVideoState = component.expandedVideoState {
if expandedVideoState.mainParticipant.id == participant.peer.id, !expandedVideoState.mainParticipant.isPresentation { if expandedVideoState.mainParticipant.id == participant.id, !expandedVideoState.mainParticipant.isPresentation {
if component.maxVideoQuality == Int.max { if component.maxVideoQuality == Int.max {
maxVideoQuality = .full maxVideoQuality = .full
} else if component.maxVideoQuality == 360 { } else if component.maxVideoQuality == 360 {
@ -1942,7 +1957,7 @@ final class VideoChatParticipantsComponent: Component {
var maxPresentationQuality: PresentationGroupCallRequestedVideo.Quality = .medium var maxPresentationQuality: PresentationGroupCallRequestedVideo.Quality = .medium
if let expandedVideoState = component.expandedVideoState { if let expandedVideoState = component.expandedVideoState {
if expandedVideoState.mainParticipant.id == participant.peer.id, expandedVideoState.mainParticipant.isPresentation { if expandedVideoState.mainParticipant.id == participant.id, expandedVideoState.mainParticipant.isPresentation {
if component.maxVideoQuality == Int.max { if component.maxVideoQuality == Int.max {
maxVideoQuality = .full maxVideoQuality = .full
} else if component.maxVideoQuality == 360 { } else if component.maxVideoQuality == 360 {

View File

@ -343,17 +343,17 @@ final class VideoChatScreenComponent: Component {
sourceCallControllerView?.removeFromSuperview() sourceCallControllerView?.removeFromSuperview()
} }
var expandedPeer: (id: EnginePeer.Id, isPresentation: Bool)? var expandedPeer: (id: GroupCallParticipantsContext.Participant.Id, isPresentation: Bool)?
if let animateOutData, animateOutData.incomingVideoLayer != nil, let members = self.members { if let animateOutData, animateOutData.incomingVideoLayer != nil, let members = self.members {
if let participant = members.participants.first(where: { $0.peer.id == animateOutData.incomingPeerId }) { if let participant = members.participants.first(where: { $0.id == .peer(animateOutData.incomingPeerId) }) {
if let _ = participant.videoDescription { if let _ = participant.videoDescription {
expandedPeer = (participant.peer.id, false) expandedPeer = (participant.id, false)
self.expandedParticipantsVideoState = VideoChatParticipantsComponent.ExpandedVideoState(mainParticipant: VideoChatParticipantsComponent.VideoParticipantKey(id: participant.peer.id, isPresentation: false), isMainParticipantPinned: false, isUIHidden: true) self.expandedParticipantsVideoState = VideoChatParticipantsComponent.ExpandedVideoState(mainParticipant: VideoChatParticipantsComponent.VideoParticipantKey(id: participant.id, isPresentation: false), isMainParticipantPinned: false, isUIHidden: true)
} }
} else if let participant = members.participants.first(where: { $0.peer.id == sourceCallController.call.context.account.peerId }) { } else if let participant = members.participants.first(where: { $0.id == .peer(sourceCallController.call.context.account.peerId) }) {
if let _ = participant.videoDescription { if let _ = participant.videoDescription {
expandedPeer = (participant.peer.id, false) expandedPeer = (participant.id, false)
self.expandedParticipantsVideoState = VideoChatParticipantsComponent.ExpandedVideoState(mainParticipant: VideoChatParticipantsComponent.VideoParticipantKey(id: participant.peer.id, isPresentation: false), isMainParticipantPinned: false, isUIHidden: true) self.expandedParticipantsVideoState = VideoChatParticipantsComponent.ExpandedVideoState(mainParticipant: VideoChatParticipantsComponent.VideoParticipantKey(id: participant.id, isPresentation: false), isMainParticipantPinned: false, isUIHidden: true)
} }
} }
} }
@ -1164,7 +1164,8 @@ final class VideoChatScreenComponent: Component {
} }
participants.append(GroupCallParticipantsContext.Participant( participants.append(GroupCallParticipantsContext.Participant(
peer: myPeer._asPeer(), id: .peer(myPeer.id),
peer: myPeer,
ssrc: nil, ssrc: nil,
videoDescription: myVideoDescription, videoDescription: myVideoDescription,
presentationDescription: nil, presentationDescription: nil,
@ -1189,7 +1190,8 @@ final class VideoChatScreenComponent: Component {
} }
participants.append(GroupCallParticipantsContext.Participant( participants.append(GroupCallParticipantsContext.Participant(
peer: remotePeer._asPeer(), id: .peer(remotePeer.id),
peer: remotePeer,
ssrc: nil, ssrc: nil,
videoDescription: remoteVideoDescription, videoDescription: remoteVideoDescription,
presentationDescription: nil, presentationDescription: nil,
@ -1235,7 +1237,7 @@ final class VideoChatScreenComponent: Component {
self.members = component.initialData.members self.members = component.initialData.members
self.invitedPeers = component.initialData.invitedPeers self.invitedPeers = component.initialData.invitedPeers
if let members = self.members { if let members = self.members {
self.invitedPeers.removeAll(where: { invitedPeer in members.participants.contains(where: { $0.peer.id == invitedPeer.peer.id }) }) self.invitedPeers.removeAll(where: { invitedPeer in members.participants.contains(where: { $0.id == .peer(invitedPeer.peer.id) }) })
} }
self.callState = component.initialData.callState self.callState = component.initialData.callState
} }
@ -1276,7 +1278,7 @@ final class VideoChatScreenComponent: Component {
self.members = members self.members = members
if let members { if let members {
self.invitedPeers.removeAll(where: { invitedPeer in members.participants.contains(where: { $0.peer.id == invitedPeer.peer.id }) }) self.invitedPeers.removeAll(where: { invitedPeer in members.participants.contains(where: { $0.id == .peer(invitedPeer.peer.id) }) })
} }
if let members, let expandedParticipantsVideoState = self.expandedParticipantsVideoState, !expandedParticipantsVideoState.isUIHidden { if let members, let expandedParticipantsVideoState = self.expandedParticipantsVideoState, !expandedParticipantsVideoState.isUIHidden {
@ -1299,28 +1301,28 @@ final class VideoChatScreenComponent: Component {
if let expandedParticipantsVideoState = self.expandedParticipantsVideoState, let members { if let expandedParticipantsVideoState = self.expandedParticipantsVideoState, let members {
if CFAbsoluteTimeGetCurrent() > self.focusedSpeakerAutoSwitchDeadline, !expandedParticipantsVideoState.isMainParticipantPinned, let participant = members.participants.first(where: { participant in if CFAbsoluteTimeGetCurrent() > self.focusedSpeakerAutoSwitchDeadline, !expandedParticipantsVideoState.isMainParticipantPinned, let participant = members.participants.first(where: { participant in
if let callState = self.callState, participant.peer.id == callState.myPeerId { if let callState = self.callState, participant.id == .peer(callState.myPeerId) {
return false return false
} }
if participant.videoDescription != nil || participant.presentationDescription != nil { if participant.videoDescription != nil || participant.presentationDescription != nil {
if members.speakingParticipants.contains(participant.peer.id) { if let participantPeer = participant.peer, members.speakingParticipants.contains(participantPeer.id) {
return true return true
} }
} }
return false return false
}) { }) {
if participant.peer.id != expandedParticipantsVideoState.mainParticipant.id { if participant.id != expandedParticipantsVideoState.mainParticipant.id {
if participant.presentationDescription != nil { if participant.presentationDescription != nil {
self.expandedParticipantsVideoState = VideoChatParticipantsComponent.ExpandedVideoState(mainParticipant: VideoChatParticipantsComponent.VideoParticipantKey(id: participant.peer.id, isPresentation: true), isMainParticipantPinned: false, isUIHidden: expandedParticipantsVideoState.isUIHidden) self.expandedParticipantsVideoState = VideoChatParticipantsComponent.ExpandedVideoState(mainParticipant: VideoChatParticipantsComponent.VideoParticipantKey(id: participant.id, isPresentation: true), isMainParticipantPinned: false, isUIHidden: expandedParticipantsVideoState.isUIHidden)
} else { } else {
self.expandedParticipantsVideoState = VideoChatParticipantsComponent.ExpandedVideoState(mainParticipant: VideoChatParticipantsComponent.VideoParticipantKey(id: participant.peer.id, isPresentation: false), isMainParticipantPinned: false, isUIHidden: expandedParticipantsVideoState.isUIHidden) self.expandedParticipantsVideoState = VideoChatParticipantsComponent.ExpandedVideoState(mainParticipant: VideoChatParticipantsComponent.VideoParticipantKey(id: participant.id, isPresentation: false), isMainParticipantPinned: false, isUIHidden: expandedParticipantsVideoState.isUIHidden)
} }
self.focusedSpeakerAutoSwitchDeadline = CFAbsoluteTimeGetCurrent() + 1.0 self.focusedSpeakerAutoSwitchDeadline = CFAbsoluteTimeGetCurrent() + 1.0
} }
} }
if let _ = members.participants.first(where: { participant in if let _ = members.participants.first(where: { participant in
if participant.peer.id == expandedParticipantsVideoState.mainParticipant.id { if participant.id == expandedParticipantsVideoState.mainParticipant.id {
if expandedParticipantsVideoState.mainParticipant.isPresentation { if expandedParticipantsVideoState.mainParticipant.isPresentation {
if participant.presentationDescription == nil { if participant.presentationDescription == nil {
return false return false
@ -1344,9 +1346,9 @@ final class VideoChatScreenComponent: Component {
return false return false
}) { }) {
if participant.presentationDescription != nil { if participant.presentationDescription != nil {
self.expandedParticipantsVideoState = VideoChatParticipantsComponent.ExpandedVideoState(mainParticipant: VideoChatParticipantsComponent.VideoParticipantKey(id: participant.peer.id, isPresentation: true), isMainParticipantPinned: false, isUIHidden: expandedParticipantsVideoState.isUIHidden) self.expandedParticipantsVideoState = VideoChatParticipantsComponent.ExpandedVideoState(mainParticipant: VideoChatParticipantsComponent.VideoParticipantKey(id: participant.id, isPresentation: true), isMainParticipantPinned: false, isUIHidden: expandedParticipantsVideoState.isUIHidden)
} else { } else {
self.expandedParticipantsVideoState = VideoChatParticipantsComponent.ExpandedVideoState(mainParticipant: VideoChatParticipantsComponent.VideoParticipantKey(id: participant.peer.id, isPresentation: false), isMainParticipantPinned: false, isUIHidden: expandedParticipantsVideoState.isUIHidden) self.expandedParticipantsVideoState = VideoChatParticipantsComponent.ExpandedVideoState(mainParticipant: VideoChatParticipantsComponent.VideoParticipantKey(id: participant.id, isPresentation: false), isMainParticipantPinned: false, isUIHidden: expandedParticipantsVideoState.isUIHidden)
} }
self.focusedSpeakerAutoSwitchDeadline = CFAbsoluteTimeGetCurrent() + 1.0 self.focusedSpeakerAutoSwitchDeadline = CFAbsoluteTimeGetCurrent() + 1.0
} else { } else {
@ -1365,8 +1367,8 @@ final class VideoChatScreenComponent: Component {
var speakingParticipantPeers: [EnginePeer] = [] var speakingParticipantPeers: [EnginePeer] = []
if let members, !members.speakingParticipants.isEmpty { if let members, !members.speakingParticipants.isEmpty {
for participant in members.participants { for participant in members.participants {
if members.speakingParticipants.contains(participant.peer.id) { if let participantPeer = participant.peer, members.speakingParticipants.contains(participantPeer.id) {
speakingParticipantPeers.append(EnginePeer(participant.peer)) speakingParticipantPeers.append(participantPeer)
} }
} }
} }
@ -1401,7 +1403,7 @@ final class VideoChatScreenComponent: Component {
var invitedPeers = invitedPeers var invitedPeers = invitedPeers
if let members { if let members {
invitedPeers.removeAll(where: { invitedPeer in members.participants.contains(where: { $0.peer.id == invitedPeer.peer.id }) }) invitedPeers.removeAll(where: { invitedPeer in members.participants.contains(where: { $0.id == .peer(invitedPeer.peer.id) }) })
} }
if self.invitedPeers != invitedPeers { if self.invitedPeers != invitedPeers {
@ -1612,28 +1614,28 @@ final class VideoChatScreenComponent: Component {
if let expandedParticipantsVideoState = self.expandedParticipantsVideoState { if let expandedParticipantsVideoState = self.expandedParticipantsVideoState {
if CFAbsoluteTimeGetCurrent() > self.focusedSpeakerAutoSwitchDeadline, !expandedParticipantsVideoState.isMainParticipantPinned, let participant = members.participants.first(where: { participant in if CFAbsoluteTimeGetCurrent() > self.focusedSpeakerAutoSwitchDeadline, !expandedParticipantsVideoState.isMainParticipantPinned, let participant = members.participants.first(where: { participant in
if let callState = self.callState, participant.peer.id == callState.myPeerId { if let callState = self.callState, participant.id == .peer(callState.myPeerId) {
return false return false
} }
if participant.videoDescription != nil || participant.presentationDescription != nil { if participant.videoDescription != nil || participant.presentationDescription != nil {
if members.speakingParticipants.contains(participant.peer.id) { if let participantPeer = participant.peer, members.speakingParticipants.contains(participantPeer.id) {
return true return true
} }
} }
return false return false
}) { }) {
if participant.peer.id != expandedParticipantsVideoState.mainParticipant.id { if participant.id != expandedParticipantsVideoState.mainParticipant.id {
if participant.presentationDescription != nil { if participant.presentationDescription != nil {
self.expandedParticipantsVideoState = VideoChatParticipantsComponent.ExpandedVideoState(mainParticipant: VideoChatParticipantsComponent.VideoParticipantKey(id: participant.peer.id, isPresentation: true), isMainParticipantPinned: false, isUIHidden: expandedParticipantsVideoState.isUIHidden) self.expandedParticipantsVideoState = VideoChatParticipantsComponent.ExpandedVideoState(mainParticipant: VideoChatParticipantsComponent.VideoParticipantKey(id: participant.id, isPresentation: true), isMainParticipantPinned: false, isUIHidden: expandedParticipantsVideoState.isUIHidden)
} else { } else {
self.expandedParticipantsVideoState = VideoChatParticipantsComponent.ExpandedVideoState(mainParticipant: VideoChatParticipantsComponent.VideoParticipantKey(id: participant.peer.id, isPresentation: false), isMainParticipantPinned: false, isUIHidden: expandedParticipantsVideoState.isUIHidden) self.expandedParticipantsVideoState = VideoChatParticipantsComponent.ExpandedVideoState(mainParticipant: VideoChatParticipantsComponent.VideoParticipantKey(id: participant.id, isPresentation: false), isMainParticipantPinned: false, isUIHidden: expandedParticipantsVideoState.isUIHidden)
} }
self.focusedSpeakerAutoSwitchDeadline = CFAbsoluteTimeGetCurrent() + 1.0 self.focusedSpeakerAutoSwitchDeadline = CFAbsoluteTimeGetCurrent() + 1.0
} }
} }
if let _ = members.participants.first(where: { participant in if let _ = members.participants.first(where: { participant in
if participant.peer.id == expandedParticipantsVideoState.mainParticipant.id { if participant.id == expandedParticipantsVideoState.mainParticipant.id {
if expandedParticipantsVideoState.mainParticipant.isPresentation { if expandedParticipantsVideoState.mainParticipant.isPresentation {
if participant.presentationDescription == nil { if participant.presentationDescription == nil {
return false return false
@ -1657,9 +1659,9 @@ final class VideoChatScreenComponent: Component {
return false return false
}) { }) {
if participant.presentationDescription != nil { if participant.presentationDescription != nil {
self.expandedParticipantsVideoState = VideoChatParticipantsComponent.ExpandedVideoState(mainParticipant: VideoChatParticipantsComponent.VideoParticipantKey(id: participant.peer.id, isPresentation: true), isMainParticipantPinned: false, isUIHidden: expandedParticipantsVideoState.isUIHidden) self.expandedParticipantsVideoState = VideoChatParticipantsComponent.ExpandedVideoState(mainParticipant: VideoChatParticipantsComponent.VideoParticipantKey(id: participant.id, isPresentation: true), isMainParticipantPinned: false, isUIHidden: expandedParticipantsVideoState.isUIHidden)
} else { } else {
self.expandedParticipantsVideoState = VideoChatParticipantsComponent.ExpandedVideoState(mainParticipant: VideoChatParticipantsComponent.VideoParticipantKey(id: participant.peer.id, isPresentation: false), isMainParticipantPinned: false, isUIHidden: expandedParticipantsVideoState.isUIHidden) self.expandedParticipantsVideoState = VideoChatParticipantsComponent.ExpandedVideoState(mainParticipant: VideoChatParticipantsComponent.VideoParticipantKey(id: participant.id, isPresentation: false), isMainParticipantPinned: false, isUIHidden: expandedParticipantsVideoState.isUIHidden)
} }
self.focusedSpeakerAutoSwitchDeadline = CFAbsoluteTimeGetCurrent() + 1.0 self.focusedSpeakerAutoSwitchDeadline = CFAbsoluteTimeGetCurrent() + 1.0
} else { } else {
@ -1678,8 +1680,8 @@ final class VideoChatScreenComponent: Component {
var speakingParticipantPeers: [EnginePeer] = [] var speakingParticipantPeers: [EnginePeer] = []
if !members.speakingParticipants.isEmpty { if !members.speakingParticipants.isEmpty {
for participant in members.participants { for participant in members.participants {
if members.speakingParticipants.contains(participant.peer.id) { if let participantPeer = participant.peer, members.speakingParticipants.contains(participantPeer.id) {
speakingParticipantPeers.append(EnginePeer(participant.peer)) speakingParticipantPeers.append(participantPeer)
} }
} }
} }

View File

@ -21,8 +21,8 @@ extension VideoChatScreenComponent.View {
disablePeerIds.append(groupCall.accountContext.account.peerId) disablePeerIds.append(groupCall.accountContext.account.peerId)
if let members = self.members { if let members = self.members {
for participant in members.participants { for participant in members.participants {
if !disablePeerIds.contains(participant.peer.id) { if let participantPeer = participant.peer, !disablePeerIds.contains(participantPeer.id) {
disablePeerIds.append(participant.peer.id) disablePeerIds.append(participantPeer.id)
} }
} }
} }
@ -99,7 +99,7 @@ extension VideoChatScreenComponent.View {
var filters: [ChannelMembersSearchFilter] = [] var filters: [ChannelMembersSearchFilter] = []
if let members = self.members { if let members = self.members {
filters.append(.disable(Array(members.participants.map { $0.peer.id }))) filters.append(.disable(Array(members.participants.compactMap { $0.peer?.id })))
} }
if case let .channel(groupPeer) = groupPeer { if case let .channel(groupPeer) = groupPeer {
if !groupPeer.hasPermission(.inviteMembers) && inviteLinks?.listenerLink == nil { if !groupPeer.hasPermission(.inviteMembers) && inviteLinks?.listenerLink == nil {

View File

@ -18,7 +18,7 @@ extension VideoChatScreenComponent.View {
guard let environment = self.environment else { guard let environment = self.environment else {
return return
} }
guard let members = self.members, let participant = members.participants.first(where: { $0.peer.id == id }) else { guard let members = self.members, let participant = members.participants.first(where: { $0.id == .peer(id) }) else {
return return
} }
guard let currentCall = self.currentCall else { guard let currentCall = self.currentCall else {
@ -35,10 +35,13 @@ extension VideoChatScreenComponent.View {
return [] return []
} }
var items: [ContextMenuItem] = [] guard let peer = participant.peer else {
return []
}
var items: [ContextMenuItem] = []
var hasVolumeSlider = false var hasVolumeSlider = false
let peer = participant.peer
if let muteState = muteState, !muteState.canUnmute || muteState.mutedByYou { if let muteState = muteState, !muteState.canUnmute || muteState.mutedByYou {
} else { } else {
if callState.canManageCall || callState.myPeerId != id { if callState.canManageCall || callState.myPeerId != id {
@ -65,7 +68,7 @@ extension VideoChatScreenComponent.View {
} }
} }
if callState.myPeerId == id && !hasVolumeSlider && ((participant.about?.isEmpty ?? true) || participant.peer.smallProfileImage == nil) { if callState.myPeerId == id && !hasVolumeSlider && ((participant.about?.isEmpty ?? true) || participant.peer?.smallProfileImage == nil) {
items.append(.custom(VoiceChatInfoContextItem(text: environment.strings.VoiceChat_ImproveYourProfileText, icon: { theme in items.append(.custom(VoiceChatInfoContextItem(text: environment.strings.VoiceChat_ImproveYourProfileText, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Tip"), color: theme.actionSheet.primaryTextColor) return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Tip"), color: theme.actionSheet.primaryTextColor)
}), true)) }), true))
@ -134,7 +137,7 @@ extension VideoChatScreenComponent.View {
} }
}))) })))
if let peer = peer as? TelegramUser { if case let .user(peer) = peer {
items.append(.action(ContextMenuActionItem(text: environment.strings.VoiceChat_ChangeName, icon: { theme in items.append(.action(ContextMenuActionItem(text: environment.strings.VoiceChat_ChangeName, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Call/Context Menu/ChangeName"), color: theme.actionSheet.primaryTextColor) return generateTintedImage(image: UIImage(bundleImageName: "Call/Context Menu/ChangeName"), color: theme.actionSheet.primaryTextColor)
}, action: { [weak self] _, f in }, action: { [weak self] _, f in
@ -184,7 +187,9 @@ extension VideoChatScreenComponent.View {
let _ = groupCall.updateMuteState(peerId: peer.id, isMuted: false) let _ = groupCall.updateMuteState(peerId: peer.id, isMuted: false)
f(.default) f(.default)
self.presentUndoOverlay(content: .voiceChatCanSpeak(text: environment.strings.VoiceChat_UserCanNowSpeak(EnginePeer(participant.peer).displayTitle(strings: environment.strings, displayOrder: groupCall.accountContext.sharedContext.currentPresentationData.with({ $0 }).nameDisplayOrder)).string), action: { _ in return true }) if let participantPeer = participant.peer {
self.presentUndoOverlay(content: .voiceChatCanSpeak(text: environment.strings.VoiceChat_UserCanNowSpeak(participantPeer.displayTitle(strings: environment.strings, displayOrder: groupCall.accountContext.sharedContext.currentPresentationData.with({ $0 }).nameDisplayOrder)).string), action: { _ in return true })
}
}))) })))
} else { } else {
items.append(.action(ContextMenuActionItem(text: environment.strings.VoiceChat_MutePeer, icon: { theme in items.append(.action(ContextMenuActionItem(text: environment.strings.VoiceChat_MutePeer, icon: { theme in
@ -207,7 +212,6 @@ extension VideoChatScreenComponent.View {
guard let self, case let .group(groupCall) = self.currentCall else { guard let self, case let .group(groupCall) = self.currentCall else {
return return
} }
let _ = groupCall.updateMuteState(peerId: peer.id, isMuted: false) let _ = groupCall.updateMuteState(peerId: peer.id, isMuted: false)
f(.default) f(.default)
}))) })))
@ -228,7 +232,7 @@ extension VideoChatScreenComponent.View {
let openTitle: String let openTitle: String
let openIcon: UIImage? let openIcon: UIImage?
if [Namespaces.Peer.CloudChannel, Namespaces.Peer.CloudGroup].contains(peer.id.namespace) { if [Namespaces.Peer.CloudChannel, Namespaces.Peer.CloudGroup].contains(peer.id.namespace) {
if let peer = peer as? TelegramChannel, case .broadcast = peer.info { if case let .channel(peer) = peer, case .broadcast = peer.info {
openTitle = environment.strings.VoiceChat_OpenChannel openTitle = environment.strings.VoiceChat_OpenChannel
openIcon = UIImage(bundleImageName: "Chat/Context Menu/Channels") openIcon = UIImage(bundleImageName: "Chat/Context Menu/Channels")
} else { } else {
@ -256,7 +260,7 @@ extension VideoChatScreenComponent.View {
guard let navigationController else { guard let navigationController else {
return return
} }
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(EnginePeer(peer)), keepStack: .always, purposefulAction: {}, peekData: nil)) context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(peer), keepStack: .always, purposefulAction: {}, peekData: nil))
} }
}) })
@ -293,7 +297,7 @@ extension VideoChatScreenComponent.View {
let nameDisplayOrder = presentationData.nameDisplayOrder let nameDisplayOrder = presentationData.nameDisplayOrder
if let chatPeer { if let chatPeer {
items.append(DeleteChatPeerActionSheetItem(context: groupCall.accountContext, peer: EnginePeer(peer), chatPeer: chatPeer, action: .removeFromGroup, strings: environment.strings, nameDisplayOrder: nameDisplayOrder)) items.append(DeleteChatPeerActionSheetItem(context: groupCall.accountContext, peer: peer, chatPeer: chatPeer, action: .removeFromGroup, strings: environment.strings, nameDisplayOrder: nameDisplayOrder))
} }
items.append(ActionSheetButtonItem(title: environment.strings.VoiceChat_RemovePeerRemove, color: .destructive, action: { [weak self, weak actionSheet] in items.append(ActionSheetButtonItem(title: environment.strings.VoiceChat_RemovePeerRemove, color: .destructive, action: { [weak self, weak actionSheet] in
@ -312,7 +316,7 @@ extension VideoChatScreenComponent.View {
} }
} }
self.presentUndoOverlay(content: .banned(text: environment.strings.VoiceChat_RemovedPeerText(EnginePeer(peer).displayTitle(strings: environment.strings, displayOrder: nameDisplayOrder)).string), action: { _ in return false }) self.presentUndoOverlay(content: .banned(text: environment.strings.VoiceChat_RemovedPeerText(peer.displayTitle(strings: environment.strings, displayOrder: nameDisplayOrder)).string), action: { _ in return false })
})) }))
actionSheet.setItemGroups([ actionSheet.setItemGroups([

File diff suppressed because it is too large Load Diff

View File

@ -4,6 +4,7 @@ import SwiftSignalKit
public protocol ConferenceCallE2EContextState: AnyObject { public protocol ConferenceCallE2EContextState: AnyObject {
func getEmojiState() -> Data? func getEmojiState() -> Data?
func getParticipantIds() -> [Int64] func getParticipantIds() -> [Int64]
func getParticipants() -> [ConferenceCallE2EContext.BlockchainParticipant]
func applyBlock(block: Data) func applyBlock(block: Data)
func applyBroadcastBlock(block: Data) func applyBroadcastBlock(block: Data)
@ -25,6 +26,16 @@ public final class ConferenceCallE2EContext {
} }
} }
public struct BlockchainParticipant: Equatable {
public let userId: Int64
public let internalId: String
public init(userId: Int64, internalId: String) {
self.userId = userId
self.internalId = internalId
}
}
private final class Impl { private final class Impl {
private let queue: Queue private let queue: Queue
@ -38,6 +49,7 @@ public final class ConferenceCallE2EContext {
private let keyPair: TelegramKeyPair private let keyPair: TelegramKeyPair
let e2eEncryptionKeyHashValue = ValuePromise<Data?>(nil) let e2eEncryptionKeyHashValue = ValuePromise<Data?>(nil)
let blockchainParticipantsValue = ValuePromise<[BlockchainParticipant]>([])
private var e2ePoll0Offset: Int? private var e2ePoll0Offset: Int?
private var e2ePoll0Timer: Foundation.Timer? private var e2ePoll0Timer: Foundation.Timer?
@ -154,7 +166,7 @@ public final class ConferenceCallE2EContext {
let keyPair = self.keyPair let keyPair = self.keyPair
let userId = self.userId let userId = self.userId
let initializeState = self.initializeState let initializeState = self.initializeState
let (outBlocks, outEmoji) = self.state.with({ callState -> ([Data], Data) in let (outBlocks, outEmoji, outBlockchainParticipants) = self.state.with({ callState -> ([Data], Data, [BlockchainParticipant]) in
if let state = callState.state { if let state = callState.state {
for block in blocks { for block in blocks {
if subChainId == 0 { if subChainId == 0 {
@ -163,30 +175,31 @@ public final class ConferenceCallE2EContext {
state.applyBroadcastBlock(block: block) state.applyBroadcastBlock(block: block)
} }
} }
return (state.takeOutgoingBroadcastBlocks(), state.getEmojiState() ?? Data()) return (state.takeOutgoingBroadcastBlocks(), state.getEmojiState() ?? Data(), state.getParticipants())
} else { } else {
if subChainId == 0 { if subChainId == 0 {
guard let block = blocks.last else { guard let block = blocks.last else {
return ([], Data()) return ([], Data(), [])
} }
guard let state = initializeState(keyPair, userId, block) else { guard let state = initializeState(keyPair, userId, block) else {
return ([], Data()) return ([], Data(), [])
} }
callState.state = state callState.state = state
for block in callState.pendingIncomingBroadcastBlocks { for block in callState.pendingIncomingBroadcastBlocks {
state.applyBroadcastBlock(block: block) state.applyBroadcastBlock(block: block)
} }
callState.pendingIncomingBroadcastBlocks.removeAll() callState.pendingIncomingBroadcastBlocks.removeAll()
return (state.takeOutgoingBroadcastBlocks(), state.getEmojiState() ?? Data()) return (state.takeOutgoingBroadcastBlocks(), state.getEmojiState() ?? Data(), state.getParticipants())
} else if subChainId == 1 { } else if subChainId == 1 {
callState.pendingIncomingBroadcastBlocks.append(contentsOf: blocks) callState.pendingIncomingBroadcastBlocks.append(contentsOf: blocks)
return ([], Data()) return ([], Data(), [])
} else { } else {
return ([], Data()) return ([], Data(), [])
} }
} }
}) })
self.e2eEncryptionKeyHashValue.set(outEmoji.isEmpty ? nil : outEmoji) self.e2eEncryptionKeyHashValue.set(outEmoji.isEmpty ? nil : outEmoji)
self.blockchainParticipantsValue.set(outBlockchainParticipants)
for outBlock in outBlocks { for outBlock in outBlocks {
let _ = self.engine.calls.sendConferenceCallBroadcast(callId: self.callId, accessHash: self.accessHash, block: outBlock).startStandalone() let _ = self.engine.calls.sendConferenceCallBroadcast(callId: self.callId, accessHash: self.accessHash, block: outBlock).startStandalone()
@ -278,6 +291,7 @@ public final class ConferenceCallE2EContext {
let state = self.state let state = self.state
let callId = self.callId let callId = self.callId
let accessHash = self.accessHash let accessHash = self.accessHash
let accountPeerId = engine.account.peerId
if !self.pendingKickPeers.isEmpty { if !self.pendingKickPeers.isEmpty {
let pendingKickPeers = self.pendingKickPeers let pendingKickPeers = self.pendingKickPeers
@ -363,9 +377,16 @@ public final class ConferenceCallE2EContext {
} }
// Peer ids that are in the blockchain but not in the server list // Peer ids that are in the blockchain but not in the server list
let removedPeerIds = blockchainPeerIds.filter { blockchainPeerId in var removedPeerIds = blockchainPeerIds.filter { blockchainPeerId in
return !result.participants.contains(where: { $0.peer.id.id._internalGetInt64Value() == blockchainPeerId }) return !result.participants.contains(where: { participant in
if case let .peer(id) = participant.id, id.namespace == Namespaces.Peer.CloudChannel, id.id._internalGetInt64Value() == blockchainPeerId {
return true
} else {
return false
}
})
} }
removedPeerIds.removeAll(where: { $0 == accountPeerId.id._internalGetInt64Value() })
if removedPeerIds.isEmpty { if removedPeerIds.isEmpty {
return .single(false) return .single(false)
@ -422,6 +443,12 @@ public final class ConferenceCallE2EContext {
return impl.e2eEncryptionKeyHashValue.get().start(next: subscriber.putNext) return impl.e2eEncryptionKeyHashValue.get().start(next: subscriber.putNext)
} }
} }
public var blockchainParticipants: Signal<[BlockchainParticipant], NoError> {
return self.impl.signalWith { impl, subscriber in
return impl.blockchainParticipantsValue.get().start(next: subscriber.putNext)
}
}
public init(engine: TelegramEngine, callId: Int64, accessHash: Int64, userId: Int64, reference: InternalGroupCallReference, keyPair: TelegramKeyPair, initializeState: @escaping (TelegramKeyPair, Int64, Data) -> ConferenceCallE2EContextState?) { public init(engine: TelegramEngine, callId: Int64, accessHash: Int64, userId: Int64, reference: InternalGroupCallReference, keyPair: TelegramKeyPair, initializeState: @escaping (TelegramKeyPair, Int64, Data) -> ConferenceCallE2EContextState?) {
let queue = Queue.mainQueue() let queue = Queue.mainQueue()

View File

@ -280,7 +280,7 @@ func _internal_getCurrentGroupCallInfo(account: Account, reference: InternalGrou
let parsedParticipants = participants.compactMap { GroupCallParticipantsContext.Participant($0, transaction: transaction) } let parsedParticipants = participants.compactMap { GroupCallParticipantsContext.Participant($0, transaction: transaction) }
return ( return (
parsedParticipants.map(\.peer.id), parsedParticipants.compactMap(\.peer?.id),
nil nil
) )
} }
@ -847,9 +847,10 @@ func _internal_joinGroupCall(account: Account, peerId: PeerId?, joinAs: PeerId?,
presentationDescription = nil presentationDescription = nil
} }
let joinedVideo = (flags & (1 << 15)) != 0 let joinedVideo = (flags & (1 << 15)) != 0
if !state.participants.contains(where: { $0.peer.id == peer.id }) { if !state.participants.contains(where: { $0.id == .peer(peer.id) }) {
state.participants.append(GroupCallParticipantsContext.Participant( state.participants.append(GroupCallParticipantsContext.Participant(
peer: peer, id: .peer(peer.id),
peer: EnginePeer(peer),
ssrc: ssrc, ssrc: ssrc,
videoDescription: videoDescription, videoDescription: videoDescription,
presentationDescription: presentationDescription, presentationDescription: presentationDescription,
@ -1175,7 +1176,41 @@ public final class GroupCallParticipantsContext {
} }
} }
public var peer: Peer public enum Id: Hashable, Comparable, CustomStringConvertible {
case peer(EnginePeer.Id)
case blockchain(String)
public var description: String {
switch self {
case let .peer(id):
return "\(id)"
case let .blockchain(internalId):
return internalId
}
}
public static func <(lhs: Id, rhs: Id) -> Bool {
switch lhs {
case let .peer(lhsId):
switch rhs {
case let .peer(rhsId):
return lhsId < rhsId
case .blockchain:
return true
}
case let .blockchain(lhsData):
switch rhs {
case .peer:
return false
case let .blockchain(rhsData):
return lhsData < rhsData
}
}
}
}
public var id: Id
public var peer: EnginePeer?
public var ssrc: UInt32? public var ssrc: UInt32?
public var videoDescription: VideoDescription? public var videoDescription: VideoDescription?
public var presentationDescription: VideoDescription? public var presentationDescription: VideoDescription?
@ -1190,7 +1225,8 @@ public final class GroupCallParticipantsContext {
public var joinedVideo: Bool public var joinedVideo: Bool
public init( public init(
peer: Peer, id: Id,
peer: EnginePeer?,
ssrc: UInt32?, ssrc: UInt32?,
videoDescription: VideoDescription?, videoDescription: VideoDescription?,
presentationDescription: VideoDescription?, presentationDescription: VideoDescription?,
@ -1204,6 +1240,7 @@ public final class GroupCallParticipantsContext {
about: String?, about: String?,
joinedVideo: Bool joinedVideo: Bool
) { ) {
self.id = id
self.peer = peer self.peer = peer
self.ssrc = ssrc self.ssrc = ssrc
self.videoDescription = videoDescription self.videoDescription = videoDescription
@ -1220,7 +1257,7 @@ public final class GroupCallParticipantsContext {
} }
public var description: String { public var description: String {
return "Participant(peer: \(peer.id): \(peer.debugDisplayTitle), ssrc: \(String(describing: self.ssrc))" return "Participant(peer: \(self.id): \(peer?.debugDisplayTitle ?? "User \(self.id)"), ssrc: \(String(describing: self.ssrc))"
} }
public mutating func mergeActivity(from other: Participant, mergeActivityTimestamp: Bool) { public mutating func mergeActivity(from other: Participant, mergeActivityTimestamp: Bool) {
@ -1231,7 +1268,10 @@ public final class GroupCallParticipantsContext {
} }
public static func ==(lhs: Participant, rhs: Participant) -> Bool { public static func ==(lhs: Participant, rhs: Participant) -> Bool {
if !lhs.peer.isEqual(rhs.peer) { if lhs.id != rhs.id {
return false
}
if lhs.peer != rhs.peer {
return false return false
} }
if lhs.ssrc != rhs.ssrc { if lhs.ssrc != rhs.ssrc {
@ -1318,7 +1358,7 @@ public final class GroupCallParticipantsContext {
} }
} }
return lhs.peer.id < rhs.peer.id return lhs.id < rhs.id
} }
} }
@ -1352,13 +1392,15 @@ public final class GroupCallParticipantsContext {
public mutating func mergeActivity(from other: State, myPeerId: PeerId?, previousMyPeerId: PeerId?, mergeActivityTimestamps: Bool) { public mutating func mergeActivity(from other: State, myPeerId: PeerId?, previousMyPeerId: PeerId?, mergeActivityTimestamps: Bool) {
var indexMap: [PeerId: Int] = [:] var indexMap: [PeerId: Int] = [:]
for i in 0 ..< other.participants.count { for i in 0 ..< other.participants.count {
indexMap[other.participants[i].peer.id] = i if let otherParticipantPeer = other.participants[i].peer {
indexMap[otherParticipantPeer.id] = i
}
} }
for i in 0 ..< self.participants.count { for i in 0 ..< self.participants.count {
if let index = indexMap[self.participants[i].peer.id] { if let selfParticipantPeer = self.participants[i].peer, let index = indexMap[selfParticipantPeer.id] {
self.participants[i].mergeActivity(from: other.participants[index], mergeActivityTimestamp: mergeActivityTimestamps) self.participants[i].mergeActivity(from: other.participants[index], mergeActivityTimestamp: mergeActivityTimestamps)
if self.participants[i].peer.id == myPeerId || self.participants[i].peer.id == previousMyPeerId { if selfParticipantPeer.id == myPeerId || selfParticipantPeer.id == previousMyPeerId {
self.participants[i].joinTimestamp = other.participants[index].joinTimestamp self.participants[i].joinTimestamp = other.participants[index].joinTimestamp
} }
} }
@ -1437,9 +1479,28 @@ public final class GroupCallParticipantsContext {
} }
} }
private final class ResolvedBlockchainParticipant: Equatable {
let participant: ConferenceCallE2EContext.BlockchainParticipant
let peer: EnginePeer?
init(participant: ConferenceCallE2EContext.BlockchainParticipant, peer: EnginePeer?) {
self.participant = participant
self.peer = peer
}
static func ==(lhs: ResolvedBlockchainParticipant, rhs: ResolvedBlockchainParticipant) -> Bool {
return lhs.participant == rhs.participant && lhs.peer == rhs.peer
}
}
private struct BlockchainState: Equatable {
var blockchainParticipants: [ResolvedBlockchainParticipant]
}
private struct InternalState: Equatable { private struct InternalState: Equatable {
var state: State var state: State
var overlayState: OverlayState var overlayState: OverlayState
var blockchainState: BlockchainState
} }
public enum Update { public enum Update {
@ -1547,7 +1608,7 @@ public final class GroupCallParticipantsContext {
var sortAgain = false var sortAgain = false
var canSeeHands = state.state.isCreator || state.state.adminIds.contains(accountPeerId) var canSeeHands = state.state.isCreator || state.state.adminIds.contains(accountPeerId)
for participant in publicState.participants { for participant in publicState.participants {
if participant.peer.id == myPeerId { if participant.id == .peer(myPeerId) {
if let muteState = participant.muteState { if let muteState = participant.muteState {
if muteState.canUnmute { if muteState.canUnmute {
canSeeHands = true canSeeHands = true
@ -1559,7 +1620,7 @@ public final class GroupCallParticipantsContext {
} }
} }
for i in 0 ..< publicState.participants.count { for i in 0 ..< publicState.participants.count {
if let pendingMuteState = state.overlayState.pendingMuteStateChanges[publicState.participants[i].peer.id] { if let participantPeer = publicState.participants[i].peer, let pendingMuteState = state.overlayState.pendingMuteStateChanges[participantPeer.id] {
publicState.participants[i].muteState = pendingMuteState.state publicState.participants[i].muteState = pendingMuteState.state
publicState.participants[i].volume = pendingMuteState.volume publicState.participants[i].volume = pendingMuteState.volume
} }
@ -1578,6 +1639,27 @@ public final class GroupCallParticipantsContext {
if sortAgain { if sortAgain {
publicState.participants.sort(by: { GroupCallParticipantsContext.Participant.compare(lhs: $0, rhs: $1, sortAscending: publicState.sortAscending) }) publicState.participants.sort(by: { GroupCallParticipantsContext.Participant.compare(lhs: $0, rhs: $1, sortAscending: publicState.sortAscending) })
} }
for blockchainParticipant in state.blockchainState.blockchainParticipants {
let blockchainParticipantPeerId = EnginePeer.Id(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(blockchainParticipant.participant.userId))
if !publicState.participants.contains(where: { $0.id == .peer(blockchainParticipantPeerId) }) {
publicState.participants.append(Participant(
id: .peer(blockchainParticipantPeerId),
peer: blockchainParticipant.peer,
ssrc: nil,
videoDescription: nil,
presentationDescription: nil,
joinTimestamp: 0,
raiseHandRating: nil,
hasRaiseHand: false,
activityTimestamp: nil,
activityRank: nil,
muteState: nil,
volume: nil,
about: nil,
joinedVideo: false
))
}
}
return publicState return publicState
} }
|> beforeNext { [weak self] next in |> beforeNext { [weak self] next in
@ -1631,13 +1713,17 @@ public final class GroupCallParticipantsContext {
public private(set) var serviceState: ServiceState public private(set) var serviceState: ServiceState
init(account: Account, peerId: PeerId?, myPeerId: PeerId, id: Int64, reference: InternalGroupCallReference, state: State, previousServiceState: ServiceState?) { private var e2eStateUpdateDisposable: Disposable?
private var pendingBlockchainState: [ResolvedBlockchainParticipant]?
private var pendingApplyBlockchainStateTimer: Foundation.Timer?
init(account: Account, peerId: PeerId?, myPeerId: PeerId, id: Int64, reference: InternalGroupCallReference, state: State, previousServiceState: ServiceState?, e2eContext: ConferenceCallE2EContext?) {
self.account = account self.account = account
self.peerId = peerId self.peerId = peerId
self.myPeerId = myPeerId self.myPeerId = myPeerId
self.id = id self.id = id
self.reference = reference self.reference = reference
self.stateValue = InternalState(state: state, overlayState: OverlayState()) self.stateValue = InternalState(state: state, overlayState: OverlayState(), blockchainState: BlockchainState(blockchainParticipants: []))
self.statePromise = ValuePromise<InternalState>(self.stateValue) self.statePromise = ValuePromise<InternalState>(self.stateValue)
self.serviceState = previousServiceState ?? ServiceState() self.serviceState = previousServiceState ?? ServiceState()
@ -1660,7 +1746,7 @@ public final class GroupCallParticipantsContext {
if let peerId { if let peerId {
let activityCategory: PeerActivitySpace.Category = .voiceChat let activityCategory: PeerActivitySpace.Category = .voiceChat
self.activitiesDisposable = (self.account.peerInputActivities(peerId: PeerActivitySpace(peerId: peerId, category: activityCategory)) self.activitiesDisposable = (self.account.peerInputActivities(peerId: PeerActivitySpace(peerId: peerId, category: activityCategory))
|> deliverOnMainQueue).start(next: { [weak self] activities in |> deliverOnMainQueue).start(next: { [weak self] activities in
guard let strongSelf = self else { guard let strongSelf = self else {
return return
} }
@ -1674,7 +1760,9 @@ public final class GroupCallParticipantsContext {
var updatedParticipants = strongSelf.stateValue.state.participants var updatedParticipants = strongSelf.stateValue.state.participants
var indexMap: [PeerId: Int] = [:] var indexMap: [PeerId: Int] = [:]
for i in 0 ..< updatedParticipants.count { for i in 0 ..< updatedParticipants.count {
indexMap[updatedParticipants[i].peer.id] = i if let participantPeer = updatedParticipants[i].peer {
indexMap[participantPeer.id] = i
}
} }
var updated = false var updated = false
@ -1717,7 +1805,8 @@ public final class GroupCallParticipantsContext {
isStream: strongSelf.stateValue.state.isStream, isStream: strongSelf.stateValue.state.isStream,
version: strongSelf.stateValue.state.version version: strongSelf.stateValue.state.version
), ),
overlayState: strongSelf.stateValue.overlayState overlayState: strongSelf.stateValue.overlayState,
blockchainState: strongSelf.stateValue.blockchainState
) )
} }
} }
@ -1753,6 +1842,53 @@ public final class GroupCallParticipantsContext {
} }
}, queue: .mainQueue()) }, queue: .mainQueue())
self.activityRankResetTimer?.start() self.activityRankResetTimer?.start()
if let e2eContext {
let postbox = self.account.postbox
self.e2eStateUpdateDisposable = (e2eContext.blockchainParticipants
|> mapToSignal { value -> Signal<[ResolvedBlockchainParticipant], NoError> in
return postbox.transaction { transaction -> [ResolvedBlockchainParticipant] in
var result: [ResolvedBlockchainParticipant] = []
for participant in value {
let blockchainParticipantPeerId = EnginePeer.Id(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(participant.userId))
if let peer = transaction.getPeer(blockchainParticipantPeerId) {
result.append(ResolvedBlockchainParticipant(participant: participant, peer: EnginePeer(peer)))
} else {
result.append(ResolvedBlockchainParticipant(participant: participant, peer: nil))
}
}
return result
}
}
|> deliverOnMainQueue).startStrict(next: { [weak self] blockchainParticipants in
guard let self else {
return
}
self.pendingBlockchainState = blockchainParticipants
self.pendingApplyBlockchainStateTimer?.invalidate()
self.pendingApplyBlockchainStateTimer = nil
var hasUnknownParticipants: Bool = false
for blockchainParticipant in blockchainParticipants {
if !self.stateValue.state.participants.contains(where: { $0.id == .peer(EnginePeer.Id(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(blockchainParticipant.participant.userId))) }) {
hasUnknownParticipants = true
break
}
}
if hasUnknownParticipants {
self.pendingApplyBlockchainStateTimer = Foundation.Timer.scheduledTimer(withTimeInterval: 1.0, repeats: false, block: { [weak self] _ in
guard let self else {
return
}
self.applyPendingBlockchainState()
})
} else {
self.applyPendingBlockchainState()
}
})
}
} }
deinit { deinit {
@ -1764,6 +1900,19 @@ public final class GroupCallParticipantsContext {
self.activityRankResetTimer?.invalidate() self.activityRankResetTimer?.invalidate()
self.resetInviteLinksDisposable.dispose() self.resetInviteLinksDisposable.dispose()
self.subscribeDisposable.dispose() self.subscribeDisposable.dispose()
self.e2eStateUpdateDisposable?.dispose()
self.pendingApplyBlockchainStateTimer?.invalidate()
}
private func applyPendingBlockchainState() {
self.pendingApplyBlockchainStateTimer?.invalidate()
self.pendingApplyBlockchainStateTimer = nil
if let pendingBlockchainState = self.pendingBlockchainState {
self.pendingBlockchainState = nil
self.stateValue.blockchainState = BlockchainState(blockchainParticipants: pendingBlockchainState)
}
} }
public func addUpdates(updates: [Update]) { public func addUpdates(updates: [Update]) {
@ -1795,7 +1944,7 @@ public final class GroupCallParticipantsContext {
public func removeLocalPeerId() { public func removeLocalPeerId() {
var state = self.stateValue.state var state = self.stateValue.state
state.participants.removeAll(where: { $0.peer.id == self.myPeerId }) state.participants.removeAll(where: { $0.id == .peer(self.myPeerId) })
self.stateValue.state = state self.stateValue.state = state
} }
@ -1822,7 +1971,9 @@ public final class GroupCallParticipantsContext {
var updatedParticipants = strongSelf.stateValue.state.participants var updatedParticipants = strongSelf.stateValue.state.participants
var indexMap: [PeerId: Int] = [:] var indexMap: [PeerId: Int] = [:]
for i in 0 ..< updatedParticipants.count { for i in 0 ..< updatedParticipants.count {
indexMap[updatedParticipants[i].peer.id] = i if let participantPeer = updatedParticipants[i].peer {
indexMap[participantPeer.id] = i
}
} }
var updated = false var updated = false
@ -1869,7 +2020,8 @@ public final class GroupCallParticipantsContext {
isStream: strongSelf.stateValue.state.isStream, isStream: strongSelf.stateValue.state.isStream,
version: strongSelf.stateValue.state.version version: strongSelf.stateValue.state.version
), ),
overlayState: strongSelf.stateValue.overlayState overlayState: strongSelf.stateValue.overlayState,
blockchainState: strongSelf.stateValue.blockchainState
) )
} }
@ -2000,7 +2152,7 @@ public final class GroupCallParticipantsContext {
for participantUpdate in update.participantUpdates { for participantUpdate in update.participantUpdates {
if case .left = participantUpdate.participationStatusChange { if case .left = participantUpdate.participationStatusChange {
if let index = updatedParticipants.firstIndex(where: { $0.peer.id == participantUpdate.peerId }) { if let index = updatedParticipants.firstIndex(where: { $0.id == .peer(participantUpdate.peerId) }) {
updatedParticipants.remove(at: index) updatedParticipants.remove(at: index)
updatedTotalCount = max(0, updatedTotalCount - 1) updatedTotalCount = max(0, updatedTotalCount - 1)
strongSelf.memberEventsPipe.putNext(MemberEvent(peerId: participantUpdate.peerId, canUnmute: false, joined: false)) strongSelf.memberEventsPipe.putNext(MemberEvent(peerId: participantUpdate.peerId, canUnmute: false, joined: false))
@ -2017,7 +2169,7 @@ public final class GroupCallParticipantsContext {
var previousActivityRank: Int? var previousActivityRank: Int?
var previousMuteState: GroupCallParticipantsContext.Participant.MuteState? var previousMuteState: GroupCallParticipantsContext.Participant.MuteState?
var previousVolume: Int32? var previousVolume: Int32?
if let index = updatedParticipants.firstIndex(where: { $0.peer.id == participantUpdate.peerId }) { if let index = updatedParticipants.firstIndex(where: { $0.id == .peer(participantUpdate.peerId) }) {
previousJoinTimestamp = updatedParticipants[index].joinTimestamp previousJoinTimestamp = updatedParticipants[index].joinTimestamp
previousActivityTimestamp = updatedParticipants[index].activityTimestamp previousActivityTimestamp = updatedParticipants[index].activityTimestamp
previousActivityRank = updatedParticipants[index].activityRank previousActivityRank = updatedParticipants[index].activityRank
@ -2054,7 +2206,8 @@ public final class GroupCallParticipantsContext {
} }
} }
let participant = Participant( let participant = Participant(
peer: peer, id: .peer(peer.id),
peer: EnginePeer(peer),
ssrc: participantUpdate.ssrc, ssrc: participantUpdate.ssrc,
videoDescription: participantUpdate.videoDescription, videoDescription: participantUpdate.videoDescription,
presentationDescription: participantUpdate.presentationDescription, presentationDescription: participantUpdate.presentationDescription,
@ -2111,7 +2264,8 @@ public final class GroupCallParticipantsContext {
isStream: isStream, isStream: isStream,
version: update.version version: update.version
), ),
overlayState: updatedOverlayState overlayState: updatedOverlayState,
blockchainState: strongSelf.stateValue.blockchainState
) )
strongSelf.endedProcessingUpdate() strongSelf.endedProcessingUpdate()
@ -2158,7 +2312,7 @@ public final class GroupCallParticipantsContext {
} }
for participant in self.stateValue.state.participants { for participant in self.stateValue.state.participants {
if participant.peer.id == peerId { if participant.id == .peer(peerId) {
var raiseHandEqual: Bool = true var raiseHandEqual: Bool = true
if let raiseHand = raiseHand { if let raiseHand = raiseHand {
raiseHandEqual = (participant.raiseHandRating == nil && !raiseHand) || raiseHandEqual = (participant.raiseHandRating == nil && !raiseHand) ||
@ -2744,14 +2898,14 @@ func _internal_updatedCurrentPeerGroupCall(postbox: Postbox, network: Network, a
private func mergeAndSortParticipants(current currentParticipants: [GroupCallParticipantsContext.Participant], with updatedParticipants: [GroupCallParticipantsContext.Participant], sortAscending: Bool) -> [GroupCallParticipantsContext.Participant] { private func mergeAndSortParticipants(current currentParticipants: [GroupCallParticipantsContext.Participant], with updatedParticipants: [GroupCallParticipantsContext.Participant], sortAscending: Bool) -> [GroupCallParticipantsContext.Participant] {
var mergedParticipants = currentParticipants var mergedParticipants = currentParticipants
var existingParticipantIndices: [PeerId: Int] = [:] var existingParticipantIndices: [GroupCallParticipantsContext.Participant.Id: Int] = [:]
for i in 0 ..< mergedParticipants.count { for i in 0 ..< mergedParticipants.count {
existingParticipantIndices[mergedParticipants[i].peer.id] = i existingParticipantIndices[mergedParticipants[i].id] = i
} }
for participant in updatedParticipants { for participant in updatedParticipants {
if let _ = existingParticipantIndices[participant.peer.id] { if let _ = existingParticipantIndices[participant.id] {
} else { } else {
existingParticipantIndices[participant.peer.id] = mergedParticipants.count existingParticipantIndices[participant.id] = mergedParticipants.count
mergedParticipants.append(participant) mergedParticipants.append(participant)
} }
} }
@ -2942,7 +3096,8 @@ extension GroupCallParticipantsContext.Participant {
let joinedVideo = (flags & (1 << 15)) != 0 let joinedVideo = (flags & (1 << 15)) != 0
self.init( self.init(
peer: peer, id: .peer(peer.id),
peer: EnginePeer(peer),
ssrc: ssrc, ssrc: ssrc,
videoDescription: videoDescription, videoDescription: videoDescription,
presentationDescription: presentationDescription, presentationDescription: presentationDescription,

View File

@ -144,8 +144,8 @@ public extension TelegramEngine {
return _internal_getVideoBroadcastPart(dataSource: dataSource, callId: callId, accessHash: accessHash, timestampIdMilliseconds: timestampIdMilliseconds, durationMilliseconds: durationMilliseconds, channelId: channelId, quality: quality) return _internal_getVideoBroadcastPart(dataSource: dataSource, callId: callId, accessHash: accessHash, timestampIdMilliseconds: timestampIdMilliseconds, durationMilliseconds: durationMilliseconds, channelId: channelId, quality: quality)
} }
public func groupCall(peerId: PeerId?, myPeerId: PeerId, id: Int64, reference: InternalGroupCallReference, state: GroupCallParticipantsContext.State, previousServiceState: GroupCallParticipantsContext.ServiceState?) -> GroupCallParticipantsContext { public func groupCall(peerId: PeerId?, myPeerId: PeerId, id: Int64, reference: InternalGroupCallReference, state: GroupCallParticipantsContext.State, previousServiceState: GroupCallParticipantsContext.ServiceState?, e2eContext: ConferenceCallE2EContext?) -> GroupCallParticipantsContext {
return GroupCallParticipantsContext(account: self.account, peerId: peerId, myPeerId: myPeerId, id: id, reference: reference, state: state, previousServiceState: previousServiceState) return GroupCallParticipantsContext(account: self.account, peerId: peerId, myPeerId: myPeerId, id: id, reference: reference, state: state, previousServiceState: previousServiceState, e2eContext: e2eContext)
} }
public func serverTime() -> Signal<Int64, NoError> { public func serverTime() -> Signal<Int64, NoError> {

View File

@ -172,7 +172,9 @@ func _internal_joinCallLinkInformation(_ hash: String, account: Account) -> Sign
} }
var members: [EnginePeer] = [] var members: [EnginePeer] = []
for participant in call.topParticipants { for participant in call.topParticipants {
members.append(EnginePeer(participant.peer)) if let peer = participant.peer {
members.append(peer)
}
} }
return .single(JoinCallLinkInformation(id: call.info.id, accessHash: call.info.accessHash, inviter: nil, members: members, totalMemberCount: call.info.participantCount)) return .single(JoinCallLinkInformation(id: call.info.id, accessHash: call.info.accessHash, inviter: nil, members: members, totalMemberCount: call.info.participantCount))
} }
@ -198,7 +200,9 @@ func _internal_joinCallInvitationInformation(account: Account, messageId: Messag
} }
var members: [EnginePeer] = [] var members: [EnginePeer] = []
for participant in call.topParticipants { for participant in call.topParticipants {
members.append(EnginePeer(participant.peer)) if let peer = participant.peer {
members.append(peer)
}
} }
return .single(JoinCallLinkInformation(id: call.info.id, accessHash: call.info.accessHash, inviter: nil, members: members, totalMemberCount: call.info.participantCount)) return .single(JoinCallLinkInformation(id: call.info.id, accessHash: call.info.accessHash, inviter: nil, members: members, totalMemberCount: call.info.participantCount))
} }

View File

@ -4,41 +4,30 @@ import SwiftSignalKit
public struct CallListSettings: Codable, Equatable { public struct CallListSettings: Codable, Equatable {
public var _showTab: Bool? public var _showTab: Bool?
public var defaultShowTab: Bool?
public static var defaultSettings: CallListSettings { public static var defaultSettings: CallListSettings {
return CallListSettings(showTab: false) return CallListSettings(showTab: nil)
} }
public var showTab: Bool { public var showTab: Bool {
get { get {
if let value = self._showTab { if let value = self._showTab {
return value return value
} else if let defaultValue = self.defaultShowTab {
return defaultValue
} else { } else {
return CallListSettings.defaultSettings.showTab return true
} }
} set { } set {
self._showTab = newValue self._showTab = newValue
} }
} }
public init(showTab: Bool) { public init(showTab: Bool?) {
self._showTab = showTab self._showTab = showTab
} }
public init(showTab: Bool?, defaultShowTab: Bool?) {
self._showTab = showTab
self.defaultShowTab = defaultShowTab
}
public init(from decoder: Decoder) throws { public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: StringCodingKey.self) let container = try decoder.container(keyedBy: StringCodingKey.self)
if let alternativeDefaultValue = try container.decodeIfPresent(Int32.self, forKey: "defaultShowTab") {
self.defaultShowTab = alternativeDefaultValue != 0
}
if let value = try container.decodeIfPresent(Int32.self, forKey: "showTab") { if let value = try container.decodeIfPresent(Int32.self, forKey: "showTab") {
self._showTab = value != 0 self._showTab = value != 0
} }
@ -47,11 +36,6 @@ public struct CallListSettings: Codable, Equatable {
public func encode(to encoder: Encoder) throws { public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: StringCodingKey.self) var container = encoder.container(keyedBy: StringCodingKey.self)
if let defaultShowTab = self.defaultShowTab {
try container.encode((defaultShowTab ? 1 : 0) as Int32, forKey: "defaultShowTab")
} else {
try container.encodeNil(forKey: "defaultShowTab")
}
if let showTab = self._showTab { if let showTab = self._showTab {
try container.encode((showTab ? 1 : 0) as Int32, forKey: "showTab") try container.encode((showTab ? 1 : 0) as Int32, forKey: "showTab")
} else { } else {
@ -60,11 +44,11 @@ public struct CallListSettings: Codable, Equatable {
} }
public static func ==(lhs: CallListSettings, rhs: CallListSettings) -> Bool { public static func ==(lhs: CallListSettings, rhs: CallListSettings) -> Bool {
return lhs._showTab == rhs._showTab && lhs.defaultShowTab == rhs.defaultShowTab return lhs._showTab == rhs._showTab
} }
public func withUpdatedShowTab(_ showTab: Bool) -> CallListSettings { public func withUpdatedShowTab(_ showTab: Bool) -> CallListSettings {
return CallListSettings(showTab: showTab, defaultShowTab: self.defaultShowTab) return CallListSettings(showTab: showTab)
} }
} }

@ -1 +1 @@
Subproject commit 579cae3c3c70c6ed8bc0d88cc41de28bd50a7f1b Subproject commit 36161286bd7fae1b9bb2e8dad817ae9af6d68055

View File

@ -22,10 +22,10 @@ NS_ASSUME_NONNULL_BEGIN
@interface TdCallParticipant : NSObject @interface TdCallParticipant : NSObject
@property (nonatomic, strong, readonly) NSData *publicKey; @property (nonatomic, strong, readonly) NSString *internalId;
@property (nonatomic, readonly) int64_t userId; @property (nonatomic, readonly) int64_t userId;
- (nullable instancetype)initWithPublicKey:(NSData *)publicKey userId:(int64_t)userId; - (nullable instancetype)initWithInternalId:(NSString *)internalId userId:(int64_t)userId;
@end @end
@ -35,7 +35,7 @@ NS_ASSUME_NONNULL_BEGIN
- (NSArray<NSData *> *)takeOutgoingBroadcastBlocks; - (NSArray<NSData *> *)takeOutgoingBroadcastBlocks;
- (NSData *)emojiState; - (NSData *)emojiState;
- (NSArray<NSNumber *> *)participantIds; - (NSArray<TdCallParticipant *> *)participants;
- (void)applyBlock:(NSData *)block; - (void)applyBlock:(NSData *)block;
- (void)applyBroadcastBlock:(NSData *)block; - (void)applyBroadcastBlock:(NSData *)block;

View File

@ -58,10 +58,10 @@ static NSString *hexStringFromData(NSData *data) {
@implementation TdCallParticipant @implementation TdCallParticipant
- (nullable instancetype)initWithPublicKey:(NSData *)publicKey userId:(int64_t)userId { - (nullable instancetype)initWithInternalId:(NSString *)internalId userId:(int64_t)userId {
self = [super init]; self = [super init];
if (self != nil) { if (self != nil) {
_publicKey = publicKey; _internalId = internalId;
_userId = userId; _userId = userId;
} }
return self; return self;
@ -176,17 +176,18 @@ static NSString *hexStringFromData(NSData *data) {
return outEmojiHash; return outEmojiHash;
} }
- (NSArray<NSNumber *> *)participantIds { - (NSArray<TdCallParticipant *> *)participants {
auto result = tde2e_api::call_get_state(_callId); auto result = tde2e_api::call_get_state(_callId);
if (!result.is_ok()) { if (!result.is_ok()) {
return @[]; return @[];
} }
auto state = result.value(); auto state = result.value();
NSMutableArray<NSNumber *> *participantIds = [[NSMutableArray alloc] init]; NSMutableArray<TdCallParticipant *> *participants = [[NSMutableArray alloc] init];
for (const auto &it : state.participants) { for (const auto &it : state.participants) {
[participantIds addObject:[NSNumber numberWithLongLong:it.user_id]]; NSString *internalId = [[NSString alloc] initWithFormat:@"%lld", it.public_key_id];
[participants addObject:[[TdCallParticipant alloc] initWithInternalId:internalId userId:it.user_id]];
} }
return participantIds; return participants;
} }
- (void)applyBlock:(NSData *)block { - (void)applyBlock:(NSData *)block {

2
third-party/td/td vendored

@ -1 +1 @@
Subproject commit a03a90470d6fca9a5a3db747ba3f3e4a465b5fe7 Subproject commit 04adfc87deea4c804def118e88c89a08c388b32b

View File

@ -1,5 +1,5 @@
{ {
"app": "11.9.1", "app": "11.10",
"xcode": "16.2", "xcode": "16.2",
"bazel": "7.3.1:981f82a470bad1349322b6f51c9c6ffa0aa291dab1014fac411543c12e661dff", "bazel": "7.3.1:981f82a470bad1349322b6f51c9c6ffa0aa291dab1014fac411543c12e661dff",
"macos": "15" "macos": "15"